mirror of
https://gitee.com/johng/gf
synced 2026-06-11 03:41:44 +08:00
Compare commits
71 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f01dca0895 | |||
| 55137b2aa3 | |||
| 22540921b3 | |||
| 52de11b1fe | |||
| 5a646179ad | |||
| 33ae93e050 | |||
| f3d859159d | |||
| 4fb01e68f7 | |||
| 2812a247aa | |||
| 2b46e765c4 | |||
| c7f911cae2 | |||
| 9f0548c03d | |||
| c578df06a3 | |||
| 8ed3cf9c97 | |||
| 2438f565e9 | |||
| 8230c72ec6 | |||
| d716037caa | |||
| 855a4ddb2c | |||
| 74be9fac18 | |||
| e9fba5a166 | |||
| 0a92df691b | |||
| e513cd10ed | |||
| b702d98700 | |||
| 99f1d9d0ed | |||
| 707b08c585 | |||
| f44c19868c | |||
| 13ab139afc | |||
| cacb2f142b | |||
| 11f0317e92 | |||
| 9a667c8803 | |||
| c1cce17934 | |||
| 5a92d7de0d | |||
| 53bf378868 | |||
| cd7c45c00c | |||
| f13a5ad82e | |||
| 2e9be609c8 | |||
| 2f2f6e1ffe | |||
| f3bd2b67f7 | |||
| 645cecdffb | |||
| 22e9965629 | |||
| 24ea9f9245 | |||
| 9dbde6e8f1 | |||
| fe0b34544d | |||
| 5e489d59b4 | |||
| 61f49574a9 | |||
| 2fabcb62a8 | |||
| 3bc3b652c1 | |||
| 042a6f12f5 | |||
| 5acce82e63 | |||
| 4732bf46ad | |||
| 5bed5a1532 | |||
| 5b7576430f | |||
| 356f4cd701 | |||
| c444630d1e | |||
| 8e40cded42 | |||
| 0e52d467d3 | |||
| 6665d62e7e | |||
| 5bdf1a71b8 | |||
| a34ca0ff4b | |||
| 7f0163d958 | |||
| 31f19b0eee | |||
| 93d0760898 | |||
| 4863e7a6ae | |||
| a161b44cc7 | |||
| 7072244420 | |||
| f68b66e606 | |||
| 4e7c6c1fb4 | |||
| d8a7e36478 | |||
| 8971ad8445 | |||
| 270af8accb | |||
| b0ef63fc9d |
@ -9,7 +9,7 @@ import (
|
||||
|
||||
func main() {
|
||||
// 创建一个对象池,过期时间为1000毫秒
|
||||
p := gpool.New(1000, nil)
|
||||
p := gpool.New(1000*time.Millisecond, nil)
|
||||
|
||||
// 从池中取一个对象,返回nil及错误信息
|
||||
fmt.Println(p.Get())
|
||||
|
||||
@ -11,7 +11,7 @@ import (
|
||||
|
||||
func main() {
|
||||
// 创建对象复用池,对象过期时间为3000毫秒,并给定创建及销毁方法
|
||||
p := gpool.New(3000, func() (interface{}, error) {
|
||||
p := gpool.New(3000*time.Millisecond, func() (interface{}, error) {
|
||||
return gtcp.NewConn("www.baidu.com:80")
|
||||
}, func(i interface{}) {
|
||||
glog.Println("expired")
|
||||
|
||||
119
.example/database/gdb/driver/driver/driver.go
Normal file
119
.example/database/gdb/driver/driver/driver.go
Normal file
@ -0,0 +1,119 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package driver
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/database/gdb"
|
||||
"github.com/gogf/gf/internal/intlog"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
)
|
||||
|
||||
type MyDriver struct {
|
||||
*gdb.Core
|
||||
}
|
||||
|
||||
// Open creates and returns a underlying sql.DB object for mysql.
|
||||
func (d *MyDriver) Open(config *gdb.ConfigNode) (*sql.DB, error) {
|
||||
var source string
|
||||
if config.LinkInfo != "" {
|
||||
source = config.LinkInfo
|
||||
} else {
|
||||
source = fmt.Sprintf(
|
||||
"%s:%s@tcp(%s:%s)/%s?charset=%s&multiStatements=true&parseTime=true&loc=Local",
|
||||
config.User, config.Pass, config.Host, config.Port, config.Name, config.Charset,
|
||||
)
|
||||
}
|
||||
intlog.Printf("Open: %s", source)
|
||||
if db, err := sql.Open("mysql", source); err == nil {
|
||||
return db, nil
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// getChars returns the security char for this type of database.
|
||||
func (d *MyDriver) GetChars() (charLeft string, charRight string) {
|
||||
return "`", "`"
|
||||
}
|
||||
|
||||
// handleSqlBeforeExec handles the sql before posts it to database.
|
||||
func (d *MyDriver) HandleSqlBeforeExec(sql string) string {
|
||||
return sql
|
||||
}
|
||||
|
||||
// Tables retrieves and returns the tables of current schema.
|
||||
func (d *MyDriver) Tables(schema ...string) (tables []string, err error) {
|
||||
var result gdb.Result
|
||||
link, err := d.DB.GetSlave(schema...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result, err = d.DB.DoGetAll(link, `SHOW TABLES`)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, m := range result {
|
||||
for _, v := range m {
|
||||
tables = append(tables, v.String())
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// gdb.TableFields retrieves and returns the fields information of specified table of current schema.
|
||||
//
|
||||
// Note that it returns a map containing the field name and its corresponding fields.
|
||||
// As a map is unsorted, the gdb.TableField struct has a "Index" field marks its sequence in the fields.
|
||||
//
|
||||
// It's using cache feature to enhance the performance, which is never expired util the process restarts.
|
||||
func (d *MyDriver) TableFields(table string, schema ...string) (fields map[string]*gdb.TableField, err error) {
|
||||
table = gstr.Trim(table)
|
||||
if gstr.Contains(table, " ") {
|
||||
panic("function gdb.TableFields supports only single table operations")
|
||||
}
|
||||
checkSchema := d.DB.GetSchema()
|
||||
if len(schema) > 0 && schema[0] != "" {
|
||||
checkSchema = schema[0]
|
||||
}
|
||||
v := d.DB.GetCache().GetOrSetFunc(
|
||||
fmt.Sprintf(`mysql_table_fields_%s_%s`, table, checkSchema),
|
||||
func() interface{} {
|
||||
var result gdb.Result
|
||||
var link *sql.DB
|
||||
link, err = d.DB.GetSlave(checkSchema)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
result, err = d.DB.DoGetAll(
|
||||
link,
|
||||
fmt.Sprintf(`SHOW FULL COLUMNS FROM %s`, d.DB.QuoteWord(table)),
|
||||
)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
fields = make(map[string]*gdb.TableField)
|
||||
for i, m := range result {
|
||||
fields[m["Field"].String()] = &gdb.TableField{
|
||||
Index: i,
|
||||
Name: m["Field"].String(),
|
||||
Type: m["Type"].String(),
|
||||
Null: m["Null"].Bool(),
|
||||
Key: m["Key"].String(),
|
||||
Default: m["Default"].Val(),
|
||||
Extra: m["Extra"].String(),
|
||||
Comment: m["Comment"].String(),
|
||||
}
|
||||
}
|
||||
return fields
|
||||
}, 0)
|
||||
if err == nil {
|
||||
fields = v.(map[string]*gdb.TableField)
|
||||
}
|
||||
return
|
||||
}
|
||||
1
.example/database/gdb/driver/main.go
Normal file
1
.example/database/gdb/driver/main.go
Normal file
@ -0,0 +1 @@
|
||||
package main
|
||||
@ -11,11 +11,11 @@ func main() {
|
||||
// 开启调试模式,以便于记录所有执行的SQL
|
||||
db.SetDebug(true)
|
||||
|
||||
r, e := db.Table("test").OrderBy("id asc").All()
|
||||
r, e := db.Table("test").Order("id asc").All()
|
||||
if e != nil {
|
||||
panic(e)
|
||||
fmt.Println(e)
|
||||
}
|
||||
if r != nil {
|
||||
fmt.Println(r.ToList())
|
||||
fmt.Println(r.List())
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,5 +9,4 @@ func main() {
|
||||
db.SetDebug(true)
|
||||
|
||||
db.Table("user").Fields("DISTINCT id,nickname").Filter().All()
|
||||
|
||||
}
|
||||
|
||||
17
.example/net/ghttp/server/template/config/config.go
Normal file
17
.example/net/ghttp/server/template/config/config.go
Normal file
@ -0,0 +1,17 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/net/ghttp"
|
||||
)
|
||||
|
||||
func main() {
|
||||
s := g.Server()
|
||||
s.BindHandler("/", func(r *ghttp.Request) {
|
||||
r.Response.WriteTplContent(`${.name}`, g.Map{
|
||||
"name": "john",
|
||||
})
|
||||
})
|
||||
s.SetPort(8199)
|
||||
s.Run()
|
||||
}
|
||||
2
.example/net/ghttp/server/template/config/config.toml
Normal file
2
.example/net/ghttp/server/template/config/config.toml
Normal file
@ -0,0 +1,2 @@
|
||||
[viewer]
|
||||
delimiters = ["${", "}"]
|
||||
@ -11,7 +11,7 @@ import (
|
||||
func main() {
|
||||
for {
|
||||
time.Sleep(time.Second)
|
||||
if f, err := gfpool.Open("/home/john/temp/log.log", os.O_RDONLY, 0666, 60000000*1000); err == nil {
|
||||
if f, err := gfpool.Open("/home/john/temp/log.log", os.O_RDONLY, 0666, time.Hour); err == nil {
|
||||
fmt.Println(f.Name())
|
||||
f.Close()
|
||||
} else {
|
||||
|
||||
@ -1,37 +1,35 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/net/ghttp"
|
||||
"github.com/gogf/gf/os/glog"
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
"github.com/gogf/gf/os/gtimer"
|
||||
"time"
|
||||
)
|
||||
|
||||
func MiddlewareAuth(r *ghttp.Request) {
|
||||
token := r.Get("token")
|
||||
if token == "123456" {
|
||||
r.Response.Writeln("auth")
|
||||
r.Middleware.Next()
|
||||
} else {
|
||||
r.Response.WriteStatus(http.StatusForbidden)
|
||||
func GetList() {
|
||||
START:
|
||||
for {
|
||||
res, err := g.Redis().DoVar("RPOP", "mill")
|
||||
if err != nil {
|
||||
glog.Debug("Rpop:", err)
|
||||
break
|
||||
}
|
||||
glog.Debug(res)
|
||||
if res.IsEmpty() {
|
||||
glog.Debug("nil")
|
||||
continue START
|
||||
}
|
||||
interval := 50 * time.Second
|
||||
gtimer.AddOnce(interval, func() {
|
||||
glog.Debug("end------:", res, gtime.Now().Format("Y-m-d H:i:s"))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func MiddlewareCORS(r *ghttp.Request) {
|
||||
r.Response.Writeln("cors")
|
||||
r.Response.CORSDefault()
|
||||
r.Middleware.Next()
|
||||
}
|
||||
|
||||
func main() {
|
||||
s := g.Server()
|
||||
s.Use(MiddlewareCORS)
|
||||
s.Group("/api.v2", func(group *ghttp.RouterGroup) {
|
||||
group.Middleware(MiddlewareAuth)
|
||||
group.ALL("/user/list", func(r *ghttp.Request) {
|
||||
r.Response.Writeln("list")
|
||||
})
|
||||
})
|
||||
s.SetPort(8199)
|
||||
s.Run()
|
||||
g.Redis().SetMaxActive(2)
|
||||
//g.Redis().SetMaxIdle(100)
|
||||
GetList()
|
||||
}
|
||||
|
||||
@ -4,13 +4,12 @@ import (
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/net/ghttp"
|
||||
"github.com/gogf/gf/os/gview"
|
||||
"github.com/gogf/gf/util/gpage"
|
||||
)
|
||||
|
||||
func main() {
|
||||
s := ghttp.GetServer()
|
||||
s.BindHandler("/page/demo", func(r *ghttp.Request) {
|
||||
page := gpage.New(100, 10, r.Get("page"), r.URL.String())
|
||||
page := r.GetPage(100, 10)
|
||||
buffer, _ := gview.ParseContent(`
|
||||
<html>
|
||||
<head>
|
||||
|
||||
@ -4,14 +4,13 @@ import (
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/net/ghttp"
|
||||
"github.com/gogf/gf/os/gview"
|
||||
"github.com/gogf/gf/util/gpage"
|
||||
)
|
||||
|
||||
func main() {
|
||||
s := ghttp.GetServer()
|
||||
s.BindHandler("/page/ajax", func(r *ghttp.Request) {
|
||||
page := gpage.New(100, 10, r.Get("page"), r.URL.String(), r.Router)
|
||||
page.EnableAjax("DoAjax")
|
||||
page := r.GetPage(100, 10)
|
||||
page.AjaxActionName = "DoAjax"
|
||||
buffer, _ := gview.ParseContent(`
|
||||
<html>
|
||||
<head>
|
||||
@ -29,11 +28,17 @@ func main() {
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div>{{.page}}</div>
|
||||
<div>{{.page1}}</div>
|
||||
<div>{{.page2}}</div>
|
||||
<div>{{.page3}}</div>
|
||||
<div>{{.page4}}</div>
|
||||
</body>
|
||||
</html>
|
||||
`, g.Map{
|
||||
"page": page.GetContent(1),
|
||||
"page1": page.GetContent(1),
|
||||
"page2": page.GetContent(2),
|
||||
"page3": page.GetContent(3),
|
||||
"page4": page.GetContent(4),
|
||||
})
|
||||
r.Response.Write(buffer)
|
||||
})
|
||||
|
||||
@ -23,7 +23,7 @@ func wrapContent(page *gpage.Page) string {
|
||||
func main() {
|
||||
s := ghttp.GetServer()
|
||||
s.BindHandler("/page/custom1/*page", func(r *ghttp.Request) {
|
||||
page := gpage.New(100, 10, r.Get("page"), r.URL.String(), r.Router)
|
||||
page := r.GetPage(100, 10)
|
||||
content := wrapContent(page)
|
||||
buffer, _ := gview.ParseContent(`
|
||||
<html>
|
||||
|
||||
@ -15,7 +15,7 @@ func pageContent(page *gpage.Page) string {
|
||||
page.LastPageTag = "LastPage"
|
||||
pageStr := page.FirstPage()
|
||||
pageStr += page.PrevPage()
|
||||
pageStr += page.PageBar("current-page")
|
||||
pageStr += page.PageBar()
|
||||
pageStr += page.NextPage()
|
||||
pageStr += page.LastPage()
|
||||
return pageStr
|
||||
@ -24,7 +24,7 @@ func pageContent(page *gpage.Page) string {
|
||||
func main() {
|
||||
s := ghttp.GetServer()
|
||||
s.BindHandler("/page/custom2/*page", func(r *ghttp.Request) {
|
||||
page := gpage.New(100, 10, r.Get("page"), r.URL.String(), r.Router)
|
||||
page := r.GetPage(100, 10)
|
||||
buffer, _ := gview.ParseContent(`
|
||||
<html>
|
||||
<head>
|
||||
|
||||
@ -4,13 +4,12 @@ import (
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/net/ghttp"
|
||||
"github.com/gogf/gf/os/gview"
|
||||
"github.com/gogf/gf/util/gpage"
|
||||
)
|
||||
|
||||
func main() {
|
||||
s := g.Server()
|
||||
s.BindHandler("/page/static/*page", func(r *ghttp.Request) {
|
||||
page := gpage.New(100, 10, r.Get("page"), r.URL.String(), r.Router)
|
||||
page := r.GetPage(100, 10)
|
||||
buffer, _ := gview.ParseContent(`
|
||||
<html>
|
||||
<head>
|
||||
|
||||
@ -4,13 +4,12 @@ import (
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/net/ghttp"
|
||||
"github.com/gogf/gf/os/gview"
|
||||
"github.com/gogf/gf/util/gpage"
|
||||
)
|
||||
|
||||
func main() {
|
||||
s := g.Server()
|
||||
s.BindHandler("/:obj/*action/{page}.html", func(r *ghttp.Request) {
|
||||
page := gpage.New(100, 10, r.Get("page"), r.URL.String(), r.Router)
|
||||
page := r.GetPage(100, 10)
|
||||
buffer, _ := gview.ParseContent(`
|
||||
<html>
|
||||
<head>
|
||||
|
||||
@ -4,14 +4,13 @@ import (
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/net/ghttp"
|
||||
"github.com/gogf/gf/os/gview"
|
||||
"github.com/gogf/gf/util/gpage"
|
||||
)
|
||||
|
||||
func main() {
|
||||
s := g.Server()
|
||||
s.BindHandler("/page/template/{page}.html", func(r *ghttp.Request) {
|
||||
page := gpage.New(100, 10, r.Get("page"), r.URL.String())
|
||||
page.SetUrlTemplate("/order/list/{.page}.html")
|
||||
page := r.GetPage(100, 10)
|
||||
page.UrlTemplate = "/order/list/{.page}.html"
|
||||
buffer, _ := gview.ParseContent(`
|
||||
<html>
|
||||
<head>
|
||||
|
||||
21
DONATOR.MD
21
DONATOR.MD
@ -15,7 +15,7 @@ We currently accept donation by Alipay/WechatPay, please note your github/gitee
|
||||
|[zhuhuan12](https://gitee.com/zhuhuan12)|gitee|¥50.00 |
|
||||
|[zfan_codes](https://gitee.com/zfan_codes)|gitee|¥10.00 |
|
||||
|[arden](https://github.com/arden)|alipay|¥10.00 |
|
||||
|[macnie](https://www.macnie.com)|wechat|¥100.00 |
|
||||
|[macnie](https://www.macnie.com)|wechat|¥110.00 |
|
||||
|lah|wechat|¥100.00 |
|
||||
|x*z|wechat|¥20.00 |
|
||||
|潘兄|wechat|¥100.00 |
|
||||
@ -40,7 +40,7 @@ We currently accept donation by Alipay/WechatPay, please note your github/gitee
|
||||
|R*s|wechat|¥18.88| 谢谢GF!辛苦了!
|
||||
|粟*e|wechat|¥50.00|
|
||||
|[李超](https://github.com/effortlee)|wechat|¥124.00|
|
||||
|张炳贤|wechat+qq|¥600.00|
|
||||
|soidea666|wechat+qq|¥800.00|
|
||||
|[王哈哈](https://gitee.com/develop1024)|wechat|¥6.66| 希望gf越来越好
|
||||
|夕景|alipay+qq|¥9.96+3.57|
|
||||
|struggler|alipay|¥18.80|
|
||||
@ -51,6 +51,23 @@ We currently accept donation by Alipay/WechatPay, please note your github/gitee
|
||||
|[Zeroing-ZY](https://gitee.com/yunjieg)|gitee|¥20.00| 感谢您的开源项目!
|
||||
|[katydid酱](https://gitee.com/katydid2005)|gitee|¥50.00| 感谢您的开源项目!框架给予了很大的帮助!谢谢大佬!
|
||||
|[李海峰](https://gitee.com/dlhf)|gitee|¥10.00| 希望GF越来越好,框架很牛逼!
|
||||
|陆昱天|alipay|¥100.00|
|
||||
|[Dockercore](https://github.com/dockercore)|wechat|¥200.00| 非常喜欢!简洁好用!文档超级全!
|
||||
|🚶|wechat|¥6.88| 喝杯冰阔落
|
||||
|a*l|wechat|¥10.00| gf
|
||||
|[wxkj](https://gitee.com/wxkj)|wechat|¥10.00|
|
||||
|*包|wechat|¥9.99|
|
||||
|重庆宝尔威科技|wechat|¥6.66|
|
||||
|琦玉-QPT|wechat|¥6.66|
|
||||
|sailsea|wechat|¥11.00|
|
||||
|[seny0929](https://gitee.com/seny0929)|wechat|¥99.90|
|
||||
|*华|wechat|¥6.66| 感谢郭强的热心
|
||||
|[Playhi](https://github.com/Playhi)|alipay|¥10.00|
|
||||
|北京京纬互动科技|alipay|¥200.00|
|
||||
|米司特包|wechat|¥99.99|
|
||||
|金毛|alipay|¥100.00|
|
||||
|1*1x|wechat|¥100.00|
|
||||
|[ywanbing](https://github.com/ywanbing)|wechat|¥66.66|
|
||||
|
||||
|
||||
<img src="https://goframe.org/images/donate.png"/>
|
||||
|
||||
@ -28,7 +28,7 @@ require github.com/gogf/gf latest
|
||||
|
||||
# Limitation
|
||||
```
|
||||
golang version >= 1.10
|
||||
golang version >= 1.11
|
||||
```
|
||||
|
||||
# Documentation
|
||||
|
||||
11
README_ZH.MD
11
README_ZH.MD
@ -8,11 +8,12 @@
|
||||
|
||||
[English](README.MD) | 简体中文
|
||||
|
||||
`GF(Go Frame)`是一款模块化、高性能、生产级的Go基础开发框架。实现了比较完善的基础设施建设,包括常用的核心开发组件,
|
||||
如:缓存、日志、文件、时间、队列、数组、集合、字符串、定时器、命令行、文件锁、内存锁、对象池、连接池、资源管理、数据校验、数据编码、文件监控、
|
||||
定时任务、数据库ORM、TCP/UDP组件、进程管理/通信、并发安全容器等等。
|
||||
并提供了Web服务开发的系列核心组件,如:Router、Cookie、Session、Middleware、服务注册、配置管理、模板引擎等等,
|
||||
支持热重启、热更新、多域名、多端口、多服务、HTTPS、Rewrite等特性。
|
||||
`GF(Go Frame)`是一款模块化、高性能、生产级的Go基础开发框架。
|
||||
实现了比较完善的基础设施建设以及开发工具链,提供了常用的基础开发模块,
|
||||
如:缓存、日志、队列、数组、集合、容器、定时器、命令行、内存锁、对象池、
|
||||
配置管理、资源管理、数据校验、数据编码、定时任务、数据库ORM、TCP/UDP组件、进程管理/通信等等。
|
||||
并提供了Web服务开发的系列核心组件,如:Router、Cookie、Session、Middleware、服务注册、模板引擎等等,
|
||||
支持热重启、热更新、域名绑定、TLS/HTTPS、Rewrite等特性。
|
||||
|
||||
|
||||
# 特点
|
||||
|
||||
@ -33,9 +33,9 @@ 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,
|
||||
// if it returns value < 0, means v1 < v2;
|
||||
// if it returns value = 0, means v1 = v2;
|
||||
// if it returns value > 0, means v1 > v2;
|
||||
// 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;
|
||||
func NewSortedArray(comparator func(a, b interface{}) int, safe ...bool) *SortedArray {
|
||||
return NewSortedArraySize(0, comparator, safe...)
|
||||
}
|
||||
@ -439,6 +439,9 @@ func (a *SortedArray) SetUnique(unique bool) *SortedArray {
|
||||
func (a *SortedArray) Unique() *SortedArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
if len(a.array) == 0 {
|
||||
return a
|
||||
}
|
||||
i := 0
|
||||
for {
|
||||
if i == len(a.array)-1 {
|
||||
|
||||
@ -429,6 +429,10 @@ func (a *SortedIntArray) SetUnique(unique bool) *SortedIntArray {
|
||||
// Unique uniques the array, clear repeated items.
|
||||
func (a *SortedIntArray) Unique() *SortedIntArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
if len(a.array) == 0 {
|
||||
return a
|
||||
}
|
||||
i := 0
|
||||
for {
|
||||
if i == len(a.array)-1 {
|
||||
@ -440,7 +444,6 @@ func (a *SortedIntArray) Unique() *SortedIntArray {
|
||||
i++
|
||||
}
|
||||
}
|
||||
a.mu.Unlock()
|
||||
return a
|
||||
}
|
||||
|
||||
|
||||
@ -414,6 +414,10 @@ func (a *SortedStrArray) SetUnique(unique bool) *SortedStrArray {
|
||||
// Unique uniques the array, clear repeated items.
|
||||
func (a *SortedStrArray) Unique() *SortedStrArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
if len(a.array) == 0 {
|
||||
return a
|
||||
}
|
||||
i := 0
|
||||
for {
|
||||
if i == len(a.array)-1 {
|
||||
@ -425,7 +429,6 @@ func (a *SortedStrArray) Unique() *SortedStrArray {
|
||||
i++
|
||||
}
|
||||
}
|
||||
a.mu.Unlock()
|
||||
return a
|
||||
}
|
||||
|
||||
|
||||
@ -13,8 +13,8 @@ import (
|
||||
)
|
||||
|
||||
func Example_basic() {
|
||||
// 创建普通的数组,默认并发安全(带锁)
|
||||
a := garray.New(true)
|
||||
// 创建普通的数组
|
||||
a := garray.New()
|
||||
|
||||
// 添加数据项
|
||||
for i := 0; i < 10; i++ {
|
||||
|
||||
@ -17,22 +17,31 @@ import (
|
||||
"github.com/gogf/gf/os/gtimer"
|
||||
)
|
||||
|
||||
// Object-Reusable Pool.
|
||||
// Pool is an Object-Reusable Pool.
|
||||
type Pool struct {
|
||||
list *glist.List // Available/idle list.
|
||||
closed *gtype.Bool // Whether the pool is closed.
|
||||
Expire int64 // Max idle time(ms), after which it is recycled.
|
||||
NewFunc func() (interface{}, error) // Callback function to create item.
|
||||
ExpireFunc func(interface{}) // Expired destruction function for objects.
|
||||
// This function needs to be defined when the pool object
|
||||
// needs to perform additional destruction operations.
|
||||
// Available/idle items list.
|
||||
list *glist.List
|
||||
|
||||
// Whether the pool is closed.
|
||||
closed *gtype.Bool
|
||||
|
||||
// Time To Live for pool items.
|
||||
TTL time.Duration
|
||||
|
||||
// Callback function to create pool item.
|
||||
NewFunc func() (interface{}, error)
|
||||
|
||||
// ExpireFunc is the for expired items destruction.
|
||||
// This function needs to be defined when the pool items
|
||||
// need to perform additional destruction operations.
|
||||
// Eg: net.Conn, os.File, etc.
|
||||
ExpireFunc func(interface{})
|
||||
}
|
||||
|
||||
// Pool item.
|
||||
type poolItem struct {
|
||||
expire int64 // Expire time(millisecond).
|
||||
value interface{} // Value.
|
||||
expire int64 // Expire timestamp in milliseconds.
|
||||
value interface{} // Item value.
|
||||
}
|
||||
|
||||
// Creation function for object.
|
||||
@ -41,47 +50,64 @@ type NewFunc func() (interface{}, error)
|
||||
// Destruction function for object.
|
||||
type ExpireFunc func(interface{})
|
||||
|
||||
// New returns a new object pool.
|
||||
// New creates and returns a new object pool.
|
||||
// To ensure execution efficiency, the expiration time cannot be modified once it is set.
|
||||
//
|
||||
// Expiration logic:
|
||||
// expire = 0 : not expired;
|
||||
// expire < 0 : immediate expired after use;
|
||||
// expire > 0 : timeout expired;
|
||||
// Note that the expiration time unit is ** milliseconds **.
|
||||
func New(expire int, newFunc NewFunc, expireFunc ...ExpireFunc) *Pool {
|
||||
// Note the expiration logic:
|
||||
// ttl = 0 : not expired;
|
||||
// ttl < 0 : immediate expired after use;
|
||||
// ttl > 0 : timeout expired;
|
||||
func New(ttl time.Duration, newFunc NewFunc, expireFunc ...ExpireFunc) *Pool {
|
||||
r := &Pool{
|
||||
list: glist.New(true),
|
||||
closed: gtype.NewBool(),
|
||||
Expire: int64(expire),
|
||||
TTL: ttl,
|
||||
NewFunc: newFunc,
|
||||
}
|
||||
if len(expireFunc) > 0 {
|
||||
r.ExpireFunc = expireFunc[0]
|
||||
}
|
||||
gtimer.AddSingleton(time.Second, r.checkExpire)
|
||||
gtimer.AddSingleton(time.Second, r.checkExpireItems)
|
||||
return r
|
||||
}
|
||||
|
||||
// Put puts an item to pool.
|
||||
func (p *Pool) Put(value interface{}) {
|
||||
func (p *Pool) Put(value interface{}) error {
|
||||
if p.closed.Val() {
|
||||
return errors.New("pool is closed")
|
||||
}
|
||||
item := &poolItem{
|
||||
value: value,
|
||||
}
|
||||
if p.Expire == 0 {
|
||||
if p.TTL == 0 {
|
||||
item.expire = 0
|
||||
} else {
|
||||
item.expire = gtime.TimestampMilli() + p.Expire
|
||||
// As for Golang version < 1.13, there's no method Milliseconds for time.Duration.
|
||||
// So we need calculate the milliseconds using its nanoseconds value.
|
||||
item.expire = gtime.TimestampMilli() + p.TTL.Nanoseconds()/1000000
|
||||
}
|
||||
p.list.PushBack(item)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Clear clears pool, which means it will remove all items from pool.
|
||||
func (p *Pool) Clear() {
|
||||
p.list.RemoveAll()
|
||||
if p.ExpireFunc != nil {
|
||||
for {
|
||||
if r := p.list.PopFront(); r != nil {
|
||||
p.ExpireFunc(r.(*poolItem).value)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
p.list.RemoveAll()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Get picks an item from pool.
|
||||
// Get picks and returns an item from pool. If the pool is empty and NewFunc is defined,
|
||||
// it creates and returns one from NewFunc.
|
||||
func (p *Pool) Get() (interface{}, error) {
|
||||
for !p.closed.Val() {
|
||||
if r := p.list.PopFront(); r != nil {
|
||||
@ -106,12 +132,13 @@ func (p *Pool) Size() int {
|
||||
|
||||
// Close closes the pool. If <p> has ExpireFunc,
|
||||
// then it automatically closes all items using this function before it's closed.
|
||||
// Commonly you do not need call this function manually.
|
||||
func (p *Pool) Close() {
|
||||
p.closed.Set(true)
|
||||
}
|
||||
|
||||
// checkExpire removes expired items from pool every second.
|
||||
func (p *Pool) checkExpire() {
|
||||
// checkExpire removes expired items from pool in every second.
|
||||
func (p *Pool) checkExpireItems() {
|
||||
if p.closed.Val() {
|
||||
// If p has ExpireFunc,
|
||||
// then it must close all items using this function.
|
||||
@ -126,11 +153,24 @@ func (p *Pool) checkExpire() {
|
||||
}
|
||||
gtimer.Exit()
|
||||
}
|
||||
// All items do not expire.
|
||||
if p.TTL == 0 {
|
||||
return
|
||||
}
|
||||
// The latest item expire timestamp in milliseconds.
|
||||
var latestExpire int64 = -1
|
||||
// Retrieve the current timestamp in milliseconds, it expires the items
|
||||
// by comparing with this timestamp. It is not accurate comparison for
|
||||
// every items expired, but high performance.
|
||||
var timestampMilli = gtime.TimestampMilli()
|
||||
for {
|
||||
// TODO Do not use Pop and Push mechanism, which is not graceful.
|
||||
if latestExpire > timestampMilli {
|
||||
break
|
||||
}
|
||||
if r := p.list.PopFront(); r != nil {
|
||||
item := r.(*poolItem)
|
||||
if item.expire == 0 || item.expire > gtime.TimestampMilli() {
|
||||
latestExpire = item.expire
|
||||
if item.expire > timestampMilli {
|
||||
p.list.PushFront(item)
|
||||
break
|
||||
}
|
||||
|
||||
@ -11,11 +11,12 @@ package gpool_test
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/container/gpool"
|
||||
)
|
||||
|
||||
var pool = gpool.New(99999999, nil)
|
||||
var pool = gpool.New(time.Hour, nil)
|
||||
var syncp = sync.Pool{}
|
||||
|
||||
func BenchmarkGPoolPut(b *testing.B) {
|
||||
|
||||
@ -62,7 +62,7 @@ func Test_Gpool(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
//
|
||||
//expire > 0
|
||||
p2 := gpool.New(2000, nil, ef)
|
||||
p2 := gpool.New(2*time.Second, nil, ef)
|
||||
for index := 0; index < 10; index++ {
|
||||
p2.Put(index)
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// Package gqueue provides a dynamic/static concurrent-safe queue.
|
||||
// Package gqueue provides dynamic/static concurrent-safe queue.
|
||||
//
|
||||
// Features:
|
||||
//
|
||||
@ -25,6 +25,7 @@ import (
|
||||
"github.com/gogf/gf/container/gtype"
|
||||
)
|
||||
|
||||
// Queue is a concurrent-safe queue built on doubly linked list and channel.
|
||||
type Queue struct {
|
||||
limit int // Limit for queue size.
|
||||
list *glist.List // Underlying list structure for data maintaining.
|
||||
@ -54,14 +55,14 @@ func New(limit ...int) *Queue {
|
||||
q.list = glist.New(true)
|
||||
q.events = make(chan struct{}, math.MaxInt32)
|
||||
q.C = make(chan interface{}, gDEFAULT_QUEUE_SIZE)
|
||||
go q.startAsyncLoop()
|
||||
go q.asyncLoopFromListToChannel()
|
||||
}
|
||||
return q
|
||||
}
|
||||
|
||||
// startAsyncLoop starts an asynchronous goroutine,
|
||||
// asyncLoopFromListToChannel starts an asynchronous goroutine,
|
||||
// which handles the data synchronization from list <q.list> to channel <q.C>.
|
||||
func (q *Queue) startAsyncLoop() {
|
||||
func (q *Queue) asyncLoopFromListToChannel() {
|
||||
defer func() {
|
||||
if q.closed.Val() {
|
||||
_ = recover()
|
||||
|
||||
@ -157,6 +157,12 @@ func BenchmarkBool_Val(b *testing.B) {
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkBool_Cas(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
bl.Cas(false, true)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkString_Set(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
str.Set(strconv.Itoa(i))
|
||||
|
||||
@ -334,6 +334,15 @@ func Test_Struct(t *testing.T) {
|
||||
|
||||
gtest.Assert(testObj.Test, Kv["Test"])
|
||||
})
|
||||
gtest.Case(t, func() {
|
||||
type StTest struct {
|
||||
Test int8
|
||||
}
|
||||
o := &StTest{}
|
||||
v := gvar.New(g.Slice{"Test", "-25"})
|
||||
v.Struct(o)
|
||||
gtest.Assert(o.Test, -25)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Json(t *testing.T) {
|
||||
|
||||
@ -22,7 +22,7 @@ import (
|
||||
"github.com/gogf/gf/util/grand"
|
||||
)
|
||||
|
||||
// DB is the interface for ORM operations.
|
||||
// DB defines the interfaces for ORM operations.
|
||||
type DB interface {
|
||||
// Open creates a raw connection object for database with given node configuration.
|
||||
// Note that it is not recommended using the this function manually.
|
||||
@ -34,29 +34,30 @@ type DB interface {
|
||||
Prepare(sql string, execOnMaster ...bool) (*sql.Stmt, error)
|
||||
|
||||
// Internal APIs for CURD, which can be overwrote for custom CURD implements.
|
||||
doQuery(link dbLink, query string, args ...interface{}) (rows *sql.Rows, err error)
|
||||
doGetAll(link dbLink, query string, args ...interface{}) (result Result, err error)
|
||||
doExec(link dbLink, query string, args ...interface{}) (result sql.Result, err error)
|
||||
doPrepare(link dbLink, query string) (*sql.Stmt, error)
|
||||
doInsert(link dbLink, table string, data interface{}, option int, batch ...int) (result sql.Result, err error)
|
||||
doBatchInsert(link dbLink, table string, list interface{}, option int, batch ...int) (result sql.Result, err error)
|
||||
doUpdate(link dbLink, table string, data interface{}, condition string, args ...interface{}) (result sql.Result, err error)
|
||||
doDelete(link dbLink, table string, condition string, args ...interface{}) (result sql.Result, err error)
|
||||
DoQuery(link Link, query string, args ...interface{}) (rows *sql.Rows, err error)
|
||||
DoGetAll(link Link, query string, args ...interface{}) (result Result, err error)
|
||||
DoExec(link Link, query string, args ...interface{}) (result sql.Result, err error)
|
||||
DoPrepare(link Link, query string) (*sql.Stmt, error)
|
||||
DoInsert(link Link, table string, data interface{}, option int, batch ...int) (result sql.Result, err error)
|
||||
DoBatchInsert(link Link, table string, list interface{}, option int, batch ...int) (result sql.Result, err error)
|
||||
DoUpdate(link Link, table string, data interface{}, condition string, args ...interface{}) (result sql.Result, err error)
|
||||
DoDelete(link Link, table string, condition string, args ...interface{}) (result sql.Result, err error)
|
||||
|
||||
// Query APIs for convenience purpose.
|
||||
GetAll(query string, args ...interface{}) (Result, error)
|
||||
GetOne(query string, args ...interface{}) (Record, error)
|
||||
GetValue(query string, args ...interface{}) (Value, error)
|
||||
GetArray(query string, args ...interface{}) ([]Value, error)
|
||||
GetCount(query string, args ...interface{}) (int, error)
|
||||
GetStruct(objPointer interface{}, query string, args ...interface{}) error
|
||||
GetStructs(objPointerSlice interface{}, query string, args ...interface{}) error
|
||||
GetScan(objPointer interface{}, query string, args ...interface{}) error
|
||||
|
||||
// Master/Slave support.
|
||||
// Master/Slave specification support.
|
||||
Master() (*sql.DB, error)
|
||||
Slave() (*sql.DB, error)
|
||||
|
||||
// Ping.
|
||||
// Ping-Pong.
|
||||
PingMaster() error
|
||||
PingSlave() error
|
||||
|
||||
@ -75,48 +76,49 @@ type DB interface {
|
||||
Update(table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error)
|
||||
Delete(table string, condition interface{}, args ...interface{}) (sql.Result, error)
|
||||
|
||||
// Create model.
|
||||
// Model creation.
|
||||
From(tables string) *Model
|
||||
Table(tables string) *Model
|
||||
Schema(schema string) *Schema
|
||||
|
||||
// Configuration methods.
|
||||
GetCache() *gcache.Cache
|
||||
SetDebug(debug bool)
|
||||
GetDebug() bool
|
||||
SetSchema(schema string)
|
||||
GetSchema() string
|
||||
GetPrefix() string
|
||||
SetLogger(logger *glog.Logger)
|
||||
GetLogger() *glog.Logger
|
||||
SetMaxIdleConnCount(n int)
|
||||
SetMaxOpenConnCount(n int)
|
||||
SetMaxConnLifetime(d time.Duration)
|
||||
|
||||
// Utility methods.
|
||||
GetChars() (charLeft string, charRight string)
|
||||
GetMaster(schema ...string) (*sql.DB, error)
|
||||
GetSlave(schema ...string) (*sql.DB, error)
|
||||
QuoteWord(s string) string
|
||||
QuoteString(s string) string
|
||||
QuotePrefixTableName(table string) string
|
||||
Tables(schema ...string) (tables []string, err error)
|
||||
TableFields(table string, schema ...string) (map[string]*TableField, error)
|
||||
|
||||
// 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 <query> and its
|
||||
// arguments <args> as you wish before they're committed to driver.
|
||||
HandleSqlBeforeCommit(link Link, query string, args []interface{}) (string, []interface{})
|
||||
|
||||
// Internal methods.
|
||||
getCache() *gcache.Cache
|
||||
getChars() (charLeft string, charRight string)
|
||||
getDebug() bool
|
||||
getPrefix() string
|
||||
getMaster(schema ...string) (*sql.DB, error)
|
||||
getSlave(schema ...string) (*sql.DB, error)
|
||||
quoteWord(s string) string
|
||||
quoteString(s string) string
|
||||
handleTableName(table string) string
|
||||
filterFields(schema, table string, data map[string]interface{}) map[string]interface{}
|
||||
convertValue(fieldValue []byte, fieldType string) interface{}
|
||||
rowsToResult(rows *sql.Rows) (Result, error)
|
||||
handleSqlBeforeExec(sql string) string
|
||||
}
|
||||
|
||||
// dbLink is a common database function wrapper interface for internal usage.
|
||||
type dbLink interface {
|
||||
Query(query string, args ...interface{}) (*sql.Rows, error)
|
||||
Exec(sql string, args ...interface{}) (sql.Result, error)
|
||||
Prepare(sql string) (*sql.Stmt, error)
|
||||
}
|
||||
|
||||
// dbBase is the base struct for database management.
|
||||
type dbBase struct {
|
||||
db DB // DB interface object.
|
||||
// Core is the base struct for database management.
|
||||
type Core struct {
|
||||
DB DB // DB interface object.
|
||||
group string // Configuration group name.
|
||||
debug *gtype.Bool // Enable debug mode for the database.
|
||||
cache *gcache.Cache // Cache manager.
|
||||
@ -128,6 +130,12 @@ type dbBase struct {
|
||||
maxConnLifetime time.Duration // Max TTL for a connection.
|
||||
}
|
||||
|
||||
// Driver is the interface for integrating sql drivers into package gdb.
|
||||
type Driver interface {
|
||||
// New creates and returns a database object for specified database server.
|
||||
New(core *Core, node *ConfigNode) (DB, error)
|
||||
}
|
||||
|
||||
// Sql is the sql recording struct.
|
||||
type Sql struct {
|
||||
Sql string // SQL string(may contain reserved char '?').
|
||||
@ -150,6 +158,13 @@ type TableField struct {
|
||||
Comment string // Comment.
|
||||
}
|
||||
|
||||
// Link is a common database function wrapper interface.
|
||||
type Link interface {
|
||||
Query(query string, args ...interface{}) (*sql.Rows, error)
|
||||
Exec(sql string, args ...interface{}) (sql.Result, error)
|
||||
Prepare(sql string) (*sql.Stmt, error)
|
||||
}
|
||||
|
||||
// Value is the field value type.
|
||||
type Value = *gvar.Var
|
||||
|
||||
@ -167,19 +182,34 @@ type Map = map[string]interface{}
|
||||
type List = []Map
|
||||
|
||||
const (
|
||||
gINSERT_OPTION_DEFAULT = 0
|
||||
gINSERT_OPTION_REPLACE = 1
|
||||
gINSERT_OPTION_SAVE = 2
|
||||
gINSERT_OPTION_IGNORE = 3
|
||||
gDEFAULT_BATCH_NUM = 10 // Per count for batch insert/replace/save
|
||||
gDEFAULT_CONN_MAX_LIFE_TIME = 30 // Max life time for per connection in pool in seconds.
|
||||
gINSERT_OPTION_DEFAULT = 0
|
||||
gINSERT_OPTION_REPLACE = 1
|
||||
gINSERT_OPTION_SAVE = 2
|
||||
gINSERT_OPTION_IGNORE = 3
|
||||
gDEFAULT_BATCH_NUM = 10 // Per count for batch insert/replace/save
|
||||
gDEFAULT_CONN_MAX_IDLE_COUNT = 10 // Max idle connection count in pool.
|
||||
gDEFAULT_CONN_MAX_LIFE_TIME = 30 // Max life time for per connection in pool in seconds.
|
||||
)
|
||||
|
||||
var (
|
||||
// Instance map.
|
||||
// instances is the management map for instances.
|
||||
instances = gmap.NewStrAnyMap(true)
|
||||
// driverMap manages all custom registered driver.
|
||||
driverMap = map[string]Driver{
|
||||
"mysql": &DriverMysql{},
|
||||
"mssql": &DriverMssql{},
|
||||
"pgsql": &DriverPgsql{},
|
||||
"oracle": &DriverOracle{},
|
||||
"sqlite": &DriverSqlite{},
|
||||
}
|
||||
)
|
||||
|
||||
// Register registers custom database driver to gdb.
|
||||
func Register(name string, driver Driver) error {
|
||||
driverMap[name] = driver
|
||||
return nil
|
||||
}
|
||||
|
||||
// New creates and returns an ORM object with global configurations.
|
||||
// The parameter <name> specifies the configuration group name,
|
||||
// which is DEFAULT_GROUP_NAME in default.
|
||||
@ -196,31 +226,25 @@ func New(name ...string) (db DB, err error) {
|
||||
}
|
||||
if _, ok := configs.config[group]; ok {
|
||||
if node, err := getConfigNodeByGroup(group, true); err == nil {
|
||||
base := &dbBase{
|
||||
group: group,
|
||||
debug: gtype.NewBool(),
|
||||
cache: gcache.New(),
|
||||
schema: gtype.NewString(),
|
||||
logger: glog.New(),
|
||||
prefix: node.Prefix,
|
||||
// Default max connection life time if user does not configure.
|
||||
maxConnLifetime: gDEFAULT_CONN_MAX_LIFE_TIME,
|
||||
c := &Core{
|
||||
group: group,
|
||||
debug: gtype.NewBool(),
|
||||
cache: gcache.New(),
|
||||
schema: gtype.NewString(),
|
||||
logger: glog.New(),
|
||||
prefix: node.Prefix,
|
||||
maxIdleConnCount: gDEFAULT_CONN_MAX_IDLE_COUNT,
|
||||
maxConnLifetime: gDEFAULT_CONN_MAX_LIFE_TIME, // Default max connection life time if user does not configure.
|
||||
}
|
||||
switch node.Type {
|
||||
case "mysql":
|
||||
base.db = &dbMysql{dbBase: base}
|
||||
case "pgsql":
|
||||
base.db = &dbPgsql{dbBase: base}
|
||||
case "mssql":
|
||||
base.db = &dbMssql{dbBase: base}
|
||||
case "sqlite":
|
||||
base.db = &dbSqlite{dbBase: base}
|
||||
case "oracle":
|
||||
base.db = &dbOracle{dbBase: base}
|
||||
default:
|
||||
if v, ok := driverMap[node.Type]; ok {
|
||||
c.DB, err = v.New(c, node)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.DB, nil
|
||||
} else {
|
||||
return nil, errors.New(fmt.Sprintf(`unsupported database type "%s"`, node.Type))
|
||||
}
|
||||
return base.db, nil
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
@ -321,9 +345,9 @@ func getConfigNodeByWeight(cg ConfigGroup) *ConfigNode {
|
||||
// getSqlDb retrieves and returns a underlying database connection object.
|
||||
// The parameter <master> specifies whether retrieves master node connection if
|
||||
// master-slave nodes are configured.
|
||||
func (bs *dbBase) getSqlDb(master bool, schema ...string) (sqlDb *sql.DB, err error) {
|
||||
func (c *Core) getSqlDb(master bool, schema ...string) (sqlDb *sql.DB, err error) {
|
||||
// Load balance.
|
||||
node, err := getConfigNodeByGroup(bs.group, master)
|
||||
node, err := getConfigNodeByGroup(c.group, master)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -332,7 +356,7 @@ func (bs *dbBase) getSqlDb(master bool, schema ...string) (sqlDb *sql.DB, err er
|
||||
node.Charset = "utf8"
|
||||
}
|
||||
// Changes the schema.
|
||||
nodeSchema := bs.schema.Val()
|
||||
nodeSchema := c.schema.Val()
|
||||
if len(schema) > 0 && schema[0] != "" {
|
||||
nodeSchema = schema[0]
|
||||
}
|
||||
@ -342,26 +366,26 @@ func (bs *dbBase) getSqlDb(master bool, schema ...string) (sqlDb *sql.DB, err er
|
||||
n.Name = nodeSchema
|
||||
node = &n
|
||||
}
|
||||
// Cache the underlying connection object by node.
|
||||
v := bs.cache.GetOrSetFuncLock(node.String(), func() interface{} {
|
||||
sqlDb, err = bs.db.Open(node)
|
||||
// Cache the underlying connection pool object by node.
|
||||
v := c.cache.GetOrSetFuncLock(node.String(), func() interface{} {
|
||||
sqlDb, err = c.DB.Open(node)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if bs.maxIdleConnCount > 0 {
|
||||
sqlDb.SetMaxIdleConns(bs.maxIdleConnCount)
|
||||
if c.maxIdleConnCount > 0 {
|
||||
sqlDb.SetMaxIdleConns(c.maxIdleConnCount)
|
||||
} else if node.MaxIdleConnCount > 0 {
|
||||
sqlDb.SetMaxIdleConns(node.MaxIdleConnCount)
|
||||
}
|
||||
|
||||
if bs.maxOpenConnCount > 0 {
|
||||
sqlDb.SetMaxOpenConns(bs.maxOpenConnCount)
|
||||
if c.maxOpenConnCount > 0 {
|
||||
sqlDb.SetMaxOpenConns(c.maxOpenConnCount)
|
||||
} else if node.MaxOpenConnCount > 0 {
|
||||
sqlDb.SetMaxOpenConns(node.MaxOpenConnCount)
|
||||
}
|
||||
|
||||
if bs.maxConnLifetime > 0 {
|
||||
sqlDb.SetConnMaxLifetime(bs.maxConnLifetime * time.Second)
|
||||
if c.maxConnLifetime > 0 {
|
||||
sqlDb.SetConnMaxLifetime(c.maxConnLifetime * time.Second)
|
||||
} else if node.MaxConnLifetime > 0 {
|
||||
sqlDb.SetConnMaxLifetime(node.MaxConnLifetime * time.Second)
|
||||
}
|
||||
@ -371,40 +395,7 @@ func (bs *dbBase) getSqlDb(master bool, schema ...string) (sqlDb *sql.DB, err er
|
||||
sqlDb = v.(*sql.DB)
|
||||
}
|
||||
if node.Debug {
|
||||
bs.db.SetDebug(node.Debug)
|
||||
c.DB.SetDebug(node.Debug)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// SetSchema changes the schema for this database connection object.
|
||||
// Importantly note that when schema configuration changed for the database,
|
||||
// it affects all operations on the database object in the future.
|
||||
func (bs *dbBase) SetSchema(schema string) {
|
||||
bs.schema.Set(schema)
|
||||
}
|
||||
|
||||
// Master creates and returns a connection from master node if master-slave configured.
|
||||
// It returns the default connection if master-slave not configured.
|
||||
func (bs *dbBase) Master() (*sql.DB, error) {
|
||||
return bs.getSqlDb(true, bs.schema.Val())
|
||||
}
|
||||
|
||||
// Slave creates and returns a connection from slave node if master-slave configured.
|
||||
// It returns the default connection if master-slave not configured.
|
||||
func (bs *dbBase) Slave() (*sql.DB, error) {
|
||||
return bs.getSqlDb(false, bs.schema.Val())
|
||||
}
|
||||
|
||||
// getMaster acts like function Master but with additional <schema> parameter specifying
|
||||
// the schema for the connection. It is defined for internal usage.
|
||||
// Also see Master.
|
||||
func (bs *dbBase) getMaster(schema ...string) (*sql.DB, error) {
|
||||
return bs.getSqlDb(true, schema...)
|
||||
}
|
||||
|
||||
// getSlave acts like function Slave but with additional <schema> parameter specifying
|
||||
// the schema for the connection. It is defined for internal usage.
|
||||
// Also see Slave.
|
||||
func (bs *dbBase) getSlave(schema ...string) (*sql.DB, error) {
|
||||
return bs.getSqlDb(false, schema...)
|
||||
}
|
||||
|
||||
@ -1,25 +0,0 @@
|
||||
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gdb
|
||||
|
||||
import "database/sql"
|
||||
|
||||
// batchSqlResult is execution result for batch operations.
|
||||
type batchSqlResult struct {
|
||||
rowsAffected int64
|
||||
lastResult sql.Result
|
||||
}
|
||||
|
||||
// see sql.Result.RowsAffected
|
||||
func (r *batchSqlResult) RowsAffected() (int64, error) {
|
||||
return r.rowsAffected, nil
|
||||
}
|
||||
|
||||
// see sql.Result.LastInsertId
|
||||
func (r *batchSqlResult) LastInsertId() (int64, error) {
|
||||
return r.lastResult.LastInsertId()
|
||||
}
|
||||
@ -16,7 +16,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/container/gvar"
|
||||
"github.com/gogf/gf/os/gcache"
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
"github.com/gogf/gf/text/gregex"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
@ -32,22 +31,34 @@ var (
|
||||
lastOperatorReg = regexp.MustCompile(`[<>=]+\s*$`)
|
||||
)
|
||||
|
||||
// Master creates and returns a connection from master node if master-slave configured.
|
||||
// It returns the default connection if master-slave not configured.
|
||||
func (c *Core) Master() (*sql.DB, error) {
|
||||
return c.getSqlDb(true, c.schema.Val())
|
||||
}
|
||||
|
||||
// Slave creates and returns a connection from slave node if master-slave configured.
|
||||
// It returns the default connection if master-slave not configured.
|
||||
func (c *Core) Slave() (*sql.DB, error) {
|
||||
return c.getSqlDb(false, c.schema.Val())
|
||||
}
|
||||
|
||||
// Query commits one query SQL to underlying driver and returns the execution result.
|
||||
// It is most commonly used for data querying.
|
||||
func (bs *dbBase) Query(query string, args ...interface{}) (rows *sql.Rows, err error) {
|
||||
link, err := bs.db.Slave()
|
||||
func (c *Core) Query(query string, args ...interface{}) (rows *sql.Rows, err error) {
|
||||
link, err := c.DB.Slave()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bs.db.doQuery(link, query, args...)
|
||||
return c.DB.DoQuery(link, query, args...)
|
||||
}
|
||||
|
||||
// doQuery commits the query string and its arguments to underlying driver
|
||||
// through given link object and returns the execution result.
|
||||
func (bs *dbBase) doQuery(link dbLink, query string, args ...interface{}) (rows *sql.Rows, err error) {
|
||||
func (c *Core) DoQuery(link Link, query string, args ...interface{}) (rows *sql.Rows, err error) {
|
||||
query, args = formatQuery(query, args)
|
||||
query = bs.db.handleSqlBeforeExec(query)
|
||||
if bs.db.getDebug() {
|
||||
query, args = c.DB.HandleSqlBeforeCommit(link, query, args)
|
||||
if c.DB.GetDebug() {
|
||||
mTime1 := gtime.TimestampMilli()
|
||||
rows, err = link.Query(query, args...)
|
||||
mTime2 := gtime.TimestampMilli()
|
||||
@ -59,7 +70,7 @@ func (bs *dbBase) doQuery(link dbLink, query string, args ...interface{}) (rows
|
||||
Start: mTime1,
|
||||
End: mTime2,
|
||||
}
|
||||
bs.printSql(s)
|
||||
c.writeSqlToLogger(s)
|
||||
} else {
|
||||
rows, err = link.Query(query, args...)
|
||||
}
|
||||
@ -73,20 +84,20 @@ func (bs *dbBase) doQuery(link dbLink, query string, args ...interface{}) (rows
|
||||
|
||||
// Exec commits one query SQL to underlying driver and returns the execution result.
|
||||
// It is most commonly used for data inserting and updating.
|
||||
func (bs *dbBase) Exec(query string, args ...interface{}) (result sql.Result, err error) {
|
||||
link, err := bs.db.Master()
|
||||
func (c *Core) Exec(query string, args ...interface{}) (result sql.Result, err error) {
|
||||
link, err := c.DB.Master()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bs.db.doExec(link, query, args...)
|
||||
return c.DB.DoExec(link, query, args...)
|
||||
}
|
||||
|
||||
// doExec commits the query string and its arguments to underlying driver
|
||||
// through given link object and returns the execution result.
|
||||
func (bs *dbBase) doExec(link dbLink, query string, args ...interface{}) (result sql.Result, err error) {
|
||||
func (c *Core) DoExec(link Link, query string, args ...interface{}) (result sql.Result, err error) {
|
||||
query, args = formatQuery(query, args)
|
||||
query = bs.db.handleSqlBeforeExec(query)
|
||||
if bs.db.getDebug() {
|
||||
query, args = c.DB.HandleSqlBeforeCommit(link, query, args)
|
||||
if c.DB.GetDebug() {
|
||||
mTime1 := gtime.TimestampMilli()
|
||||
result, err = link.Exec(query, args...)
|
||||
mTime2 := gtime.TimestampMilli()
|
||||
@ -98,7 +109,7 @@ func (bs *dbBase) doExec(link dbLink, query string, args ...interface{}) (result
|
||||
Start: mTime1,
|
||||
End: mTime2,
|
||||
}
|
||||
bs.printSql(s)
|
||||
c.writeSqlToLogger(s)
|
||||
} else {
|
||||
result, err = link.Exec(query, args...)
|
||||
}
|
||||
@ -113,50 +124,50 @@ func (bs *dbBase) doExec(link dbLink, query string, args ...interface{}) (result
|
||||
//
|
||||
// The parameter <execOnMaster> specifies whether executing the sql on master node,
|
||||
// or else it executes the sql on slave node if master-slave configured.
|
||||
func (bs *dbBase) Prepare(query string, execOnMaster ...bool) (*sql.Stmt, error) {
|
||||
func (c *Core) Prepare(query string, execOnMaster ...bool) (*sql.Stmt, error) {
|
||||
err := (error)(nil)
|
||||
link := (dbLink)(nil)
|
||||
link := (Link)(nil)
|
||||
if len(execOnMaster) > 0 && execOnMaster[0] {
|
||||
if link, err = bs.db.Master(); err != nil {
|
||||
if link, err = c.DB.Master(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
if link, err = bs.db.Slave(); err != nil {
|
||||
if link, err = c.DB.Slave(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return bs.db.doPrepare(link, query)
|
||||
return c.DB.DoPrepare(link, query)
|
||||
}
|
||||
|
||||
// doPrepare calls prepare function on given link object and returns the statement object.
|
||||
func (bs *dbBase) doPrepare(link dbLink, query string) (*sql.Stmt, error) {
|
||||
func (c *Core) DoPrepare(link Link, query string) (*sql.Stmt, error) {
|
||||
return link.Prepare(query)
|
||||
}
|
||||
|
||||
// GetAll queries and returns data records from database.
|
||||
func (bs *dbBase) GetAll(query string, args ...interface{}) (Result, error) {
|
||||
return bs.db.doGetAll(nil, query, args...)
|
||||
func (c *Core) GetAll(query string, args ...interface{}) (Result, error) {
|
||||
return c.DB.DoGetAll(nil, query, args...)
|
||||
}
|
||||
|
||||
// doGetAll queries and returns data records from database.
|
||||
func (bs *dbBase) doGetAll(link dbLink, query string, args ...interface{}) (result Result, err error) {
|
||||
func (c *Core) DoGetAll(link Link, query string, args ...interface{}) (result Result, err error) {
|
||||
if link == nil {
|
||||
link, err = bs.db.Slave()
|
||||
link, err = c.DB.Slave()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
rows, err := bs.doQuery(link, query, args...)
|
||||
rows, err := c.DB.DoQuery(link, query, args...)
|
||||
if err != nil || rows == nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
return bs.db.rowsToResult(rows)
|
||||
return c.DB.rowsToResult(rows)
|
||||
}
|
||||
|
||||
// GetOne queries and returns one record from database.
|
||||
func (bs *dbBase) GetOne(query string, args ...interface{}) (Record, error) {
|
||||
list, err := bs.GetAll(query, args...)
|
||||
func (c *Core) GetOne(query string, args ...interface{}) (Record, error) {
|
||||
list, err := c.DB.GetAll(query, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -166,10 +177,20 @@ func (bs *dbBase) GetOne(query string, args ...interface{}) (Record, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// GetArray queries and returns data values as slice from database.
|
||||
// Note that if there're multiple columns in the result, it returns just one column values randomly.
|
||||
func (c *Core) GetArray(query string, args ...interface{}) ([]Value, error) {
|
||||
all, err := c.DB.DoGetAll(nil, query, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return all.Array(), nil
|
||||
}
|
||||
|
||||
// GetStruct queries one record from database and converts it to given struct.
|
||||
// The parameter <pointer> should be a pointer to struct.
|
||||
func (bs *dbBase) GetStruct(pointer interface{}, query string, args ...interface{}) error {
|
||||
one, err := bs.GetOne(query, args...)
|
||||
func (c *Core) GetStruct(pointer interface{}, query string, args ...interface{}) error {
|
||||
one, err := c.DB.GetOne(query, args...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -181,8 +202,8 @@ func (bs *dbBase) GetStruct(pointer interface{}, query string, args ...interface
|
||||
|
||||
// GetStructs queries records from database and converts them to given struct.
|
||||
// The parameter <pointer> should be type of struct slice: []struct/[]*struct.
|
||||
func (bs *dbBase) GetStructs(pointer interface{}, query string, args ...interface{}) error {
|
||||
all, err := bs.GetAll(query, args...)
|
||||
func (c *Core) GetStructs(pointer interface{}, query string, args ...interface{}) error {
|
||||
all, err := c.DB.GetAll(query, args...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -198,7 +219,7 @@ func (bs *dbBase) GetStructs(pointer interface{}, query string, args ...interfac
|
||||
// If parameter <pointer> is type of struct pointer, it calls GetStruct internally for
|
||||
// the conversion. If parameter <pointer> is type of slice, it calls GetStructs internally
|
||||
// for conversion.
|
||||
func (bs *dbBase) GetScan(pointer interface{}, query string, args ...interface{}) error {
|
||||
func (c *Core) GetScan(pointer interface{}, query string, args ...interface{}) error {
|
||||
t := reflect.TypeOf(pointer)
|
||||
k := t.Kind()
|
||||
if k != reflect.Ptr {
|
||||
@ -207,9 +228,9 @@ func (bs *dbBase) GetScan(pointer interface{}, query string, args ...interface{}
|
||||
k = t.Elem().Kind()
|
||||
switch k {
|
||||
case reflect.Array, reflect.Slice:
|
||||
return bs.db.GetStructs(pointer, query, args...)
|
||||
return c.DB.GetStructs(pointer, query, args...)
|
||||
case reflect.Struct:
|
||||
return bs.db.GetStruct(pointer, query, args...)
|
||||
return c.DB.GetStruct(pointer, query, args...)
|
||||
}
|
||||
return fmt.Errorf("element type should be type of struct/slice, unsupported: %v", k)
|
||||
}
|
||||
@ -217,8 +238,8 @@ func (bs *dbBase) GetScan(pointer interface{}, query string, args ...interface{}
|
||||
// GetValue queries and returns the field value from database.
|
||||
// The sql should queries only one field from database, or else it returns only one
|
||||
// field of the result.
|
||||
func (bs *dbBase) GetValue(query string, args ...interface{}) (Value, error) {
|
||||
one, err := bs.GetOne(query, args...)
|
||||
func (c *Core) GetValue(query string, args ...interface{}) (Value, error) {
|
||||
one, err := c.DB.GetOne(query, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -229,13 +250,13 @@ func (bs *dbBase) GetValue(query string, args ...interface{}) (Value, error) {
|
||||
}
|
||||
|
||||
// GetCount queries and returns the count from database.
|
||||
func (bs *dbBase) GetCount(query string, args ...interface{}) (int, error) {
|
||||
func (c *Core) GetCount(query string, args ...interface{}) (int, error) {
|
||||
// If the query fields do not contains function "COUNT",
|
||||
// it replaces the query string and adds the "COUNT" function to the fields.
|
||||
if !gregex.IsMatchString(`(?i)SELECT\s+COUNT\(.+\)\s+FROM`, query) {
|
||||
query, _ = gregex.ReplaceString(`(?i)(SELECT)\s+(.+)\s+(FROM)`, `$1 COUNT($2) $3`, query)
|
||||
}
|
||||
value, err := bs.GetValue(query, args...)
|
||||
value, err := c.DB.GetValue(query, args...)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@ -243,8 +264,8 @@ func (bs *dbBase) GetCount(query string, args ...interface{}) (int, error) {
|
||||
}
|
||||
|
||||
// PingMaster pings the master node to check authentication or keeps the connection alive.
|
||||
func (bs *dbBase) PingMaster() error {
|
||||
if master, err := bs.db.Master(); err != nil {
|
||||
func (c *Core) PingMaster() error {
|
||||
if master, err := c.DB.Master(); err != nil {
|
||||
return err
|
||||
} else {
|
||||
return master.Ping()
|
||||
@ -252,8 +273,8 @@ func (bs *dbBase) PingMaster() error {
|
||||
}
|
||||
|
||||
// PingSlave pings the slave node to check authentication or keeps the connection alive.
|
||||
func (bs *dbBase) PingSlave() error {
|
||||
if slave, err := bs.db.Slave(); err != nil {
|
||||
func (c *Core) PingSlave() error {
|
||||
if slave, err := c.DB.Slave(); err != nil {
|
||||
return err
|
||||
} else {
|
||||
return slave.Ping()
|
||||
@ -264,13 +285,13 @@ func (bs *dbBase) PingSlave() error {
|
||||
// You should call Commit or Rollback functions of the transaction object
|
||||
// if you no longer use the transaction. Commit or Rollback functions will also
|
||||
// close the transaction automatically.
|
||||
func (bs *dbBase) Begin() (*TX, error) {
|
||||
if master, err := bs.db.Master(); err != nil {
|
||||
func (c *Core) Begin() (*TX, error) {
|
||||
if master, err := c.DB.Master(); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
if tx, err := master.Begin(); err == nil {
|
||||
return &TX{
|
||||
db: bs.db,
|
||||
db: c.DB,
|
||||
tx: tx,
|
||||
master: master,
|
||||
}, nil
|
||||
@ -289,8 +310,8 @@ func (bs *dbBase) Begin() (*TX, error) {
|
||||
// Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"})
|
||||
//
|
||||
// The parameter <batch> specifies the batch operation count when given data is slice.
|
||||
func (bs *dbBase) Insert(table string, data interface{}, batch ...int) (sql.Result, error) {
|
||||
return bs.db.doInsert(nil, table, data, gINSERT_OPTION_DEFAULT, batch...)
|
||||
func (c *Core) Insert(table string, data interface{}, batch ...int) (sql.Result, error) {
|
||||
return c.DB.DoInsert(nil, table, data, gINSERT_OPTION_DEFAULT, batch...)
|
||||
}
|
||||
|
||||
// InsertIgnore does "INSERT IGNORE INTO ..." statement for the table.
|
||||
@ -302,8 +323,8 @@ func (bs *dbBase) Insert(table string, data interface{}, batch ...int) (sql.Resu
|
||||
// Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"})
|
||||
//
|
||||
// The parameter <batch> specifies the batch operation count when given data is slice.
|
||||
func (bs *dbBase) InsertIgnore(table string, data interface{}, batch ...int) (sql.Result, error) {
|
||||
return bs.db.doInsert(nil, table, data, gINSERT_OPTION_IGNORE, batch...)
|
||||
func (c *Core) InsertIgnore(table string, data interface{}, batch ...int) (sql.Result, error) {
|
||||
return c.DB.DoInsert(nil, table, data, gINSERT_OPTION_IGNORE, batch...)
|
||||
}
|
||||
|
||||
// Replace does "REPLACE INTO ..." statement for the table.
|
||||
@ -318,8 +339,8 @@ func (bs *dbBase) InsertIgnore(table string, data interface{}, batch ...int) (sq
|
||||
// The parameter <data> can be type of map/gmap/struct/*struct/[]map/[]struct, etc.
|
||||
// If given data is type of slice, it then does batch replacing, and the optional parameter
|
||||
// <batch> specifies the batch operation count.
|
||||
func (bs *dbBase) Replace(table string, data interface{}, batch ...int) (sql.Result, error) {
|
||||
return bs.db.doInsert(nil, table, data, gINSERT_OPTION_REPLACE, batch...)
|
||||
func (c *Core) Replace(table string, data interface{}, batch ...int) (sql.Result, error) {
|
||||
return c.DB.DoInsert(nil, table, data, gINSERT_OPTION_REPLACE, batch...)
|
||||
}
|
||||
|
||||
// Save does "INSERT INTO ... ON DUPLICATE KEY UPDATE..." statement for the table.
|
||||
@ -333,54 +354,60 @@ func (bs *dbBase) Replace(table string, data interface{}, batch ...int) (sql.Res
|
||||
//
|
||||
// If given data is type of slice, it then does batch saving, and the optional parameter
|
||||
// <batch> specifies the batch operation count.
|
||||
func (bs *dbBase) Save(table string, data interface{}, batch ...int) (sql.Result, error) {
|
||||
return bs.db.doInsert(nil, table, data, gINSERT_OPTION_SAVE, batch...)
|
||||
func (c *Core) Save(table string, data interface{}, batch ...int) (sql.Result, error) {
|
||||
return c.DB.DoInsert(nil, table, data, gINSERT_OPTION_SAVE, batch...)
|
||||
}
|
||||
|
||||
// doInsert inserts or updates data for given table.
|
||||
//
|
||||
// The parameter <data> can be type of map/gmap/struct/*struct/[]map/[]struct, etc.
|
||||
// Eg:
|
||||
// Data(g.Map{"uid": 10000, "name":"john"})
|
||||
// Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"})
|
||||
//
|
||||
// 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 (bs *dbBase) doInsert(link dbLink, table string, data interface{}, option int, batch ...int) (result sql.Result, err error) {
|
||||
func (c *Core) DoInsert(link Link, table string, data interface{}, option int, batch ...int) (result sql.Result, err error) {
|
||||
var fields []string
|
||||
var values []string
|
||||
var params []interface{}
|
||||
var dataMap Map
|
||||
table = bs.db.handleTableName(table)
|
||||
rv := reflect.ValueOf(data)
|
||||
kind := rv.Kind()
|
||||
if kind == reflect.Ptr {
|
||||
rv = rv.Elem()
|
||||
kind = rv.Kind()
|
||||
table = c.DB.QuotePrefixTableName(table)
|
||||
reflectValue := reflect.ValueOf(data)
|
||||
reflectKind := reflectValue.Kind()
|
||||
if reflectKind == reflect.Ptr {
|
||||
reflectValue = reflectValue.Elem()
|
||||
reflectKind = reflectValue.Kind()
|
||||
}
|
||||
switch kind {
|
||||
switch reflectKind {
|
||||
case reflect.Slice, reflect.Array:
|
||||
return bs.db.doBatchInsert(link, table, data, option, batch...)
|
||||
return c.DB.DoBatchInsert(link, table, data, option, batch...)
|
||||
case reflect.Map, reflect.Struct:
|
||||
dataMap = varToMapDeep(data)
|
||||
dataMap = DataToMapDeep(data)
|
||||
default:
|
||||
return result, errors.New(fmt.Sprint("unsupported data type:", kind))
|
||||
return result, errors.New(fmt.Sprint("unsupported data type:", reflectKind))
|
||||
}
|
||||
if len(dataMap) == 0 {
|
||||
return nil, errors.New("data cannot be empty")
|
||||
}
|
||||
charL, charR := bs.db.getChars()
|
||||
charL, charR := c.DB.GetChars()
|
||||
for k, v := range dataMap {
|
||||
fields = append(fields, charL+k+charR)
|
||||
values = append(values, "?")
|
||||
params = append(params, v)
|
||||
}
|
||||
operation := getInsertOperationByOption(option)
|
||||
operation := GetInsertOperationByOption(option)
|
||||
updateStr := ""
|
||||
if option == gINSERT_OPTION_SAVE {
|
||||
for k, _ := range dataMap {
|
||||
if len(updateStr) > 0 {
|
||||
updateStr += ","
|
||||
}
|
||||
updateStr += fmt.Sprintf("%s%s%s=VALUES(%s%s%s)",
|
||||
updateStr += fmt.Sprintf(
|
||||
"%s%s%s=VALUES(%s%s%s)",
|
||||
charL, k, charR,
|
||||
charL, k, charR,
|
||||
)
|
||||
@ -388,45 +415,50 @@ func (bs *dbBase) doInsert(link dbLink, table string, data interface{}, option i
|
||||
updateStr = fmt.Sprintf("ON DUPLICATE KEY UPDATE %s", updateStr)
|
||||
}
|
||||
if link == nil {
|
||||
if link, err = bs.db.Master(); err != nil {
|
||||
if link, err = c.DB.Master(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return bs.db.doExec(link, fmt.Sprintf("%s INTO %s(%s) VALUES(%s) %s",
|
||||
operation, table, strings.Join(fields, ","),
|
||||
strings.Join(values, ","), updateStr),
|
||||
params...)
|
||||
return c.DB.DoExec(
|
||||
link,
|
||||
fmt.Sprintf(
|
||||
"%s INTO %s(%s) VALUES(%s) %s",
|
||||
operation, table, strings.Join(fields, ","),
|
||||
strings.Join(values, ","), updateStr,
|
||||
),
|
||||
params...,
|
||||
)
|
||||
}
|
||||
|
||||
// BatchInsert batch inserts data.
|
||||
// The parameter <list> must be type of slice of map or struct.
|
||||
func (bs *dbBase) BatchInsert(table string, list interface{}, batch ...int) (sql.Result, error) {
|
||||
return bs.db.doBatchInsert(nil, table, list, gINSERT_OPTION_DEFAULT, batch...)
|
||||
func (c *Core) BatchInsert(table string, list interface{}, batch ...int) (sql.Result, error) {
|
||||
return c.DB.DoBatchInsert(nil, table, list, gINSERT_OPTION_DEFAULT, batch...)
|
||||
}
|
||||
|
||||
// BatchInsert batch inserts data with ignore option.
|
||||
// The parameter <list> must be type of slice of map or struct.
|
||||
func (bs *dbBase) BatchInsertIgnore(table string, list interface{}, batch ...int) (sql.Result, error) {
|
||||
return bs.db.doBatchInsert(nil, table, list, gINSERT_OPTION_IGNORE, batch...)
|
||||
func (c *Core) BatchInsertIgnore(table string, list interface{}, batch ...int) (sql.Result, error) {
|
||||
return c.DB.DoBatchInsert(nil, table, list, gINSERT_OPTION_IGNORE, batch...)
|
||||
}
|
||||
|
||||
// BatchReplace batch replaces data.
|
||||
// The parameter <list> must be type of slice of map or struct.
|
||||
func (bs *dbBase) BatchReplace(table string, list interface{}, batch ...int) (sql.Result, error) {
|
||||
return bs.db.doBatchInsert(nil, table, list, gINSERT_OPTION_REPLACE, batch...)
|
||||
func (c *Core) BatchReplace(table string, list interface{}, batch ...int) (sql.Result, error) {
|
||||
return c.DB.DoBatchInsert(nil, table, list, gINSERT_OPTION_REPLACE, batch...)
|
||||
}
|
||||
|
||||
// BatchSave batch replaces data.
|
||||
// The parameter <list> must be type of slice of map or struct.
|
||||
func (bs *dbBase) BatchSave(table string, list interface{}, batch ...int) (sql.Result, error) {
|
||||
return bs.db.doBatchInsert(nil, table, list, gINSERT_OPTION_SAVE, batch...)
|
||||
func (c *Core) BatchSave(table string, list interface{}, batch ...int) (sql.Result, error) {
|
||||
return c.DB.DoBatchInsert(nil, table, list, gINSERT_OPTION_SAVE, batch...)
|
||||
}
|
||||
|
||||
// doBatchInsert batch inserts/replaces/saves data.
|
||||
func (bs *dbBase) doBatchInsert(link dbLink, table string, list interface{}, option int, batch ...int) (result sql.Result, err error) {
|
||||
func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option int, batch ...int) (result sql.Result, err error) {
|
||||
var keys, values []string
|
||||
var params []interface{}
|
||||
table = bs.db.handleTableName(table)
|
||||
table = c.DB.QuotePrefixTableName(table)
|
||||
listMap := (List)(nil)
|
||||
switch v := list.(type) {
|
||||
case Result:
|
||||
@ -449,10 +481,10 @@ func (bs *dbBase) doBatchInsert(link dbLink, table string, list interface{}, opt
|
||||
case reflect.Slice, reflect.Array:
|
||||
listMap = make(List, rv.Len())
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
listMap[i] = varToMapDeep(rv.Index(i).Interface())
|
||||
listMap[i] = DataToMapDeep(rv.Index(i).Interface())
|
||||
}
|
||||
case reflect.Map, reflect.Struct:
|
||||
listMap = List{varToMapDeep(list)}
|
||||
listMap = List{DataToMapDeep(list)}
|
||||
default:
|
||||
return result, errors.New(fmt.Sprint("unsupported list type:", kind))
|
||||
}
|
||||
@ -461,7 +493,7 @@ func (bs *dbBase) doBatchInsert(link dbLink, table string, list interface{}, opt
|
||||
return result, errors.New("data list cannot be empty")
|
||||
}
|
||||
if link == nil {
|
||||
if link, err = bs.db.Master(); err != nil {
|
||||
if link, err = c.DB.Master(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -471,20 +503,21 @@ func (bs *dbBase) doBatchInsert(link dbLink, table string, list interface{}, opt
|
||||
keys = append(keys, k)
|
||||
holders = append(holders, "?")
|
||||
}
|
||||
// Prepare the result pointer.
|
||||
batchResult := new(batchSqlResult)
|
||||
charL, charR := bs.db.getChars()
|
||||
// Prepare the batch result pointer.
|
||||
batchResult := new(SqlResult)
|
||||
charL, charR := c.DB.GetChars()
|
||||
keysStr := charL + strings.Join(keys, charR+","+charL) + charR
|
||||
valueHolderStr := "(" + strings.Join(holders, ",") + ")"
|
||||
|
||||
operation := getInsertOperationByOption(option)
|
||||
operation := GetInsertOperationByOption(option)
|
||||
updateStr := ""
|
||||
if option == gINSERT_OPTION_SAVE {
|
||||
for _, k := range keys {
|
||||
if len(updateStr) > 0 {
|
||||
updateStr += ","
|
||||
}
|
||||
updateStr += fmt.Sprintf("%s%s%s=VALUES(%s%s%s)",
|
||||
updateStr += fmt.Sprintf(
|
||||
"%s%s%s=VALUES(%s%s%s)",
|
||||
charL, k, charR,
|
||||
charL, k, charR,
|
||||
)
|
||||
@ -504,7 +537,7 @@ func (bs *dbBase) doBatchInsert(link dbLink, table string, list interface{}, opt
|
||||
}
|
||||
values = append(values, valueHolderStr)
|
||||
if len(values) == batchNum || (i == listMapLen-1 && len(values) > 0) {
|
||||
r, err := bs.db.doExec(
|
||||
r, err := c.DB.DoExec(
|
||||
link,
|
||||
fmt.Sprintf(
|
||||
"%s INTO %s(%s) VALUES%s %s",
|
||||
@ -522,8 +555,8 @@ func (bs *dbBase) doBatchInsert(link dbLink, table string, list interface{}, opt
|
||||
if n, err := r.RowsAffected(); err != nil {
|
||||
return r, err
|
||||
} else {
|
||||
batchResult.lastResult = r
|
||||
batchResult.rowsAffected += n
|
||||
batchResult.result = r
|
||||
batchResult.affected += n
|
||||
}
|
||||
params = params[:0]
|
||||
values = values[:0]
|
||||
@ -546,18 +579,18 @@ func (bs *dbBase) doBatchInsert(link dbLink, table string, list interface{}, opt
|
||||
// "status IN (?)", g.Slice{1,2,3}
|
||||
// "age IN(?,?)", 18, 50
|
||||
// User{ Id : 1, UserName : "john"}
|
||||
func (bs *dbBase) Update(table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error) {
|
||||
newWhere, newArgs := formatWhere(bs.db, condition, args, false)
|
||||
func (c *Core) Update(table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error) {
|
||||
newWhere, newArgs := formatWhere(c.DB, condition, args, false)
|
||||
if newWhere != "" {
|
||||
newWhere = " WHERE " + newWhere
|
||||
}
|
||||
return bs.db.doUpdate(nil, table, data, newWhere, newArgs...)
|
||||
return c.DB.DoUpdate(nil, table, data, newWhere, newArgs...)
|
||||
}
|
||||
|
||||
// doUpdate does "UPDATE ... " statement for the table.
|
||||
// Also see Update.
|
||||
func (bs *dbBase) doUpdate(link dbLink, table string, data interface{}, condition string, args ...interface{}) (result sql.Result, err error) {
|
||||
table = bs.db.handleTableName(table)
|
||||
func (c *Core) DoUpdate(link Link, table string, data interface{}, condition string, args ...interface{}) (result sql.Result, err error) {
|
||||
table = c.DB.QuotePrefixTableName(table)
|
||||
updates := ""
|
||||
rv := reflect.ValueOf(data)
|
||||
kind := rv.Kind()
|
||||
@ -569,8 +602,8 @@ func (bs *dbBase) doUpdate(link dbLink, table string, data interface{}, conditio
|
||||
switch kind {
|
||||
case reflect.Map, reflect.Struct:
|
||||
var fields []string
|
||||
for k, v := range varToMapDeep(data) {
|
||||
fields = append(fields, bs.db.quoteWord(k)+"=?")
|
||||
for k, v := range DataToMapDeep(data) {
|
||||
fields = append(fields, c.DB.QuoteWord(k)+"=?")
|
||||
params = append(params, v)
|
||||
}
|
||||
updates = strings.Join(fields, ",")
|
||||
@ -585,11 +618,15 @@ func (bs *dbBase) doUpdate(link dbLink, table string, data interface{}, conditio
|
||||
}
|
||||
// If no link passed, it then uses the master link.
|
||||
if link == nil {
|
||||
if link, err = bs.db.Master(); err != nil {
|
||||
if link, err = c.DB.Master(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return bs.db.doExec(link, fmt.Sprintf("UPDATE %s SET %s%s", table, updates, condition), args...)
|
||||
return c.DB.DoExec(
|
||||
link,
|
||||
fmt.Sprintf("UPDATE %s SET %s%s", table, updates, condition),
|
||||
args...,
|
||||
)
|
||||
}
|
||||
|
||||
// Delete does "DELETE FROM ... " statement for the table.
|
||||
@ -603,38 +640,28 @@ func (bs *dbBase) doUpdate(link dbLink, table string, data interface{}, conditio
|
||||
// "status IN (?)", g.Slice{1,2,3}
|
||||
// "age IN(?,?)", 18, 50
|
||||
// User{ Id : 1, UserName : "john"}
|
||||
func (bs *dbBase) Delete(table string, condition interface{}, args ...interface{}) (result sql.Result, err error) {
|
||||
newWhere, newArgs := formatWhere(bs.db, condition, args, false)
|
||||
func (c *Core) Delete(table string, condition interface{}, args ...interface{}) (result sql.Result, err error) {
|
||||
newWhere, newArgs := formatWhere(c.DB, condition, args, false)
|
||||
if newWhere != "" {
|
||||
newWhere = " WHERE " + newWhere
|
||||
}
|
||||
return bs.db.doDelete(nil, table, newWhere, newArgs...)
|
||||
return c.DB.DoDelete(nil, table, newWhere, newArgs...)
|
||||
}
|
||||
|
||||
// doDelete does "DELETE FROM ... " statement for the table.
|
||||
// Also see Delete.
|
||||
func (bs *dbBase) doDelete(link dbLink, table string, condition string, args ...interface{}) (result sql.Result, err error) {
|
||||
func (c *Core) DoDelete(link Link, table string, condition string, args ...interface{}) (result sql.Result, err error) {
|
||||
if link == nil {
|
||||
if link, err = bs.db.Master(); err != nil {
|
||||
if link, err = c.DB.Master(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
table = bs.db.handleTableName(table)
|
||||
return bs.db.doExec(link, fmt.Sprintf("DELETE FROM %s%s", table, condition), args...)
|
||||
}
|
||||
|
||||
// getCache returns the internal cache object.
|
||||
func (bs *dbBase) getCache() *gcache.Cache {
|
||||
return bs.cache
|
||||
}
|
||||
|
||||
// getPrefix returns the table prefix string configured.
|
||||
func (bs *dbBase) getPrefix() string {
|
||||
return bs.prefix
|
||||
table = c.DB.QuotePrefixTableName(table)
|
||||
return c.DB.DoExec(link, fmt.Sprintf("DELETE FROM %s%s", table, condition), args...)
|
||||
}
|
||||
|
||||
// rowsToResult converts underlying data record type sql.Rows to Result type.
|
||||
func (bs *dbBase) rowsToResult(rows *sql.Rows) (Result, error) {
|
||||
func (c *Core) rowsToResult(rows *sql.Rows) (Result, error) {
|
||||
if !rows.Next() {
|
||||
return nil, nil
|
||||
}
|
||||
@ -671,7 +698,7 @@ func (bs *dbBase) rowsToResult(rows *sql.Rows) (Result, error) {
|
||||
// it should do a copy of it.
|
||||
v := make([]byte, len(value))
|
||||
copy(v, value)
|
||||
row[columnNames[i]] = gvar.New(bs.db.convertValue(v, columnTypes[i]))
|
||||
row[columnNames[i]] = gvar.New(c.DB.convertValue(v, columnTypes[i]))
|
||||
}
|
||||
}
|
||||
records = append(records, row)
|
||||
@ -682,39 +709,23 @@ func (bs *dbBase) rowsToResult(rows *sql.Rows) (Result, error) {
|
||||
return records, nil
|
||||
}
|
||||
|
||||
// handleTableName adds prefix string and quote chars for the table. It handles table string like:
|
||||
// "user", "user u", "user,user_detail", "user u, user_detail ut", "user as u, user_detail as ut".
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
// It just returns the pointer address.
|
||||
//
|
||||
// Note that, this will automatically checks the table prefix whether already added, if true it does
|
||||
// nothing to the table name, or else adds the prefix to the table name.
|
||||
func (bs *dbBase) handleTableName(table string) string {
|
||||
charLeft, charRight := bs.db.getChars()
|
||||
prefix := bs.db.getPrefix()
|
||||
return doHandleTableName(table, prefix, charLeft, charRight)
|
||||
// Note that this interface implements mainly for workaround for a json infinite loop bug
|
||||
// of Golang version < v1.14.
|
||||
func (c *Core) MarshalJSON() ([]byte, error) {
|
||||
return []byte(fmt.Sprintf(`%+v`, c)), nil
|
||||
}
|
||||
|
||||
// quoteWord checks given string <s> a word, if true quotes it with security chars of the database
|
||||
// and returns the quoted string; or else return <s> without any change.
|
||||
func (bs *dbBase) quoteWord(s string) string {
|
||||
charLeft, charRight := bs.db.getChars()
|
||||
return doQuoteWord(s, charLeft, charRight)
|
||||
}
|
||||
|
||||
// quoteString quotes string with quote chars. Strings like:
|
||||
// "user", "user u", "user,user_detail", "user u, user_detail ut", "u.id asc".
|
||||
func (bs *dbBase) quoteString(s string) string {
|
||||
charLeft, charRight := bs.db.getChars()
|
||||
return doQuoteString(s, charLeft, charRight)
|
||||
}
|
||||
|
||||
// printSql outputs the sql object to logger.
|
||||
// writeSqlToLogger outputs the sql object to logger.
|
||||
// It is enabled when configuration "debug" is true.
|
||||
func (bs *dbBase) printSql(v *Sql) {
|
||||
s := fmt.Sprintf("[%d ms] %s", v.End-v.Start, v.Format)
|
||||
func (c *Core) writeSqlToLogger(v *Sql) {
|
||||
s := fmt.Sprintf("[%3d ms] %s", v.End-v.Start, v.Format)
|
||||
if v.Error != nil {
|
||||
s += "\nError: " + v.Error.Error()
|
||||
bs.logger.StackWithFilter(gPATH_FILTER_KEY).Error(s)
|
||||
c.logger.StackWithFilter(gPATH_FILTER_KEY).Error(s)
|
||||
} else {
|
||||
bs.logger.StackWithFilter(gPATH_FILTER_KEY).Debug(s)
|
||||
c.logger.StackWithFilter(gPATH_FILTER_KEY).Debug(s)
|
||||
}
|
||||
}
|
||||
@ -8,6 +8,7 @@ package gdb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/os/gcache"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@ -114,29 +115,29 @@ func GetDefaultGroup() string {
|
||||
}
|
||||
|
||||
// SetLogger sets the logger for orm.
|
||||
func (bs *dbBase) SetLogger(logger *glog.Logger) {
|
||||
bs.logger = logger
|
||||
func (c *Core) SetLogger(logger *glog.Logger) {
|
||||
c.logger = logger
|
||||
}
|
||||
|
||||
// GetLogger returns the logger of the orm.
|
||||
func (bs *dbBase) GetLogger() *glog.Logger {
|
||||
return bs.logger
|
||||
func (c *Core) GetLogger() *glog.Logger {
|
||||
return c.logger
|
||||
}
|
||||
|
||||
// SetMaxIdleConnCount sets the max idle connection count for underlying connection pool.
|
||||
func (bs *dbBase) SetMaxIdleConnCount(n int) {
|
||||
bs.maxIdleConnCount = n
|
||||
func (c *Core) SetMaxIdleConnCount(n int) {
|
||||
c.maxIdleConnCount = n
|
||||
}
|
||||
|
||||
// SetMaxOpenConnCount sets the max open connection count for underlying connection pool.
|
||||
func (bs *dbBase) SetMaxOpenConnCount(n int) {
|
||||
bs.maxOpenConnCount = n
|
||||
func (c *Core) SetMaxOpenConnCount(n int) {
|
||||
c.maxOpenConnCount = n
|
||||
}
|
||||
|
||||
// SetMaxConnLifetime sets the connection TTL for underlying connection pool.
|
||||
// If parameter <d> <= 0, it means the connection never expires.
|
||||
func (bs *dbBase) SetMaxConnLifetime(d time.Duration) {
|
||||
bs.maxConnLifetime = d
|
||||
func (c *Core) SetMaxConnLifetime(d time.Duration) {
|
||||
c.maxConnLifetime = d
|
||||
}
|
||||
|
||||
// String returns the node as string.
|
||||
@ -155,14 +156,36 @@ func (node *ConfigNode) String() string {
|
||||
}
|
||||
|
||||
// SetDebug enables/disables the debug mode.
|
||||
func (bs *dbBase) SetDebug(debug bool) {
|
||||
if bs.debug.Val() == debug {
|
||||
func (c *Core) SetDebug(debug bool) {
|
||||
if c.debug.Val() == debug {
|
||||
return
|
||||
}
|
||||
bs.debug.Set(debug)
|
||||
c.debug.Set(debug)
|
||||
}
|
||||
|
||||
// getDebug returns the debug value.
|
||||
func (bs *dbBase) getDebug() bool {
|
||||
return bs.debug.Val()
|
||||
// GetDebug returns the debug value.
|
||||
func (c *Core) GetDebug() bool {
|
||||
return c.debug.Val()
|
||||
}
|
||||
|
||||
// GetCache returns the internal cache object.
|
||||
func (c *Core) GetCache() *gcache.Cache {
|
||||
return c.cache
|
||||
}
|
||||
|
||||
// GetPrefix returns the table prefix string configured.
|
||||
func (c *Core) GetPrefix() string {
|
||||
return c.prefix
|
||||
}
|
||||
|
||||
// SetSchema changes the schema for this database connection object.
|
||||
// Importantly note that when schema configuration changed for the database,
|
||||
// it affects all operations on the database object in the future.
|
||||
func (c *Core) SetSchema(schema string) {
|
||||
c.schema.Set(schema)
|
||||
}
|
||||
|
||||
// GetSchema returns the schema configured.
|
||||
func (c *Core) GetSchema() string {
|
||||
return c.schema.Val()
|
||||
}
|
||||
86
database/gdb/gdb_core_utility.go
Normal file
86
database/gdb/gdb_core_utility.go
Normal file
@ -0,0 +1,86 @@
|
||||
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
//
|
||||
|
||||
package gdb
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
)
|
||||
|
||||
// GetMaster acts like function Master but with additional <schema> parameter specifying
|
||||
// the schema for the connection. It is defined for internal usage.
|
||||
// Also see Master.
|
||||
func (c *Core) GetMaster(schema ...string) (*sql.DB, error) {
|
||||
return c.getSqlDb(true, schema...)
|
||||
}
|
||||
|
||||
// GetSlave acts like function Slave but with additional <schema> parameter specifying
|
||||
// the schema for the connection. It is defined for internal usage.
|
||||
// Also see Slave.
|
||||
func (c *Core) GetSlave(schema ...string) (*sql.DB, error) {
|
||||
return c.getSqlDb(false, schema...)
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (c *Core) QuoteWord(s string) string {
|
||||
charLeft, charRight := c.DB.GetChars()
|
||||
return doQuoteWord(s, charLeft, charRight)
|
||||
}
|
||||
|
||||
// QuoteString quotes string with quote chars. Strings like:
|
||||
// "user", "user u", "user,user_detail", "user u, user_detail ut", "u.id asc".
|
||||
func (c *Core) QuoteString(s string) string {
|
||||
charLeft, charRight := c.DB.GetChars()
|
||||
return doQuoteString(s, charLeft, charRight)
|
||||
}
|
||||
|
||||
// QuotePrefixTableName adds prefix string and quotes chars for the table.
|
||||
// It handles table string like:
|
||||
// "user", "user u",
|
||||
// "user,user_detail",
|
||||
// "user u, user_detail ut",
|
||||
// "user as u, user_detail as ut".
|
||||
//
|
||||
// Note that, this will automatically checks the table prefix whether already added,
|
||||
// if true it does nothing to the table name, or else adds the prefix to the table name.
|
||||
func (c *Core) QuotePrefixTableName(table string) string {
|
||||
charLeft, charRight := c.DB.GetChars()
|
||||
return doHandleTableName(table, c.DB.GetPrefix(), charLeft, charRight)
|
||||
}
|
||||
|
||||
// GetChars returns the security char for current database.
|
||||
// It does nothing in default.
|
||||
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.
|
||||
//
|
||||
// It does nothing in default.
|
||||
func (c *Core) Tables(schema ...string) (tables []string, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// TableFields retrieves and returns the fields information of specified table of current schema.
|
||||
//
|
||||
// Note that it returns a map containing the field name and its corresponding fields.
|
||||
// As a map is unsorted, the TableField struct has a "Index" field marks its sequence in the fields.
|
||||
//
|
||||
// It's using cache feature to enhance the performance, which is never expired util the process restarts.
|
||||
//
|
||||
// It does nothing in default.
|
||||
func (c *Core) TableFields(table string, schema ...string) (fields map[string]*TableField, err error) {
|
||||
return
|
||||
}
|
||||
@ -5,7 +5,7 @@
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
//
|
||||
// Note:
|
||||
// 1. It needs manually import: _ "github.com/lib/pq"
|
||||
// 1. It needs manually import: _ "github.com/denisenkom/go-mssqldb"
|
||||
// 2. It does not support Save/Replace features.
|
||||
// 3. It does not support LastInsertId.
|
||||
|
||||
@ -22,11 +22,21 @@ import (
|
||||
"github.com/gogf/gf/text/gregex"
|
||||
)
|
||||
|
||||
type dbMssql struct {
|
||||
*dbBase
|
||||
// DriverMssql is the driver for SQL server database.
|
||||
type DriverMssql struct {
|
||||
*Core
|
||||
}
|
||||
|
||||
func (db *dbMssql) Open(config *ConfigNode) (*sql.DB, error) {
|
||||
// New creates and returns a database object for SQL server.
|
||||
// It implements the interface of gdb.Driver for extra database driver installation.
|
||||
func (d *DriverMssql) New(core *Core, node *ConfigNode) (DB, error) {
|
||||
return &DriverMssql{
|
||||
Core: core,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 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
|
||||
@ -44,21 +54,24 @@ func (db *dbMssql) Open(config *ConfigNode) (*sql.DB, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (db *dbMssql) getChars() (charLeft string, charRight string) {
|
||||
// GetChars returns the security char for this type of database.
|
||||
func (d *DriverMssql) GetChars() (charLeft string, charRight string) {
|
||||
return "\"", "\""
|
||||
}
|
||||
|
||||
func (db *dbMssql) handleSqlBeforeExec(query string) string {
|
||||
index := 0
|
||||
// HandleSqlBeforeCommit deals with the sql string before commits it to underlying sql driver.
|
||||
func (d *DriverMssql) HandleSqlBeforeCommit(link Link, query string, args []interface{}) (string, []interface{}) {
|
||||
var index int
|
||||
// Convert place holder char '?' to string "@px".
|
||||
str, _ := gregex.ReplaceStringFunc("\\?", query, func(s string) string {
|
||||
index++
|
||||
return fmt.Sprintf("@p%d", index)
|
||||
})
|
||||
str, _ = gregex.ReplaceString("\"", "", str)
|
||||
return db.parseSql(str)
|
||||
return d.parseSql(str), args
|
||||
}
|
||||
|
||||
func (db *dbMssql) parseSql(sql string) string {
|
||||
func (d *DriverMssql) parseSql(sql string) string {
|
||||
// SELECT * FROM USER WHERE ID=1 LIMIT 1
|
||||
if m, _ := gregex.MatchString(`^SELECT(.+)LIMIT 1$`, sql); len(m) > 1 {
|
||||
return fmt.Sprintf(`SELECT TOP 1 %s`, m[1])
|
||||
@ -95,20 +108,26 @@ func (db *dbMssql) parseSql(sql string) string {
|
||||
if haveOrder {
|
||||
// 取order by 前面的字符串
|
||||
queryExpr, _ := gregex.MatchString("((?i)SELECT)(.+)((?i)ORDER BY)", sql)
|
||||
if len(queryExpr) != 4 || strings.EqualFold(queryExpr[1], "SELECT") == false || strings.EqualFold(queryExpr[3], "ORDER BY") == false {
|
||||
if len(queryExpr) != 4 ||
|
||||
strings.EqualFold(queryExpr[1], "SELECT") == false ||
|
||||
strings.EqualFold(queryExpr[3], "ORDER BY") == false {
|
||||
break
|
||||
}
|
||||
selectStr = queryExpr[2]
|
||||
|
||||
// 取order by表达式的值
|
||||
orderExpr, _ := gregex.MatchString("((?i)ORDER BY)(.+)((?i)LIMIT)", sql)
|
||||
if len(orderExpr) != 4 || strings.EqualFold(orderExpr[1], "ORDER BY") == false || strings.EqualFold(orderExpr[3], "LIMIT") == false {
|
||||
if len(orderExpr) != 4 ||
|
||||
strings.EqualFold(orderExpr[1], "ORDER BY") == false ||
|
||||
strings.EqualFold(orderExpr[3], "LIMIT") == false {
|
||||
break
|
||||
}
|
||||
orderStr = orderExpr[2]
|
||||
} else {
|
||||
queryExpr, _ := gregex.MatchString("((?i)SELECT)(.+)((?i)LIMIT)", sql)
|
||||
if len(queryExpr) != 4 || strings.EqualFold(queryExpr[1], "SELECT") == false || strings.EqualFold(queryExpr[3], "LIMIT") == false {
|
||||
if len(queryExpr) != 4 ||
|
||||
strings.EqualFold(queryExpr[1], "SELECT") == false ||
|
||||
strings.EqualFold(queryExpr[3], "LIMIT") == false {
|
||||
break
|
||||
}
|
||||
selectStr = queryExpr[2]
|
||||
@ -121,7 +140,8 @@ func (db *dbMssql) parseSql(sql string) string {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(res[index][i], "LIMIT") || strings.HasPrefix(res[index][i], "limit") {
|
||||
if strings.HasPrefix(res[index][i], "LIMIT") ||
|
||||
strings.HasPrefix(res[index][i], "limit") {
|
||||
first, _ = strconv.Atoi(res[index][i+1])
|
||||
limit, _ = strconv.Atoi(res[index][i+2])
|
||||
break
|
||||
@ -151,29 +171,46 @@ func (db *dbMssql) parseSql(sql string) string {
|
||||
return sql
|
||||
}
|
||||
|
||||
// TODO
|
||||
func (db *dbMssql) Tables(schema ...string) (tables []string, err error) {
|
||||
// Tables retrieves and returns the tables of current schema.
|
||||
// It's mainly used in cli tool chain for automatically generating the models.
|
||||
func (d *DriverMssql) Tables(schema ...string) (tables []string, err error) {
|
||||
var result Result
|
||||
link, err := d.DB.GetSlave(schema...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result, err = d.DB.DoGetAll(link, `SELECT NAME FROM SYSOBJECTS WHERE XTYPE='U' AND STATUS >= 0 ORDER BY NAME`)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, m := range result {
|
||||
for _, v := range m {
|
||||
tables = append(tables, v.String())
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (db *dbMssql) TableFields(table string, schema ...string) (fields map[string]*TableField, err error) {
|
||||
// TableFields retrieves and returns the fields information of specified table of current schema.
|
||||
func (d *DriverMssql) TableFields(table string, schema ...string) (fields map[string]*TableField, err error) {
|
||||
table = gstr.Trim(table)
|
||||
if gstr.Contains(table, " ") {
|
||||
panic("function TableFields supports only single table operations")
|
||||
}
|
||||
checkSchema := db.schema.Val()
|
||||
checkSchema := d.DB.GetSchema()
|
||||
if len(schema) > 0 && schema[0] != "" {
|
||||
checkSchema = schema[0]
|
||||
}
|
||||
v := db.cache.GetOrSetFunc(
|
||||
v := d.DB.GetCache().GetOrSetFunc(
|
||||
fmt.Sprintf(`mssql_table_fields_%s_%s`, table, checkSchema), func() interface{} {
|
||||
var result Result
|
||||
var link *sql.DB
|
||||
link, err = db.getSlave(checkSchema)
|
||||
link, err = d.DB.GetSlave(checkSchema)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
result, err = db.doGetAll(link, fmt.Sprintf(`
|
||||
result, err = d.DB.DoGetAll(link, fmt.Sprintf(`
|
||||
SELECT c.name as FIELD, CASE t.name
|
||||
WHEN 'numeric' THEN t.name + '(' + convert(varchar(20),c.xprec) + ',' + convert(varchar(20),c.xscale) + ')'
|
||||
WHEN 'char' THEN t.name + '(' + convert(varchar(20),c.length)+ ')'
|
||||
130
database/gdb/gdb_driver_mysql.go
Normal file
130
database/gdb/gdb_driver_mysql.go
Normal file
@ -0,0 +1,130 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gdb
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/internal/intlog"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
)
|
||||
|
||||
// DriverMysql is the driver for mysql database.
|
||||
type DriverMysql struct {
|
||||
*Core
|
||||
}
|
||||
|
||||
// New creates and returns a database object for mysql.
|
||||
// It implements the interface of gdb.Driver for extra database driver installation.
|
||||
func (d *DriverMysql) New(core *Core, node *ConfigNode) (DB, error) {
|
||||
return &DriverMysql{
|
||||
Core: core,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Open creates and returns a underlying sql.DB object for mysql.
|
||||
func (d *DriverMysql) Open(config *ConfigNode) (*sql.DB, error) {
|
||||
var source string
|
||||
if config.LinkInfo != "" {
|
||||
source = config.LinkInfo
|
||||
} else {
|
||||
source = fmt.Sprintf(
|
||||
"%s:%s@tcp(%s:%s)/%s?charset=%s&multiStatements=true&parseTime=true&loc=Local",
|
||||
config.User, config.Pass, config.Host, config.Port, config.Name, config.Charset,
|
||||
)
|
||||
}
|
||||
intlog.Printf("Open: %s", source)
|
||||
if db, err := sql.Open("mysql", source); err == nil {
|
||||
return db, nil
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// GetChars returns the security char for this type of database.
|
||||
func (d *DriverMysql) GetChars() (charLeft string, charRight string) {
|
||||
return "`", "`"
|
||||
}
|
||||
|
||||
// HandleSqlBeforeCommit handles the sql before posts it to database.
|
||||
func (d *DriverMysql) HandleSqlBeforeCommit(link Link, sql string, args []interface{}) (string, []interface{}) {
|
||||
return sql, args
|
||||
}
|
||||
|
||||
// Tables retrieves and returns the tables of current schema.
|
||||
// It's mainly used in cli tool chain for automatically generating the models.
|
||||
func (d *DriverMysql) Tables(schema ...string) (tables []string, err error) {
|
||||
var result Result
|
||||
link, err := d.DB.GetSlave(schema...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result, err = d.DB.DoGetAll(link, `SHOW TABLES`)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, m := range result {
|
||||
for _, v := range m {
|
||||
tables = append(tables, v.String())
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// TableFields retrieves and returns the fields information of specified table of current schema.
|
||||
//
|
||||
// Note that it returns a map containing the field name and its corresponding fields.
|
||||
// As a map is unsorted, the TableField struct has a "Index" field marks its sequence in the fields.
|
||||
//
|
||||
// It's using cache feature to enhance the performance, which is never expired util the process restarts.
|
||||
func (d *DriverMysql) TableFields(table string, schema ...string) (fields map[string]*TableField, err error) {
|
||||
table = gstr.Trim(table)
|
||||
if gstr.Contains(table, " ") {
|
||||
panic("function TableFields supports only single table operations")
|
||||
}
|
||||
checkSchema := d.schema.Val()
|
||||
if len(schema) > 0 && schema[0] != "" {
|
||||
checkSchema = schema[0]
|
||||
}
|
||||
v := d.cache.GetOrSetFunc(
|
||||
fmt.Sprintf(`mysql_table_fields_%s_%s`, table, checkSchema),
|
||||
func() interface{} {
|
||||
var result Result
|
||||
var link *sql.DB
|
||||
link, err = d.DB.GetSlave(checkSchema)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
result, err = d.DB.DoGetAll(
|
||||
link,
|
||||
fmt.Sprintf(`SHOW FULL COLUMNS FROM %s`, d.DB.QuoteWord(table)),
|
||||
)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
fields = make(map[string]*TableField)
|
||||
for i, m := range result {
|
||||
fields[m["Field"].String()] = &TableField{
|
||||
Index: i,
|
||||
Name: m["Field"].String(),
|
||||
Type: m["Type"].String(),
|
||||
Null: m["Null"].Bool(),
|
||||
Key: m["Key"].String(),
|
||||
Default: m["Default"].Val(),
|
||||
Extra: m["Extra"].String(),
|
||||
Comment: m["Comment"].String(),
|
||||
}
|
||||
}
|
||||
return fields
|
||||
}, 0)
|
||||
if err == nil {
|
||||
fields = v.(map[string]*TableField)
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -24,8 +24,9 @@ import (
|
||||
"github.com/gogf/gf/text/gregex"
|
||||
)
|
||||
|
||||
type dbOracle struct {
|
||||
*dbBase
|
||||
// DriverOracle is the driver for oracle database.
|
||||
type DriverOracle struct {
|
||||
*Core
|
||||
}
|
||||
|
||||
const (
|
||||
@ -33,7 +34,16 @@ const (
|
||||
tableAlias2 = "GFORM2"
|
||||
)
|
||||
|
||||
func (db *dbOracle) Open(config *ConfigNode) (*sql.DB, error) {
|
||||
// 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) {
|
||||
return &DriverOracle{
|
||||
Core: core,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 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
|
||||
@ -48,21 +58,24 @@ func (db *dbOracle) Open(config *ConfigNode) (*sql.DB, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (db *dbOracle) getChars() (charLeft string, charRight string) {
|
||||
// GetChars returns the security char for this type of database.
|
||||
func (d *DriverOracle) GetChars() (charLeft string, charRight string) {
|
||||
return "\"", "\""
|
||||
}
|
||||
|
||||
func (db *dbOracle) handleSqlBeforeExec(query string) string {
|
||||
index := 0
|
||||
// HandleSqlBeforeCommit deals with the sql string before commits it to underlying sql driver.
|
||||
func (d *DriverOracle) HandleSqlBeforeCommit(link Link, query string, args []interface{}) (string, []interface{}) {
|
||||
var index int
|
||||
// Convert place holder char '?' to string ":x".
|
||||
str, _ := gregex.ReplaceStringFunc("\\?", query, func(s string) string {
|
||||
index++
|
||||
return fmt.Sprintf(":%d", index)
|
||||
})
|
||||
str, _ = gregex.ReplaceString("\"", "", str)
|
||||
return db.parseSql(str)
|
||||
return d.parseSql(str), args
|
||||
}
|
||||
|
||||
func (db *dbOracle) parseSql(sql string) string {
|
||||
func (d *DriverOracle) parseSql(sql string) string {
|
||||
patten := `^\s*(?i)(SELECT)|(LIMIT\s*(\d+)\s*,\s*(\d+))`
|
||||
if gregex.IsMatchString(patten, sql) == false {
|
||||
return sql
|
||||
@ -109,31 +122,47 @@ func (db *dbOracle) parseSql(sql string) string {
|
||||
}
|
||||
}
|
||||
|
||||
//也可以使用between,据说这种写法的性能会比between好点,里层SQL中的ROWNUM_ >= limit可以缩小查询后的数据集规模
|
||||
sql = fmt.Sprintf("SELECT * FROM (SELECT GFORM.*, ROWNUM ROWNUM_ FROM (%s %s) GFORM WHERE ROWNUM <= %d) WHERE ROWNUM_ >= %d", queryExpr[1], queryExpr[2], limit, first)
|
||||
// 也可以使用between,据说这种写法的性能会比between好点,里层SQL中的ROWNUM_ >= limit可以缩小查询后的数据集规模
|
||||
sql = fmt.Sprintf(
|
||||
"SELECT * FROM (SELECT GFORM.*, ROWNUM ROWNUM_ FROM (%s %s) GFORM WHERE ROWNUM <= %d) WHERE ROWNUM_ >= %d",
|
||||
queryExpr[1], queryExpr[2], limit, first,
|
||||
)
|
||||
}
|
||||
return sql
|
||||
}
|
||||
|
||||
// TODO
|
||||
func (db *dbOracle) Tables(schema ...string) (tables []string, err error) {
|
||||
// Tables retrieves and returns the tables of current schema.
|
||||
// It's mainly used in cli tool chain for automatically generating the models.
|
||||
// Note that it ignores the parameter <schema> in oracle database, as it is not necessary.
|
||||
func (d *DriverOracle) Tables(schema ...string) (tables []string, err error) {
|
||||
var result Result
|
||||
result, err = d.DB.DoGetAll(nil, "SELECT TABLE_NAME FROM USER_TABLES ORDER BY TABLE_NAME")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, m := range result {
|
||||
for _, v := range m {
|
||||
tables = append(tables, v.String())
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (db *dbOracle) TableFields(table string, schema ...string) (fields map[string]*TableField, err error) {
|
||||
// TableFields retrieves and returns the fields information of specified table of current schema.
|
||||
func (d *DriverOracle) TableFields(table string, schema ...string) (fields map[string]*TableField, err error) {
|
||||
table = gstr.Trim(table)
|
||||
if gstr.Contains(table, " ") {
|
||||
panic("function TableFields supports only single table operations")
|
||||
}
|
||||
checkSchema := db.schema.Val()
|
||||
checkSchema := d.DB.GetSchema()
|
||||
if len(schema) > 0 && schema[0] != "" {
|
||||
checkSchema = schema[0]
|
||||
}
|
||||
v := db.cache.GetOrSetFunc(
|
||||
v := d.DB.GetCache().GetOrSetFunc(
|
||||
fmt.Sprintf(`oracle_table_fields_%s_%s`, table, checkSchema),
|
||||
func() interface{} {
|
||||
result := (Result)(nil)
|
||||
result, err = db.GetAll(fmt.Sprintf(`
|
||||
result, err = d.DB.GetAll(fmt.Sprintf(`
|
||||
SELECT COLUMN_NAME AS FIELD, CASE DATA_TYPE
|
||||
WHEN 'NUMBER' THEN DATA_TYPE||'('||DATA_PRECISION||','||DATA_SCALE||')'
|
||||
WHEN 'FLOAT' THEN DATA_TYPE||'('||DATA_PRECISION||','||DATA_SCALE||')'
|
||||
@ -158,11 +187,11 @@ func (db *dbOracle) TableFields(table string, schema ...string) (fields map[stri
|
||||
return
|
||||
}
|
||||
|
||||
func (db *dbOracle) getTableUniqueIndex(table string) (fields map[string]map[string]string, err error) {
|
||||
func (d *DriverOracle) getTableUniqueIndex(table string) (fields map[string]map[string]string, err error) {
|
||||
table = strings.ToUpper(table)
|
||||
v := db.cache.GetOrSetFunc("table_unique_index_"+table, func() interface{} {
|
||||
v := d.DB.GetCache().GetOrSetFunc("table_unique_index_"+table, func() interface{} {
|
||||
res := (Result)(nil)
|
||||
res, err = db.GetAll(fmt.Sprintf(`
|
||||
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')
|
||||
@ -184,7 +213,7 @@ func (db *dbOracle) getTableUniqueIndex(table string) (fields map[string]map[str
|
||||
return
|
||||
}
|
||||
|
||||
func (db *dbOracle) doInsert(link dbLink, table string, data interface{}, option int, batch ...int) (result sql.Result, err error) {
|
||||
func (d *DriverOracle) DoInsert(link Link, table string, data interface{}, option int, batch ...int) (result sql.Result, err error) {
|
||||
var fields []string
|
||||
var values []string
|
||||
var params []interface{}
|
||||
@ -199,11 +228,11 @@ func (db *dbOracle) doInsert(link dbLink, table string, data interface{}, option
|
||||
case reflect.Slice:
|
||||
fallthrough
|
||||
case reflect.Array:
|
||||
return db.db.doBatchInsert(link, table, data, option, batch...)
|
||||
return d.DB.DoBatchInsert(link, table, data, option, batch...)
|
||||
case reflect.Map:
|
||||
fallthrough
|
||||
case reflect.Struct:
|
||||
dataMap = varToMapDeep(data)
|
||||
dataMap = DataToMapDeep(data)
|
||||
default:
|
||||
return result, errors.New(fmt.Sprint("unsupported data type:", kind))
|
||||
}
|
||||
@ -212,7 +241,7 @@ func (db *dbOracle) doInsert(link dbLink, table string, data interface{}, option
|
||||
indexMap := make(map[string]string)
|
||||
indexExists := false
|
||||
if option != gINSERT_OPTION_DEFAULT {
|
||||
index, err := db.getTableUniqueIndex(table)
|
||||
index, err := d.getTableUniqueIndex(table)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -234,7 +263,7 @@ func (db *dbOracle) doInsert(link dbLink, table string, data interface{}, option
|
||||
onStr := make([]string, 0)
|
||||
updateStr := make([]string, 0)
|
||||
|
||||
charL, charR := db.db.getChars()
|
||||
charL, charR := d.DB.GetChars()
|
||||
for k, v := range dataMap {
|
||||
k = strings.ToUpper(k)
|
||||
|
||||
@ -260,7 +289,7 @@ func (db *dbOracle) doInsert(link dbLink, table string, data interface{}, option
|
||||
}
|
||||
|
||||
if link == nil {
|
||||
if link, err = db.db.Master(); err != nil {
|
||||
if link, err = d.DB.Master(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
@ -275,9 +304,9 @@ func (db *dbOracle) doInsert(link dbLink, table string, data interface{}, option
|
||||
table, tableAlias1, strings.Join(subSqlStr, ","), tableAlias2,
|
||||
strings.Join(onStr, "AND"), strings.Join(updateStr, ","), strings.Join(fields, ","), strings.Join(values, ","),
|
||||
)
|
||||
return db.db.doExec(link, tmp, params...)
|
||||
return d.DB.DoExec(link, tmp, params...)
|
||||
case gINSERT_OPTION_IGNORE:
|
||||
return db.db.doExec(link,
|
||||
return d.DB.DoExec(link,
|
||||
fmt.Sprintf(
|
||||
"INSERT /*+ IGNORE_ROW_ON_DUPKEY_INDEX(%s(%s)) */ INTO %s(%s) VALUES(%s)",
|
||||
table, strings.Join(indexs, ","), table, strings.Join(fields, ","), strings.Join(values, ","),
|
||||
@ -286,7 +315,7 @@ func (db *dbOracle) doInsert(link dbLink, table string, data interface{}, option
|
||||
}
|
||||
}
|
||||
|
||||
return db.db.doExec(
|
||||
return d.DB.DoExec(
|
||||
link,
|
||||
fmt.Sprintf(
|
||||
"INSERT INTO %s(%s) VALUES(%s)",
|
||||
@ -295,7 +324,7 @@ func (db *dbOracle) doInsert(link dbLink, table string, data interface{}, option
|
||||
params...)
|
||||
}
|
||||
|
||||
func (db *dbOracle) doBatchInsert(link dbLink, table string, list interface{}, option int, batch ...int) (result sql.Result, err error) {
|
||||
func (d *DriverOracle) DoBatchInsert(link Link, table string, list interface{}, option int, batch ...int) (result sql.Result, err error) {
|
||||
var keys []string
|
||||
var values []string
|
||||
var params []interface{}
|
||||
@ -323,12 +352,12 @@ func (db *dbOracle) doBatchInsert(link dbLink, table string, list interface{}, o
|
||||
case reflect.Array:
|
||||
listMap = make(List, rv.Len())
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
listMap[i] = varToMapDeep(rv.Index(i).Interface())
|
||||
listMap[i] = DataToMapDeep(rv.Index(i).Interface())
|
||||
}
|
||||
case reflect.Map:
|
||||
fallthrough
|
||||
case reflect.Struct:
|
||||
listMap = List{Map(varToMapDeep(list))}
|
||||
listMap = List{Map(DataToMapDeep(list))}
|
||||
default:
|
||||
return result, errors.New(fmt.Sprint("unsupported list type:", kind))
|
||||
}
|
||||
@ -338,7 +367,7 @@ func (db *dbOracle) doBatchInsert(link dbLink, table string, list interface{}, o
|
||||
return result, errors.New("empty data list")
|
||||
}
|
||||
if link == nil {
|
||||
if link, err = db.db.Master(); err != nil {
|
||||
if link, err = d.DB.Master(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -348,15 +377,15 @@ func (db *dbOracle) doBatchInsert(link dbLink, table string, list interface{}, o
|
||||
keys = append(keys, k)
|
||||
holders = append(holders, "?")
|
||||
}
|
||||
batchResult := new(batchSqlResult)
|
||||
charL, charR := db.db.getChars()
|
||||
batchResult := new(SqlResult)
|
||||
charL, charR := d.DB.GetChars()
|
||||
keyStr := charL + strings.Join(keys, charL+","+charR) + charR
|
||||
valueHolderStr := strings.Join(holders, ",")
|
||||
|
||||
// 当操作类型非insert时调用单笔的insert功能
|
||||
if option != gINSERT_OPTION_DEFAULT {
|
||||
for _, v := range listMap {
|
||||
r, err := db.doInsert(link, table, v, option, 1)
|
||||
r, err := d.DB.DoInsert(link, table, v, option, 1)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
@ -364,8 +393,8 @@ func (db *dbOracle) doBatchInsert(link dbLink, table string, list interface{}, o
|
||||
if n, err := r.RowsAffected(); err != nil {
|
||||
return r, err
|
||||
} else {
|
||||
batchResult.lastResult = r
|
||||
batchResult.rowsAffected += n
|
||||
batchResult.result = r
|
||||
batchResult.affected += n
|
||||
}
|
||||
}
|
||||
return batchResult, nil
|
||||
@ -383,18 +412,17 @@ func (db *dbOracle) doBatchInsert(link dbLink, table string, list interface{}, o
|
||||
params = append(params, listMap[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 := db.db.doExec(link, fmt.Sprintf("INSERT ALL %s SELECT * FROM DUAL", strings.Join(intoStr, " ")), params...)
|
||||
r, err := d.DB.DoExec(link, fmt.Sprintf("INSERT ALL %s SELECT * FROM DUAL", strings.Join(intoStr, " ")), params...)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
if n, err := r.RowsAffected(); err != nil {
|
||||
return r, err
|
||||
} else {
|
||||
batchResult.lastResult = r
|
||||
batchResult.rowsAffected += n
|
||||
batchResult.result = r
|
||||
batchResult.affected += n
|
||||
}
|
||||
params = params[:0]
|
||||
intoStr = intoStr[:0]
|
||||
@ -402,15 +430,15 @@ func (db *dbOracle) doBatchInsert(link dbLink, table string, list interface{}, o
|
||||
}
|
||||
// 处理最后不构成指定批量的数据
|
||||
if len(intoStr) > 0 {
|
||||
r, err := db.db.doExec(link, fmt.Sprintf("INSERT ALL %s SELECT * FROM DUAL", strings.Join(intoStr, " ")), params...)
|
||||
r, err := d.DB.DoExec(link, fmt.Sprintf("INSERT ALL %s SELECT * FROM DUAL", strings.Join(intoStr, " ")), params...)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
if n, err := r.RowsAffected(); err != nil {
|
||||
return r, err
|
||||
} else {
|
||||
batchResult.lastResult = r
|
||||
batchResult.rowsAffected += n
|
||||
batchResult.result = r
|
||||
batchResult.affected += n
|
||||
}
|
||||
}
|
||||
return batchResult, nil
|
||||
@ -21,11 +21,21 @@ import (
|
||||
"github.com/gogf/gf/text/gregex"
|
||||
)
|
||||
|
||||
type dbPgsql struct {
|
||||
*dbBase
|
||||
// DriverPgsql is the driver for postgresql database.
|
||||
type DriverPgsql struct {
|
||||
*Core
|
||||
}
|
||||
|
||||
func (db *dbPgsql) Open(config *ConfigNode) (*sql.DB, error) {
|
||||
// New creates and returns a database object for postgresql.
|
||||
// It implements the interface of gdb.Driver for extra database driver installation.
|
||||
func (d *DriverPgsql) New(core *Core, node *ConfigNode) (DB, error) {
|
||||
return &DriverPgsql{
|
||||
Core: core,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 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
|
||||
@ -43,44 +53,68 @@ func (db *dbPgsql) Open(config *ConfigNode) (*sql.DB, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (db *dbPgsql) getChars() (charLeft string, charRight string) {
|
||||
// GetChars returns the security char for this type of database.
|
||||
func (d *DriverPgsql) GetChars() (charLeft string, charRight string) {
|
||||
return "\"", "\""
|
||||
}
|
||||
|
||||
func (db *dbPgsql) handleSqlBeforeExec(sql string) string {
|
||||
index := 0
|
||||
// HandleSqlBeforeCommit deals with the sql string before commits it to underlying sql driver.
|
||||
func (d *DriverPgsql) HandleSqlBeforeCommit(link Link, sql string, args []interface{}) (string, []interface{}) {
|
||||
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 $1 OFFSET $2`, sql)
|
||||
return sql
|
||||
sql, _ = gregex.ReplaceString(` LIMIT (\d+),\s*(\d+)`, ` LIMIT $2 OFFSET $1`, sql)
|
||||
return sql, args
|
||||
}
|
||||
|
||||
// TODO
|
||||
func (db *dbPgsql) Tables(schema ...string) (tables []string, err error) {
|
||||
// Tables retrieves and returns the tables of current schema.
|
||||
// It's mainly used in cli tool chain for automatically generating the models.
|
||||
func (d *DriverPgsql) Tables(schema ...string) (tables []string, err error) {
|
||||
var result Result
|
||||
link, err := d.DB.GetSlave(schema...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
query := "SELECT TABLENAME FROM PG_TABLES WHERE SCHEMANAME = 'public' ORDER BY TABLENAME"
|
||||
if len(schema) > 0 && schema[0] != "" {
|
||||
query = fmt.Sprintf("SELECT TABLENAME FROM PG_TABLES WHERE SCHEMANAME = '%s' ORDER BY TABLENAME", schema[0])
|
||||
}
|
||||
result, err = d.DB.DoGetAll(link, query)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, m := range result {
|
||||
for _, v := range m {
|
||||
tables = append(tables, v.String())
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (db *dbPgsql) TableFields(table string, schema ...string) (fields map[string]*TableField, err error) {
|
||||
// TableFields retrieves and returns the fields information of specified table of current schema.
|
||||
func (d *DriverPgsql) TableFields(table string, schema ...string) (fields map[string]*TableField, err error) {
|
||||
table = gstr.Trim(table)
|
||||
if gstr.Contains(table, " ") {
|
||||
panic("function TableFields supports only single table operations")
|
||||
}
|
||||
table, _ = gregex.ReplaceString("\"", "", table)
|
||||
checkSchema := db.schema.Val()
|
||||
checkSchema := d.DB.GetSchema()
|
||||
if len(schema) > 0 && schema[0] != "" {
|
||||
checkSchema = schema[0]
|
||||
}
|
||||
v := db.cache.GetOrSetFunc(
|
||||
v := d.DB.GetCache().GetOrSetFunc(
|
||||
fmt.Sprintf(`pgsql_table_fields_%s_%s`, table, checkSchema), func() interface{} {
|
||||
var result Result
|
||||
var link *sql.DB
|
||||
link, err = db.getSlave(checkSchema)
|
||||
link, err = d.DB.GetSlave(checkSchema)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
result, err = db.doGetAll(link, fmt.Sprintf(`
|
||||
result, err = d.DB.DoGetAll(link, fmt.Sprintf(`
|
||||
SELECT a.attname AS field, t.typname AS type FROM pg_class c, pg_attribute a
|
||||
LEFT OUTER JOIN pg_description b ON a.attrelid=b.objoid AND a.attnum = b.objsubid,pg_type t
|
||||
WHERE c.relname = '%s' and a.attnum > 0 and a.attrelid = c.oid and a.atttypid = t.oid
|
||||
120
database/gdb/gdb_driver_sqlite.go
Normal file
120
database/gdb/gdb_driver_sqlite.go
Normal file
@ -0,0 +1,120 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
//
|
||||
// Note:
|
||||
// 1. It needs manually import: _ "github.com/mattn/go-sqlite3"
|
||||
// 2. It does not support Save/Replace features.
|
||||
|
||||
package gdb
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/internal/intlog"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// DriverSqlite is the driver for sqlite database.
|
||||
type DriverSqlite struct {
|
||||
*Core
|
||||
}
|
||||
|
||||
// New creates and returns a database object for sqlite.
|
||||
// It implements the interface of gdb.Driver for extra database driver installation.
|
||||
func (d *DriverSqlite) New(core *Core, node *ConfigNode) (DB, error) {
|
||||
return &DriverSqlite{
|
||||
Core: core,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 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
|
||||
} else {
|
||||
source = config.Name
|
||||
}
|
||||
intlog.Printf("Open: %s", source)
|
||||
if db, err := sql.Open("sqlite3", source); err == nil {
|
||||
return db, nil
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// GetChars returns the security char for this type of database.
|
||||
func (d *DriverSqlite) GetChars() (charLeft string, charRight string) {
|
||||
return "`", "`"
|
||||
}
|
||||
|
||||
// HandleSqlBeforeCommit deals with the sql string before commits it to underlying sql driver.
|
||||
// @todo 需要增加对Save方法的支持,可使用正则来实现替换,
|
||||
// @todo 将ON DUPLICATE KEY UPDATE触发器修改为两条SQL语句(INSERT OR IGNORE & UPDATE)
|
||||
func (d *DriverSqlite) HandleSqlBeforeCommit(link Link, sql string, args []interface{}) (string, []interface{}) {
|
||||
return sql, args
|
||||
}
|
||||
|
||||
// Tables retrieves and returns the tables of current schema.
|
||||
// It's mainly used in cli tool chain for automatically generating the models.
|
||||
func (d *DriverSqlite) Tables(schema ...string) (tables []string, err error) {
|
||||
var result Result
|
||||
link, err := d.DB.GetSlave(schema...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result, err = d.DB.DoGetAll(link, `SELECT NAME FROM SQLITE_MASTER WHERE TYPE='table' ORDER BY NAME`)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, m := range result {
|
||||
for _, v := range m {
|
||||
tables = append(tables, v.String())
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// TableFields retrieves and returns the fields information of specified table of current schema.
|
||||
func (d *DriverSqlite) TableFields(table string, schema ...string) (fields map[string]*TableField, err error) {
|
||||
table = gstr.Trim(table)
|
||||
if gstr.Contains(table, " ") {
|
||||
panic("function TableFields supports only single table operations")
|
||||
}
|
||||
|
||||
checkSchema := d.DB.GetSchema()
|
||||
if len(schema) > 0 && schema[0] != "" {
|
||||
checkSchema = schema[0]
|
||||
}
|
||||
v := d.DB.GetCache().GetOrSetFunc(
|
||||
fmt.Sprintf(`sqlite_table_fields_%s_%s`, table, checkSchema), func() interface{} {
|
||||
var result Result
|
||||
var link *sql.DB
|
||||
link, err = d.DB.GetSlave(checkSchema)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
result, err = d.DB.DoGetAll(link, fmt.Sprintf(`PRAGMA TABLE_INFO(%s)`, table))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
fields = make(map[string]*TableField)
|
||||
for i, m := range result {
|
||||
fields[strings.ToLower(m["name"].String())] = &TableField{
|
||||
Index: i,
|
||||
Name: strings.ToLower(m["name"].String()),
|
||||
Type: strings.ToLower(m["type"].String()),
|
||||
}
|
||||
}
|
||||
return fields
|
||||
}, 0)
|
||||
if err == nil {
|
||||
fields = v.(map[string]*TableField)
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -12,6 +12,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/internal/empty"
|
||||
"github.com/gogf/gf/internal/utils"
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
"reflect"
|
||||
"regexp"
|
||||
@ -51,7 +52,54 @@ var (
|
||||
quoteWordReg = regexp.MustCompile(`^[a-zA-Z0-9\-_]+$`)
|
||||
)
|
||||
|
||||
// handleTableName adds prefix string and quote chars for the table. It handles table string like:
|
||||
// GetInsertOperationByOption returns proper insert option with given parameter <option>.
|
||||
func GetInsertOperationByOption(option int) string {
|
||||
var operator string
|
||||
switch option {
|
||||
case gINSERT_OPTION_REPLACE:
|
||||
operator = "REPLACE"
|
||||
case gINSERT_OPTION_IGNORE:
|
||||
operator = "INSERT IGNORE"
|
||||
default:
|
||||
operator = "INSERT"
|
||||
}
|
||||
return operator
|
||||
}
|
||||
|
||||
// DataToMapDeep converts struct object to map type recursively.
|
||||
func DataToMapDeep(obj interface{}) map[string]interface{} {
|
||||
data := gconv.Map(obj, ORM_TAG_FOR_STRUCT)
|
||||
for key, value := range data {
|
||||
rv := reflect.ValueOf(value)
|
||||
kind := rv.Kind()
|
||||
if kind == reflect.Ptr {
|
||||
rv = rv.Elem()
|
||||
kind = rv.Kind()
|
||||
}
|
||||
switch kind {
|
||||
case reflect.Struct:
|
||||
// The underlying driver supports time.Time/*time.Time types.
|
||||
if _, ok := value.(time.Time); ok {
|
||||
continue
|
||||
}
|
||||
if _, ok := value.(*time.Time); ok {
|
||||
continue
|
||||
}
|
||||
// Use string conversion in default.
|
||||
if s, ok := value.(apiString); ok {
|
||||
data[key] = s.String()
|
||||
continue
|
||||
}
|
||||
delete(data, key)
|
||||
for k, v := range DataToMapDeep(value) {
|
||||
data[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// QuotePrefixTableName adds prefix string and quote chars for the table. It handles table string like:
|
||||
// "user", "user u", "user,user_detail", "user u, user_detail ut", "user as u, user_detail as ut", "user.user u".
|
||||
//
|
||||
// Note that, this will automatically checks the table prefix whether already added, if true it does
|
||||
@ -196,7 +244,7 @@ func formatWhere(db DB, where interface{}, args []interface{}, omitEmpty bool) (
|
||||
newArgs = formatWhereInterfaces(db, gconv.Interfaces(where), buffer, newArgs)
|
||||
|
||||
case reflect.Map:
|
||||
for key, value := range varToMapDeep(where) {
|
||||
for key, value := range DataToMapDeep(where) {
|
||||
if omitEmpty && empty.IsEmpty(value) {
|
||||
continue
|
||||
}
|
||||
@ -218,7 +266,7 @@ func formatWhere(db DB, where interface{}, args []interface{}, omitEmpty bool) (
|
||||
})
|
||||
break
|
||||
}
|
||||
for key, value := range varToMapDeep(where) {
|
||||
for key, value := range DataToMapDeep(where) {
|
||||
if omitEmpty && empty.IsEmpty(value) {
|
||||
continue
|
||||
}
|
||||
@ -235,12 +283,25 @@ func formatWhere(db DB, where interface{}, args []interface{}, omitEmpty bool) (
|
||||
newArgs = append(newArgs, args...)
|
||||
newWhere = buffer.String()
|
||||
if len(newArgs) > 0 {
|
||||
// It supports formats like: Where/And/Or("uid", 1) , Where/And/Or("uid>=", 1)
|
||||
if gstr.Pos(newWhere, "?") == -1 {
|
||||
if lastOperatorReg.MatchString(newWhere) {
|
||||
// Eg: Where/And/Or("uid>=", 1)
|
||||
newWhere += "?"
|
||||
} else if gregex.IsMatchString(`^[\w\.\-]+$`, newWhere) {
|
||||
newWhere += "=?"
|
||||
newWhere = db.QuoteString(newWhere)
|
||||
if len(newArgs) > 0 {
|
||||
if utils.IsArray(newArgs[0]) {
|
||||
// Eg: Where("id", []int{1,2,3})
|
||||
newWhere += " IN (?)"
|
||||
} else if empty.IsNil(newArgs[0]) {
|
||||
// Eg: Where("id", nil)
|
||||
newWhere += " IS NULL"
|
||||
newArgs = nil
|
||||
} else {
|
||||
// Eg: Where/And/Or("uid", 1)
|
||||
newWhere += "=?"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -269,7 +330,7 @@ func formatWhereInterfaces(db DB, where []interface{}, buffer *bytes.Buffer, new
|
||||
|
||||
// formatWhereKeyValue handles each key-value pair of the parameter map.
|
||||
func formatWhereKeyValue(db DB, buffer *bytes.Buffer, newArgs []interface{}, key string, value interface{}) []interface{} {
|
||||
key = db.quoteWord(key)
|
||||
quotedKey := db.QuoteWord(key)
|
||||
if buffer.Len() > 0 {
|
||||
buffer.WriteString(" AND ")
|
||||
}
|
||||
@ -277,36 +338,43 @@ func formatWhereKeyValue(db DB, buffer *bytes.Buffer, newArgs []interface{}, key
|
||||
// the key string, it automatically adds '?' holder chars according to its arguments count
|
||||
// and converts it to "IN" statement.
|
||||
rv := reflect.ValueOf(value)
|
||||
switch rv.Kind() {
|
||||
kind := rv.Kind()
|
||||
switch kind {
|
||||
case reflect.Slice, reflect.Array:
|
||||
count := gstr.Count(key, "?")
|
||||
count := gstr.Count(quotedKey, "?")
|
||||
if count == 0 {
|
||||
buffer.WriteString(key + " IN(?)")
|
||||
buffer.WriteString(quotedKey + " IN(?)")
|
||||
newArgs = append(newArgs, value)
|
||||
} else if count != rv.Len() {
|
||||
buffer.WriteString(key)
|
||||
buffer.WriteString(quotedKey)
|
||||
newArgs = append(newArgs, value)
|
||||
} else {
|
||||
buffer.WriteString(key)
|
||||
buffer.WriteString(quotedKey)
|
||||
newArgs = append(newArgs, gconv.Interfaces(value)...)
|
||||
}
|
||||
default:
|
||||
if value == nil {
|
||||
buffer.WriteString(key)
|
||||
if value == nil || empty.IsNil(rv) {
|
||||
if gregex.IsMatchString(`^[\w\.\-]+$`, key) {
|
||||
// The key is a single field name.
|
||||
buffer.WriteString(quotedKey + " IS NULL")
|
||||
} else {
|
||||
// The key may have operation chars.
|
||||
buffer.WriteString(quotedKey)
|
||||
}
|
||||
} else {
|
||||
// It also supports "LIKE" statement, which we considers it an operator.
|
||||
key = gstr.Trim(key)
|
||||
if gstr.Pos(key, "?") == -1 {
|
||||
quotedKey = gstr.Trim(quotedKey)
|
||||
if gstr.Pos(quotedKey, "?") == -1 {
|
||||
like := " like"
|
||||
if len(key) > len(like) && gstr.Equal(key[len(key)-len(like):], like) {
|
||||
buffer.WriteString(key + " ?")
|
||||
} else if lastOperatorReg.MatchString(key) {
|
||||
buffer.WriteString(key + " ?")
|
||||
if len(quotedKey) > len(like) && gstr.Equal(quotedKey[len(quotedKey)-len(like):], like) {
|
||||
buffer.WriteString(quotedKey + " ?")
|
||||
} else if lastOperatorReg.MatchString(quotedKey) {
|
||||
buffer.WriteString(quotedKey + " ?")
|
||||
} else {
|
||||
buffer.WriteString(key + "=?")
|
||||
buffer.WriteString(quotedKey + "=?")
|
||||
}
|
||||
} else {
|
||||
buffer.WriteString(key)
|
||||
buffer.WriteString(quotedKey)
|
||||
}
|
||||
newArgs = append(newArgs, value)
|
||||
}
|
||||
@ -314,39 +382,6 @@ func formatWhereKeyValue(db DB, buffer *bytes.Buffer, newArgs []interface{}, key
|
||||
return newArgs
|
||||
}
|
||||
|
||||
// varToMapDeep converts struct object to map type recursively.
|
||||
func varToMapDeep(obj interface{}) map[string]interface{} {
|
||||
data := gconv.Map(obj, ORM_TAG_FOR_STRUCT)
|
||||
for key, value := range data {
|
||||
rv := reflect.ValueOf(value)
|
||||
kind := rv.Kind()
|
||||
if kind == reflect.Ptr {
|
||||
rv = rv.Elem()
|
||||
kind = rv.Kind()
|
||||
}
|
||||
switch kind {
|
||||
case reflect.Struct:
|
||||
// The underlying driver supports time.Time/*time.Time types.
|
||||
if _, ok := value.(time.Time); ok {
|
||||
continue
|
||||
}
|
||||
if _, ok := value.(*time.Time); ok {
|
||||
continue
|
||||
}
|
||||
// Use string conversion in default.
|
||||
if s, ok := value.(apiString); ok {
|
||||
data[key] = s.String()
|
||||
continue
|
||||
}
|
||||
delete(data, key)
|
||||
for k, v := range varToMapDeep(value) {
|
||||
data[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// handleArguments is a nice function which handles the query and its arguments before committing to
|
||||
// underlying driver.
|
||||
func handleArguments(query string, args []interface{}) (newQuery string, newArgs []interface{}) {
|
||||
@ -422,52 +457,39 @@ func formatError(err error, query string, args ...interface{}) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// getInsertOperationByOption returns proper insert option with given parameter <option>.
|
||||
func getInsertOperationByOption(option int) string {
|
||||
var operator string
|
||||
switch option {
|
||||
case gINSERT_OPTION_REPLACE:
|
||||
operator = "REPLACE"
|
||||
case gINSERT_OPTION_IGNORE:
|
||||
operator = "INSERT IGNORE"
|
||||
default:
|
||||
operator = "INSERT"
|
||||
}
|
||||
return operator
|
||||
}
|
||||
|
||||
// bindArgsToQuery binds the arguments to the query string and returns a complete
|
||||
// sql string, just for debugging.
|
||||
func bindArgsToQuery(query string, args []interface{}) string {
|
||||
index := -1
|
||||
newQuery, _ := gregex.ReplaceStringFunc(`\?`, query, func(s string) string {
|
||||
index++
|
||||
if len(args) > index {
|
||||
if args[index] == nil {
|
||||
return "null"
|
||||
}
|
||||
rv := reflect.ValueOf(args[index])
|
||||
kind := rv.Kind()
|
||||
if kind == reflect.Ptr {
|
||||
if rv.IsNil() || !rv.IsValid() {
|
||||
newQuery, _ := gregex.ReplaceStringFunc(
|
||||
`(\?|:\d+|\$\d+|@p\d+)`, query, func(s string) string {
|
||||
index++
|
||||
if len(args) > index {
|
||||
if args[index] == nil {
|
||||
return "null"
|
||||
}
|
||||
rv = rv.Elem()
|
||||
kind = rv.Kind()
|
||||
}
|
||||
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 `'` + gtime.NewFromTime(t).String() + `'`
|
||||
rv := reflect.ValueOf(args[index])
|
||||
kind := rv.Kind()
|
||||
if kind == reflect.Ptr {
|
||||
if rv.IsNil() || !rv.IsValid() {
|
||||
return "null"
|
||||
}
|
||||
rv = rv.Elem()
|
||||
kind = rv.Kind()
|
||||
}
|
||||
return `'` + gstr.QuoteMeta(gconv.String(args[index]), `'`) + `'`
|
||||
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 `'` + gtime.NewFromTime(t).String() + `'`
|
||||
}
|
||||
return `'` + gstr.QuoteMeta(gconv.String(args[index]), `'`) + `'`
|
||||
}
|
||||
return gconv.String(args[index])
|
||||
}
|
||||
return gconv.String(args[index])
|
||||
}
|
||||
return s
|
||||
})
|
||||
return s
|
||||
})
|
||||
return newQuery
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
36
database/gdb/gdb_model_cache.go
Normal file
36
database/gdb/gdb_model_cache.go
Normal file
@ -0,0 +1,36 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gdb
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Cache sets the cache feature for the model. It caches the result of the sql, which means
|
||||
// if there's another same sql request, it just reads and returns the result from cache, it
|
||||
// but not committed and executed into the database.
|
||||
//
|
||||
// If the parameter <duration> < 0, which means it clear the cache with given <name>.
|
||||
// If the parameter <duration> = 0, which means it never expires.
|
||||
// If the parameter <duration> > 0, which means it expires after <duration>.
|
||||
//
|
||||
// The optional parameter <name> is used to bind a name to the cache, which means you can later
|
||||
// control the cache like changing the <duration> or clearing the cache with specified <name>.
|
||||
//
|
||||
// Note that, the cache feature is disabled if the model is operating on a transaction.
|
||||
func (m *Model) Cache(duration time.Duration, name ...string) *Model {
|
||||
model := m.getModel()
|
||||
model.cacheDuration = duration
|
||||
if len(name) > 0 {
|
||||
model.cacheName = name[0]
|
||||
}
|
||||
// It does not support cache on transaction.
|
||||
if model.tx == nil {
|
||||
model.cacheEnabled = true
|
||||
}
|
||||
return model
|
||||
}
|
||||
170
database/gdb/gdb_model_condition.go
Normal file
170
database/gdb/gdb_model_condition.go
Normal file
@ -0,0 +1,170 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gdb
|
||||
|
||||
import "github.com/gogf/gf/util/gconv"
|
||||
|
||||
// Where sets the condition statement for the model. The parameter <where> can be type of
|
||||
// string/map/gmap/slice/struct/*struct, etc. Note that, if it's called more than one times,
|
||||
// multiple conditions will be joined into where statement using "AND".
|
||||
// Eg:
|
||||
// Where("uid=10000")
|
||||
// Where("uid", 10000)
|
||||
// Where("money>? AND name like ?", 99999, "vip_%")
|
||||
// Where("uid", 1).Where("name", "john")
|
||||
// Where("status IN (?)", g.Slice{1,2,3})
|
||||
// Where("age IN(?,?)", 18, 50)
|
||||
// Where(User{ Id : 1, UserName : "john"})
|
||||
func (m *Model) Where(where interface{}, args ...interface{}) *Model {
|
||||
model := m.getModel()
|
||||
if model.whereHolder == nil {
|
||||
model.whereHolder = make([]*whereHolder, 0)
|
||||
}
|
||||
model.whereHolder = append(model.whereHolder, &whereHolder{
|
||||
operator: gWHERE_HOLDER_WHERE,
|
||||
where: where,
|
||||
args: args,
|
||||
})
|
||||
return model
|
||||
}
|
||||
|
||||
// WherePri does the same logic as Model.Where except that if the parameter <where>
|
||||
// is a single condition like int/string/float/slice, it treats the condition as the primary
|
||||
// key value. That is, if primary key is "id" and given <where> parameter as "123", the
|
||||
// WherePri function treats it as "id=123", but Model.Where treats it as string "123".
|
||||
func (m *Model) WherePri(where interface{}, args ...interface{}) *Model {
|
||||
if len(args) > 0 {
|
||||
return m.Where(where, args...)
|
||||
}
|
||||
newWhere := GetPrimaryKeyCondition(m.getPrimaryKey(), where)
|
||||
return m.Where(newWhere[0], newWhere[1:]...)
|
||||
}
|
||||
|
||||
// And adds "AND" condition to the where statement.
|
||||
func (m *Model) And(where interface{}, args ...interface{}) *Model {
|
||||
model := m.getModel()
|
||||
if model.whereHolder == nil {
|
||||
model.whereHolder = make([]*whereHolder, 0)
|
||||
}
|
||||
model.whereHolder = append(model.whereHolder, &whereHolder{
|
||||
operator: gWHERE_HOLDER_AND,
|
||||
where: where,
|
||||
args: args,
|
||||
})
|
||||
return model
|
||||
}
|
||||
|
||||
// Or adds "OR" condition to the where statement.
|
||||
func (m *Model) Or(where interface{}, args ...interface{}) *Model {
|
||||
model := m.getModel()
|
||||
if model.whereHolder == nil {
|
||||
model.whereHolder = make([]*whereHolder, 0)
|
||||
}
|
||||
model.whereHolder = append(model.whereHolder, &whereHolder{
|
||||
operator: gWHERE_HOLDER_OR,
|
||||
where: where,
|
||||
args: args,
|
||||
})
|
||||
return model
|
||||
}
|
||||
|
||||
// Group sets the "GROUP BY" statement for the model.
|
||||
func (m *Model) Group(groupBy string) *Model {
|
||||
model := m.getModel()
|
||||
model.groupBy = m.db.QuoteString(groupBy)
|
||||
return model
|
||||
}
|
||||
|
||||
// GroupBy is alias of Model.Group.
|
||||
// See Model.Group.
|
||||
// Deprecated.
|
||||
func (m *Model) GroupBy(groupBy string) *Model {
|
||||
return m.Group(groupBy)
|
||||
}
|
||||
|
||||
// Order sets the "ORDER BY" statement for the model.
|
||||
func (m *Model) Order(orderBy string) *Model {
|
||||
model := m.getModel()
|
||||
model.orderBy = m.db.QuoteString(orderBy)
|
||||
return model
|
||||
}
|
||||
|
||||
// OrderBy is alias of Model.Order.
|
||||
// See Model.Order.
|
||||
// Deprecated.
|
||||
func (m *Model) OrderBy(orderBy string) *Model {
|
||||
return m.Order(orderBy)
|
||||
}
|
||||
|
||||
// Limit sets the "LIMIT" statement for the model.
|
||||
// The parameter <limit> can be either one or two number, if passed two number is passed,
|
||||
// it then sets "LIMIT limit[0],limit[1]" statement for the model, or else it sets "LIMIT limit[0]"
|
||||
// statement.
|
||||
func (m *Model) Limit(limit ...int) *Model {
|
||||
model := m.getModel()
|
||||
switch len(limit) {
|
||||
case 1:
|
||||
model.limit = limit[0]
|
||||
case 2:
|
||||
model.start = limit[0]
|
||||
model.limit = limit[1]
|
||||
}
|
||||
return model
|
||||
}
|
||||
|
||||
// Offset sets the "OFFSET" statement for the model.
|
||||
// It only makes sense for some databases like SQLServer, PostgreSQL, etc.
|
||||
func (m *Model) Offset(offset int) *Model {
|
||||
model := m.getModel()
|
||||
model.offset = offset
|
||||
return model
|
||||
}
|
||||
|
||||
// Page sets the paging number for the model.
|
||||
// The parameter <page> is started from 1 for paging.
|
||||
// Note that, it differs that the Limit function start from 0 for "LIMIT" statement.
|
||||
func (m *Model) Page(page, limit int) *Model {
|
||||
model := m.getModel()
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
model.start = (page - 1) * limit
|
||||
model.limit = limit
|
||||
return model
|
||||
}
|
||||
|
||||
// ForPage is alias of Model.Page.
|
||||
// See Model.Page.
|
||||
// Deprecated.
|
||||
func (m *Model) ForPage(page, limit int) *Model {
|
||||
return m.Page(page, limit)
|
||||
}
|
||||
|
||||
// getAll does the query from database.
|
||||
func (m *Model) getAll(query string, args ...interface{}) (result Result, err error) {
|
||||
cacheKey := ""
|
||||
// Retrieve from cache.
|
||||
if m.cacheEnabled {
|
||||
cacheKey = m.cacheName
|
||||
if len(cacheKey) == 0 {
|
||||
cacheKey = query + "/" + gconv.String(args)
|
||||
}
|
||||
if v := m.db.GetCache().Get(cacheKey); v != nil {
|
||||
return v.(Result), nil
|
||||
}
|
||||
}
|
||||
result, err = m.db.DoGetAll(m.getLink(false), query, m.mergeArguments(args)...)
|
||||
// Cache the result.
|
||||
if len(cacheKey) > 0 && err == nil {
|
||||
if m.cacheDuration < 0 {
|
||||
m.db.GetCache().Remove(cacheKey)
|
||||
} else {
|
||||
m.db.GetCache().Set(cacheKey, result, m.cacheDuration)
|
||||
}
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
27
database/gdb/gdb_model_delete.go
Normal file
27
database/gdb/gdb_model_delete.go
Normal file
@ -0,0 +1,27 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gdb
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
)
|
||||
|
||||
// Delete does "DELETE FROM ... " statement for the model.
|
||||
// The optional parameter <where> is the same as the parameter of Model.Where function,
|
||||
// see Model.Where.
|
||||
func (m *Model) Delete(where ...interface{}) (result sql.Result, err error) {
|
||||
if len(where) > 0 {
|
||||
return m.Where(where[0], where[1:]...).Delete()
|
||||
}
|
||||
defer func() {
|
||||
if err == nil {
|
||||
m.checkAndRemoveCache()
|
||||
}
|
||||
}()
|
||||
condition, conditionArgs := m.formatCondition(false)
|
||||
return m.db.DoDelete(m.getLink(true), m.tables, condition, conditionArgs...)
|
||||
}
|
||||
94
database/gdb/gdb_model_fields.go
Normal file
94
database/gdb/gdb_model_fields.go
Normal file
@ -0,0 +1,94 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gdb
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/container/garray"
|
||||
"github.com/gogf/gf/container/gset"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
)
|
||||
|
||||
// Filter marks filtering the fields which does not exist in the fields of the operated table.
|
||||
func (m *Model) Filter() *Model {
|
||||
if gstr.Contains(m.tables, " ") {
|
||||
panic("function Filter supports only single table operations")
|
||||
}
|
||||
model := m.getModel()
|
||||
model.filter = true
|
||||
return model
|
||||
}
|
||||
|
||||
// Fields sets the operation fields of the model, multiple fields joined using char ','.
|
||||
func (m *Model) Fields(fields string) *Model {
|
||||
model := m.getModel()
|
||||
model.fields = fields
|
||||
return model
|
||||
}
|
||||
|
||||
// FieldsEx sets the excluded operation fields of the model, multiple fields joined using char ','.
|
||||
func (m *Model) FieldsEx(fields string) *Model {
|
||||
if gstr.Contains(m.tables, " ") {
|
||||
panic("function FieldsEx supports only single table operations")
|
||||
}
|
||||
model := m.getModel()
|
||||
model.fieldsEx = fields
|
||||
fieldsExSet := gset.NewStrSetFrom(gstr.SplitAndTrim(fields, ","))
|
||||
if m, err := m.db.TableFields(m.tables); err == nil {
|
||||
model.fields = ""
|
||||
for k, _ := range m {
|
||||
if fieldsExSet.Contains(k) {
|
||||
continue
|
||||
}
|
||||
if len(model.fields) > 0 {
|
||||
model.fields += ","
|
||||
}
|
||||
model.fields += k
|
||||
}
|
||||
}
|
||||
return model
|
||||
}
|
||||
|
||||
// FieldsStr retrieves and returns all fields from the table, joined with char ','.
|
||||
// The optional parameter <prefix> specifies the prefix for each field, eg: FieldsStr("u.").
|
||||
func (m *Model) FieldsStr(prefix ...string) string {
|
||||
prefixStr := ""
|
||||
if len(prefix) > 0 {
|
||||
prefixStr = prefix[0]
|
||||
}
|
||||
if m, err := m.db.TableFields(m.tables); err == nil {
|
||||
fieldsArray := garray.NewStrArraySize(len(m), len(m))
|
||||
for _, field := range m {
|
||||
fieldsArray.Set(field.Index, prefixStr+field.Name)
|
||||
}
|
||||
return fieldsArray.Join(",")
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// FieldsExStr retrieves and returns fields which are not in parameter <fields> from the table,
|
||||
// joined with char ','.
|
||||
// The parameter <fields> specifies the fields that are excluded.
|
||||
// The optional parameter <prefix> specifies the prefix for each field, eg: FieldsExStr("id", "u.").
|
||||
func (m *Model) FieldsExStr(fields string, prefix ...string) string {
|
||||
prefixStr := ""
|
||||
if len(prefix) > 0 {
|
||||
prefixStr = prefix[0]
|
||||
}
|
||||
if m, err := m.db.TableFields(m.tables); err == nil {
|
||||
fieldsArray := garray.NewStrArraySize(len(m), len(m))
|
||||
fieldsExSet := gset.NewStrSetFrom(gstr.SplitAndTrim(fields, ","))
|
||||
for _, field := range m {
|
||||
if fieldsExSet.Contains(field.Name) {
|
||||
continue
|
||||
}
|
||||
fieldsArray.Set(field.Index, prefixStr+field.Name)
|
||||
}
|
||||
fieldsArray.FilterEmpty()
|
||||
return fieldsArray.Join(",")
|
||||
}
|
||||
return ""
|
||||
}
|
||||
213
database/gdb/gdb_model_insert.go
Normal file
213
database/gdb/gdb_model_insert.go
Normal file
@ -0,0 +1,213 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gdb
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// Batch sets the batch operation number for the model.
|
||||
func (m *Model) Batch(batch int) *Model {
|
||||
model := m.getModel()
|
||||
model.batch = batch
|
||||
return model
|
||||
}
|
||||
|
||||
// Data sets the operation data for the model.
|
||||
// The parameter <data> can be type of string/map/gmap/slice/struct/*struct, etc.
|
||||
// Eg:
|
||||
// Data("uid=10000")
|
||||
// Data("uid", 10000)
|
||||
// Data("uid=? AND name=?", 10000, "john")
|
||||
// Data(g.Map{"uid": 10000, "name":"john"})
|
||||
// Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"})
|
||||
func (m *Model) Data(data ...interface{}) *Model {
|
||||
model := m.getModel()
|
||||
if len(data) > 1 {
|
||||
s := gconv.String(data[0])
|
||||
if gstr.Contains(s, "?") {
|
||||
model.data = s
|
||||
model.extraArgs = data[1:]
|
||||
} else {
|
||||
m := make(map[string]interface{})
|
||||
for i := 0; i < len(data); i += 2 {
|
||||
m[gconv.String(data[i])] = data[i+1]
|
||||
}
|
||||
model.data = m
|
||||
}
|
||||
} else {
|
||||
switch params := data[0].(type) {
|
||||
case Result:
|
||||
model.data = params.List()
|
||||
case Record:
|
||||
model.data = params.Map()
|
||||
case List:
|
||||
model.data = params
|
||||
case Map:
|
||||
model.data = params
|
||||
default:
|
||||
rv := reflect.ValueOf(params)
|
||||
kind := rv.Kind()
|
||||
if kind == reflect.Ptr {
|
||||
rv = rv.Elem()
|
||||
kind = rv.Kind()
|
||||
}
|
||||
switch kind {
|
||||
case reflect.Slice, reflect.Array:
|
||||
list := make(List, rv.Len())
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
list[i] = DataToMapDeep(rv.Index(i).Interface())
|
||||
}
|
||||
model.data = list
|
||||
case reflect.Map, reflect.Struct:
|
||||
model.data = DataToMapDeep(data[0])
|
||||
default:
|
||||
model.data = data[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.
|
||||
func (m *Model) Insert(data ...interface{}) (result sql.Result, err error) {
|
||||
return m.doInsertWithOption(gINSERT_OPTION_DEFAULT, data...)
|
||||
}
|
||||
|
||||
// InsertIgnore does "INSERT IGNORE INTO ..." statement for the model.
|
||||
// The optional parameter <data> is the same as the parameter of Model.Data function,
|
||||
// see Model.Data.
|
||||
func (m *Model) InsertIgnore(data ...interface{}) (result sql.Result, err error) {
|
||||
return m.doInsertWithOption(gINSERT_OPTION_IGNORE, data...)
|
||||
}
|
||||
|
||||
// doInsertWithOption inserts data with option parameter.
|
||||
func (m *Model) doInsertWithOption(option int, data ...interface{}) (result sql.Result, err error) {
|
||||
if len(data) > 0 {
|
||||
return m.Data(data...).Insert()
|
||||
}
|
||||
defer func() {
|
||||
if err == nil {
|
||||
m.checkAndRemoveCache()
|
||||
}
|
||||
}()
|
||||
if m.data == nil {
|
||||
return nil, errors.New("inserting into table with empty data")
|
||||
}
|
||||
if list, ok := m.data.(List); ok {
|
||||
// Batch insert.
|
||||
batch := 10
|
||||
if m.batch > 0 {
|
||||
batch = m.batch
|
||||
}
|
||||
return m.db.DoBatchInsert(
|
||||
m.getLink(true),
|
||||
m.tables,
|
||||
m.filterDataForInsertOrUpdate(list),
|
||||
option,
|
||||
batch,
|
||||
)
|
||||
} else if data, ok := m.data.(Map); ok {
|
||||
// Single insert.
|
||||
return m.db.DoInsert(
|
||||
m.getLink(true),
|
||||
m.tables,
|
||||
m.filterDataForInsertOrUpdate(data),
|
||||
option,
|
||||
)
|
||||
}
|
||||
return nil, errors.New("inserting into table with invalid data type")
|
||||
}
|
||||
|
||||
// Replace does "REPLACE INTO ..." statement for the model.
|
||||
// The optional parameter <data> is the same as the parameter of Model.Data function,
|
||||
// see Model.Data.
|
||||
func (m *Model) Replace(data ...interface{}) (result sql.Result, err error) {
|
||||
if len(data) > 0 {
|
||||
return m.Data(data...).Replace()
|
||||
}
|
||||
defer func() {
|
||||
if err == nil {
|
||||
m.checkAndRemoveCache()
|
||||
}
|
||||
}()
|
||||
if m.data == nil {
|
||||
return nil, errors.New("replacing into table with empty data")
|
||||
}
|
||||
if list, ok := m.data.(List); ok {
|
||||
// Batch replace.
|
||||
batch := 10
|
||||
if m.batch > 0 {
|
||||
batch = m.batch
|
||||
}
|
||||
return m.db.DoBatchInsert(
|
||||
m.getLink(true),
|
||||
m.tables,
|
||||
m.filterDataForInsertOrUpdate(list),
|
||||
gINSERT_OPTION_REPLACE,
|
||||
batch,
|
||||
)
|
||||
} else if data, ok := m.data.(Map); ok {
|
||||
// Single insert.
|
||||
return m.db.DoInsert(
|
||||
m.getLink(true),
|
||||
m.tables,
|
||||
m.filterDataForInsertOrUpdate(data),
|
||||
gINSERT_OPTION_REPLACE,
|
||||
)
|
||||
}
|
||||
return nil, errors.New("replacing into table with invalid data type")
|
||||
}
|
||||
|
||||
// Save does "INSERT INTO ... ON DUPLICATE KEY UPDATE..." statement for the model.
|
||||
// The optional parameter <data> is the same as the parameter of Model.Data function,
|
||||
// see Model.Data.
|
||||
//
|
||||
// It updates the record if there's primary or unique index in the saving data,
|
||||
// or else it inserts a new record into the table.
|
||||
func (m *Model) Save(data ...interface{}) (result sql.Result, err error) {
|
||||
if len(data) > 0 {
|
||||
return m.Data(data...).Save()
|
||||
}
|
||||
defer func() {
|
||||
if err == nil {
|
||||
m.checkAndRemoveCache()
|
||||
}
|
||||
}()
|
||||
if m.data == nil {
|
||||
return nil, errors.New("saving into table with empty data")
|
||||
}
|
||||
if list, ok := m.data.(List); ok {
|
||||
// Batch save.
|
||||
batch := gDEFAULT_BATCH_NUM
|
||||
if m.batch > 0 {
|
||||
batch = m.batch
|
||||
}
|
||||
return m.db.DoBatchInsert(
|
||||
m.getLink(true),
|
||||
m.tables,
|
||||
m.filterDataForInsertOrUpdate(list),
|
||||
gINSERT_OPTION_SAVE,
|
||||
batch,
|
||||
)
|
||||
} else if data, ok := m.data.(Map); ok {
|
||||
// Single save.
|
||||
return m.db.DoInsert(
|
||||
m.getLink(true),
|
||||
m.tables,
|
||||
m.filterDataForInsertOrUpdate(data),
|
||||
gINSERT_OPTION_SAVE,
|
||||
)
|
||||
}
|
||||
return nil, errors.New("saving into table with invalid data type")
|
||||
}
|
||||
30
database/gdb/gdb_model_join.go
Normal file
30
database/gdb/gdb_model_join.go
Normal file
@ -0,0 +1,30 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gdb
|
||||
|
||||
import "fmt"
|
||||
|
||||
// LeftJoin does "LEFT JOIN ... ON ..." statement on the model.
|
||||
func (m *Model) LeftJoin(table string, on string) *Model {
|
||||
model := m.getModel()
|
||||
model.tables += fmt.Sprintf(" LEFT JOIN %s ON (%s)", m.db.QuotePrefixTableName(table), on)
|
||||
return model
|
||||
}
|
||||
|
||||
// RightJoin does "RIGHT JOIN ... ON ..." statement on the model.
|
||||
func (m *Model) RightJoin(table string, on string) *Model {
|
||||
model := m.getModel()
|
||||
model.tables += fmt.Sprintf(" RIGHT JOIN %s ON (%s)", m.db.QuotePrefixTableName(table), on)
|
||||
return model
|
||||
}
|
||||
|
||||
// InnerJoin does "INNER JOIN ... ON ..." statement on the model.
|
||||
func (m *Model) InnerJoin(table string, on string) *Model {
|
||||
model := m.getModel()
|
||||
model.tables += fmt.Sprintf(" INNER JOIN %s ON (%s)", m.db.QuotePrefixTableName(table), on)
|
||||
return model
|
||||
}
|
||||
21
database/gdb/gdb_model_lock.go
Normal file
21
database/gdb/gdb_model_lock.go
Normal file
@ -0,0 +1,21 @@
|
||||
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gdb
|
||||
|
||||
// LockUpdate sets the lock for update for current operation.
|
||||
func (m *Model) LockUpdate() *Model {
|
||||
model := m.getModel()
|
||||
model.lockInfo = "FOR UPDATE"
|
||||
return model
|
||||
}
|
||||
|
||||
// LockShared sets the lock in share mode for current operation.
|
||||
func (m *Model) LockShared() *Model {
|
||||
model := m.getModel()
|
||||
model.lockInfo = "LOCK IN SHARE MODE"
|
||||
return model
|
||||
}
|
||||
27
database/gdb/gdb_model_option.go
Normal file
27
database/gdb/gdb_model_option.go
Normal file
@ -0,0 +1,27 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gdb
|
||||
|
||||
// Option adds extra operation option for the model.
|
||||
func (m *Model) Option(option int) *Model {
|
||||
model := m.getModel()
|
||||
model.option = model.option | option
|
||||
return model
|
||||
}
|
||||
|
||||
// OptionOmitEmpty sets OPTION_OMITEMPTY option for the model, which automatically filers
|
||||
// the data and where attributes for empty values.
|
||||
// Deprecated, use OmitEmpty instead.
|
||||
func (m *Model) OptionOmitEmpty() *Model {
|
||||
return m.Option(OPTION_OMITEMPTY)
|
||||
}
|
||||
|
||||
// OmitEmpty sets OPTION_OMITEMPTY option for the model, which automatically filers
|
||||
// the data and where attributes for empty values.
|
||||
func (m *Model) OmitEmpty() *Model {
|
||||
return m.Option(OPTION_OMITEMPTY)
|
||||
}
|
||||
315
database/gdb/gdb_model_select.go
Normal file
315
database/gdb/gdb_model_select.go
Normal file
@ -0,0 +1,315 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gdb
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// Select is alias of Model.All.
|
||||
// See Model.All.
|
||||
// Deprecated.
|
||||
func (m *Model) Select(where ...interface{}) (Result, error) {
|
||||
return m.All(where...)
|
||||
}
|
||||
|
||||
// All does "SELECT FROM ..." statement for the model.
|
||||
// It retrieves the records from table and returns the result as slice type.
|
||||
// It returns nil if there's no record retrieved with the given conditions from table.
|
||||
//
|
||||
// The optional parameter <where> is the same as the parameter of Model.Where function,
|
||||
// see Model.Where.
|
||||
func (m *Model) All(where ...interface{}) (Result, error) {
|
||||
if len(where) > 0 {
|
||||
return m.Where(where[0], where[1:]...).All()
|
||||
}
|
||||
condition, conditionArgs := m.formatCondition(false)
|
||||
return m.getAll(
|
||||
fmt.Sprintf("SELECT %s FROM %s%s", m.fields, m.tables, condition),
|
||||
conditionArgs...,
|
||||
)
|
||||
}
|
||||
|
||||
// Chunk iterates the query result with given size and callback function.
|
||||
func (m *Model) Chunk(limit int, callback func(result Result, err error) bool) {
|
||||
page := m.start
|
||||
if page == 0 {
|
||||
page = 1
|
||||
}
|
||||
model := m
|
||||
for {
|
||||
model = model.Page(page, limit)
|
||||
data, err := model.All()
|
||||
if err != nil {
|
||||
callback(nil, err)
|
||||
break
|
||||
}
|
||||
if len(data) == 0 {
|
||||
break
|
||||
}
|
||||
if callback(data, err) == false {
|
||||
break
|
||||
}
|
||||
if len(data) < limit {
|
||||
break
|
||||
}
|
||||
page++
|
||||
}
|
||||
}
|
||||
|
||||
// One retrieves one record from table and returns the result as map type.
|
||||
// It returns nil if there's no record retrieved with the given conditions from table.
|
||||
//
|
||||
// The optional parameter <where> is the same as the parameter of Model.Where function,
|
||||
// see Model.Where.
|
||||
func (m *Model) One(where ...interface{}) (Record, error) {
|
||||
if len(where) > 0 {
|
||||
return m.Where(where[0], where[1:]...).One()
|
||||
}
|
||||
condition, conditionArgs := m.formatCondition(true)
|
||||
all, err := m.getAll(fmt.Sprintf("SELECT %s FROM %s%s", m.fields, m.tables, condition), conditionArgs...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(all) > 0 {
|
||||
return all[0], nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Value retrieves a specified record value from table and returns the result as interface type.
|
||||
// It returns nil if there's no record found with the given conditions from table.
|
||||
//
|
||||
// If the optional parameter <fieldsAndWhere> is given, the fieldsAndWhere[0] is the selected fields
|
||||
// and fieldsAndWhere[1:] is treated as where condition fields.
|
||||
// Also see Model.Fields and Model.Where functions.
|
||||
func (m *Model) Value(fieldsAndWhere ...interface{}) (Value, error) {
|
||||
if len(fieldsAndWhere) > 0 {
|
||||
if len(fieldsAndWhere) > 2 {
|
||||
return m.Fields(gconv.String(fieldsAndWhere[0])).Where(fieldsAndWhere[1], fieldsAndWhere[2:]...).Value()
|
||||
} else if len(fieldsAndWhere) == 2 {
|
||||
return m.Fields(gconv.String(fieldsAndWhere[0])).Where(fieldsAndWhere[1]).Value()
|
||||
} else {
|
||||
return m.Fields(gconv.String(fieldsAndWhere[0])).Value()
|
||||
}
|
||||
}
|
||||
one, err := m.One()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, v := range one {
|
||||
return v, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Array queries and returns data values as slice from database.
|
||||
// Note that if there're multiple columns in the result, it returns just one column values randomly.
|
||||
//
|
||||
// If the optional parameter <fieldsAndWhere> is given, the fieldsAndWhere[0] is the selected fields
|
||||
// and fieldsAndWhere[1:] is treated as where condition fields.
|
||||
// Also see Model.Fields and Model.Where functions.
|
||||
func (m *Model) Array(fieldsAndWhere ...interface{}) ([]Value, error) {
|
||||
if len(fieldsAndWhere) > 0 {
|
||||
if len(fieldsAndWhere) > 2 {
|
||||
return m.Fields(gconv.String(fieldsAndWhere[0])).Where(fieldsAndWhere[1], fieldsAndWhere[2:]...).Array()
|
||||
} else if len(fieldsAndWhere) == 2 {
|
||||
return m.Fields(gconv.String(fieldsAndWhere[0])).Where(fieldsAndWhere[1]).Array()
|
||||
} else {
|
||||
return m.Fields(gconv.String(fieldsAndWhere[0])).Array()
|
||||
}
|
||||
}
|
||||
all, err := m.All()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// Eg:
|
||||
// user := new(User)
|
||||
// err := db.Table("user").Where("id", 1).Struct(user)
|
||||
//
|
||||
// user := (*User)(nil)
|
||||
// err := db.Table("user").Where("id", 1).Struct(&user)
|
||||
func (m *Model) Struct(pointer interface{}, where ...interface{}) error {
|
||||
one, err := m.One(where...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(one) == 0 {
|
||||
return sql.ErrNoRows
|
||||
}
|
||||
return one.Struct(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.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// Eg:
|
||||
// users := ([]User)(nil)
|
||||
// err := db.Table("user").Structs(&users)
|
||||
//
|
||||
// users := ([]*User)(nil)
|
||||
// err := db.Table("user").Structs(&users)
|
||||
func (m *Model) Structs(pointer interface{}, where ...interface{}) error {
|
||||
all, err := m.All(where...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(all) == 0 {
|
||||
return sql.ErrNoRows
|
||||
}
|
||||
return all.Structs(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.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// Eg:
|
||||
// user := new(User)
|
||||
// err := db.Table("user").Where("id", 1).Struct(user)
|
||||
//
|
||||
// user := (*User)(nil)
|
||||
// err := db.Table("user").Where("id", 1).Struct(&user)
|
||||
//
|
||||
// users := ([]User)(nil)
|
||||
// err := db.Table("user").Structs(&users)
|
||||
//
|
||||
// users := ([]*User)(nil)
|
||||
// err := db.Table("user").Structs(&users)
|
||||
func (m *Model) Scan(pointer interface{}, where ...interface{}) error {
|
||||
t := reflect.TypeOf(pointer)
|
||||
k := t.Kind()
|
||||
if k != reflect.Ptr {
|
||||
return fmt.Errorf("params should be type of pointer, but got: %v", k)
|
||||
}
|
||||
switch t.Elem().Kind() {
|
||||
case reflect.Array:
|
||||
case reflect.Slice:
|
||||
return m.Structs(pointer, where...)
|
||||
default:
|
||||
return m.Struct(pointer, where...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Count does "SELECT COUNT(x) FROM ..." statement for the model.
|
||||
// The optional parameter <where> is the same as the parameter of Model.Where function,
|
||||
// see Model.Where.
|
||||
func (m *Model) Count(where ...interface{}) (int, error) {
|
||||
if len(where) > 0 {
|
||||
return m.Where(where[0], where[1:]...).Count()
|
||||
}
|
||||
countFields := "COUNT(1)"
|
||||
if m.fields != "" && m.fields != "*" {
|
||||
countFields = fmt.Sprintf(`COUNT(%s)`, m.fields)
|
||||
}
|
||||
condition, conditionArgs := m.formatCondition(false)
|
||||
s := fmt.Sprintf("SELECT %s FROM %s %s", countFields, m.tables, condition)
|
||||
if len(m.groupBy) > 0 {
|
||||
s = fmt.Sprintf("SELECT COUNT(1) FROM (%s) count_alias", s)
|
||||
}
|
||||
list, err := m.getAll(s, conditionArgs...)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if len(list) > 0 {
|
||||
for _, v := range list[0] {
|
||||
return v.Int(), nil
|
||||
}
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// FindOne retrieves and returns a single Record by Model.WherePri and Model.One.
|
||||
// Also see Model.WherePri and Model.One.
|
||||
func (m *Model) FindOne(where ...interface{}) (Record, error) {
|
||||
if len(where) > 0 {
|
||||
return m.WherePri(where[0], where[1:]...).One()
|
||||
}
|
||||
return m.One()
|
||||
}
|
||||
|
||||
// FindAll retrieves and returns Result by by Model.WherePri and Model.All.
|
||||
// Also see Model.WherePri and Model.All.
|
||||
func (m *Model) FindAll(where ...interface{}) (Result, error) {
|
||||
if len(where) > 0 {
|
||||
return m.WherePri(where[0], where[1:]...).All()
|
||||
}
|
||||
return m.All()
|
||||
}
|
||||
|
||||
// FindValue retrieves and returns single field value by Model.WherePri and Model.Value.
|
||||
// Also see Model.WherePri and Model.Value.
|
||||
func (m *Model) FindValue(fieldsAndWhere ...interface{}) (Value, error) {
|
||||
if len(fieldsAndWhere) >= 2 {
|
||||
return m.WherePri(fieldsAndWhere[1], fieldsAndWhere[2:]...).Fields(gconv.String(fieldsAndWhere[0])).Value()
|
||||
}
|
||||
if len(fieldsAndWhere) == 1 {
|
||||
return m.Fields(gconv.String(fieldsAndWhere[0])).Value()
|
||||
}
|
||||
return m.Value()
|
||||
}
|
||||
|
||||
// FindArray queries and returns data values as slice from database.
|
||||
// Note that if there're multiple columns in the result, it returns just one column values randomly.
|
||||
// Also see Model.WherePri and Model.Value.
|
||||
func (m *Model) FindArray(fieldsAndWhere ...interface{}) ([]Value, error) {
|
||||
if len(fieldsAndWhere) >= 2 {
|
||||
return m.WherePri(fieldsAndWhere[1], fieldsAndWhere[2:]...).Fields(gconv.String(fieldsAndWhere[0])).Array()
|
||||
}
|
||||
if len(fieldsAndWhere) == 1 {
|
||||
return m.Fields(gconv.String(fieldsAndWhere[0])).Array()
|
||||
}
|
||||
return m.Array()
|
||||
}
|
||||
|
||||
// FindCount retrieves and returns the record number by Model.WherePri and Model.Count.
|
||||
// Also see Model.WherePri and Model.Count.
|
||||
func (m *Model) FindCount(where ...interface{}) (int, error) {
|
||||
if len(where) > 0 {
|
||||
return m.WherePri(where[0], where[1:]...).Count()
|
||||
}
|
||||
return m.Count()
|
||||
}
|
||||
|
||||
// FindScan retrieves and returns the record/records by Model.WherePri and Model.Scan.
|
||||
// Also see Model.WherePri and Model.Scan.
|
||||
func (m *Model) FindScan(pointer interface{}, where ...interface{}) error {
|
||||
if len(where) > 0 {
|
||||
return m.WherePri(where[0], where[1:]...).Scan(pointer)
|
||||
}
|
||||
return m.Scan(pointer)
|
||||
}
|
||||
45
database/gdb/gdb_model_update.go
Normal file
45
database/gdb/gdb_model_update.go
Normal file
@ -0,0 +1,45 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gdb
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
)
|
||||
|
||||
// Update does "UPDATE ... " statement for the model.
|
||||
//
|
||||
// If the optional parameter <dataAndWhere> is given, the dataAndWhere[0] is the updated data field,
|
||||
// and dataAndWhere[1:] is treated as where condition fields.
|
||||
// Also see Model.Data and Model.Where functions.
|
||||
func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err error) {
|
||||
if len(dataAndWhere) > 0 {
|
||||
if len(dataAndWhere) > 2 {
|
||||
return m.Data(dataAndWhere[0]).Where(dataAndWhere[1], dataAndWhere[2:]...).Update()
|
||||
} else if len(dataAndWhere) == 2 {
|
||||
return m.Data(dataAndWhere[0]).Where(dataAndWhere[1]).Update()
|
||||
} else {
|
||||
return m.Data(dataAndWhere[0]).Update()
|
||||
}
|
||||
}
|
||||
defer func() {
|
||||
if err == nil {
|
||||
m.checkAndRemoveCache()
|
||||
}
|
||||
}()
|
||||
if m.data == nil {
|
||||
return nil, errors.New("updating table with empty data")
|
||||
}
|
||||
condition, conditionArgs := m.formatCondition(false)
|
||||
return m.db.DoUpdate(
|
||||
m.getLink(true),
|
||||
m.tables,
|
||||
m.filterDataForInsertOrUpdate(m.data),
|
||||
condition,
|
||||
m.mergeArguments(conditionArgs)...,
|
||||
)
|
||||
}
|
||||
205
database/gdb/gdb_model_utility.go
Normal file
205
database/gdb/gdb_model_utility.go
Normal file
@ -0,0 +1,205 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gdb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/container/gmap"
|
||||
"github.com/gogf/gf/container/gset"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
)
|
||||
|
||||
// getModel creates and returns a cloned model of current model if <safe> is true, or else it returns
|
||||
// the current model.
|
||||
func (m *Model) getModel() *Model {
|
||||
if !m.safe {
|
||||
return m
|
||||
} else {
|
||||
return m.Clone()
|
||||
}
|
||||
}
|
||||
|
||||
// filterDataForInsertOrUpdate does filter feature with data for inserting/updating operations.
|
||||
// Note that, it does not filter list item, which is also type of map, for "omit empty" feature.
|
||||
func (m *Model) filterDataForInsertOrUpdate(data interface{}) interface{} {
|
||||
if list, ok := m.data.(List); ok {
|
||||
for k, item := range list {
|
||||
list[k] = m.doFilterDataMapForInsertOrUpdate(item, false)
|
||||
}
|
||||
return list
|
||||
} else if item, ok := m.data.(Map); ok {
|
||||
return m.doFilterDataMapForInsertOrUpdate(item, true)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// doFilterDataMapForInsertOrUpdate does the filter features for map.
|
||||
// Note that, it does not filter list item, which is also type of map, for "omit empty" feature.
|
||||
func (m *Model) doFilterDataMapForInsertOrUpdate(data Map, allowOmitEmpty bool) Map {
|
||||
if m.filter {
|
||||
data = m.db.filterFields(m.schema, m.tables, data)
|
||||
}
|
||||
// Remove key-value pairs of which the value is empty.
|
||||
if allowOmitEmpty && m.option&OPTION_OMITEMPTY > 0 {
|
||||
m := gmap.NewStrAnyMapFrom(data)
|
||||
m.FilterEmpty()
|
||||
data = m.Map()
|
||||
}
|
||||
|
||||
if len(m.fields) > 0 && m.fields != "*" {
|
||||
// Keep specified fields.
|
||||
set := gset.NewStrSetFrom(gstr.SplitAndTrim(m.fields, ","))
|
||||
for k := range data {
|
||||
if !set.Contains(k) {
|
||||
delete(data, k)
|
||||
}
|
||||
}
|
||||
} else if len(m.fieldsEx) > 0 {
|
||||
// Filter specified fields.
|
||||
for _, v := range gstr.SplitAndTrim(m.fieldsEx, ",") {
|
||||
delete(data, v)
|
||||
}
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// getLink returns the underlying database link object with configured <linkType> attribute.
|
||||
// The parameter <master> specifies whether using the master node if master-slave configured.
|
||||
func (m *Model) getLink(master bool) Link {
|
||||
if m.tx != nil {
|
||||
return m.tx.tx
|
||||
}
|
||||
linkType := m.linkType
|
||||
if linkType == 0 {
|
||||
if master {
|
||||
linkType = gLINK_TYPE_MASTER
|
||||
} else {
|
||||
linkType = gLINK_TYPE_SLAVE
|
||||
}
|
||||
}
|
||||
switch linkType {
|
||||
case gLINK_TYPE_MASTER:
|
||||
link, err := m.db.GetMaster(m.schema)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return link
|
||||
case gLINK_TYPE_SLAVE:
|
||||
link, err := m.db.GetSlave(m.schema)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return link
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getPrimaryKey retrieves and returns the primary key name of the model table.
|
||||
// It parses m.tables to retrieve the primary table name, supporting m.tables like:
|
||||
// "user", "user u", "user as u, user_detail as ud".
|
||||
func (m *Model) getPrimaryKey() string {
|
||||
table := gstr.SplitAndTrim(m.tables, " ")[0]
|
||||
tableFields, err := m.db.TableFields(table)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
for name, field := range tableFields {
|
||||
if gstr.ContainsI(field.Key, "pri") {
|
||||
return name
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// checkAndRemoveCache checks and remove the cache if necessary.
|
||||
func (m *Model) checkAndRemoveCache() {
|
||||
if m.cacheEnabled && m.cacheDuration < 0 && len(m.cacheName) > 0 {
|
||||
m.db.GetCache().Remove(m.cacheName)
|
||||
}
|
||||
}
|
||||
|
||||
// formatCondition formats where arguments of the model and returns a new condition sql and its arguments.
|
||||
// Note that this function does not change any attribute value of the <m>.
|
||||
//
|
||||
// The parameter <limit> specifies whether limits querying only one record if m.limit is not set.
|
||||
func (m *Model) formatCondition(limit bool) (condition string, conditionArgs []interface{}) {
|
||||
var where string
|
||||
if len(m.whereHolder) > 0 {
|
||||
for _, v := range m.whereHolder {
|
||||
switch v.operator {
|
||||
case gWHERE_HOLDER_WHERE:
|
||||
if where == "" {
|
||||
newWhere, newArgs := formatWhere(m.db, v.where, v.args, m.option&OPTION_OMITEMPTY > 0)
|
||||
if len(newWhere) > 0 {
|
||||
where = newWhere
|
||||
conditionArgs = newArgs
|
||||
}
|
||||
continue
|
||||
}
|
||||
fallthrough
|
||||
|
||||
case gWHERE_HOLDER_AND:
|
||||
newWhere, newArgs := formatWhere(m.db, v.where, v.args, m.option&OPTION_OMITEMPTY > 0)
|
||||
if len(newWhere) > 0 {
|
||||
if where[0] == '(' {
|
||||
where = fmt.Sprintf(`%s AND (%s)`, where, newWhere)
|
||||
} else {
|
||||
where = fmt.Sprintf(`(%s) AND (%s)`, where, newWhere)
|
||||
}
|
||||
conditionArgs = append(conditionArgs, newArgs...)
|
||||
}
|
||||
|
||||
case gWHERE_HOLDER_OR:
|
||||
newWhere, newArgs := formatWhere(m.db, v.where, v.args, m.option&OPTION_OMITEMPTY > 0)
|
||||
if len(newWhere) > 0 {
|
||||
if where[0] == '(' {
|
||||
where = fmt.Sprintf(`%s OR (%s)`, where, newWhere)
|
||||
} else {
|
||||
where = fmt.Sprintf(`(%s) OR (%s)`, where, newWhere)
|
||||
}
|
||||
conditionArgs = append(conditionArgs, newArgs...)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if where != "" {
|
||||
condition += " WHERE " + where
|
||||
}
|
||||
if m.groupBy != "" {
|
||||
condition += " GROUP BY " + m.groupBy
|
||||
}
|
||||
if m.orderBy != "" {
|
||||
condition += " ORDER BY " + m.orderBy
|
||||
}
|
||||
if m.limit != 0 {
|
||||
if m.start >= 0 {
|
||||
condition += fmt.Sprintf(" LIMIT %d,%d", m.start, m.limit)
|
||||
} else {
|
||||
condition += fmt.Sprintf(" LIMIT %d", m.limit)
|
||||
}
|
||||
} else if limit {
|
||||
condition += " LIMIT 1"
|
||||
}
|
||||
if m.offset >= 0 {
|
||||
condition += fmt.Sprintf(" OFFSET %d", m.offset)
|
||||
}
|
||||
if m.lockInfo != "" {
|
||||
condition += " " + m.lockInfo
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// mergeArguments creates and returns new arguments by merging <m.extraArgs> and given <args>.
|
||||
func (m *Model) mergeArguments(args []interface{}) []interface{} {
|
||||
if len(m.extraArgs) > 0 {
|
||||
newArgs := make([]interface{}, len(m.extraArgs)+len(args))
|
||||
copy(newArgs, m.extraArgs)
|
||||
copy(newArgs[len(m.extraArgs):], args)
|
||||
return newArgs
|
||||
}
|
||||
return args
|
||||
}
|
||||
@ -1,48 +0,0 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gdb
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/internal/intlog"
|
||||
|
||||
_ "github.com/gf-third/mysql"
|
||||
)
|
||||
|
||||
type dbMysql struct {
|
||||
*dbBase
|
||||
}
|
||||
|
||||
// Open creates and returns a underlying database connection with given configuration.
|
||||
func (db *dbMysql) Open(config *ConfigNode) (*sql.DB, error) {
|
||||
var source string
|
||||
if config.LinkInfo != "" {
|
||||
source = config.LinkInfo
|
||||
} else {
|
||||
source = fmt.Sprintf(
|
||||
"%s:%s@tcp(%s:%s)/%s?charset=%s&multiStatements=true&parseTime=true&loc=Local",
|
||||
config.User, config.Pass, config.Host, config.Port, config.Name, config.Charset,
|
||||
)
|
||||
}
|
||||
intlog.Printf("Open: %s", source)
|
||||
if db, err := sql.Open("gf-mysql", source); err == nil {
|
||||
return db, nil
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// getChars returns the quote chars for field.
|
||||
func (db *dbMysql) getChars() (charLeft string, charRight string) {
|
||||
return "`", "`"
|
||||
}
|
||||
|
||||
// handleSqlBeforeExec handles the sql before posts it to database.
|
||||
func (db *dbMysql) handleSqlBeforeExec(sql string) string {
|
||||
return sql
|
||||
}
|
||||
47
database/gdb/gdb_result.go
Normal file
47
database/gdb/gdb_result.go
Normal file
@ -0,0 +1,47 @@
|
||||
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gdb
|
||||
|
||||
import "database/sql"
|
||||
|
||||
// SqlResult is execution result for sql operations.
|
||||
// It also supports batch operation result for rowsAffected.
|
||||
type SqlResult struct {
|
||||
result sql.Result
|
||||
affected int64
|
||||
}
|
||||
|
||||
// MustGetAffected returns the affected rows count, if any error occurs, it panics.
|
||||
func (r *SqlResult) MustGetAffected() int64 {
|
||||
rows, err := r.RowsAffected()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
// MustGetInsertId returns the last insert id, if any error occurs, it panics.
|
||||
func (r *SqlResult) MustGetInsertId() int64 {
|
||||
id, err := r.LastInsertId()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
// see sql.Result.RowsAffected
|
||||
func (r *SqlResult) RowsAffected() (int64, error) {
|
||||
if r.affected > 0 {
|
||||
return r.affected, nil
|
||||
}
|
||||
return r.result.RowsAffected()
|
||||
}
|
||||
|
||||
// see sql.Result.LastInsertId
|
||||
func (r *SqlResult) LastInsertId() (int64, error) {
|
||||
return r.result.LastInsertId()
|
||||
}
|
||||
@ -14,9 +14,9 @@ type Schema struct {
|
||||
}
|
||||
|
||||
// Schema creates and returns a schema.
|
||||
func (bs *dbBase) Schema(schema string) *Schema {
|
||||
func (c *Core) Schema(schema string) *Schema {
|
||||
return &Schema{
|
||||
db: bs.db,
|
||||
db: c.DB,
|
||||
schema: schema,
|
||||
}
|
||||
}
|
||||
@ -44,8 +44,8 @@ func (s *Schema) Table(table string) *Model {
|
||||
return m
|
||||
}
|
||||
|
||||
// Model is alias of dbBase.Table.
|
||||
// See dbBase.Table.
|
||||
// Model is alias of Core.Table.
|
||||
// See Core.Table.
|
||||
func (s *Schema) Model(table string) *Model {
|
||||
return s.Table(table)
|
||||
}
|
||||
|
||||
@ -1,60 +0,0 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
//
|
||||
// Note:
|
||||
// 1. It needs manually import: _ "github.com/mattn/go-sqlite3"
|
||||
// 2. It does not support Save/Replace features.
|
||||
|
||||
package gdb
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"github.com/gogf/gf/internal/intlog"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
)
|
||||
|
||||
type dbSqlite struct {
|
||||
*dbBase
|
||||
}
|
||||
|
||||
func (db *dbSqlite) Open(config *ConfigNode) (*sql.DB, error) {
|
||||
var source string
|
||||
if config.LinkInfo != "" {
|
||||
source = config.LinkInfo
|
||||
} else {
|
||||
source = config.Name
|
||||
}
|
||||
intlog.Printf("Open: %s", source)
|
||||
if db, err := sql.Open("sqlite3", source); err == nil {
|
||||
return db, nil
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
func (db *dbSqlite) getChars() (charLeft string, charRight string) {
|
||||
return "`", "`"
|
||||
}
|
||||
|
||||
// TODO
|
||||
func (db *dbSqlite) Tables(schema ...string) (tables []string, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO
|
||||
func (db *dbSqlite) TableFields(table string, schema ...string) (fields map[string]*TableField, err error) {
|
||||
table = gstr.Trim(table)
|
||||
if gstr.Contains(table, " ") {
|
||||
panic("function TableFields supports only single table operations")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// @todo 需要增加对Save方法的支持,可使用正则来实现替换,
|
||||
// @todo 将ON DUPLICATE KEY UPDATE触发器修改为两条SQL语句(INSERT OR IGNORE & UPDATE)
|
||||
func (db *dbSqlite) handleSqlBeforeExec(sql string) string {
|
||||
return sql
|
||||
}
|
||||
@ -7,8 +7,6 @@
|
||||
package gdb
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
@ -23,7 +21,7 @@ import (
|
||||
|
||||
// convertValue automatically checks and converts field value from database type
|
||||
// to golang variable type.
|
||||
func (bs *dbBase) convertValue(fieldValue []byte, fieldType string) interface{} {
|
||||
func (c *Core) convertValue(fieldValue []byte, fieldType string) interface{} {
|
||||
t, _ := gregex.ReplaceString(`\(.+\)`, "", fieldType)
|
||||
t = strings.ToLower(t)
|
||||
switch t {
|
||||
@ -108,10 +106,10 @@ func (bs *dbBase) convertValue(fieldValue []byte, fieldType string) interface{}
|
||||
}
|
||||
|
||||
// filterFields removes all key-value pairs which are not the field of given table.
|
||||
func (bs *dbBase) filterFields(schema, table string, data map[string]interface{}) map[string]interface{} {
|
||||
func (c *Core) filterFields(schema, table string, data map[string]interface{}) map[string]interface{} {
|
||||
// It must use data copy here to avoid its changing the origin data map.
|
||||
newDataMap := make(map[string]interface{}, len(data))
|
||||
if fields, err := bs.db.TableFields(table, schema); err == nil {
|
||||
if fields, err := c.DB.TableFields(table, schema); err == nil {
|
||||
for k, v := range data {
|
||||
if _, ok := fields[k]; ok {
|
||||
newDataMap[k] = v
|
||||
@ -120,74 +118,3 @@ func (bs *dbBase) filterFields(schema, table string, data map[string]interface{}
|
||||
}
|
||||
return newDataMap
|
||||
}
|
||||
|
||||
// Tables returns the table name array of current schema.
|
||||
func (bs *dbBase) Tables(schema ...string) (tables []string, err error) {
|
||||
var result Result
|
||||
link, err := bs.db.getSlave(schema...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result, err = bs.db.doGetAll(link, `SHOW TABLES`)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, m := range result {
|
||||
for _, v := range m {
|
||||
tables = append(tables, v.String())
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// TableFields retrieves and returns the fields of given table.
|
||||
//
|
||||
// Note that it returns a map containing the field name and its corresponding fields.
|
||||
// As a map is unsorted, the TableField struct has a "Index" field marks its sequence in the fields.
|
||||
//
|
||||
// It's using cache feature to enhance the performance, which is never expired util the process restarts.
|
||||
func (bs *dbBase) TableFields(table string, schema ...string) (fields map[string]*TableField, err error) {
|
||||
table = gstr.Trim(table)
|
||||
if gstr.Contains(table, " ") {
|
||||
panic("function TableFields supports only single table operations")
|
||||
}
|
||||
checkSchema := bs.schema.Val()
|
||||
if len(schema) > 0 && schema[0] != "" {
|
||||
checkSchema = schema[0]
|
||||
}
|
||||
v := bs.cache.GetOrSetFunc(
|
||||
fmt.Sprintf(`mysql_table_fields_%s_%s`, table, checkSchema),
|
||||
func() interface{} {
|
||||
var result Result
|
||||
var link *sql.DB
|
||||
link, err = bs.db.getSlave(checkSchema)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
result, err = bs.doGetAll(
|
||||
link,
|
||||
fmt.Sprintf(`SHOW FULL COLUMNS FROM %s`, bs.db.quoteWord(table)),
|
||||
)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
fields = make(map[string]*TableField)
|
||||
for i, m := range result {
|
||||
fields[m["Field"].String()] = &TableField{
|
||||
Index: i,
|
||||
Name: m["Field"].String(),
|
||||
Type: m["Type"].String(),
|
||||
Null: m["Null"].Bool(),
|
||||
Key: m["Key"].String(),
|
||||
Default: m["Default"].Val(),
|
||||
Extra: m["Extra"].String(),
|
||||
Comment: m["Comment"].String(),
|
||||
}
|
||||
}
|
||||
return fields
|
||||
}, 0)
|
||||
if err == nil {
|
||||
fields = v.(map[string]*TableField)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@ -32,15 +32,15 @@ func (tx *TX) Rollback() error {
|
||||
}
|
||||
|
||||
// Query does query operation on transaction.
|
||||
// See dbBase.Query.
|
||||
// See Core.Query.
|
||||
func (tx *TX) Query(query string, args ...interface{}) (rows *sql.Rows, err error) {
|
||||
return tx.db.doQuery(tx.tx, query, args...)
|
||||
return tx.db.DoQuery(tx.tx, query, args...)
|
||||
}
|
||||
|
||||
// Exec does none query operation on transaction.
|
||||
// See dbBase.Exec.
|
||||
// See Core.Exec.
|
||||
func (tx *TX) Exec(query string, args ...interface{}) (sql.Result, error) {
|
||||
return tx.db.doExec(tx.tx, query, args...)
|
||||
return tx.db.DoExec(tx.tx, query, args...)
|
||||
}
|
||||
|
||||
// Prepare creates a prepared statement for later queries or executions.
|
||||
@ -49,7 +49,7 @@ func (tx *TX) Exec(query 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(query string) (*sql.Stmt, error) {
|
||||
return tx.db.doPrepare(tx.tx, query)
|
||||
return tx.db.DoPrepare(tx.tx, query)
|
||||
}
|
||||
|
||||
// GetAll queries and returns data records from database.
|
||||
@ -154,7 +154,7 @@ func (tx *TX) GetCount(query string, args ...interface{}) (int, error) {
|
||||
//
|
||||
// The parameter <batch> specifies the batch operation count when given data is slice.
|
||||
func (tx *TX) Insert(table string, data interface{}, batch ...int) (sql.Result, error) {
|
||||
return tx.db.doInsert(tx.tx, table, data, gINSERT_OPTION_DEFAULT, batch...)
|
||||
return tx.db.DoInsert(tx.tx, table, data, gINSERT_OPTION_DEFAULT, batch...)
|
||||
}
|
||||
|
||||
// InsertIgnore does "INSERT IGNORE INTO ..." statement for the table.
|
||||
@ -167,7 +167,7 @@ func (tx *TX) Insert(table string, data interface{}, batch ...int) (sql.Result,
|
||||
//
|
||||
// The parameter <batch> specifies the batch operation count when given data is slice.
|
||||
func (tx *TX) InsertIgnore(table string, data interface{}, batch ...int) (sql.Result, error) {
|
||||
return tx.db.doInsert(tx.tx, table, data, gINSERT_OPTION_IGNORE, batch...)
|
||||
return tx.db.DoInsert(tx.tx, table, data, gINSERT_OPTION_IGNORE, batch...)
|
||||
}
|
||||
|
||||
// Replace does "REPLACE INTO ..." statement for the table.
|
||||
@ -183,7 +183,7 @@ func (tx *TX) InsertIgnore(table string, data interface{}, batch ...int) (sql.Re
|
||||
// If given data is type of slice, it then does batch replacing, and the optional parameter
|
||||
// <batch> specifies the batch operation count.
|
||||
func (tx *TX) Replace(table string, data interface{}, batch ...int) (sql.Result, error) {
|
||||
return tx.db.doInsert(tx.tx, table, data, gINSERT_OPTION_REPLACE, batch...)
|
||||
return tx.db.DoInsert(tx.tx, table, data, gINSERT_OPTION_REPLACE, batch...)
|
||||
}
|
||||
|
||||
// Save does "INSERT INTO ... ON DUPLICATE KEY UPDATE..." statement for the table.
|
||||
@ -198,31 +198,31 @@ func (tx *TX) Replace(table string, data interface{}, batch ...int) (sql.Result,
|
||||
// If given data is type of slice, it then does batch saving, and the optional parameter
|
||||
// <batch> specifies the batch operation count.
|
||||
func (tx *TX) Save(table string, data interface{}, batch ...int) (sql.Result, error) {
|
||||
return tx.db.doInsert(tx.tx, table, data, gINSERT_OPTION_SAVE, batch...)
|
||||
return tx.db.DoInsert(tx.tx, table, data, gINSERT_OPTION_SAVE, batch...)
|
||||
}
|
||||
|
||||
// BatchInsert batch inserts data.
|
||||
// The parameter <list> must be type of slice of map or struct.
|
||||
func (tx *TX) BatchInsert(table string, list interface{}, batch ...int) (sql.Result, error) {
|
||||
return tx.db.doBatchInsert(tx.tx, table, list, gINSERT_OPTION_DEFAULT, batch...)
|
||||
return tx.db.DoBatchInsert(tx.tx, table, list, gINSERT_OPTION_DEFAULT, batch...)
|
||||
}
|
||||
|
||||
// BatchInsert batch inserts data with ignore option.
|
||||
// The parameter <list> must be type of slice of map or struct.
|
||||
func (tx *TX) BatchInsertIgnore(table string, list interface{}, batch ...int) (sql.Result, error) {
|
||||
return tx.db.doBatchInsert(tx.tx, table, list, gINSERT_OPTION_IGNORE, batch...)
|
||||
return tx.db.DoBatchInsert(tx.tx, table, list, gINSERT_OPTION_IGNORE, batch...)
|
||||
}
|
||||
|
||||
// BatchReplace batch replaces data.
|
||||
// The parameter <list> must be type of slice of map or struct.
|
||||
func (tx *TX) BatchReplace(table string, list interface{}, batch ...int) (sql.Result, error) {
|
||||
return tx.db.doBatchInsert(tx.tx, table, list, gINSERT_OPTION_REPLACE, batch...)
|
||||
return tx.db.DoBatchInsert(tx.tx, table, list, gINSERT_OPTION_REPLACE, batch...)
|
||||
}
|
||||
|
||||
// BatchSave batch replaces data.
|
||||
// The parameter <list> must be type of slice of map or struct.
|
||||
func (tx *TX) BatchSave(table string, list interface{}, batch ...int) (sql.Result, error) {
|
||||
return tx.db.doBatchInsert(tx.tx, table, list, gINSERT_OPTION_SAVE, batch...)
|
||||
return tx.db.DoBatchInsert(tx.tx, table, list, gINSERT_OPTION_SAVE, batch...)
|
||||
}
|
||||
|
||||
// Update does "UPDATE ... " statement for the table.
|
||||
@ -244,7 +244,7 @@ func (tx *TX) Update(table string, data interface{}, condition interface{}, args
|
||||
if newWhere != "" {
|
||||
newWhere = " WHERE " + newWhere
|
||||
}
|
||||
return tx.db.doUpdate(tx.tx, table, data, newWhere, newArgs...)
|
||||
return tx.db.DoUpdate(tx.tx, table, data, newWhere, newArgs...)
|
||||
}
|
||||
|
||||
// Delete does "DELETE FROM ... " statement for the table.
|
||||
@ -263,5 +263,5 @@ func (tx *TX) Delete(table string, condition interface{}, args ...interface{}) (
|
||||
if newWhere != "" {
|
||||
newWhere = " WHERE " + newWhere
|
||||
}
|
||||
return tx.db.doDelete(tx.tx, table, newWhere, newArgs...)
|
||||
return tx.db.DoDelete(tx.tx, table, newWhere, newArgs...)
|
||||
}
|
||||
|
||||
@ -28,11 +28,33 @@ func (r Result) Xml(rootTag ...string) string {
|
||||
|
||||
// List converts <r> to a List.
|
||||
func (r Result) List() List {
|
||||
l := make(List, len(r))
|
||||
list := make(List, len(r))
|
||||
for k, v := range r {
|
||||
l[k] = v.Map()
|
||||
list[k] = v.Map()
|
||||
}
|
||||
return l
|
||||
return list
|
||||
}
|
||||
|
||||
// Array retrieves and returns specified column values as slice.
|
||||
// The parameter <field> is optional is the column field is only one.
|
||||
func (r Result) Array(field ...string) []Value {
|
||||
array := make([]Value, len(r))
|
||||
if len(r) == 0 {
|
||||
return array
|
||||
}
|
||||
key := ""
|
||||
if len(field) > 0 && field[0] != "" {
|
||||
key = field[0]
|
||||
} else {
|
||||
for k, _ := range r[0] {
|
||||
key = k
|
||||
break
|
||||
}
|
||||
}
|
||||
for k, v := range r {
|
||||
array[k] = v[key]
|
||||
}
|
||||
return array
|
||||
}
|
||||
|
||||
// MapKeyStr converts <r> to a map[string]Map of which key is specified by <key>.
|
||||
|
||||
74
database/gdb/gdb_unit_z_driver_test.go
Normal file
74
database/gdb/gdb_unit_z_driver_test.go
Normal file
@ -0,0 +1,74 @@
|
||||
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gdb_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/container/gtype"
|
||||
"github.com/gogf/gf/database/gdb"
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// MyDriver is a custom database driver, which is used for testing only.
|
||||
// For simplifying the unit testing case purpose, MyDriver struct inherits the mysql driver
|
||||
// gdb.DriverMysql and overwrites its function HandleSqlBeforeCommit.
|
||||
// So if there's any sql execution, it goes through MyDriver.HandleSqlBeforeCommit firstly and
|
||||
// then gdb.DriverMysql.HandleSqlBeforeCommit.
|
||||
// You can call it sql "HOOK" or "HiJack" as your will.
|
||||
type MyDriver struct {
|
||||
*gdb.DriverMysql
|
||||
}
|
||||
|
||||
var (
|
||||
customDriverName = "MyDriver"
|
||||
latestSqlString = gtype.NewString() // For simplifying unit testing only.
|
||||
)
|
||||
|
||||
// New creates and returns a database object for mysql.
|
||||
// It implements the interface of gdb.Driver for extra database driver installation.
|
||||
func (d *MyDriver) New(core *gdb.Core, node *gdb.ConfigNode) (gdb.DB, error) {
|
||||
return &MyDriver{
|
||||
&gdb.DriverMysql{
|
||||
Core: core,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// HandleSqlBeforeCommit handles the sql before posts it to database.
|
||||
// It here overwrites the same method of gdb.DriverMysql and makes some custom changes.
|
||||
func (d *MyDriver) HandleSqlBeforeCommit(link gdb.Link, sql string, args []interface{}) (string, []interface{}) {
|
||||
latestSqlString.Set(sql)
|
||||
return d.DriverMysql.HandleSqlBeforeCommit(link, sql, args)
|
||||
}
|
||||
|
||||
func init() {
|
||||
// It here registers my custom driver in package initialization function "init".
|
||||
// You can later use this type in the database configuration.
|
||||
gdb.Register(customDriverName, &MyDriver{})
|
||||
}
|
||||
|
||||
func Test_Custom_Driver(t *testing.T) {
|
||||
gdb.AddConfigNode("driver-test", gdb.ConfigNode{
|
||||
Host: "127.0.0.1",
|
||||
Port: "3306",
|
||||
User: "root",
|
||||
Pass: "12345678",
|
||||
Name: "test",
|
||||
Type: customDriverName,
|
||||
Role: "master",
|
||||
Charset: "utf8",
|
||||
})
|
||||
gtest.Case(t, func() {
|
||||
gtest.Assert(latestSqlString.Val(), "")
|
||||
sqlString := "select 10000"
|
||||
value, err := g.DB("driver-test").GetValue(sqlString)
|
||||
gtest.Assert(err, nil)
|
||||
gtest.Assert(value, 10000)
|
||||
gtest.Assert(latestSqlString.Val(), sqlString)
|
||||
})
|
||||
}
|
||||
@ -11,6 +11,33 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_Func_bindArgsToQuery(t *testing.T) {
|
||||
// mysql
|
||||
gtest.Case(t, func() {
|
||||
var s string
|
||||
s = bindArgsToQuery("select * from table where id>=? and sex=?", []interface{}{100, 1})
|
||||
gtest.Assert(s, "select * from table where id>=100 and sex=1")
|
||||
})
|
||||
// mssql
|
||||
gtest.Case(t, func() {
|
||||
var s string
|
||||
s = bindArgsToQuery("select * from table where id>=@p1 and sex=@p2", []interface{}{100, 1})
|
||||
gtest.Assert(s, "select * from table where id>=100 and sex=1")
|
||||
})
|
||||
// pgsql
|
||||
gtest.Case(t, func() {
|
||||
var s string
|
||||
s = bindArgsToQuery("select * from table where id>=$1 and sex=$2", []interface{}{100, 1})
|
||||
gtest.Assert(s, "select * from table where id>=100 and sex=1")
|
||||
})
|
||||
// oracle
|
||||
gtest.Case(t, func() {
|
||||
var s string
|
||||
s = bindArgsToQuery("select * from table where id>=:1 and sex=:2", []interface{}{100, 1})
|
||||
gtest.Assert(s, "select * from table where id>=100 and sex=1")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Func_doQuoteWord(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
array := map[string]string{
|
||||
|
||||
@ -534,6 +534,38 @@ func Test_Model_Value(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_Array(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.Case(t, func() {
|
||||
all, err := db.Table(table).Where("id", g.Slice{1, 2, 3}).All()
|
||||
gtest.Assert(err, nil)
|
||||
gtest.Assert(all.Array("id"), g.Slice{1, 2, 3})
|
||||
gtest.Assert(all.Array("nickname"), g.Slice{"name_1", "name_2", "name_3"})
|
||||
})
|
||||
gtest.Case(t, func() {
|
||||
array, err := db.Table(table).Fields("nickname").Where("id", g.Slice{1, 2, 3}).Array()
|
||||
gtest.Assert(err, nil)
|
||||
gtest.Assert(array, g.Slice{"name_1", "name_2", "name_3"})
|
||||
})
|
||||
gtest.Case(t, func() {
|
||||
array, err := db.Table(table).Array("nickname", "id", g.Slice{1, 2, 3})
|
||||
gtest.Assert(err, nil)
|
||||
gtest.Assert(array, g.Slice{"name_1", "name_2", "name_3"})
|
||||
})
|
||||
gtest.Case(t, func() {
|
||||
array, err := db.Table(table).FindArray("nickname", "id", g.Slice{1, 2, 3})
|
||||
gtest.Assert(err, nil)
|
||||
gtest.Assert(array, g.Slice{"name_1", "name_2", "name_3"})
|
||||
})
|
||||
gtest.Case(t, func() {
|
||||
array, err := db.Table(table).FindArray("nickname", g.Slice{1, 2, 3})
|
||||
gtest.Assert(err, nil)
|
||||
gtest.Assert(array, g.Slice{"name_1", "name_2", "name_3"})
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_FindValue(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
@ -681,6 +713,28 @@ func Test_Model_Struct(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_Struct_CustomType(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
type MyInt int
|
||||
|
||||
gtest.Case(t, func() {
|
||||
type User struct {
|
||||
Id MyInt
|
||||
Passport string
|
||||
Password string
|
||||
NickName string
|
||||
CreateTime gtime.Time
|
||||
}
|
||||
user := new(User)
|
||||
err := db.Table(table).Where("id=1").Struct(user)
|
||||
gtest.Assert(err, nil)
|
||||
gtest.Assert(user.NickName, "name_1")
|
||||
gtest.Assert(user.CreateTime.String(), "2018-10-24 10:00:00")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_Structs(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
@ -882,6 +936,18 @@ func Test_Model_GroupBy(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_Data(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.Case(t, func() {
|
||||
result, err := db.Table(table).Data("nickname=?", "test").Where("id=?", 3).Update()
|
||||
gtest.Assert(err, nil)
|
||||
n, _ := result.RowsAffected()
|
||||
gtest.Assert(n, 1)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_Where(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
@ -1125,6 +1191,44 @@ func Test_Model_Where(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_Where_ISNULL_1(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.Case(t, func() {
|
||||
//db.SetDebug(true)
|
||||
result, err := db.Table(table).Data("nickname", nil).Where("id", 2).Update()
|
||||
gtest.Assert(err, nil)
|
||||
n, _ := result.RowsAffected()
|
||||
gtest.Assert(n, 1)
|
||||
|
||||
one, err := db.Table(table).Where("nickname", nil).One()
|
||||
gtest.Assert(err, nil)
|
||||
gtest.Assert(one.IsEmpty(), false)
|
||||
gtest.Assert(one["id"], 2)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_Where_ISNULL_2(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
// complicated one.
|
||||
gtest.Case(t, func() {
|
||||
//db.SetDebug(true)
|
||||
conditions := g.Map{
|
||||
"nickname like ?": "%name%",
|
||||
"id between ? and ?": g.Slice{1, 3},
|
||||
"id > 0": nil,
|
||||
"create_time > 0": nil,
|
||||
"id": g.Slice{1, 2, 3},
|
||||
}
|
||||
result, err := db.Table(table).WherePri(conditions).Order("id asc").All()
|
||||
gtest.Assert(err, nil)
|
||||
gtest.Assert(len(result), 3)
|
||||
gtest.Assert(result[0]["id"].Int(), 1)
|
||||
})
|
||||
}
|
||||
func Test_Model_WherePri(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
@ -41,10 +41,10 @@ type Config struct {
|
||||
Port int
|
||||
Db int
|
||||
Pass string // Password for AUTH.
|
||||
MaxIdle int // Maximum number of connections allowed to be idle (default is 0 means no idle connection)
|
||||
MaxActive int // Maximum number of connections limit (default is 0 means no limit)
|
||||
IdleTimeout time.Duration // Maximum idle time for connection (default is 60 seconds, not allowed to be set to 0)
|
||||
MaxConnLifetime time.Duration // Maximum lifetime of the connection (default is 60 seconds, not allowed to be set to 0)
|
||||
MaxIdle int // Maximum number of connections allowed to be idle (default is 10)
|
||||
MaxActive int // Maximum number of connections limit (default is 0 means no limit).
|
||||
IdleTimeout time.Duration // Maximum idle time for connection (default is 10 seconds, not allowed to be set to 0)
|
||||
MaxConnLifetime time.Duration // Maximum lifetime of the connection (default is 30 seconds, not allowed to be set to 0)
|
||||
ConnectTimeout time.Duration // Dial connection timeout.
|
||||
}
|
||||
|
||||
@ -54,9 +54,10 @@ type PoolStats struct {
|
||||
}
|
||||
|
||||
const (
|
||||
gDEFAULT_POOL_IDLE_TIMEOUT = 60 * time.Second
|
||||
gDEFAULT_POOL_IDLE_TIMEOUT = 10 * time.Second
|
||||
gDEFAULT_POOL_CONN_TIMEOUT = 10 * time.Second
|
||||
gDEFAULT_POOL_MAX_LIFE_TIME = 60 * time.Second
|
||||
gDEFAULT_POOL_MAX_IDLE = 10
|
||||
gDEFAULT_POOL_MAX_LIFE_TIME = 30 * time.Second
|
||||
)
|
||||
|
||||
var (
|
||||
@ -67,6 +68,12 @@ var (
|
||||
// New creates a redis client object with given configuration.
|
||||
// Redis client maintains a connection pool automatically.
|
||||
func New(config Config) *Redis {
|
||||
// The MaxIdle is the most important attribute of the connection pool.
|
||||
// Only if this attribute is set, the created connections from client
|
||||
// can not exceed the limit of the server.
|
||||
if config.MaxIdle == 0 {
|
||||
config.MaxIdle = gDEFAULT_POOL_MAX_IDLE
|
||||
}
|
||||
if config.IdleTimeout == 0 {
|
||||
config.IdleTimeout = gDEFAULT_POOL_IDLE_TIMEOUT
|
||||
}
|
||||
@ -80,6 +87,7 @@ func New(config Config) *Redis {
|
||||
config: config,
|
||||
pool: pools.GetOrSetFuncLock(fmt.Sprintf("%v", config), func() interface{} {
|
||||
return &redis.Pool{
|
||||
Wait: true,
|
||||
IdleTimeout: config.IdleTimeout,
|
||||
MaxActive: config.MaxActive,
|
||||
MaxIdle: config.MaxIdle,
|
||||
@ -131,7 +139,8 @@ func NewFromStr(str string) (*Redis, error) {
|
||||
// It is not necessary to call Close manually.
|
||||
func (r *Redis) Close() error {
|
||||
if r.group != "" {
|
||||
// If it is an instance object, it needs to remove it from the instance Map.
|
||||
// If it is an instance object,
|
||||
// it needs to remove it from the instance Map.
|
||||
instances.Remove(r.group)
|
||||
}
|
||||
pools.Remove(fmt.Sprintf("%v", r.config))
|
||||
@ -150,22 +159,31 @@ func (r *Redis) GetConn() *Conn {
|
||||
return r.Conn()
|
||||
}
|
||||
|
||||
// SetMaxIdle sets the MaxIdle attribute of the connection pool.
|
||||
// SetMaxIdle sets the maximum number of idle connections in the pool.
|
||||
func (r *Redis) SetMaxIdle(value int) {
|
||||
r.pool.MaxIdle = value
|
||||
}
|
||||
|
||||
// SetMaxActive sets the MaxActive attribute of the connection pool.
|
||||
// SetMaxActive sets the maximum number of connections allocated by the pool at a given time.
|
||||
// When zero, there is no limit on the number of connections in the pool.
|
||||
//
|
||||
// Note that if the pool is at the MaxActive limit, then all the operations will wait for
|
||||
// a connection to be returned to the pool before returning.
|
||||
func (r *Redis) SetMaxActive(value int) {
|
||||
r.pool.MaxActive = value
|
||||
}
|
||||
|
||||
// SetIdleTimeout sets the IdleTimeout attribute of the connection pool.
|
||||
// It closes connections after remaining idle for this duration. If the value
|
||||
// is zero, then idle connections are not closed. Applications should set
|
||||
// the timeout to a value less than the server's timeout.
|
||||
func (r *Redis) SetIdleTimeout(value time.Duration) {
|
||||
r.pool.IdleTimeout = value
|
||||
}
|
||||
|
||||
// SetMaxConnLifetime sets the MaxConnLifetime attribute of the connection pool.
|
||||
// It closes connections older than this duration. If the value is zero, then
|
||||
// the pool does not close connections based on age.
|
||||
func (r *Redis) SetMaxConnLifetime(value time.Duration) {
|
||||
r.pool.MaxConnLifetime = value
|
||||
}
|
||||
|
||||
@ -210,6 +210,11 @@ func Test_Error(t *testing.T) {
|
||||
func Test_Bool(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
redis := gredis.New(config)
|
||||
defer func() {
|
||||
redis.Do("DEL", "key-true")
|
||||
redis.Do("DEL", "key-false")
|
||||
}()
|
||||
|
||||
_, err := redis.Do("SET", "key-true", true)
|
||||
gtest.Assert(err, nil)
|
||||
|
||||
@ -230,6 +235,8 @@ func Test_Int(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
redis := gredis.New(config)
|
||||
key := guuid.New()
|
||||
defer redis.Do("DEL", key)
|
||||
|
||||
_, err := redis.Do("SET", key, 1)
|
||||
gtest.Assert(err, nil)
|
||||
|
||||
@ -243,6 +250,8 @@ func Test_HSet(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
redis := gredis.New(config)
|
||||
key := guuid.New()
|
||||
defer redis.Do("DEL", key)
|
||||
|
||||
_, err := redis.Do("HSET", key, "name", "john")
|
||||
gtest.Assert(err, nil)
|
||||
|
||||
@ -251,3 +260,24 @@ func Test_HSet(t *testing.T) {
|
||||
gtest.Assert(r.Strings(), g.ArrayStr{"name", "john"})
|
||||
})
|
||||
}
|
||||
|
||||
func Test_HGetAll(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
var err error
|
||||
redis := gredis.New(config)
|
||||
key := guuid.New()
|
||||
defer redis.Do("DEL", key)
|
||||
|
||||
_, err = redis.Do("HSET", key, "id", "100")
|
||||
gtest.Assert(err, nil)
|
||||
_, err = redis.Do("HSET", key, "name", "john")
|
||||
gtest.Assert(err, nil)
|
||||
|
||||
r, err := redis.DoVar("HGETALL", key)
|
||||
gtest.Assert(err, nil)
|
||||
gtest.Assert(r.Map(), g.MapStrAny{
|
||||
"id": 100,
|
||||
"name": "john",
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@ -6,260 +6,3 @@
|
||||
|
||||
// Package gdebug contains facilities for programs to debug themselves while they are running.
|
||||
package gdebug
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/encoding/ghash"
|
||||
|
||||
"github.com/gogf/gf/crypto/gmd5"
|
||||
"github.com/gogf/gf/os/gfile"
|
||||
)
|
||||
|
||||
const (
|
||||
gMAX_DEPTH = 1000
|
||||
gFILTER_KEY = "/debug/gdebug/gdebug"
|
||||
)
|
||||
|
||||
var (
|
||||
goRootForFilter = runtime.GOROOT() // goRootForFilter is used for stack filtering purpose.
|
||||
binaryVersion = "" // The version of current running binary(uint64 hex).
|
||||
binaryVersionMd5 = "" // The version of current running binary(MD5).
|
||||
)
|
||||
|
||||
func init() {
|
||||
if goRootForFilter != "" {
|
||||
goRootForFilter = strings.Replace(goRootForFilter, "\\", "/", -1)
|
||||
}
|
||||
}
|
||||
|
||||
// BinVersion returns the version of current running binary.
|
||||
// It uses ghash.BKDRHash+BASE36 algorithm to calculate the unique version of the binary.
|
||||
func BinVersion() string {
|
||||
if binaryVersion == "" {
|
||||
binaryVersion = strconv.FormatInt(
|
||||
int64(ghash.BKDRHash(gfile.GetBytes(gfile.SelfPath()))),
|
||||
36,
|
||||
)
|
||||
}
|
||||
return binaryVersion
|
||||
}
|
||||
|
||||
// BinVersionMd5 returns the version of current running binary.
|
||||
// It uses MD5 algorithm to calculate the unique version of the binary.
|
||||
func BinVersionMd5() string {
|
||||
if binaryVersionMd5 == "" {
|
||||
binaryVersionMd5, _ = gmd5.EncryptFile(gfile.SelfPath())
|
||||
}
|
||||
return binaryVersionMd5
|
||||
}
|
||||
|
||||
// PrintStack prints to standard error the stack trace returned by runtime.Stack.
|
||||
func PrintStack(skip ...int) {
|
||||
fmt.Print(Stack(skip...))
|
||||
}
|
||||
|
||||
// Stack returns a formatted stack trace of the goroutine that calls it.
|
||||
// It calls runtime.Stack with a large enough buffer to capture the entire trace.
|
||||
func Stack(skip ...int) string {
|
||||
return StackWithFilter("", skip...)
|
||||
}
|
||||
|
||||
// StackWithFilter returns a formatted stack trace of the goroutine that calls it.
|
||||
// It calls runtime.Stack with a large enough buffer to capture the entire trace.
|
||||
//
|
||||
// The parameter <filter> is used to filter the path of the caller.
|
||||
func StackWithFilter(filter string, skip ...int) string {
|
||||
return StackWithFilters([]string{filter}, skip...)
|
||||
}
|
||||
|
||||
// StackWithFilters returns a formatted stack trace of the goroutine that calls it.
|
||||
// It calls runtime.Stack with a large enough buffer to capture the entire trace.
|
||||
//
|
||||
// The parameter <filters> is a slice of strings, which are used to filter the path of the caller.
|
||||
func StackWithFilters(filters []string, skip ...int) string {
|
||||
number := 0
|
||||
if len(skip) > 0 {
|
||||
number = skip[0]
|
||||
}
|
||||
name := ""
|
||||
space := " "
|
||||
index := 1
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
filtered := false
|
||||
ok := true
|
||||
pc, file, line, start := callerFromIndex(filters)
|
||||
for i := start + number; i < gMAX_DEPTH; i++ {
|
||||
if i != start {
|
||||
pc, file, line, ok = runtime.Caller(i)
|
||||
}
|
||||
if ok {
|
||||
if goRootForFilter != "" && len(file) >= len(goRootForFilter) && file[0:len(goRootForFilter)] == goRootForFilter {
|
||||
continue
|
||||
}
|
||||
filtered = false
|
||||
for _, filter := range filters {
|
||||
if filter != "" && strings.Contains(file, filter) {
|
||||
filtered = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if filtered {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(file, gFILTER_KEY) {
|
||||
continue
|
||||
}
|
||||
if fn := runtime.FuncForPC(pc); fn == nil {
|
||||
name = "unknown"
|
||||
} else {
|
||||
name = fn.Name()
|
||||
}
|
||||
if index > 9 {
|
||||
space = " "
|
||||
}
|
||||
buffer.WriteString(fmt.Sprintf("%d.%s%s\n %s:%d\n", index, space, name, file, line))
|
||||
index++
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
// CallerPath returns the function name and the absolute file path along with its line number of the caller.
|
||||
func Caller(skip ...int) (function string, path string, line int) {
|
||||
return CallerWithFilter("", skip...)
|
||||
}
|
||||
|
||||
// CallerPathWithFilter returns the function name and the absolute file path along with its line number of the caller.
|
||||
//
|
||||
// The parameter <filter> is used to filter the path of the caller.
|
||||
func CallerWithFilter(filter string, skip ...int) (function string, path string, line int) {
|
||||
number := 0
|
||||
if len(skip) > 0 {
|
||||
number = skip[0]
|
||||
}
|
||||
ok := true
|
||||
pc, file, line, start := callerFromIndex([]string{filter})
|
||||
if start != -1 {
|
||||
for i := start + number; i < gMAX_DEPTH; i++ {
|
||||
if i != start {
|
||||
pc, file, line, ok = runtime.Caller(i)
|
||||
}
|
||||
if ok {
|
||||
if filter != "" && strings.Contains(file, filter) {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(file, gFILTER_KEY) {
|
||||
continue
|
||||
}
|
||||
function := ""
|
||||
if fn := runtime.FuncForPC(pc); fn == nil {
|
||||
function = "unknown"
|
||||
} else {
|
||||
function = fn.Name()
|
||||
}
|
||||
return function, file, line
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return "", "", -1
|
||||
}
|
||||
|
||||
// callerFromIndex returns the caller position and according information exclusive of the debug package.
|
||||
func callerFromIndex(filters []string) (pc uintptr, file string, line int, index int) {
|
||||
var filtered, ok bool
|
||||
for index = 0; index < gMAX_DEPTH; index++ {
|
||||
if pc, file, line, ok = runtime.Caller(index); ok {
|
||||
filtered = false
|
||||
for _, filter := range filters {
|
||||
if filter != "" && strings.Contains(file, filter) {
|
||||
filtered = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if filtered {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(file, gFILTER_KEY) {
|
||||
continue
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
return 0, "", -1, -1
|
||||
}
|
||||
|
||||
// CallerPackage returns the package name of the caller.
|
||||
func CallerPackage() string {
|
||||
function, _, _ := Caller()
|
||||
indexSplit := strings.LastIndexByte(function, '/')
|
||||
if indexSplit == -1 {
|
||||
return function[:strings.IndexByte(function, '.')]
|
||||
} else {
|
||||
leftPart := function[:indexSplit+1]
|
||||
rightPart := function[indexSplit+1:]
|
||||
indexDot := strings.IndexByte(function, '.')
|
||||
rightPart = rightPart[:indexDot-1]
|
||||
return leftPart + rightPart
|
||||
}
|
||||
}
|
||||
|
||||
// CallerFunction returns the function name of the caller.
|
||||
func CallerFunction() string {
|
||||
function, _, _ := Caller()
|
||||
function = function[strings.LastIndexByte(function, '/')+1:]
|
||||
function = function[strings.IndexByte(function, '.')+1:]
|
||||
return function
|
||||
}
|
||||
|
||||
// CallerFilePath returns the file path of the caller.
|
||||
func CallerFilePath() string {
|
||||
_, path, _ := Caller()
|
||||
return path
|
||||
}
|
||||
|
||||
// CallerDirectory returns the directory of the caller.
|
||||
func CallerDirectory() string {
|
||||
_, path, _ := Caller()
|
||||
return filepath.Dir(path)
|
||||
}
|
||||
|
||||
// CallerFileLine returns the file path along with the line number of the caller.
|
||||
func CallerFileLine() string {
|
||||
_, path, line := Caller()
|
||||
return fmt.Sprintf(`%s:%d`, path, line)
|
||||
}
|
||||
|
||||
// CallerFileLineShort returns the file name along with the line number of the caller.
|
||||
func CallerFileLineShort() string {
|
||||
_, path, line := Caller()
|
||||
return fmt.Sprintf(`%s:%d`, filepath.Base(path), line)
|
||||
}
|
||||
|
||||
// FuncPath returns the complete function path of given <f>.
|
||||
func FuncPath(f interface{}) string {
|
||||
return runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()
|
||||
}
|
||||
|
||||
// FuncName returns the function name of given <f>.
|
||||
func FuncName(f interface{}) string {
|
||||
path := FuncPath(f)
|
||||
if path == "" {
|
||||
return ""
|
||||
}
|
||||
index := strings.LastIndexByte(path, '/')
|
||||
if index < 0 {
|
||||
index = strings.LastIndexByte(path, '\\')
|
||||
}
|
||||
return path[index+1:]
|
||||
}
|
||||
|
||||
174
debug/gdebug/gdebug_caller.go
Normal file
174
debug/gdebug/gdebug_caller.go
Normal file
@ -0,0 +1,174 @@
|
||||
// Copyright 2019-2020 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gdebug
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
gMAX_DEPTH = 1000
|
||||
gFILTER_KEY = "/debug/gdebug/gdebug"
|
||||
)
|
||||
|
||||
var (
|
||||
goRootForFilter = runtime.GOROOT() // goRootForFilter is used for stack filtering purpose.
|
||||
binaryVersion = "" // The version of current running binary(uint64 hex).
|
||||
binaryVersionMd5 = "" // The version of current running binary(MD5).
|
||||
selfPath = "" // Current running binary absolute path.
|
||||
)
|
||||
|
||||
func init() {
|
||||
if goRootForFilter != "" {
|
||||
goRootForFilter = strings.Replace(goRootForFilter, "\\", "/", -1)
|
||||
}
|
||||
// Initialize internal package variable: selfPath.
|
||||
selfPath, _ := exec.LookPath(os.Args[0])
|
||||
if selfPath != "" {
|
||||
selfPath, _ = filepath.Abs(selfPath)
|
||||
}
|
||||
if selfPath == "" {
|
||||
selfPath, _ = filepath.Abs(os.Args[0])
|
||||
}
|
||||
}
|
||||
|
||||
// CallerPath returns the function name and the absolute file path along with its line number of the caller.
|
||||
func Caller(skip ...int) (function string, path string, line int) {
|
||||
return CallerWithFilter("", skip...)
|
||||
}
|
||||
|
||||
// CallerPathWithFilter returns the function name and the absolute file path along with its line number of the caller.
|
||||
//
|
||||
// The parameter <filter> is used to filter the path of the caller.
|
||||
func CallerWithFilter(filter string, skip ...int) (function string, path string, line int) {
|
||||
number := 0
|
||||
if len(skip) > 0 {
|
||||
number = skip[0]
|
||||
}
|
||||
ok := true
|
||||
pc, file, line, start := callerFromIndex([]string{filter})
|
||||
if start != -1 {
|
||||
for i := start + number; i < gMAX_DEPTH; i++ {
|
||||
if i != start {
|
||||
pc, file, line, ok = runtime.Caller(i)
|
||||
}
|
||||
if ok {
|
||||
if filter != "" && strings.Contains(file, filter) {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(file, gFILTER_KEY) {
|
||||
continue
|
||||
}
|
||||
function := ""
|
||||
if fn := runtime.FuncForPC(pc); fn == nil {
|
||||
function = "unknown"
|
||||
} else {
|
||||
function = fn.Name()
|
||||
}
|
||||
return function, file, line
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return "", "", -1
|
||||
}
|
||||
|
||||
// callerFromIndex returns the caller position and according information exclusive of the debug package.
|
||||
func callerFromIndex(filters []string) (pc uintptr, file string, line int, index int) {
|
||||
var filtered, ok bool
|
||||
for index = 0; index < gMAX_DEPTH; index++ {
|
||||
if pc, file, line, ok = runtime.Caller(index); ok {
|
||||
filtered = false
|
||||
for _, filter := range filters {
|
||||
if filter != "" && strings.Contains(file, filter) {
|
||||
filtered = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if filtered {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(file, gFILTER_KEY) {
|
||||
continue
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
return 0, "", -1, -1
|
||||
}
|
||||
|
||||
// CallerPackage returns the package name of the caller.
|
||||
func CallerPackage() string {
|
||||
function, _, _ := Caller()
|
||||
indexSplit := strings.LastIndexByte(function, '/')
|
||||
if indexSplit == -1 {
|
||||
return function[:strings.IndexByte(function, '.')]
|
||||
} else {
|
||||
leftPart := function[:indexSplit+1]
|
||||
rightPart := function[indexSplit+1:]
|
||||
indexDot := strings.IndexByte(function, '.')
|
||||
rightPart = rightPart[:indexDot-1]
|
||||
return leftPart + rightPart
|
||||
}
|
||||
}
|
||||
|
||||
// CallerFunction returns the function name of the caller.
|
||||
func CallerFunction() string {
|
||||
function, _, _ := Caller()
|
||||
function = function[strings.LastIndexByte(function, '/')+1:]
|
||||
function = function[strings.IndexByte(function, '.')+1:]
|
||||
return function
|
||||
}
|
||||
|
||||
// CallerFilePath returns the file path of the caller.
|
||||
func CallerFilePath() string {
|
||||
_, path, _ := Caller()
|
||||
return path
|
||||
}
|
||||
|
||||
// CallerDirectory returns the directory of the caller.
|
||||
func CallerDirectory() string {
|
||||
_, path, _ := Caller()
|
||||
return filepath.Dir(path)
|
||||
}
|
||||
|
||||
// CallerFileLine returns the file path along with the line number of the caller.
|
||||
func CallerFileLine() string {
|
||||
_, path, line := Caller()
|
||||
return fmt.Sprintf(`%s:%d`, path, line)
|
||||
}
|
||||
|
||||
// CallerFileLineShort returns the file name along with the line number of the caller.
|
||||
func CallerFileLineShort() string {
|
||||
_, path, line := Caller()
|
||||
return fmt.Sprintf(`%s:%d`, filepath.Base(path), line)
|
||||
}
|
||||
|
||||
// FuncPath returns the complete function path of given <f>.
|
||||
func FuncPath(f interface{}) string {
|
||||
return runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()
|
||||
}
|
||||
|
||||
// FuncName returns the function name of given <f>.
|
||||
func FuncName(f interface{}) string {
|
||||
path := FuncPath(f)
|
||||
if path == "" {
|
||||
return ""
|
||||
}
|
||||
index := strings.LastIndexByte(path, '/')
|
||||
if index < 0 {
|
||||
index = strings.LastIndexByte(path, '\\')
|
||||
}
|
||||
return path[index+1:]
|
||||
}
|
||||
87
debug/gdebug/gdebug_stack.go
Normal file
87
debug/gdebug/gdebug_stack.go
Normal file
@ -0,0 +1,87 @@
|
||||
// Copyright 2019-2020 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gdebug
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// PrintStack prints to standard error the stack trace returned by runtime.Stack.
|
||||
func PrintStack(skip ...int) {
|
||||
fmt.Print(Stack(skip...))
|
||||
}
|
||||
|
||||
// Stack returns a formatted stack trace of the goroutine that calls it.
|
||||
// It calls runtime.Stack with a large enough buffer to capture the entire trace.
|
||||
func Stack(skip ...int) string {
|
||||
return StackWithFilter("", skip...)
|
||||
}
|
||||
|
||||
// StackWithFilter returns a formatted stack trace of the goroutine that calls it.
|
||||
// It calls runtime.Stack with a large enough buffer to capture the entire trace.
|
||||
//
|
||||
// The parameter <filter> is used to filter the path of the caller.
|
||||
func StackWithFilter(filter string, skip ...int) string {
|
||||
return StackWithFilters([]string{filter}, skip...)
|
||||
}
|
||||
|
||||
// StackWithFilters returns a formatted stack trace of the goroutine that calls it.
|
||||
// It calls runtime.Stack with a large enough buffer to capture the entire trace.
|
||||
//
|
||||
// The parameter <filters> is a slice of strings, which are used to filter the path of the caller.
|
||||
func StackWithFilters(filters []string, skip ...int) string {
|
||||
number := 0
|
||||
if len(skip) > 0 {
|
||||
number = skip[0]
|
||||
}
|
||||
name := ""
|
||||
space := " "
|
||||
index := 1
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
filtered := false
|
||||
ok := true
|
||||
pc, file, line, start := callerFromIndex(filters)
|
||||
for i := start + number; i < gMAX_DEPTH; i++ {
|
||||
if i != start {
|
||||
pc, file, line, ok = runtime.Caller(i)
|
||||
}
|
||||
if ok {
|
||||
if goRootForFilter != "" && len(file) >= len(goRootForFilter) && file[0:len(goRootForFilter)] == goRootForFilter {
|
||||
continue
|
||||
}
|
||||
filtered = false
|
||||
for _, filter := range filters {
|
||||
if filter != "" && strings.Contains(file, filter) {
|
||||
filtered = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if filtered {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(file, gFILTER_KEY) {
|
||||
continue
|
||||
}
|
||||
if fn := runtime.FuncForPC(pc); fn == nil {
|
||||
name = "unknown"
|
||||
} else {
|
||||
name = fn.Name()
|
||||
}
|
||||
if index > 9 {
|
||||
space = " "
|
||||
}
|
||||
buffer.WriteString(fmt.Sprintf("%d.%s%s\n %s:%d\n", index, space, name, file, line))
|
||||
index++
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return buffer.String()
|
||||
}
|
||||
17
debug/gdebug/gdebug_testdata.go
Normal file
17
debug/gdebug/gdebug_testdata.go
Normal file
@ -0,0 +1,17 @@
|
||||
// Copyright 2019-2020 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gdebug
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// TestDataPath retrieves and returns the testdata path of current package.
|
||||
// It is used for unit testing cases only.
|
||||
func TestDataPath() string {
|
||||
return CallerDirectory() + string(filepath.Separator) + "testdata"
|
||||
}
|
||||
36
debug/gdebug/gdebug_version.go
Normal file
36
debug/gdebug/gdebug_version.go
Normal file
@ -0,0 +1,36 @@
|
||||
// Copyright 2019-2020 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gdebug
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/crypto/gmd5"
|
||||
"github.com/gogf/gf/encoding/ghash"
|
||||
"io/ioutil"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// BinVersion returns the version of current running binary.
|
||||
// It uses ghash.BKDRHash+BASE36 algorithm to calculate the unique version of the binary.
|
||||
func BinVersion() string {
|
||||
if binaryVersion == "" {
|
||||
binaryContent, _ := ioutil.ReadFile(selfPath)
|
||||
binaryVersion = strconv.FormatInt(
|
||||
int64(ghash.BKDRHash(binaryContent)),
|
||||
36,
|
||||
)
|
||||
}
|
||||
return binaryVersion
|
||||
}
|
||||
|
||||
// BinVersionMd5 returns the version of current running binary.
|
||||
// It uses MD5 algorithm to calculate the unique version of the binary.
|
||||
func BinVersionMd5() string {
|
||||
if binaryVersionMd5 == "" {
|
||||
binaryVersionMd5, _ = gmd5.EncryptFile(selfPath)
|
||||
}
|
||||
return binaryVersionMd5
|
||||
}
|
||||
@ -67,7 +67,7 @@ func Test_Basic(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_File(t *testing.T) {
|
||||
path := gfile.Join(gdebug.CallerDirectory(), "testdata", "test")
|
||||
path := gfile.Join(gdebug.TestDataPath(), "test")
|
||||
expect := "dGVzdA=="
|
||||
gtest.Case(t, func() {
|
||||
b, err := gbase64.EncodeFile(path)
|
||||
|
||||
@ -9,10 +9,11 @@ package gcompress
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"github.com/gogf/gf/os/gfile"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Gzip compresses <data> with gzip algorithm.
|
||||
// Gzip compresses <data> using gzip algorithm.
|
||||
// The optional parameter <level> specifies the compression level from
|
||||
// 1 to 9 which means from none to the best compression.
|
||||
//
|
||||
@ -38,6 +39,38 @@ func Gzip(data []byte, level ...int) ([]byte, error) {
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// GzipFile compresses the file <src> to <dst> using gzip algorithm.
|
||||
func GzipFile(src, dst string, level ...int) error {
|
||||
var writer *gzip.Writer
|
||||
var err error
|
||||
srcFile, err := gfile.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer srcFile.Close()
|
||||
dstFile, err := gfile.Create(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dstFile.Close()
|
||||
|
||||
if len(level) > 0 {
|
||||
writer, err = gzip.NewWriterLevel(dstFile, level[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
writer = gzip.NewWriter(dstFile)
|
||||
}
|
||||
defer writer.Close()
|
||||
|
||||
_, err = io.Copy(writer, srcFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnGzip decompresses <data> with gzip algorithm.
|
||||
func UnGzip(data []byte) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
@ -53,3 +86,28 @@ func UnGzip(data []byte) ([]byte, error) {
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// UnGzip decompresses file <src> to <dst> using gzip algorithm.
|
||||
func UnGzipFile(src, dst string) error {
|
||||
srcFile, err := gfile.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer srcFile.Close()
|
||||
dstFile, err := gfile.Create(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dstFile.Close()
|
||||
|
||||
reader, err := gzip.NewReader(srcFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer reader.Close()
|
||||
|
||||
if _, err = io.Copy(dstFile, reader); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
65
encoding/gcompress/gcompress_z_unit_gzip_test.go
Normal file
65
encoding/gcompress/gcompress_z_unit_gzip_test.go
Normal file
@ -0,0 +1,65 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gcompress_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/debug/gdebug"
|
||||
"github.com/gogf/gf/os/gfile"
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/encoding/gcompress"
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
)
|
||||
|
||||
func Test_Gzip_UnGzip(t *testing.T) {
|
||||
src := "Hello World!!"
|
||||
|
||||
gzip := []byte{
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xff,
|
||||
0xf2, 0x48, 0xcd, 0xc9, 0xc9,
|
||||
0x57, 0x08, 0xcf, 0x2f, 0xca,
|
||||
0x49, 0x51, 0x54, 0x04, 0x04,
|
||||
0x00, 0x00, 0xff, 0xff, 0x9d,
|
||||
0x24, 0xa8, 0xd1, 0x0d, 0x00,
|
||||
0x00, 0x00,
|
||||
}
|
||||
gtest.Case(t, func() {
|
||||
arr := []byte(src)
|
||||
data, _ := gcompress.Gzip(arr)
|
||||
gtest.Assert(data, gzip)
|
||||
|
||||
data, _ = gcompress.UnGzip(gzip)
|
||||
gtest.Assert(data, arr)
|
||||
|
||||
data, _ = gcompress.UnGzip(gzip[1:])
|
||||
gtest.Assert(data, nil)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Gzip_UnGzip_File(t *testing.T) {
|
||||
srcPath := gfile.Join(gdebug.TestDataPath(), "gzip", "file.txt")
|
||||
dstPath1 := gfile.Join(gfile.TempDir(), gtime.TimestampNanoStr(), "gzip.zip")
|
||||
dstPath2 := gfile.Join(gfile.TempDir(), gtime.TimestampNanoStr(), "file.txt")
|
||||
|
||||
// Compress.
|
||||
gtest.Case(t, func() {
|
||||
err := gcompress.GzipFile(srcPath, dstPath1, 9)
|
||||
gtest.Assert(err, nil)
|
||||
defer gfile.Remove(dstPath1)
|
||||
gtest.Assert(gfile.Exists(dstPath1), true)
|
||||
|
||||
// Decompress.
|
||||
err = gcompress.UnGzipFile(dstPath1, dstPath2)
|
||||
gtest.Assert(err, nil)
|
||||
defer gfile.Remove(dstPath2)
|
||||
gtest.Assert(gfile.Exists(dstPath2), true)
|
||||
|
||||
gtest.Assert(gfile.GetContents(srcPath), gfile.GetContents(dstPath2))
|
||||
})
|
||||
}
|
||||
153
encoding/gcompress/gcompress_z_unit_zip_test.go
Normal file
153
encoding/gcompress/gcompress_z_unit_zip_test.go
Normal file
@ -0,0 +1,153 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gcompress_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/gogf/gf/debug/gdebug"
|
||||
"github.com/gogf/gf/encoding/gcompress"
|
||||
"github.com/gogf/gf/os/gfile"
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
)
|
||||
|
||||
func Test_ZipPath(t *testing.T) {
|
||||
// file
|
||||
gtest.Case(t, func() {
|
||||
srcPath := gfile.Join(gdebug.TestDataPath(), "zip", "path1", "1.txt")
|
||||
dstPath := gfile.Join(gdebug.TestDataPath(), "zip", "zip.zip")
|
||||
|
||||
gtest.Assert(gfile.Exists(dstPath), false)
|
||||
err := gcompress.ZipPath(srcPath, dstPath)
|
||||
gtest.Assert(err, nil)
|
||||
gtest.Assert(gfile.Exists(dstPath), true)
|
||||
defer gfile.Remove(dstPath)
|
||||
|
||||
tempDirPath := gfile.Join(gfile.TempDir(), gtime.TimestampNanoStr())
|
||||
err = gfile.Mkdir(tempDirPath)
|
||||
gtest.Assert(err, nil)
|
||||
|
||||
err = gcompress.UnZipFile(dstPath, tempDirPath)
|
||||
gtest.Assert(err, nil)
|
||||
defer gfile.Remove(tempDirPath)
|
||||
|
||||
gtest.Assert(
|
||||
gfile.GetContents(gfile.Join(tempDirPath, "1.txt")),
|
||||
gfile.GetContents(gfile.Join(srcPath, "path1", "1.txt")),
|
||||
)
|
||||
})
|
||||
// directory
|
||||
gtest.Case(t, func() {
|
||||
srcPath := gfile.Join(gdebug.TestDataPath(), "zip")
|
||||
dstPath := gfile.Join(gdebug.TestDataPath(), "zip", "zip.zip")
|
||||
|
||||
pwd := gfile.Pwd()
|
||||
err := gfile.Chdir(srcPath)
|
||||
defer gfile.Chdir(pwd)
|
||||
gtest.Assert(err, nil)
|
||||
|
||||
gtest.Assert(gfile.Exists(dstPath), false)
|
||||
err = gcompress.ZipPath(srcPath, dstPath)
|
||||
gtest.Assert(err, nil)
|
||||
gtest.Assert(gfile.Exists(dstPath), true)
|
||||
defer gfile.Remove(dstPath)
|
||||
|
||||
tempDirPath := gfile.Join(gfile.TempDir(), gtime.TimestampNanoStr())
|
||||
err = gfile.Mkdir(tempDirPath)
|
||||
gtest.Assert(err, nil)
|
||||
|
||||
err = gcompress.UnZipFile(dstPath, tempDirPath)
|
||||
gtest.Assert(err, nil)
|
||||
defer gfile.Remove(tempDirPath)
|
||||
|
||||
gtest.Assert(
|
||||
gfile.GetContents(gfile.Join(tempDirPath, "zip", "path1", "1.txt")),
|
||||
gfile.GetContents(gfile.Join(srcPath, "path1", "1.txt")),
|
||||
)
|
||||
gtest.Assert(
|
||||
gfile.GetContents(gfile.Join(tempDirPath, "zip", "path2", "2.txt")),
|
||||
gfile.GetContents(gfile.Join(srcPath, "path2", "2.txt")),
|
||||
)
|
||||
})
|
||||
// multiple paths joined using char ','
|
||||
gtest.Case(t, func() {
|
||||
srcPath := gfile.Join(gdebug.TestDataPath(), "zip")
|
||||
srcPath1 := gfile.Join(gdebug.TestDataPath(), "zip", "path1")
|
||||
srcPath2 := gfile.Join(gdebug.TestDataPath(), "zip", "path2")
|
||||
dstPath := gfile.Join(gdebug.TestDataPath(), "zip", "zip.zip")
|
||||
|
||||
pwd := gfile.Pwd()
|
||||
err := gfile.Chdir(srcPath)
|
||||
defer gfile.Chdir(pwd)
|
||||
gtest.Assert(err, nil)
|
||||
|
||||
gtest.Assert(gfile.Exists(dstPath), false)
|
||||
err = gcompress.ZipPath(srcPath1+", "+srcPath2, dstPath)
|
||||
gtest.Assert(err, nil)
|
||||
gtest.Assert(gfile.Exists(dstPath), true)
|
||||
defer gfile.Remove(dstPath)
|
||||
|
||||
tempDirPath := gfile.Join(gfile.TempDir(), gtime.TimestampNanoStr())
|
||||
err = gfile.Mkdir(tempDirPath)
|
||||
gtest.Assert(err, nil)
|
||||
|
||||
zipContent := gfile.GetBytes(dstPath)
|
||||
gtest.AssertGT(len(zipContent), 0)
|
||||
err = gcompress.UnZipContent(zipContent, tempDirPath)
|
||||
gtest.Assert(err, nil)
|
||||
defer gfile.Remove(tempDirPath)
|
||||
|
||||
gtest.Assert(
|
||||
gfile.GetContents(gfile.Join(tempDirPath, "path1", "1.txt")),
|
||||
gfile.GetContents(gfile.Join(srcPath, "path1", "1.txt")),
|
||||
)
|
||||
gtest.Assert(
|
||||
gfile.GetContents(gfile.Join(tempDirPath, "path2", "2.txt")),
|
||||
gfile.GetContents(gfile.Join(srcPath, "path2", "2.txt")),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_ZipPathWriter(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
srcPath := gfile.Join(gdebug.TestDataPath(), "zip")
|
||||
srcPath1 := gfile.Join(gdebug.TestDataPath(), "zip", "path1")
|
||||
srcPath2 := gfile.Join(gdebug.TestDataPath(), "zip", "path2")
|
||||
|
||||
pwd := gfile.Pwd()
|
||||
err := gfile.Chdir(srcPath)
|
||||
defer gfile.Chdir(pwd)
|
||||
gtest.Assert(err, nil)
|
||||
|
||||
writer := bytes.NewBuffer(nil)
|
||||
gtest.Assert(writer.Len(), 0)
|
||||
err = gcompress.ZipPathWriter(srcPath1+", "+srcPath2, writer)
|
||||
gtest.Assert(err, nil)
|
||||
gtest.AssertGT(writer.Len(), 0)
|
||||
|
||||
tempDirPath := gfile.Join(gfile.TempDir(), gtime.TimestampNanoStr())
|
||||
err = gfile.Mkdir(tempDirPath)
|
||||
gtest.Assert(err, nil)
|
||||
|
||||
zipContent := writer.Bytes()
|
||||
gtest.AssertGT(len(zipContent), 0)
|
||||
err = gcompress.UnZipContent(zipContent, tempDirPath)
|
||||
gtest.Assert(err, nil)
|
||||
defer gfile.Remove(tempDirPath)
|
||||
|
||||
gtest.Assert(
|
||||
gfile.GetContents(gfile.Join(tempDirPath, "path1", "1.txt")),
|
||||
gfile.GetContents(gfile.Join(srcPath, "path1", "1.txt")),
|
||||
)
|
||||
gtest.Assert(
|
||||
gfile.GetContents(gfile.Join(tempDirPath, "path2", "2.txt")),
|
||||
gfile.GetContents(gfile.Join(srcPath, "path2", "2.txt")),
|
||||
)
|
||||
})
|
||||
}
|
||||
@ -13,7 +13,7 @@ import (
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
)
|
||||
|
||||
func TestZlib(t *testing.T) {
|
||||
func Test_Zlib_UnZlib(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
src := "hello, world\n"
|
||||
dst := []byte{120, 156, 202, 72, 205, 201, 201, 215, 81, 40, 207, 47, 202, 73, 225, 2, 4, 0, 0, 255, 255, 33, 231, 4, 147}
|
||||
@ -31,30 +31,4 @@ func TestZlib(t *testing.T) {
|
||||
data, _ = gcompress.UnZlib(dst[1:])
|
||||
gtest.Assert(data, nil)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestGzip(t *testing.T) {
|
||||
src := "Hello World!!"
|
||||
|
||||
gzip := []byte{
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xff,
|
||||
0xf2, 0x48, 0xcd, 0xc9, 0xc9,
|
||||
0x57, 0x08, 0xcf, 0x2f, 0xca,
|
||||
0x49, 0x51, 0x54, 0x04, 0x04,
|
||||
0x00, 0x00, 0xff, 0xff, 0x9d,
|
||||
0x24, 0xa8, 0xd1, 0x0d, 0x00,
|
||||
0x00, 0x00,
|
||||
}
|
||||
|
||||
arr := []byte(src)
|
||||
data, _ := gcompress.Gzip(arr)
|
||||
gtest.Assert(data, gzip)
|
||||
|
||||
data, _ = gcompress.UnGzip(gzip)
|
||||
gtest.Assert(data, arr)
|
||||
|
||||
data, _ = gcompress.UnGzip(gzip[1:])
|
||||
gtest.Assert(data, nil)
|
||||
}
|
||||
@ -9,6 +9,7 @@ package gcompress
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"github.com/gogf/gf/internal/intlog"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@ -32,7 +33,15 @@ func ZipPath(paths, dest string, prefix ...string) error {
|
||||
return err
|
||||
}
|
||||
defer writer.Close()
|
||||
return ZipPathWriter(paths, writer, prefix...)
|
||||
zipWriter := zip.NewWriter(writer)
|
||||
defer zipWriter.Close()
|
||||
for _, path := range strings.Split(paths, ",") {
|
||||
path = strings.TrimSpace(path)
|
||||
if err := doZipPathWriter(path, gfile.RealPath(dest), zipWriter, prefix...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ZipPathWriter compresses <paths> to <writer> using zip compressing algorithm.
|
||||
@ -45,17 +54,21 @@ func ZipPathWriter(paths string, writer io.Writer, prefix ...string) error {
|
||||
defer zipWriter.Close()
|
||||
for _, path := range strings.Split(paths, ",") {
|
||||
path = strings.TrimSpace(path)
|
||||
if err := doZipPathWriter(path, zipWriter, prefix...); err != nil {
|
||||
if err := doZipPathWriter(path, "", zipWriter, prefix...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func doZipPathWriter(path string, zipWriter *zip.Writer, prefix ...string) error {
|
||||
// doZipPathWriter compresses the file of given <path> and writes the content to <zipWriter>.
|
||||
// The parameter <exclude> specifies the exclusive file path that is not compressed to <zipWriter>,
|
||||
// commonly the destination zip file path.
|
||||
// The unnecessary parameter <prefix> indicates the path prefix for zip file.
|
||||
func doZipPathWriter(path string, exclude string, zipWriter *zip.Writer, prefix ...string) error {
|
||||
var err error
|
||||
var files []string
|
||||
realPath, err := gfile.Search(path)
|
||||
path, err = gfile.Search(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -80,7 +93,11 @@ func doZipPathWriter(path string, zipWriter *zip.Writer, prefix ...string) error
|
||||
}
|
||||
headerPrefix = strings.Replace(headerPrefix, "//", "/", -1)
|
||||
for _, file := range files {
|
||||
err := zipFile(file, headerPrefix+gfile.Dir(file[len(realPath):]), zipWriter)
|
||||
if exclude == file {
|
||||
intlog.Printf(`exclude file path: %s`, file)
|
||||
continue
|
||||
}
|
||||
err := zipFile(file, headerPrefix+gfile.Dir(file[len(path):]), zipWriter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -101,10 +118,10 @@ func doZipPathWriter(path string, zipWriter *zip.Writer, prefix ...string) error
|
||||
}
|
||||
|
||||
// UnZipFile decompresses <archive> to <dest> using zip compressing algorithm.
|
||||
// The parameter <path> specifies the unzipped path of <archive>,
|
||||
// The optional parameter <path> specifies the unzipped path of <archive>,
|
||||
// which can be used to specify part of the archive file to unzip.
|
||||
//
|
||||
// Note thate the parameter <dest> should be a directory.
|
||||
// Note that the parameter <dest> should be a directory.
|
||||
func UnZipFile(archive, dest string, path ...string) error {
|
||||
readerCloser, err := zip.OpenReader(archive)
|
||||
if err != nil {
|
||||
@ -118,7 +135,7 @@ func UnZipFile(archive, dest string, path ...string) error {
|
||||
// The parameter <path> specifies the unzipped path of <archive>,
|
||||
// which can be used to specify part of the archive file to unzip.
|
||||
//
|
||||
// Note thate the parameter <dest> should be a directory.
|
||||
// Note that the parameter <dest> should be a directory.
|
||||
func UnZipContent(data []byte, dest string, path ...string) error {
|
||||
reader, err := zip.NewReader(bytes.NewReader(data), int64(len(data)))
|
||||
if err != nil {
|
||||
@ -178,6 +195,8 @@ func unZipFileWithReader(reader *zip.Reader, dest string, path ...string) error
|
||||
return nil
|
||||
}
|
||||
|
||||
// zipFile compresses the file of given <path> and writes the content to <zw>.
|
||||
// The parameter <prefix> indicates the path prefix for zip file.
|
||||
func zipFile(path string, prefix string, zw *zip.Writer) error {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
1
encoding/gcompress/testdata/gzip/file.txt
vendored
Normal file
1
encoding/gcompress/testdata/gzip/file.txt
vendored
Normal file
@ -0,0 +1 @@
|
||||
This is a test file for gzip compression.
|
||||
1
encoding/gcompress/testdata/zip/path1/1.txt
vendored
Normal file
1
encoding/gcompress/testdata/zip/path1/1.txt
vendored
Normal file
@ -0,0 +1 @@
|
||||
This is a test file for zip compression purpose.
|
||||
1
encoding/gcompress/testdata/zip/path2/2.txt
vendored
Normal file
1
encoding/gcompress/testdata/zip/path2/2.txt
vendored
Normal file
@ -0,0 +1 @@
|
||||
This is an another test file for zip compression purpose.
|
||||
@ -136,7 +136,7 @@ func Test_GetMap(t *testing.T) {
|
||||
gtest.Assert(err, nil)
|
||||
gtest.Assert(j.GetMap("n"), nil)
|
||||
gtest.Assert(j.GetMap("m"), g.Map{"k": "v"})
|
||||
gtest.Assert(j.GetMap("a"), nil)
|
||||
gtest.Assert(j.GetMap("a"), g.Map{"1": "2", "3": nil})
|
||||
})
|
||||
}
|
||||
|
||||
@ -462,39 +462,3 @@ func Test_IsNil(t *testing.T) {
|
||||
gtest.Assert(j.IsNil(), true)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_ToStructDeep(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
type Item struct {
|
||||
Title string `json:"title"`
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
type M struct {
|
||||
Id string `json:"id"`
|
||||
Me map[string]interface{} `json:"me"`
|
||||
Txt string `json:"txt"`
|
||||
Items []*Item `json:"items"`
|
||||
}
|
||||
|
||||
txt := `{
|
||||
"id":"88888",
|
||||
"me":{"name":"mikey","day":"20009"},
|
||||
"txt":"hello",
|
||||
"items":null
|
||||
}`
|
||||
|
||||
j, err := gjson.LoadContent(txt)
|
||||
gtest.Assert(err, nil)
|
||||
gtest.Assert(j.GetString("me.name"), "mikey")
|
||||
gtest.Assert(j.GetString("items"), "")
|
||||
gtest.Assert(j.GetBool("items"), false)
|
||||
gtest.Assert(j.GetArray("items"), nil)
|
||||
m := new(M)
|
||||
err = j.ToStructDeep(m)
|
||||
gtest.Assert(err, nil)
|
||||
gtest.AssertNE(m.Me, nil)
|
||||
gtest.Assert(m.Me["day"], "20009")
|
||||
gtest.Assert(m.Items, nil)
|
||||
})
|
||||
}
|
||||
|
||||
132
encoding/gjson/gjson_z_unit_struct_test.go
Normal file
132
encoding/gjson/gjson_z_unit_struct_test.go
Normal file
@ -0,0 +1,132 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gjson_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/encoding/gjson"
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_ToStruct1(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
type BaseInfoItem struct {
|
||||
IdCardNumber string `db:"id_card_number" json:"idCardNumber" field:"id_card_number"`
|
||||
IsHouseholder bool `db:"is_householder" json:"isHouseholder" field:"is_householder"`
|
||||
HouseholderRelation string `db:"householder_relation" json:"householderRelation" field:"householder_relation"`
|
||||
UserName string `db:"user_name" json:"userName" field:"user_name"`
|
||||
UserSex string `db:"user_sex" json:"userSex" field:"user_sex"`
|
||||
UserAge int `db:"user_age" json:"userAge" field:"user_age"`
|
||||
UserNation string `db:"user_nation" json:"userNation" field:"user_nation"`
|
||||
}
|
||||
|
||||
type UserCollectionAddReq struct {
|
||||
BaseInfo []BaseInfoItem `db:"_" json:"baseInfo" field:"_"`
|
||||
}
|
||||
jsonContent := `{
|
||||
"baseInfo": [{
|
||||
"idCardNumber": "520101199412141111",
|
||||
"isHouseholder": true,
|
||||
"householderRelation": "户主",
|
||||
"userName": "李四",
|
||||
"userSex": "男",
|
||||
"userAge": 32,
|
||||
"userNation": "苗族",
|
||||
"userPhone": "13084183323",
|
||||
"liveAddress": {},
|
||||
"occupationInfo": [{
|
||||
"occupationType": "经商",
|
||||
"occupationBusinessInfo": [{
|
||||
"occupationClass": "制造业",
|
||||
"businessLicenseNumber": "32020000012300",
|
||||
"businessName": "土灶柴火鸡",
|
||||
"spouseName": "",
|
||||
"spouseIdCardNumber": "",
|
||||
"businessLicensePhotoId": 125,
|
||||
"businessPlace": "租赁房产",
|
||||
"hasGoodsInsurance": true,
|
||||
"businessScopeStr": "柴火鸡;烧烤",
|
||||
"businessAddress": {},
|
||||
"businessPerformAbility": {
|
||||
"businessType": "服务业",
|
||||
"businessLife": 5,
|
||||
"salesRevenue": 8000,
|
||||
"familyEquity": 6000
|
||||
}
|
||||
}],
|
||||
"occupationWorkInfo": {
|
||||
"occupationClass": "",
|
||||
"companyName": "",
|
||||
"companyType": "",
|
||||
"workYearNum": 0,
|
||||
"spouseName": "",
|
||||
"spouseIdCardNumber": "",
|
||||
"spousePhone": "",
|
||||
"spouseEducation": "",
|
||||
"spouseCompanyName": "",
|
||||
"workLevel": "",
|
||||
"workAddress": {},
|
||||
"workPerformAbility": {
|
||||
"familyAnnualIncome": 0,
|
||||
"familyEquity": 0,
|
||||
"workCooperationState": "",
|
||||
"workMoneyCooperationState": ""
|
||||
}
|
||||
},
|
||||
"occupationAgricultureInfo": []
|
||||
}],
|
||||
"assetsInfo": [],
|
||||
"expenditureInfo": [],
|
||||
"incomeInfo": [],
|
||||
"liabilityInfo": []
|
||||
}]
|
||||
}`
|
||||
data := new(UserCollectionAddReq)
|
||||
j, err := gjson.LoadJson(jsonContent)
|
||||
gtest.Assert(err, nil)
|
||||
err = j.ToStruct(data)
|
||||
gtest.Assert(err, nil)
|
||||
g.Dump(data)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_ToStructDeep(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
type Item struct {
|
||||
Title string `json:"title"`
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
type M struct {
|
||||
Id string `json:"id"`
|
||||
Me map[string]interface{} `json:"me"`
|
||||
Txt string `json:"txt"`
|
||||
Items []*Item `json:"items"`
|
||||
}
|
||||
|
||||
txt := `{
|
||||
"id":"88888",
|
||||
"me":{"name":"mikey","day":"20009"},
|
||||
"txt":"hello",
|
||||
"items":null
|
||||
}`
|
||||
|
||||
j, err := gjson.LoadContent(txt)
|
||||
gtest.Assert(err, nil)
|
||||
gtest.Assert(j.GetString("me.name"), "mikey")
|
||||
gtest.Assert(j.GetString("items"), "")
|
||||
gtest.Assert(j.GetBool("items"), false)
|
||||
gtest.Assert(j.GetArray("items"), nil)
|
||||
m := new(M)
|
||||
err = j.ToStructDeep(m)
|
||||
gtest.Assert(err, nil)
|
||||
gtest.AssertNE(m.Me, nil)
|
||||
gtest.Assert(m.Me["day"], "20009")
|
||||
gtest.Assert(m.Items, nil)
|
||||
})
|
||||
}
|
||||
@ -95,7 +95,7 @@ func Test_GetVar(t *testing.T) {
|
||||
gtest.Assert(j.GetVar("m").Map(), g.Map{"k": "v"})
|
||||
gtest.Assert(j.GetVar("a").Interfaces(), g.Slice{1, 2, 3})
|
||||
gtest.Assert(j.GetVar("a").Slice(), g.Slice{1, 2, 3})
|
||||
gtest.Assert(j.GetVar("a").Array(), g.Slice{1, 2, 3})
|
||||
gtest.Assert(j.GetMap("a"), g.Map{"1": "2", "3": nil})
|
||||
})
|
||||
}
|
||||
|
||||
@ -106,7 +106,7 @@ func Test_GetMap(t *testing.T) {
|
||||
gtest.AssertNE(j, nil)
|
||||
gtest.Assert(j.GetMap("n"), nil)
|
||||
gtest.Assert(j.GetMap("m"), g.Map{"k": "v"})
|
||||
gtest.Assert(j.GetMap("a"), nil)
|
||||
gtest.Assert(j.GetMap("a"), g.Map{"1": "2", "3": nil})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -8,12 +8,7 @@
|
||||
package gins
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/internal/intlog"
|
||||
"github.com/gogf/gf/os/gfile"
|
||||
|
||||
"github.com/gogf/gf/container/gmap"
|
||||
"github.com/gogf/gf/os/gcfg"
|
||||
"github.com/gogf/gf/os/gfsnotify"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -59,15 +54,3 @@ func GetOrSetFuncLock(name string, f func() interface{}) interface{} {
|
||||
func SetIfNotExist(name string, instance interface{}) bool {
|
||||
return instances.SetIfNotExist(name, instance)
|
||||
}
|
||||
|
||||
// addConfigMonitor adds fsnotify monitor for configuration file if it exists.
|
||||
func addConfigMonitor(key string, config *gcfg.Config) {
|
||||
if path := config.FilePath(); path != "" && gfile.Exists(path) {
|
||||
_, err := gfsnotify.Add(path, func(event *gfsnotify.Event) {
|
||||
instances.Remove(key)
|
||||
})
|
||||
if err != nil {
|
||||
intlog.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -72,7 +72,6 @@ func Database(name ...string) gdb.DB {
|
||||
gdb.SetConfigGroup(gdb.DEFAULT_GROUP_NAME, cg)
|
||||
}
|
||||
}
|
||||
addConfigMonitor(instanceKey, config)
|
||||
|
||||
if db, err := gdb.New(name...); err == nil {
|
||||
// Initialize logger for ORM.
|
||||
|
||||
@ -36,7 +36,6 @@ func Redis(name ...string) *gredis.Redis {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
addConfigMonitor(instanceKey, config)
|
||||
return gredis.New(redisConfig)
|
||||
} else {
|
||||
panic(fmt.Sprintf(`configuration for redis not found for group "%s"`, group))
|
||||
|
||||
@ -34,6 +34,9 @@ func Server(name ...interface{}) *ghttp.Server {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
// As it might use template feature,
|
||||
// it initialize the view instance as well.
|
||||
_ = getViewInstance()
|
||||
}
|
||||
return s
|
||||
}).(*ghttp.Server)
|
||||
|
||||
@ -25,22 +25,30 @@ func View(name ...string) *gview.View {
|
||||
}
|
||||
instanceKey := fmt.Sprintf("%s.%s", gFRAME_CORE_COMPONENT_NAME_VIEWER, instanceName)
|
||||
return instances.GetOrSetFuncLock(instanceKey, func() interface{} {
|
||||
view := gview.Instance(instanceName)
|
||||
// To avoid file no found error while it's not necessary.
|
||||
if Config().Available() {
|
||||
var m map[string]interface{}
|
||||
// It firstly searches the configuration of the instance name.
|
||||
if m = Config().GetMap(fmt.Sprintf(`%s.%s`, gVIEWER_NODE_NAME, instanceName)); m == nil {
|
||||
// If the configuration for the instance does not exist,
|
||||
// it uses the default view configuration.
|
||||
m = Config().GetMap(gVIEWER_NODE_NAME)
|
||||
}
|
||||
if m != nil {
|
||||
if err := view.SetConfigWithMap(m); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return view
|
||||
return getViewInstance(instanceName)
|
||||
}).(*gview.View)
|
||||
}
|
||||
|
||||
func getViewInstance(name ...string) *gview.View {
|
||||
instanceName := gview.DEFAULT_NAME
|
||||
if len(name) > 0 && name[0] != "" {
|
||||
instanceName = name[0]
|
||||
}
|
||||
view := gview.Instance(instanceName)
|
||||
// To avoid file no found error while it's not necessary.
|
||||
if Config().Available() {
|
||||
var m map[string]interface{}
|
||||
// It firstly searches the configuration of the instance name.
|
||||
if m = Config().GetMap(fmt.Sprintf(`%s.%s`, gVIEWER_NODE_NAME, instanceName)); m == nil {
|
||||
// If the configuration for the instance does not exist,
|
||||
// it uses the default view configuration.
|
||||
m = Config().GetMap(gVIEWER_NODE_NAME)
|
||||
}
|
||||
if m != nil {
|
||||
if err := view.SetConfigWithMap(m); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return view
|
||||
}
|
||||
|
||||
@ -4,9 +4,10 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gins
|
||||
package gins_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/frame/gins"
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
@ -14,30 +15,30 @@ import (
|
||||
|
||||
func Test_SetGet(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
Set("test-user", 1)
|
||||
gtest.Assert(Get("test-user"), 1)
|
||||
gtest.Assert(Get("none-exists"), nil)
|
||||
gins.Set("test-user", 1)
|
||||
gtest.Assert(gins.Get("test-user"), 1)
|
||||
gtest.Assert(gins.Get("none-exists"), nil)
|
||||
})
|
||||
gtest.Case(t, func() {
|
||||
gtest.Assert(GetOrSet("test-1", 1), 1)
|
||||
gtest.Assert(Get("test-1"), 1)
|
||||
gtest.Assert(gins.GetOrSet("test-1", 1), 1)
|
||||
gtest.Assert(gins.Get("test-1"), 1)
|
||||
})
|
||||
gtest.Case(t, func() {
|
||||
gtest.Assert(GetOrSetFunc("test-2", func() interface{} {
|
||||
gtest.Assert(gins.GetOrSetFunc("test-2", func() interface{} {
|
||||
return 2
|
||||
}), 2)
|
||||
gtest.Assert(Get("test-2"), 2)
|
||||
gtest.Assert(gins.Get("test-2"), 2)
|
||||
})
|
||||
gtest.Case(t, func() {
|
||||
gtest.Assert(GetOrSetFuncLock("test-3", func() interface{} {
|
||||
gtest.Assert(gins.GetOrSetFuncLock("test-3", func() interface{} {
|
||||
return 3
|
||||
}), 3)
|
||||
gtest.Assert(Get("test-3"), 3)
|
||||
gtest.Assert(gins.Get("test-3"), 3)
|
||||
})
|
||||
gtest.Case(t, func() {
|
||||
gtest.Assert(SetIfNotExist("test-4", 4), true)
|
||||
gtest.Assert(Get("test-4"), 4)
|
||||
gtest.Assert(SetIfNotExist("test-4", 5), false)
|
||||
gtest.Assert(Get("test-4"), 4)
|
||||
gtest.Assert(gins.SetIfNotExist("test-4", 4), true)
|
||||
gtest.Assert(gins.Get("test-4"), 4)
|
||||
gtest.Assert(gins.SetIfNotExist("test-4", 5), false)
|
||||
gtest.Assert(gins.Get("test-4"), 4)
|
||||
})
|
||||
}
|
||||
|
||||
@ -4,10 +4,12 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gins
|
||||
package gins_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/debug/gdebug"
|
||||
"github.com/gogf/gf/frame/gins"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -18,157 +20,186 @@ import (
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
)
|
||||
|
||||
func Test_Config(t *testing.T) {
|
||||
config := `
|
||||
# 模板引擎目录
|
||||
viewpath = "/home/www/templates/"
|
||||
test = "v=1"
|
||||
# MySQL数据库配置
|
||||
[database]
|
||||
[[database.default]]
|
||||
host = "127.0.0.1"
|
||||
port = "3306"
|
||||
user = "root"
|
||||
pass = ""
|
||||
name = "test"
|
||||
type = "mysql"
|
||||
role = "master"
|
||||
charset = "utf8"
|
||||
priority = "1"
|
||||
[[database.default]]
|
||||
host = "127.0.0.1"
|
||||
port = "3306"
|
||||
user = "root"
|
||||
pass = "8692651"
|
||||
name = "test"
|
||||
type = "mysql"
|
||||
role = "master"
|
||||
charset = "utf8"
|
||||
priority = "1"
|
||||
# Redis数据库配置
|
||||
[redis]
|
||||
disk = "127.0.0.1:6379,0"
|
||||
cache = "127.0.0.1:6379,1"
|
||||
`
|
||||
gtest.Case(t, func() {
|
||||
gtest.AssertNE(Config(), nil)
|
||||
})
|
||||
var (
|
||||
configContent = gfile.GetContents(
|
||||
gfile.Join(gdebug.TestDataPath(), "config", "config.toml"),
|
||||
)
|
||||
)
|
||||
|
||||
func Test_Config1(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
gtest.AssertNE(configContent, "")
|
||||
})
|
||||
gtest.Case(t, func() {
|
||||
gtest.AssertNE(gins.Config(), nil)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Config2(t *testing.T) {
|
||||
// relative path
|
||||
gtest.Case(t, func() {
|
||||
path := "config.toml"
|
||||
err := gfile.PutContents(path, config)
|
||||
var err error
|
||||
dirPath := gfile.Join(gfile.TempDir(), gtime.TimestampNanoStr())
|
||||
err = gfile.Mkdir(dirPath)
|
||||
gtest.Assert(err, nil)
|
||||
defer gfile.Remove(path)
|
||||
defer Config().Clear()
|
||||
gtest.Assert(Config().Get("test"), "v=1")
|
||||
gtest.Assert(Config().Get("database.default.1.host"), "127.0.0.1")
|
||||
gtest.Assert(Config().Get("redis.disk"), "127.0.0.1:6379,0")
|
||||
defer gfile.Remove(dirPath)
|
||||
|
||||
name := "config.toml"
|
||||
err = gfile.PutContents(gfile.Join(dirPath, name), configContent)
|
||||
gtest.Assert(err, nil)
|
||||
|
||||
err = gins.Config().AddPath(dirPath)
|
||||
gtest.Assert(err, nil)
|
||||
|
||||
defer gins.Config().Clear()
|
||||
|
||||
gtest.Assert(gins.Config().Get("test"), "v=1")
|
||||
gtest.Assert(gins.Config().Get("database.default.1.host"), "127.0.0.1")
|
||||
gtest.Assert(gins.Config().Get("redis.disk"), "127.0.0.1:6379,0")
|
||||
})
|
||||
// for gfsnotify callbacks to refresh cache of config file
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
// relative path, config folder
|
||||
gtest.Case(t, func() {
|
||||
path := "config/config.toml"
|
||||
err := gfile.PutContents(path, config)
|
||||
var err error
|
||||
dirPath := gfile.Join(gfile.TempDir(), gtime.TimestampNanoStr())
|
||||
err = gfile.Mkdir(dirPath)
|
||||
gtest.Assert(err, nil)
|
||||
defer gfile.Remove(path)
|
||||
defer Config().Clear()
|
||||
gtest.Assert(Config().Get("test"), "v=1")
|
||||
gtest.Assert(Config().Get("database.default.1.host"), "127.0.0.1")
|
||||
gtest.Assert(Config().Get("redis.disk"), "127.0.0.1:6379,0")
|
||||
})
|
||||
// for gfsnotify callbacks to refresh cache of config file
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
defer gfile.Remove(dirPath)
|
||||
|
||||
gtest.Case(t, func() {
|
||||
path := "test.toml"
|
||||
err := gfile.PutContents(path, config)
|
||||
name := "config/config.toml"
|
||||
err = gfile.PutContents(gfile.Join(dirPath, name), configContent)
|
||||
gtest.Assert(err, nil)
|
||||
defer gfile.Remove(path)
|
||||
defer Config("test").Clear()
|
||||
Config("test").SetFileName("test.toml")
|
||||
gtest.Assert(Config("test").Get("test"), "v=1")
|
||||
gtest.Assert(Config("test").Get("database.default.1.host"), "127.0.0.1")
|
||||
gtest.Assert(Config("test").Get("redis.disk"), "127.0.0.1:6379,0")
|
||||
})
|
||||
// for gfsnotify callbacks to refresh cache of config file
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
gtest.Case(t, func() {
|
||||
path := "config/test.toml"
|
||||
err := gfile.PutContents(path, config)
|
||||
err = gins.Config().AddPath(dirPath)
|
||||
gtest.Assert(err, nil)
|
||||
defer gfile.Remove(path)
|
||||
defer Config("test").Clear()
|
||||
Config("test").SetFileName("test.toml")
|
||||
gtest.Assert(Config("test").Get("test"), "v=1")
|
||||
gtest.Assert(Config("test").Get("database.default.1.host"), "127.0.0.1")
|
||||
gtest.Assert(Config("test").Get("redis.disk"), "127.0.0.1:6379,0")
|
||||
})
|
||||
// for gfsnotify callbacks to refresh cache of config file
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
// absolute path
|
||||
gtest.Case(t, func() {
|
||||
path := fmt.Sprintf(`%s/%d`, gfile.TempDir(), gtime.TimestampNano())
|
||||
file := fmt.Sprintf(`%s/%s`, path, "config.toml")
|
||||
err := gfile.PutContents(file, config)
|
||||
gtest.Assert(err, nil)
|
||||
defer gfile.Remove(file)
|
||||
defer Config().Clear()
|
||||
gtest.Assert(Config().AddPath(path), nil)
|
||||
gtest.Assert(Config().Get("test"), "v=1")
|
||||
gtest.Assert(Config().Get("database.default.1.host"), "127.0.0.1")
|
||||
gtest.Assert(Config().Get("redis.disk"), "127.0.0.1:6379,0")
|
||||
})
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
defer gins.Config().Clear()
|
||||
|
||||
gtest.Case(t, func() {
|
||||
path := fmt.Sprintf(`%s/%d/config`, gfile.TempDir(), gtime.TimestampNano())
|
||||
file := fmt.Sprintf(`%s/%s`, path, "config.toml")
|
||||
err := gfile.PutContents(file, config)
|
||||
gtest.Assert(err, nil)
|
||||
defer gfile.Remove(file)
|
||||
defer Config().Clear()
|
||||
gtest.Assert(Config().AddPath(path), nil)
|
||||
gtest.Assert(Config().Get("test"), "v=1")
|
||||
gtest.Assert(Config().Get("database.default.1.host"), "127.0.0.1")
|
||||
gtest.Assert(Config().Get("redis.disk"), "127.0.0.1:6379,0")
|
||||
})
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
gtest.Assert(gins.Config().Get("test"), "v=1")
|
||||
gtest.Assert(gins.Config().Get("database.default.1.host"), "127.0.0.1")
|
||||
gtest.Assert(gins.Config().Get("redis.disk"), "127.0.0.1:6379,0")
|
||||
|
||||
gtest.Case(t, func() {
|
||||
path := fmt.Sprintf(`%s/%d`, gfile.TempDir(), gtime.TimestampNano())
|
||||
file := fmt.Sprintf(`%s/%s`, path, "test.toml")
|
||||
err := gfile.PutContents(file, config)
|
||||
gtest.Assert(err, nil)
|
||||
defer gfile.Remove(file)
|
||||
defer Config("test").Clear()
|
||||
Config("test").SetFileName("test.toml")
|
||||
gtest.Assert(Config("test").AddPath(path), nil)
|
||||
gtest.Assert(Config("test").Get("test"), "v=1")
|
||||
gtest.Assert(Config("test").Get("database.default.1.host"), "127.0.0.1")
|
||||
gtest.Assert(Config("test").Get("redis.disk"), "127.0.0.1:6379,0")
|
||||
})
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
gtest.Case(t, func() {
|
||||
path := fmt.Sprintf(`%s/%d/config`, gfile.TempDir(), gtime.TimestampNano())
|
||||
file := fmt.Sprintf(`%s/%s`, path, "test.toml")
|
||||
err := gfile.PutContents(file, config)
|
||||
gtest.Assert(err, nil)
|
||||
defer gfile.Remove(file)
|
||||
defer Config().Clear()
|
||||
Config("test").SetFileName("test.toml")
|
||||
gtest.Assert(Config("test").AddPath(path), nil)
|
||||
gtest.Assert(Config("test").Get("test"), "v=1")
|
||||
gtest.Assert(Config("test").Get("database.default.1.host"), "127.0.0.1")
|
||||
gtest.Assert(Config("test").Get("redis.disk"), "127.0.0.1:6379,0")
|
||||
// for gfsnotify callbacks to refresh cache of config file
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Config3(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
gtest.Case(t, func() {
|
||||
var err error
|
||||
dirPath := gfile.Join(gfile.TempDir(), gtime.TimestampNanoStr())
|
||||
err = gfile.Mkdir(dirPath)
|
||||
gtest.Assert(err, nil)
|
||||
defer gfile.Remove(dirPath)
|
||||
|
||||
name := "test.toml"
|
||||
err = gfile.PutContents(gfile.Join(dirPath, name), configContent)
|
||||
gtest.Assert(err, nil)
|
||||
|
||||
err = gins.Config("test").AddPath(dirPath)
|
||||
gtest.Assert(err, nil)
|
||||
|
||||
defer gins.Config("test").Clear()
|
||||
gins.Config("test").SetFileName("test.toml")
|
||||
|
||||
gtest.Assert(gins.Config("test").Get("test"), "v=1")
|
||||
gtest.Assert(gins.Config("test").Get("database.default.1.host"), "127.0.0.1")
|
||||
gtest.Assert(gins.Config("test").Get("redis.disk"), "127.0.0.1:6379,0")
|
||||
})
|
||||
// for gfsnotify callbacks to refresh cache of config file
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
gtest.Case(t, func() {
|
||||
var err error
|
||||
dirPath := gfile.Join(gfile.TempDir(), gtime.TimestampNanoStr())
|
||||
err = gfile.Mkdir(dirPath)
|
||||
gtest.Assert(err, nil)
|
||||
defer gfile.Remove(dirPath)
|
||||
|
||||
name := "config/test.toml"
|
||||
err = gfile.PutContents(gfile.Join(dirPath, name), configContent)
|
||||
gtest.Assert(err, nil)
|
||||
|
||||
err = gins.Config("test").AddPath(dirPath)
|
||||
gtest.Assert(err, nil)
|
||||
|
||||
defer gins.Config("test").Clear()
|
||||
gins.Config("test").SetFileName("test.toml")
|
||||
|
||||
gtest.Assert(gins.Config("test").Get("test"), "v=1")
|
||||
gtest.Assert(gins.Config("test").Get("database.default.1.host"), "127.0.0.1")
|
||||
gtest.Assert(gins.Config("test").Get("redis.disk"), "127.0.0.1:6379,0")
|
||||
})
|
||||
// for gfsnotify callbacks to refresh cache of config file for next unit testing case.
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Config4(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
// absolute path
|
||||
gtest.Case(t, func() {
|
||||
path := fmt.Sprintf(`%s/%d`, gfile.TempDir(), gtime.TimestampNano())
|
||||
file := fmt.Sprintf(`%s/%s`, path, "config.toml")
|
||||
err := gfile.PutContents(file, configContent)
|
||||
gtest.Assert(err, nil)
|
||||
defer gfile.Remove(file)
|
||||
defer gins.Config().Clear()
|
||||
|
||||
gtest.Assert(gins.Config().AddPath(path), nil)
|
||||
gtest.Assert(gins.Config().Get("test"), "v=1")
|
||||
gtest.Assert(gins.Config().Get("database.default.1.host"), "127.0.0.1")
|
||||
gtest.Assert(gins.Config().Get("redis.disk"), "127.0.0.1:6379,0")
|
||||
})
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
gtest.Case(t, func() {
|
||||
path := fmt.Sprintf(`%s/%d/config`, gfile.TempDir(), gtime.TimestampNano())
|
||||
file := fmt.Sprintf(`%s/%s`, path, "config.toml")
|
||||
err := gfile.PutContents(file, configContent)
|
||||
gtest.Assert(err, nil)
|
||||
defer gfile.Remove(file)
|
||||
defer gins.Config().Clear()
|
||||
gtest.Assert(gins.Config().AddPath(path), nil)
|
||||
gtest.Assert(gins.Config().Get("test"), "v=1")
|
||||
gtest.Assert(gins.Config().Get("database.default.1.host"), "127.0.0.1")
|
||||
gtest.Assert(gins.Config().Get("redis.disk"), "127.0.0.1:6379,0")
|
||||
})
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
gtest.Case(t, func() {
|
||||
path := fmt.Sprintf(`%s/%d`, gfile.TempDir(), gtime.TimestampNano())
|
||||
file := fmt.Sprintf(`%s/%s`, path, "test.toml")
|
||||
err := gfile.PutContents(file, configContent)
|
||||
gtest.Assert(err, nil)
|
||||
defer gfile.Remove(file)
|
||||
defer gins.Config("test").Clear()
|
||||
gins.Config("test").SetFileName("test.toml")
|
||||
gtest.Assert(gins.Config("test").AddPath(path), nil)
|
||||
gtest.Assert(gins.Config("test").Get("test"), "v=1")
|
||||
gtest.Assert(gins.Config("test").Get("database.default.1.host"), "127.0.0.1")
|
||||
gtest.Assert(gins.Config("test").Get("redis.disk"), "127.0.0.1:6379,0")
|
||||
})
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
gtest.Case(t, func() {
|
||||
path := fmt.Sprintf(`%s/%d/config`, gfile.TempDir(), gtime.TimestampNano())
|
||||
file := fmt.Sprintf(`%s/%s`, path, "test.toml")
|
||||
err := gfile.PutContents(file, configContent)
|
||||
gtest.Assert(err, nil)
|
||||
defer gfile.Remove(file)
|
||||
defer gins.Config().Clear()
|
||||
gins.Config("test").SetFileName("test.toml")
|
||||
gtest.Assert(gins.Config("test").AddPath(path), nil)
|
||||
gtest.Assert(gins.Config("test").Get("test"), "v=1")
|
||||
gtest.Assert(gins.Config("test").Get("database.default.1.host"), "127.0.0.1")
|
||||
gtest.Assert(gins.Config("test").Get("redis.disk"), "127.0.0.1:6379,0")
|
||||
})
|
||||
})
|
||||
}
|
||||
func Test_Basic2(t *testing.T) {
|
||||
config := `log-path = "logs"`
|
||||
gtest.Case(t, func() {
|
||||
@ -179,6 +210,6 @@ func Test_Basic2(t *testing.T) {
|
||||
_ = gfile.Remove(path)
|
||||
}()
|
||||
|
||||
gtest.Assert(Config().Get("log-path"), "logs")
|
||||
gtest.Assert(gins.Config().Get("log-path"), "logs")
|
||||
})
|
||||
}
|
||||
|
||||
@ -4,9 +4,12 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gins
|
||||
package gins_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/debug/gdebug"
|
||||
"github.com/gogf/gf/frame/gins"
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -15,42 +18,22 @@ import (
|
||||
)
|
||||
|
||||
func Test_Database(t *testing.T) {
|
||||
config := `
|
||||
# 模板引擎目录
|
||||
viewpath = "/home/www/templates/"
|
||||
test = "v=2"
|
||||
# MySQL数据库配置
|
||||
[database]
|
||||
[[database.default]]
|
||||
host = "127.0.0.1"
|
||||
port = "3306"
|
||||
user = "root"
|
||||
pass = "12345678"
|
||||
name = "test"
|
||||
type = "mysql"
|
||||
role = "master"
|
||||
weight = "1"
|
||||
charset = "utf8"
|
||||
[[database.test]]
|
||||
host = "127.0.0.1"
|
||||
port = "3306"
|
||||
user = "root"
|
||||
pass = "12345678"
|
||||
name = "test"
|
||||
type = "mysql"
|
||||
role = "master"
|
||||
weight = "1"
|
||||
charset = "utf8"
|
||||
# Redis数据库配置
|
||||
[redis]
|
||||
default = "127.0.0.1:6379,0"
|
||||
cache = "127.0.0.1:6379,1"
|
||||
`
|
||||
path := "config.toml"
|
||||
err := gfile.PutContents(path, config)
|
||||
databaseContent := gfile.GetContents(gfile.Join(gdebug.TestDataPath(), "database", "config.toml"))
|
||||
|
||||
var err error
|
||||
dirPath := gfile.Join(gfile.TempDir(), gtime.TimestampNanoStr())
|
||||
err = gfile.Mkdir(dirPath)
|
||||
gtest.Assert(err, nil)
|
||||
defer gfile.Remove(path)
|
||||
defer Config().Clear()
|
||||
defer gfile.Remove(dirPath)
|
||||
|
||||
name := "config.toml"
|
||||
err = gfile.PutContents(gfile.Join(dirPath, name), databaseContent)
|
||||
gtest.Assert(err, nil)
|
||||
|
||||
err = gins.Config().AddPath(dirPath)
|
||||
gtest.Assert(err, nil)
|
||||
|
||||
defer gins.Config().Clear()
|
||||
|
||||
// for gfsnotify callbacks to refresh cache of config file
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
@ -58,8 +41,8 @@ test = "v=2"
|
||||
gtest.Case(t, func() {
|
||||
//fmt.Println("gins Test_Database", Config().Get("test"))
|
||||
|
||||
dbDefault := Database()
|
||||
dbTest := Database("test")
|
||||
dbDefault := gins.Database()
|
||||
dbTest := gins.Database("test")
|
||||
gtest.AssertNE(dbDefault, nil)
|
||||
gtest.AssertNE(dbTest, nil)
|
||||
|
||||
|
||||
@ -4,9 +4,12 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gins
|
||||
package gins_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/debug/gdebug"
|
||||
"github.com/gogf/gf/frame/gins"
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -15,45 +18,22 @@ import (
|
||||
)
|
||||
|
||||
func Test_Redis(t *testing.T) {
|
||||
config := `
|
||||
# 模板引擎目录
|
||||
viewpath = "/home/www/templates/"
|
||||
test = "v=3"
|
||||
# MySQL数据库配置
|
||||
[database]
|
||||
[[database.default]]
|
||||
host = "127.0.0.1"
|
||||
port = "3306"
|
||||
user = "root"
|
||||
pass = ""
|
||||
# pass = "12345678"
|
||||
name = "test"
|
||||
type = "mysql"
|
||||
role = "master"
|
||||
charset = "utf8"
|
||||
priority = "1"
|
||||
[[database.test]]
|
||||
host = "127.0.0.1"
|
||||
port = "3306"
|
||||
user = "root"
|
||||
pass = ""
|
||||
# pass = "12345678"
|
||||
name = "test"
|
||||
type = "mysql"
|
||||
role = "master"
|
||||
charset = "utf8"
|
||||
priority = "1"
|
||||
# Redis数据库配置
|
||||
[redis]
|
||||
default = "127.0.0.1:6379,7"
|
||||
cache = "127.0.0.1:6379,8"
|
||||
disk = "127.0.0.1:6379,9,?maxIdle=1&maxActive=10&idleTimeout=10&maxConnLifetime=10"
|
||||
`
|
||||
path := "config.toml"
|
||||
err := gfile.PutContents(path, config)
|
||||
redisContent := gfile.GetContents(gfile.Join(gdebug.TestDataPath(), "redis", "config.toml"))
|
||||
|
||||
var err error
|
||||
dirPath := gfile.Join(gfile.TempDir(), gtime.TimestampNanoStr())
|
||||
err = gfile.Mkdir(dirPath)
|
||||
gtest.Assert(err, nil)
|
||||
defer gfile.Remove(path)
|
||||
defer Config().Clear()
|
||||
defer gfile.Remove(dirPath)
|
||||
|
||||
name := "config.toml"
|
||||
err = gfile.PutContents(gfile.Join(dirPath, name), redisContent)
|
||||
gtest.Assert(err, nil)
|
||||
|
||||
err = gins.Config().AddPath(dirPath)
|
||||
gtest.Assert(err, nil)
|
||||
|
||||
defer gins.Config().Clear()
|
||||
|
||||
// for gfsnotify callbacks to refresh cache of config file
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
@ -61,9 +41,9 @@ test = "v=3"
|
||||
gtest.Case(t, func() {
|
||||
//fmt.Println("gins Test_Redis", Config().Get("test"))
|
||||
|
||||
redisDefault := Redis()
|
||||
redisCache := Redis("cache")
|
||||
redisDisk := Redis("disk")
|
||||
redisDefault := gins.Redis()
|
||||
redisCache := gins.Redis("cache")
|
||||
redisDisk := gins.Redis("disk")
|
||||
gtest.AssertNE(redisDefault, nil)
|
||||
gtest.AssertNE(redisCache, nil)
|
||||
gtest.AssertNE(redisDisk, nil)
|
||||
|
||||
@ -52,7 +52,7 @@ func Test_View(t *testing.T) {
|
||||
func Test_View_Config(t *testing.T) {
|
||||
// view1 test1
|
||||
gtest.Case(t, func() {
|
||||
dirPath := gfile.Join(gdebug.CallerDirectory(), "testdata", "view1")
|
||||
dirPath := gfile.Join(gdebug.TestDataPath(), "view1")
|
||||
gcfg.SetContent(gfile.GetContents(gfile.Join(dirPath, "config.toml")))
|
||||
defer gcfg.ClearContent()
|
||||
defer instances.Clear()
|
||||
@ -74,7 +74,7 @@ func Test_View_Config(t *testing.T) {
|
||||
})
|
||||
// view1 test2
|
||||
gtest.Case(t, func() {
|
||||
dirPath := gfile.Join(gdebug.CallerDirectory(), "testdata", "view1")
|
||||
dirPath := gfile.Join(gdebug.TestDataPath(), "view1")
|
||||
gcfg.SetContent(gfile.GetContents(gfile.Join(dirPath, "config.toml")))
|
||||
defer gcfg.ClearContent()
|
||||
defer instances.Clear()
|
||||
@ -96,7 +96,7 @@ func Test_View_Config(t *testing.T) {
|
||||
})
|
||||
// view2
|
||||
gtest.Case(t, func() {
|
||||
dirPath := gfile.Join(gdebug.CallerDirectory(), "testdata", "view2")
|
||||
dirPath := gfile.Join(gdebug.TestDataPath(), "view2")
|
||||
gcfg.SetContent(gfile.GetContents(gfile.Join(dirPath, "config.toml")))
|
||||
defer gcfg.ClearContent()
|
||||
defer instances.Clear()
|
||||
@ -118,7 +118,7 @@ func Test_View_Config(t *testing.T) {
|
||||
})
|
||||
// view2
|
||||
gtest.Case(t, func() {
|
||||
dirPath := gfile.Join(gdebug.CallerDirectory(), "testdata", "view2")
|
||||
dirPath := gfile.Join(gdebug.TestDataPath(), "view2")
|
||||
gcfg.SetContent(gfile.GetContents(gfile.Join(dirPath, "config.toml")))
|
||||
defer gcfg.ClearContent()
|
||||
defer instances.Clear()
|
||||
|
||||
29
frame/gins/testdata/config/config.toml
vendored
Normal file
29
frame/gins/testdata/config/config.toml
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
# 模板引擎目录
|
||||
viewpath = "/home/www/templates/"
|
||||
test = "v=1"
|
||||
# MySQL数据库配置
|
||||
[database]
|
||||
[[database.default]]
|
||||
host = "127.0.0.1"
|
||||
port = "3306"
|
||||
user = "root"
|
||||
pass = ""
|
||||
name = "test"
|
||||
type = "mysql"
|
||||
role = "master"
|
||||
charset = "utf8"
|
||||
priority = "1"
|
||||
[[database.default]]
|
||||
host = "127.0.0.1"
|
||||
port = "3306"
|
||||
user = "root"
|
||||
pass = "8692651"
|
||||
name = "test"
|
||||
type = "mysql"
|
||||
role = "master"
|
||||
charset = "utf8"
|
||||
priority = "1"
|
||||
# Redis数据库配置
|
||||
[redis]
|
||||
disk = "127.0.0.1:6379,0"
|
||||
cache = "127.0.0.1:6379,1"
|
||||
29
frame/gins/testdata/database/config.toml
vendored
Normal file
29
frame/gins/testdata/database/config.toml
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
# 模板引擎目录
|
||||
viewpath = "/home/www/templates/"
|
||||
test = "v=2"
|
||||
# MySQL数据库配置
|
||||
[database]
|
||||
[[database.default]]
|
||||
host = "127.0.0.1"
|
||||
port = "3306"
|
||||
user = "root"
|
||||
pass = "12345678"
|
||||
name = "test"
|
||||
type = "mysql"
|
||||
role = "master"
|
||||
weight = "1"
|
||||
charset = "utf8"
|
||||
[[database.test]]
|
||||
host = "127.0.0.1"
|
||||
port = "3306"
|
||||
user = "root"
|
||||
pass = "12345678"
|
||||
name = "test"
|
||||
type = "mysql"
|
||||
role = "master"
|
||||
weight = "1"
|
||||
charset = "utf8"
|
||||
# Redis数据库配置
|
||||
[redis]
|
||||
default = "127.0.0.1:6379,0"
|
||||
cache = "127.0.0.1:6379,1"
|
||||
32
frame/gins/testdata/redis/config.toml
vendored
Normal file
32
frame/gins/testdata/redis/config.toml
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
# 模板引擎目录
|
||||
viewpath = "/home/www/templates/"
|
||||
test = "v=3"
|
||||
# MySQL数据库配置
|
||||
[database]
|
||||
[[database.default]]
|
||||
host = "127.0.0.1"
|
||||
port = "3306"
|
||||
user = "root"
|
||||
pass = ""
|
||||
# pass = "12345678"
|
||||
name = "test"
|
||||
type = "mysql"
|
||||
role = "master"
|
||||
charset = "utf8"
|
||||
priority = "1"
|
||||
[[database.test]]
|
||||
host = "127.0.0.1"
|
||||
port = "3306"
|
||||
user = "root"
|
||||
pass = ""
|
||||
# pass = "12345678"
|
||||
name = "test"
|
||||
type = "mysql"
|
||||
role = "master"
|
||||
charset = "utf8"
|
||||
priority = "1"
|
||||
# Redis数据库配置
|
||||
[redis]
|
||||
default = "127.0.0.1:6379,7"
|
||||
cache = "127.0.0.1:6379,8"
|
||||
disk = "127.0.0.1:6379,9,?maxIdle=1&maxActive=10&idleTimeout=10&maxConnLifetime=10"
|
||||
@ -6,5 +6,9 @@
|
||||
|
||||
package gmvc
|
||||
|
||||
// Model is the base struct for model.
|
||||
type Model struct{}
|
||||
import "github.com/gogf/gf/database/gdb"
|
||||
|
||||
type (
|
||||
M = Model // M is alias for Model, just for short write purpose.
|
||||
Model = *gdb.Model // Model is alias for *gdb.Model.
|
||||
)
|
||||
|
||||
3
go.mod
3
go.mod
@ -7,8 +7,8 @@ require (
|
||||
github.com/clbanning/mxj v1.8.4
|
||||
github.com/fatih/structs v1.1.0
|
||||
github.com/fsnotify/fsnotify v1.4.7
|
||||
github.com/gf-third/mysql v1.4.2
|
||||
github.com/gf-third/yaml v1.0.1
|
||||
github.com/go-sql-driver/mysql v1.5.0
|
||||
github.com/gomodule/redigo v2.0.0+incompatible
|
||||
github.com/google/uuid v1.1.1
|
||||
github.com/gorilla/websocket v1.4.1
|
||||
@ -17,5 +17,4 @@ require (
|
||||
github.com/olekukonko/tablewriter v0.0.1
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e // indirect
|
||||
golang.org/x/text v0.3.2
|
||||
google.golang.org/appengine v1.6.5 // indirect
|
||||
)
|
||||
|
||||
@ -51,7 +51,12 @@ func IsEmpty(value interface{}) bool {
|
||||
return len(value) == 0
|
||||
default:
|
||||
// Finally using reflect.
|
||||
rv := reflect.ValueOf(value)
|
||||
var rv reflect.Value
|
||||
if v, ok := value.(reflect.Value); ok {
|
||||
rv = v
|
||||
} else {
|
||||
rv = reflect.ValueOf(value)
|
||||
}
|
||||
switch rv.Kind() {
|
||||
case reflect.Chan,
|
||||
reflect.Map,
|
||||
@ -77,7 +82,12 @@ func IsNil(value interface{}) bool {
|
||||
if value == nil {
|
||||
return true
|
||||
}
|
||||
rv := reflect.ValueOf(value)
|
||||
var rv reflect.Value
|
||||
if v, ok := value.(reflect.Value); ok {
|
||||
rv = v
|
||||
} else {
|
||||
rv = reflect.ValueOf(value)
|
||||
}
|
||||
switch rv.Kind() {
|
||||
case reflect.Chan,
|
||||
reflect.Map,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user