mirror of
https://gitee.com/johng/gf
synced 2026-06-10 03:23:59 +08:00
Compare commits
230 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| baf51bc68f | |||
| 5f4b585164 | |||
| 17a11187b0 | |||
| 7caf7976cf | |||
| 1a31792c14 | |||
| d56eb49e41 | |||
| a1236b5e16 | |||
| 8f278be0dc | |||
| f8ab71e7f0 | |||
| 9cca3a3ec1 | |||
| 97bcf2a438 | |||
| 85dd2e9f4f | |||
| 23d62da87f | |||
| b4d947fecd | |||
| 650c95af31 | |||
| 943116d495 | |||
| 644df7c16e | |||
| 638773b216 | |||
| 889e7914e2 | |||
| 68cc85f2b2 | |||
| c273ce576b | |||
| f8d57096a8 | |||
| d7542e87ae | |||
| b178210a31 | |||
| 3e6a23b0e1 | |||
| ee8d2afe58 | |||
| 11e102e137 | |||
| e06b62ecf2 | |||
| d178102f82 | |||
| e1dd5cce7d | |||
| 1edc1f35fb | |||
| 4df47be521 | |||
| 9cb88bca5a | |||
| fa8cc1d3f4 | |||
| 9ae8a7ca33 | |||
| f4da179140 | |||
| 13330658cb | |||
| 9f04e46166 | |||
| 1072ea3fb0 | |||
| ea9e8055a4 | |||
| 784abf2a30 | |||
| ed4a70deff | |||
| fef20d10a2 | |||
| 176dcdc7cc | |||
| 12ed05f846 | |||
| 5999f22f76 | |||
| c056fd2a06 | |||
| cb422f043e | |||
| 4ae89dc9f6 | |||
| 4f6f07db1d | |||
| cd981c7294 | |||
| 557d2967fa | |||
| a7a70636dd | |||
| 2215661f89 | |||
| a22b590b43 | |||
| 1b0b209662 | |||
| 2a2761c54f | |||
| 1c83d72f39 | |||
| fcea774b59 | |||
| da2ec21571 | |||
| ebb8d8a2f7 | |||
| 9706a9c768 | |||
| cee67a8d4e | |||
| 87650557fd | |||
| d3bf52f12f | |||
| 6b13a4849b | |||
| 8e380c0d9d | |||
| 0caf4bfcec | |||
| 9c3b978b50 | |||
| ab689a7792 | |||
| 846646d92d | |||
| 2eb2b89432 | |||
| 43441a8218 | |||
| 561a541fa1 | |||
| ffe9ecc141 | |||
| 77f7884604 | |||
| 0a203d1e22 | |||
| f4f4550483 | |||
| e87226a092 | |||
| 391a3ec9bd | |||
| dd5cd31ef5 | |||
| de92e804fe | |||
| 7725d9aaaf | |||
| bd3e25adea | |||
| 0b1d49af4b | |||
| 7efa9e351e | |||
| 0b0141954b | |||
| 82b531fbfb | |||
| 67fb626339 | |||
| 9044d5f05d | |||
| 47663aa1f1 | |||
| 63c0aab19c | |||
| 261216f5e4 | |||
| f88b799d67 | |||
| 9c48dd172c | |||
| 09ce105eee | |||
| 651aa895f8 | |||
| 0509e41198 | |||
| 1b40d6a53a | |||
| f9189d48d1 | |||
| 849874a247 | |||
| 3f6510bae7 | |||
| a585a26c39 | |||
| 9943966a86 | |||
| 3617e51c01 | |||
| c931032f08 | |||
| 37b286eaa4 | |||
| 619287c273 | |||
| aeb9b68298 | |||
| bae8f6315b | |||
| a6f70f8935 | |||
| acca6f4009 | |||
| 727fdd2391 | |||
| 1d174e00c0 | |||
| a5e3e2f5ba | |||
| da43c2d52f | |||
| 262f27748c | |||
| a29aef7e1c | |||
| 1671391195 | |||
| 28b0d59c61 | |||
| 86433cef25 | |||
| e96ccd5f71 | |||
| 50a087bb3d | |||
| 1ec049c52f | |||
| ec38805542 | |||
| 4c8517d075 | |||
| edf06da6ea | |||
| eb43a2040e | |||
| 9d0ecc7d3e | |||
| ad943c5e02 | |||
| bdb4fd0d25 | |||
| 2440e05457 | |||
| 1337c6c0d1 | |||
| 957689e07c | |||
| 3952d74f87 | |||
| 6dc4b81693 | |||
| 9cd953b7be | |||
| 631810dda2 | |||
| 8c12bc5506 | |||
| d4091a4826 | |||
| a7c269886b | |||
| f54593037b | |||
| 0415cf6a08 | |||
| 87c22d32b0 | |||
| 27dd15b403 | |||
| dd452c19ce | |||
| acc0846cf3 | |||
| 5a6738841f | |||
| bea451c9d6 | |||
| 676e904ec6 | |||
| 49aa5c61bc | |||
| a2272b852c | |||
| 1fa77630f9 | |||
| a841c4cc05 | |||
| 1874808e3b | |||
| 149b67916b | |||
| 7fb6f58162 | |||
| a309114a18 | |||
| 35cbde9530 | |||
| b9211b182a | |||
| 81ec499ae9 | |||
| f2e276eabd | |||
| f6dbaba1f8 | |||
| 65dcff052a | |||
| e3861567c7 | |||
| e89a20c725 | |||
| a8acc6bd28 | |||
| eb5efc735e | |||
| 737af527cd | |||
| 875d2b7e63 | |||
| bf1cb0e1bd | |||
| 2bfeb1b06c | |||
| 820e4302b7 | |||
| 84d761b418 | |||
| e5e27f2ac4 | |||
| efca9b18a8 | |||
| 607821ecbc | |||
| 7cc1b239d4 | |||
| efa8de34da | |||
| bcf45e3c5a | |||
| 0a3cd1d2ab | |||
| 3e621856c8 | |||
| 32e4d64ddb | |||
| 46e45ca84b | |||
| fcb13bd8ee | |||
| eacad9b453 | |||
| ed479e2a13 | |||
| 19937cb75d | |||
| a95093222c | |||
| 7b599d1882 | |||
| 4d501fd2f4 | |||
| c3d3053ded | |||
| 14d5fd3e11 | |||
| 9f79453334 | |||
| c7f1c881c0 | |||
| c39c032b4e | |||
| fa96c18308 | |||
| d90145a47f | |||
| 4871f86346 | |||
| 919eaf1e9a | |||
| 8a84ca16d1 | |||
| 12b4fdd692 | |||
| 3d8451d5d0 | |||
| cf88f28519 | |||
| 15d99eee46 | |||
| 6d68277db8 | |||
| 3e3b5557f7 | |||
| ba1a9d9f8e | |||
| 3e1a7953ec | |||
| 073fb2d717 | |||
| 7f33021184 | |||
| b396096721 | |||
| 0a5c6d832f | |||
| d279566114 | |||
| 2693cbb136 | |||
| f7a9be4292 | |||
| a926033b66 | |||
| dbcdd06b19 | |||
| ff5dab5c70 | |||
| 84b576418f | |||
| 253b124903 | |||
| 33dc5ddf79 | |||
| a456fa537c | |||
| 9e1fb93e08 | |||
| 5d24f702be | |||
| 6cc4747965 | |||
| 437fc04620 | |||
| 937f8e6919 | |||
| 6e08eebcbe | |||
| d9422d00ac |
@ -1,10 +1,15 @@
|
||||
|
||||
# MySQL数据库配置
|
||||
# MySQL.
|
||||
[database]
|
||||
debug = true
|
||||
link = "mysql:root:12345678@tcp(127.0.0.1:3306)/test?parseTime=true&loc=Local"
|
||||
MaxOpen = 100
|
||||
|
||||
# Redis.
|
||||
[redis]
|
||||
default = "127.0.0.1:6379,0"
|
||||
cache = "127.0.0.1:6379,1"
|
||||
|
||||
#[database]
|
||||
# [[database.default]]
|
||||
# type = "mysql"
|
||||
|
||||
@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"github.com/gogf/gf/database/gdb"
|
||||
"github.com/gogf/gf/util/gutil"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@ -10,7 +11,7 @@ func main() {
|
||||
Host: "127.0.0.1",
|
||||
Port: "3306",
|
||||
User: "root",
|
||||
Pass: "123456",
|
||||
Pass: "12345678",
|
||||
Name: "test",
|
||||
Type: "mysql",
|
||||
Role: "master",
|
||||
@ -20,19 +21,20 @@ func main() {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
//db.GetCache().SetAdapter(adapter.NewRedis(g.Redis()))
|
||||
// 开启调试模式,以便于记录所有执行的SQL
|
||||
db.SetDebug(true)
|
||||
|
||||
// 执行2次查询并将查询结果缓存3秒,并可执行缓存名称(可选)
|
||||
for i := 0; i < 2; i++ {
|
||||
r, _ := db.Table("user").Cache(3, "vip-user").Where("uid=?", 1).One()
|
||||
gutil.Dump(r.ToMap())
|
||||
for i := 0; i < 3; i++ {
|
||||
r, _ := db.Table("user").Cache(3000*time.Second).Where("id=?", 1).One()
|
||||
gutil.Dump(r.Map())
|
||||
}
|
||||
|
||||
// 执行更新操作,并清理指定名称的查询缓存
|
||||
db.Table("user").Cache(-1, "vip-user").Data(gdb.Map{"name": "smith"}).Where("uid=?", 1).Update()
|
||||
//db.Table("user").Cache(-1, "vip-user").Data(gdb.Map{"name": "smith"}).Where("id=?", 1).Update()
|
||||
|
||||
// 再次执行查询,启用查询缓存特性
|
||||
r, _ := db.Table("user").Cache(3, "vip-user").Where("uid=?", 1).One()
|
||||
gutil.Dump(r.ToMap())
|
||||
//r, _ := db.Table("user").Cache(300000*time.Second, "vip-user").Where("id=?", 1).One()
|
||||
//gutil.Dump(r.Map())
|
||||
}
|
||||
|
||||
@ -8,9 +8,6 @@ import (
|
||||
|
||||
func main() {
|
||||
db := g.DB()
|
||||
db.SetMaxIdleConnCount(10)
|
||||
db.SetMaxOpenConnCount(10)
|
||||
db.SetMaxConnLifetime(time.Minute)
|
||||
|
||||
// 开启调试模式,以便于记录所有执行的SQL
|
||||
db.SetDebug(true)
|
||||
@ -19,7 +16,7 @@ func main() {
|
||||
for i := 0; i < 10; i++ {
|
||||
go db.Table("user").All()
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -2,25 +2,14 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/i18n/gi18n"
|
||||
"github.com/gogf/gf/frame/g"
|
||||
)
|
||||
|
||||
func main() {
|
||||
t := gi18n.New()
|
||||
t.SetPath("/Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/.example/i18n/gi18n/i18n")
|
||||
t.SetLanguage("en")
|
||||
fmt.Println(t.Translate(`hello`))
|
||||
fmt.Println(t.Translate(`{#hello}{#world}!`))
|
||||
|
||||
t.SetLanguage("ja")
|
||||
fmt.Println(t.Translate(`hello`))
|
||||
fmt.Println(t.Translate(`{#hello}{#world}!`))
|
||||
|
||||
t.SetLanguage("ru")
|
||||
fmt.Println(t.Translate(`hello`))
|
||||
fmt.Println(t.Translate(`{#hello}{#world}!`))
|
||||
|
||||
fmt.Println(t.Translate(`hello`, "zh-CN"))
|
||||
fmt.Println(t.Translate(`{#hello}{#world}!`, "zh-CN"))
|
||||
var (
|
||||
orderId = 865271654
|
||||
orderAmount = 99.8
|
||||
)
|
||||
fmt.Println(g.I18n().Tfl(`en`, `{#OrderPaid}`, orderId, orderAmount))
|
||||
fmt.Println(g.I18n().Tfl(`zh-CN`, `{#OrderPaid}`, orderId, orderAmount))
|
||||
}
|
||||
|
||||
@ -1,3 +1 @@
|
||||
|
||||
hello = "Hello"
|
||||
world = "World"
|
||||
OrderPaid = "You have successfully complete order #%d payment, paid amount: ¥%0.2f."
|
||||
@ -1,2 +1 @@
|
||||
hello = "你好"
|
||||
world = "世界"
|
||||
OrderPaid = "您已成功完成订单号 #%d 支付,支付金额¥%.2f。"
|
||||
@ -3,15 +3,21 @@ package main
|
||||
import (
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/net/ghttp"
|
||||
"github.com/gogf/gf/os/glog"
|
||||
)
|
||||
|
||||
type GetById struct {
|
||||
Id *g.Var `p:"id" v:"required|integer#id不能为空|id必须为整数"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
s := g.Server()
|
||||
s.SetIndexFolder(true)
|
||||
s.BindHandler("/", func(r *ghttp.Request) {
|
||||
glog.Println(r.Header)
|
||||
r.Response.Write("hello world")
|
||||
var idInfo *GetById
|
||||
if err := r.Parse(&idInfo); err != nil {
|
||||
r.Response.Write(err)
|
||||
}
|
||||
r.Response.Write("ok")
|
||||
})
|
||||
s.SetPort(8999)
|
||||
s.Run()
|
||||
|
||||
@ -1,12 +1,13 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/net/ghttp"
|
||||
)
|
||||
|
||||
func main() {
|
||||
s := ghttp.GetServer()
|
||||
s.EnablePProf()
|
||||
s := g.Server()
|
||||
s.Domain("localhost").EnablePProf()
|
||||
s.BindHandler("/", func(r *ghttp.Request) {
|
||||
r.Response.Writeln("哈喽世界!")
|
||||
})
|
||||
|
||||
@ -2,19 +2,18 @@ package main
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/frame/gmvc"
|
||||
"github.com/gogf/gf/net/ghttp"
|
||||
)
|
||||
|
||||
type Controller struct {
|
||||
gmvc.Controller
|
||||
}
|
||||
|
||||
func (c *Controller) Test() {
|
||||
c.View.Display("layout.html")
|
||||
}
|
||||
func main() {
|
||||
s := g.Server()
|
||||
s.BindControllerMethod("/", new(Controller), "Test")
|
||||
s.BindHandler("/", func(r *ghttp.Request) {
|
||||
r.Response.WriteTpl("layout.html", g.Map{
|
||||
"header": "This is header",
|
||||
"container": "This is container",
|
||||
"footer": "This is footer",
|
||||
})
|
||||
})
|
||||
s.SetPort(8199)
|
||||
s.Run()
|
||||
}
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
{{define "container"}}
|
||||
<h1>CONTAINER</h1>
|
||||
<h1>{{.container}}</h1>
|
||||
{{end}}
|
||||
@ -1,3 +1,3 @@
|
||||
{{define "footer"}}
|
||||
<h1>FOOTER</h1>
|
||||
<h1>{{.footer}}</h1>
|
||||
{{end}}
|
||||
@ -1,3 +1,3 @@
|
||||
{{define "header"}}
|
||||
<h1>HEADER</h1>
|
||||
<h1>{{.header}}</h1>
|
||||
{{end}}
|
||||
@ -2,14 +2,14 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>GoFrame Layout</title>
|
||||
{{template "header"}}
|
||||
{{template "header" .}}
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
{{template "container"}}
|
||||
{{template "container" .}}
|
||||
</div>
|
||||
<div class="footer">
|
||||
{{template "footer"}}
|
||||
{{template "footer" .}}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@ -9,11 +9,13 @@ func main() {
|
||||
s := g.Server()
|
||||
s.BindHandler("/main1", func(r *ghttp.Request) {
|
||||
r.Response.WriteTpl("layout.html", g.Map{
|
||||
"name": "smith",
|
||||
"mainTpl": "main/main1.html",
|
||||
})
|
||||
})
|
||||
s.BindHandler("/main2", func(r *ghttp.Request) {
|
||||
r.Response.WriteTpl("layout.html", g.Map{
|
||||
"name": "john",
|
||||
"mainTpl": "main/main2.html",
|
||||
})
|
||||
})
|
||||
|
||||
@ -11,7 +11,7 @@ func main() {
|
||||
fmt.Println(1)
|
||||
gutil.Throw("error")
|
||||
fmt.Println(2)
|
||||
}, func(err interface{}) {
|
||||
}, func(err error) {
|
||||
fmt.Println(err)
|
||||
})
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ go:
|
||||
- "1.12.x"
|
||||
- "1.13.x"
|
||||
- "1.14.x"
|
||||
- "1.15.x"
|
||||
|
||||
branches:
|
||||
only:
|
||||
|
||||
46
DONATOR.MD
46
DONATOR.MD
@ -11,7 +11,7 @@ please note your github/gitee account in your payment bill. All the donations wi
|
||||
| Name | Channel | Amount | Comment
|
||||
|---|---|--- | ---
|
||||
|[hailaz](https://gitee.com/hailaz)|gitee|¥20.00 |
|
||||
|[ireadx](https://github.com/ireadx)|alipay|¥301.00 |
|
||||
|[ireadx](https://github.com/ireadx)|alipay|¥501.00 |
|
||||
|[mg91](https://gitee.com/mg91)|gitee|¥10.00 |
|
||||
|[pibigstar](https://github.com/pibigstar)|alipay|¥10.00 |
|
||||
|[tiangenglan](https://gitee.com/tiangenglan)|gitee|¥30.00 |
|
||||
@ -97,6 +97,50 @@ please note your github/gitee account in your payment bill. All the donations wi
|
||||
|六七 ·|wechat|¥88.88| gf越来越好
|
||||
|Tom|wechat|¥500.00| 一点心意 希望GF越来越好
|
||||
|Chao|wechat|¥166.00|
|
||||
|汤sir|wechat|¥10.00| 强哥,喝杯咖啡☕️
|
||||
|秋叶、|wechat|¥66.66| GF带我一起玩
|
||||
||wechat|¥20.00|
|
||||
|程凤明|wechat|¥18.80|
|
||||
|Glowworm|wechat|¥8.88| 感谢大佬
|
||||
|徒行|wechat|¥10.00| 目前项目在转用这个框架,鼓励一下大佬吧👍👍👍
|
||||
|产品设计.软件开发|wechat|¥10.00| 祝愿走向更成功!
|
||||
|[charlieccGuo](https://github.com/charlieccGuo)|wechat|¥10.00|
|
||||
|yiran|wechat|¥20.00| 赏给大佬喝茶🍵
|
||||
|长夏朔酒|wechat|¥20.00| 请大佬喝茶
|
||||
|💥聪จุ๊บ 🇨🇳|wechat|¥66.66| 请大佬喝茶
|
||||
|Even_|wechat|¥10.00| 日照市民发来贺电!
|
||||
|智慧人生|wechat|¥50.00| 智慧人生
|
||||
|一滴水|wechat|¥10.00| goframe 越来越强大
|
||||
|[wenzi1](https://github.com/wenzi1)|alipay|¥100.00|
|
||||
|[hyuant](https://github.com/hyuant)|alipay|¥66.66|
|
||||
|*庆|alipay|¥9.99| 支持一下,gf越来越好
|
||||
|**君|alipay|¥10.00| 加油
|
||||
|向回走的闹钟|wechat|¥20.00| 越来越好
|
||||
|金毛|wechat|¥100.00|
|
||||
|莫失莫忘|wechat|¥100.00|
|
||||
|**航|alipay|¥20.00|
|
||||
|阿康|wechat|¥100.00|
|
||||
|Tzp|wechat|¥10.00|
|
||||
|[hkxiaoyu118](https://github.com/hkxiaoyu118)|wechat|¥10.00|
|
||||
|辰|wechat|¥50.00|
|
||||
|LSJ|wechat|¥66.66|我想我是海:祝gf越来越好,统治后端
|
||||
|yu|wechat|¥100.00|感谢开源,加油!我是QQ群里的lah
|
||||
|雁字回时月满楼|wechat|¥20.00|感谢gf
|
||||
|Panda|wechat|¥20.00|支持一下!gf很棒👍
|
||||
|[Thunur](https://gitee.com/thunur)|wechat|¥100.00|
|
||||
|[Mr.奇淼](https://www.gin-vue-admin.com/)|wechat|¥18.88|强哥无敌,奇淼爱你
|
||||
|[SliverHorn](hhttps://github.com/sliverhorn)|wechat|¥17.77|强哥无敌,SliverHorn爱你
|
||||
|[fly的狐狸](https://github.com/zcool321)|wechat|¥50.00|
|
||||
|北漂生活|wechat|¥66.66|gf大展鸿图
|
||||
|YJ|wechat|¥10.00|YangJ-Eric祝愿越来越好
|
||||
|秋葵|wechat|¥20.00|之前强哥
|
||||
|陈诚|wechat|¥100.00|Loocor恭喜郭总发版🎉
|
||||
|**栋|alipay|¥100.00|
|
||||
|**浩|alipay|¥100.00|
|
||||
|RAGGA-TIME|alipay|¥50.00|
|
||||
|[ChArmy](https://gitee.com/charmy)|alipay|¥50.00|
|
||||
|[sanfenzui](https://gitee.com/sanfenzui)|alipay|¥88.00|
|
||||
|刘宇|wechat|¥30.00|请你喝咖啡
|
||||
|
||||
|
||||
|
||||
|
||||
@ -120,11 +120,11 @@ The concurrency starts from `100` to `10000`.
|
||||
- [Tencent](https://www.tencent.com/)
|
||||
- [ZTE](https://www.zte.com.cn/china/)
|
||||
- [Ant Financial Services](https://www.antfin.com/)
|
||||
- [Medlinker](https://www.medlinker.com/)
|
||||
- [Yesk](http://www.yesky.com)
|
||||
- [MedLinker](https://www.medlinker.com/)
|
||||
- [KuCoin](https://www.kucoin.io/)
|
||||
- [Leyoujia Holding Group](https://www.leyoujia.com/)
|
||||
|
||||
- [LeYouJia](https://www.leyoujia.com/)
|
||||
- [IGG](https://igg.com)
|
||||
- [XiMaLaYa](https://www.ximalaya.com)
|
||||
|
||||
> We list part of the users here, if your company or products are using `GoFrame`, please let us know [here](https://github.com/gogf/gf/issues/168).
|
||||
|
||||
|
||||
@ -141,11 +141,12 @@ ab -t 10 -c 100 http://127.0.0.1:3000/json
|
||||
- [中兴科技](https://www.zte.com.cn/china/)
|
||||
- [蚂蚁金服](https://www.antfin.com/)
|
||||
- [医联科技](https://www.medlinker.com/)
|
||||
- [天极数码](http://www.yesky.com)
|
||||
- [库币科技](https://www.kucoin.io/)
|
||||
- [乐有家](https://www.leyoujia.com/)
|
||||
- [IGG](https://igg.com)
|
||||
- [喜马拉雅](https://www.ximalaya.com)
|
||||
|
||||
> 在这里只列举了部分知名的用户,如果您的企业或者产品正在使用`GoFrame`,欢迎到 [这里] (https://github.com/gogf/gf/issues/168)留言。
|
||||
> 在这里只列举了部分知名的用户,如果您的企业或者产品正在使用`GoFrame`,欢迎到 [这里](https://github.com/gogf/gf/issues/168) 留言。
|
||||
|
||||
# 贡献
|
||||
|
||||
|
||||
174
RELEASE.2.MD
174
RELEASE.2.MD
@ -1,3 +1,177 @@
|
||||
# `v1.14.2` (2020-10-27)
|
||||
|
||||
# GoFrame
|
||||
|
||||
`GF(Go Frame)`是一款模块化、高性能、生产级的Go基础开发框架。实现了比较完善的基础设施建设以及开发工具链,提供了常用的基础开发模块,如:缓存、日志、队列、数组、集合、容器、定时器、命令行、内存锁、对象池、配置管理、资源管理、数据校验、数据编码、定时任务、数据库ORM、TCP/UDP组件、进程管理/通信等等。并提供了Web服务开发的系列核心组件,如:`Router`、`Cookie`、`Session`、`Middleware`、服务注册、模板引擎等等,支持热重启、热更新、域名绑定、`TLS/HTTPS`、`Rewrite`等特性。
|
||||
|
||||
## 特点
|
||||
|
||||
* 模块化、松耦合设计;
|
||||
* 模块丰富、开箱即用;
|
||||
* 简便易用、易于维护;
|
||||
* 高代码质量、高单元测试覆盖率;
|
||||
* 社区活跃,大牛谦逊低调脾气好;
|
||||
* 详尽的开发文档及示例;
|
||||
* 完善的本地中文化支持;
|
||||
* 设计为团队及企业使用;
|
||||
|
||||
## 支持我们
|
||||
|
||||
OSC最佳开源项目评选开始了,如果您喜欢`GoFrame`,欢迎为`GoFrame`投上您宝贵的一票🙏 https://www.oschina.net/p/goframe
|
||||
|
||||
|
||||
# Change Log
|
||||
|
||||
由于`GoFrame`是模块化设计,因此每个版本的更新记录都会以模块的形式进行介绍。
|
||||
|
||||
重要更新:
|
||||
1. 将框架内所有的`json`操作从标准库替换为`json-iterator/go`,提高操作效率。
|
||||
1. 缓存模块重构底层设计,增加适配器设计模式,并增加内存及`Redis`适配器支持。其中内存适配器默认核心模块提供,`Redis`适配器由社区模块提供:https://goframe.org/os/gcache/adapter
|
||||
1. 增加可自定义的校验规则注册特性:https://goframe.org/util/gvalid/customrule
|
||||
1. `Web Server`增加所有配置项示例:https://goframe.org/net/ghttp/config/example
|
||||
1. `ORM`新增基于`Redis`的`SQL`缓存适配器:https://goframe.org/database/gdb/model/cache
|
||||
1. `ORM`新增模型关联实验特性:https://goframe.org/database/gdb/model/association
|
||||
1. `ORM`改进时间自动更新特性增加自定义时间字段:https://goframe.org/database/gdb/model/auto-time
|
||||
1. 错误处理模块新增`Current`及`Next`方法:https://goframe.org/errors/gerror/index
|
||||
|
||||
|
||||
|
||||
## `net`
|
||||
1. `ghttp`
|
||||
- `Client`
|
||||
- 增加`GetVar/PutVar/PostVar`等`*Var`请求方法,用于发起`HTTP`请求获取内容之后直接返回泛型对象,方便类型转换,特别是针对于返回`XML/JSON`的结果处理将会更加简便:https://goframe.org/net/ghttp/client/demo/index
|
||||
- 增加`SetProxy/Proxy`方法,用于设置客户端代理,支持`HTTP/Socket5`代理类型:https://goframe.org/net/ghttp/client/demo/proxy
|
||||
- 增加`SetRedirectLimit/RedirectLimit`方法,用于设置页面跳转数量限制。
|
||||
- `Request`
|
||||
- 增加`ParseQuery`, `ParseForm`方法,用于解析指定类型的参数,并绑定到给定的对象。
|
||||
- 增加`GetHeader`方法,用于获取指定`Header`参数。
|
||||
- 增加`GetRemoteIp`方法,用于获取请求客户端IP。在IP白名单限制时应当使用`GetRemoteIp`而不是`GetClientIp`进行判断,后者可以通过`Header`伪造。
|
||||
- 增加`ReloadParam`方法,往往用在中间件处理中,当中间件修改了请求参数,需要通过调用该方法重新解析一下请求参数。
|
||||
- 增加`GetRouterMap`方法,用于获得所有的路由参数返回为`map`。
|
||||
- `Response`
|
||||
- 将`Output`方法名称改为`Flush`,用于将缓冲区的数据写入到客户端数据流中。
|
||||
- `Server`
|
||||
- `Server`增加所有配置项示例:https://goframe.org/net/ghttp/config/example
|
||||
- 增加`SessionCookieOutput`配置,用于控制是否输出`SessionId`到`Cookie`中,默认为开启。
|
||||
- 改进路由解析,增加对`URI`带有重复的`/`符号的支持。
|
||||
- `Pprof`功能路由支持`Domain`绑定。
|
||||
- 其他一些细节改进。
|
||||
- `Cookie`
|
||||
- 增加`SetHttpCookie`方法,用于根据标准库`http.Cookie`对象设置`Cookie`。
|
||||
- 其他一些功能改进
|
||||
|
||||
|
||||
## `database`
|
||||
1. `gdb`
|
||||
- 新增模型关联实验特性:https://goframe.org/database/gdb/model/association
|
||||
- 改进时间自动更新特性增加自定义时间字段:https://goframe.org/database/gdb/model/auto-time
|
||||
- 新增基于`Redis`的`SQL`缓存适配器:https://goframe.org/database/gdb/model/cache
|
||||
- 新增对输入参数的键名-字段名自动识别映射特性:https://goframe.org/database/gdb/senior
|
||||
- 新增`DB.HasTable`方法,用于判断是否当前数据库存在指定数据表。
|
||||
- 新增`Model.HasField`方法,用于判断是否当前数据表存在指定字段。
|
||||
- 新增`Model.ScanList`方法,用于智能地将当前`struct`/`slice`绑定到指定的`list`对应属性上。
|
||||
- 新增`Result.MapKeyValue`方法,用于将当前`Result`转换为`map[string]Value`类型。
|
||||
- 新增`Result.IsEmpty/Len/Size/ScanList`方法。
|
||||
- 增加`ListItemValues`及`ListItemValuesUnique`方法,用于自动获取`list`中指定名称的键值或属性值,构成`slice`返回。
|
||||
- `SQL`日志内容增加分组名称打印。
|
||||
- 改进`DataToMapDeep`方法。
|
||||
- 其他一些细节改进工作。
|
||||
|
||||
1. `gredis`
|
||||
- 新增`TLS`特性支持,并支持配置文件配置,https://goframe.org/database/gredis/config
|
||||
|
||||
## `container`
|
||||
1. `gvar`
|
||||
- 增加`Scan`及`ScanDeep`方法,用于`struct`/`slice`自动识别转换。
|
||||
- 增加`ListItemValues`及`ListItemValuesUnique`方法,用于自动获取`list`中指定名称的键值或属性值,构成`slice`返回。
|
||||
- 增加`MapStrAny`接口实现方法。
|
||||
|
||||
## `os`
|
||||
1. `gcache`
|
||||
- 增加`GetVar`方法,用于获取缓存数据并返回为泛型对象。
|
||||
- 增加`Update`方法,用于仅修改缓存数值,不修改缓存过期时间。
|
||||
- 增加`UpdateExpire`方法,用于仅修改缓存过期时间,不修改缓存数值。
|
||||
- 重构底层设计,增加适配器设计模式,并增加内存及`Redis`适配器支持。其中内存适配器默认核心模块提供,`Redis`适配器由社区模块提供:https://goframe.org/os/gcache/adapter
|
||||
- 注意,本次模块的修改会有部分方法不兼容,部分方法增加了`error`参数返回,升级时请注意查看。编译时将不会通过。
|
||||
- 其他一些功能改进。
|
||||
1. `gfile`
|
||||
- 增加`ScanDirFileFunc`方法,用于自定义函数处理的递归目录文件遍历。
|
||||
- 改进`Scan*`方法,增加递归层级限制,默认层级限制为`100000`.
|
||||
|
||||
1. `gfsnotify`
|
||||
- 去掉模块初始化时的`Watcher`对象创建,调整为运行时按需创建,并且增加了并发安全控制。
|
||||
|
||||
1. `grpool`
|
||||
- 增加`AddWithRecover`方法,用于添加异步任务时给定一个`recover`处理方法,当任务`panic`时交由该`recover`方法处理,防止异步任务`panic`引起整个进程崩溃。
|
||||
> 这里解决的痛点是`recover`只能捕获到当前`goroutine`的`panic`,因此只能在创建异步任务的时候指定`recover`处理方法。
|
||||
|
||||
1. `gtime`
|
||||
- 增加`ParseDuration`方法,增加了对时间单位`d`的支持,表示天。
|
||||
- 改进`New`方法,支持通过字符串、时间戳、`time.Time`对象创建`gtime.Time`对象,https://goframe.org/os/gtime/time
|
||||
- 改进`Add/AddStr/ToLocation/ToZone/UTCLocal/AddDate/Truncate/Round`方法,这些方法调用时,不再修改当前对象本身,而是创建并返回一个新的`gtine.Time`对象,以便保证和标准库`time.Time`的逻辑一致,防止混淆。
|
||||
- 其他一些细节改进。
|
||||
1. `gtimer`
|
||||
- 增加`Reset`方法,用于重置定时任务的计时。
|
||||
1. `gfcache`
|
||||
- 去掉了该模块,该模块的功能作用不是特别大。
|
||||
|
||||
## `debug`
|
||||
1. `gdebug`
|
||||
- 新增`GoroutineId`方法,用于获取当前执行的`goroutine id`,仅作调试使用。
|
||||
|
||||
## `encoding`
|
||||
1. `gjson`
|
||||
- 新增`GetScan/GetScanDeep`方法。
|
||||
- 新增`ToScan/ToScanDeep`方法。
|
||||
- 新增`LoadContentType`方法,用于根据指定类型的内容创建`Json`操作对象。
|
||||
- 新增`IsValidDataType`方法,用于判断给定的数据类型是否支持解析。
|
||||
- 其他一些改进。
|
||||
- 单元测试完善。
|
||||
|
||||
1. `gcompress`
|
||||
- 新增`GzipFile/UnGzipFile`基于`gzip`压缩算法的文件压缩/解压。
|
||||
|
||||
## `i18n`
|
||||
1. `gi18n`
|
||||
- 新增`TranslateFormat/TranslateFormatLang/Tf/Tfl`方法: https://goframe.org/i18n/gi18n/index
|
||||
|
||||
## `text`
|
||||
1. `gstr`
|
||||
- 增加`SnakeFirstUpperCase`方法,用于在字母大写前增加连接符,并不会处理数字,例如:`SnakeFirstUpperCase("RGBCodeMd5")`将会返回`rgb_code_md5`。
|
||||
|
||||
## `util`
|
||||
1. `gconv`
|
||||
- 增加对指针基本类型的转换支持。
|
||||
- 增加`Scan/ScanDeep`方法,用于自动识别转换`Struct/[]Struct`。
|
||||
- 改进`MapDeep`方法的层级递归处理。
|
||||
- 其他一些细节改进,性能改进。
|
||||
|
||||
1. `gutil`
|
||||
- 增加`ListItemValues`及`ListItemValuesUnique`方法,用于自动获取`list`中指定名称的键值或属性值,构成`slice`返回。
|
||||
- 增加`ItemValue`方法,用于获取指定`map/*map/struct/*struct`类型的键值/属性值。
|
||||
- 增加`MapOmitEmpty`方法,用于过滤`map`中的空值。
|
||||
- 增加`SliceDelete`方法,用于数组项删除。
|
||||
- 增加`Try`方法,通过闭包执行给定的方法,如果方法产生`panic`则该方法返回`error`,否则返回`nil`。
|
||||
- 改进`TryCatch(try func(), catch ...func(exception interface{}))`为`TryCatch(try func(), catch ...func(exception error))`
|
||||
|
||||
1. `gvalid`
|
||||
- 增加自定义规则特性,开发者可注册自定义的校验规则:https://goframe.org/util/gvalid/customrule
|
||||
- 其他一些功能改进。
|
||||
|
||||
## `error`
|
||||
1. `gerror`
|
||||
- 新增`Current`方法,用于获取当前错误层级的`error`接口对象。
|
||||
- 新增`Next`方法,用于获取层级错误的下一级错误`error`接口对象。当下一层级不存在时,返回`nil`。
|
||||
- 文档更新:https://goframe.org/errors/gerror/index
|
||||
|
||||
## Bug Fix
|
||||
1. 修复`garray`模块的`Unique`方法问题。
|
||||
1. 修复`glog`中定时器懒初始化时的`goroutine`泄露问题。
|
||||
1. 修复`gstr`中名称`Case`转换相关方法在名称中带有数字+特殊字符时的名称转换问题。
|
||||
1. 修复`ghttp`模块中的`CORS`跨域设置`Header`细节问题。
|
||||
1. 其他BUG修复:https://github.com/gogf/gf/issues?q=is%3Aissue+label%3Abug+is%3Aclosed
|
||||
|
||||
|
||||
# `v1.13.1` (2020-06-10)
|
||||
|
||||
# GoFrame
|
||||
|
||||
@ -446,7 +446,7 @@ func (a *SortedArray) binSearch(value interface{}, lock bool) (index int, result
|
||||
mid := 0
|
||||
cmp := -2
|
||||
for min <= max {
|
||||
mid = (min + max) / 2
|
||||
mid = min + int((max-min)/2)
|
||||
cmp = a.getComparator()(value, a.array[mid])
|
||||
switch {
|
||||
case cmp < 0:
|
||||
|
||||
@ -443,7 +443,7 @@ func (a *SortedIntArray) binSearch(value int, lock bool) (index int, result int)
|
||||
mid := 0
|
||||
cmp := -2
|
||||
for min <= max {
|
||||
mid = (min + max) / 2
|
||||
mid = min + int((max-min)/2)
|
||||
cmp = a.getComparator()(value, a.array[mid])
|
||||
switch {
|
||||
case cmp < 0:
|
||||
|
||||
@ -445,7 +445,7 @@ func (a *SortedStrArray) binSearch(value string, lock bool) (index int, result i
|
||||
mid := 0
|
||||
cmp := -2
|
||||
for min <= max {
|
||||
mid = (min + max) / 2
|
||||
mid = min + int((max-min)/2)
|
||||
cmp = a.getComparator()(value, a.array[mid])
|
||||
switch {
|
||||
case cmp < 0:
|
||||
|
||||
@ -457,6 +457,12 @@ func (m *AnyAnyMap) Merge(other *AnyAnyMap) {
|
||||
}
|
||||
}
|
||||
|
||||
// String returns the map as a string.
|
||||
func (m *AnyAnyMap) String() string {
|
||||
b, _ := m.MarshalJSON()
|
||||
return gconv.UnsafeBytesToStr(b)
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (m *AnyAnyMap) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(gconv.Map(m.Map()))
|
||||
|
||||
@ -455,6 +455,12 @@ func (m *IntAnyMap) Merge(other *IntAnyMap) {
|
||||
}
|
||||
}
|
||||
|
||||
// String returns the map as a string.
|
||||
func (m *IntAnyMap) String() string {
|
||||
b, _ := m.MarshalJSON()
|
||||
return gconv.UnsafeBytesToStr(b)
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (m *IntAnyMap) MarshalJSON() ([]byte, error) {
|
||||
m.mu.RLock()
|
||||
|
||||
@ -426,6 +426,12 @@ func (m *IntIntMap) Merge(other *IntIntMap) {
|
||||
}
|
||||
}
|
||||
|
||||
// String returns the map as a string.
|
||||
func (m *IntIntMap) String() string {
|
||||
b, _ := m.MarshalJSON()
|
||||
return gconv.UnsafeBytesToStr(b)
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (m *IntIntMap) MarshalJSON() ([]byte, error) {
|
||||
m.mu.RLock()
|
||||
|
||||
@ -426,6 +426,12 @@ func (m *IntStrMap) Merge(other *IntStrMap) {
|
||||
}
|
||||
}
|
||||
|
||||
// String returns the map as a string.
|
||||
func (m *IntStrMap) String() string {
|
||||
b, _ := m.MarshalJSON()
|
||||
return gconv.UnsafeBytesToStr(b)
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (m *IntStrMap) MarshalJSON() ([]byte, error) {
|
||||
m.mu.RLock()
|
||||
|
||||
@ -451,6 +451,12 @@ func (m *StrAnyMap) Merge(other *StrAnyMap) {
|
||||
}
|
||||
}
|
||||
|
||||
// String returns the map as a string.
|
||||
func (m *StrAnyMap) String() string {
|
||||
b, _ := m.MarshalJSON()
|
||||
return gconv.UnsafeBytesToStr(b)
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (m *StrAnyMap) MarshalJSON() ([]byte, error) {
|
||||
m.mu.RLock()
|
||||
|
||||
@ -429,6 +429,12 @@ func (m *StrIntMap) Merge(other *StrIntMap) {
|
||||
}
|
||||
}
|
||||
|
||||
// String returns the map as a string.
|
||||
func (m *StrIntMap) String() string {
|
||||
b, _ := m.MarshalJSON()
|
||||
return gconv.UnsafeBytesToStr(b)
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (m *StrIntMap) MarshalJSON() ([]byte, error) {
|
||||
m.mu.RLock()
|
||||
|
||||
@ -429,6 +429,12 @@ func (m *StrStrMap) Merge(other *StrStrMap) {
|
||||
}
|
||||
}
|
||||
|
||||
// String returns the map as a string.
|
||||
func (m *StrStrMap) String() string {
|
||||
b, _ := m.MarshalJSON()
|
||||
return gconv.UnsafeBytesToStr(b)
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (m *StrStrMap) MarshalJSON() ([]byte, error) {
|
||||
m.mu.RLock()
|
||||
|
||||
@ -510,6 +510,12 @@ func (m *ListMap) Merge(other *ListMap) {
|
||||
})
|
||||
}
|
||||
|
||||
// String returns the map as a string.
|
||||
func (m *ListMap) String() string {
|
||||
b, _ := m.MarshalJSON()
|
||||
return gconv.UnsafeBytesToStr(b)
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (m *ListMap) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(gconv.Map(m.Map()))
|
||||
|
||||
@ -620,7 +620,7 @@ func (tree *BTree) middle() int {
|
||||
func (tree *BTree) search(node *BTreeNode, key interface{}) (index int, found bool) {
|
||||
low, mid, high := 0, 0, len(node.Entries)-1
|
||||
for low <= high {
|
||||
mid = (high + low) / 2
|
||||
mid = low + int((high-low)/2)
|
||||
compare := tree.getComparator()(key, node.Entries[mid].Key)
|
||||
switch {
|
||||
case compare > 0:
|
||||
|
||||
@ -20,6 +20,7 @@ func (v *Var) Struct(pointer interface{}, mapping ...map[string]string) error {
|
||||
// Struct maps value of <v> to <pointer> recursively.
|
||||
// The parameter <pointer> should be a pointer to a struct instance.
|
||||
// The parameter <mapping> is used to specify the key-to-attribute mapping rules.
|
||||
// Deprecated, use Struct instead.
|
||||
func (v *Var) StructDeep(pointer interface{}, mapping ...map[string]string) error {
|
||||
return gconv.StructDeep(v.Val(), pointer, mapping...)
|
||||
}
|
||||
@ -30,6 +31,7 @@ func (v *Var) Structs(pointer interface{}, mapping ...map[string]string) error {
|
||||
}
|
||||
|
||||
// StructsDeep converts and returns <v> as given struct slice recursively.
|
||||
// Deprecated, use Struct instead.
|
||||
func (v *Var) StructsDeep(pointer interface{}, mapping ...map[string]string) error {
|
||||
return gconv.StructsDeep(v.Val(), pointer, mapping...)
|
||||
}
|
||||
|
||||
@ -11,9 +11,11 @@ import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/internal/cmdenv"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/container/gvar"
|
||||
"github.com/gogf/gf/internal/intlog"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/os/glog"
|
||||
|
||||
@ -126,6 +128,7 @@ type DB interface {
|
||||
GetDryRun() bool
|
||||
SetLogger(logger *glog.Logger)
|
||||
GetLogger() *glog.Logger
|
||||
GetConfig() *ConfigNode
|
||||
SetMaxIdleConnCount(n int)
|
||||
SetMaxOpenConnCount(n int)
|
||||
SetMaxConnLifetime(d time.Duration)
|
||||
@ -154,7 +157,7 @@ type DB interface {
|
||||
// Internal methods.
|
||||
// ===========================================================================
|
||||
|
||||
filterFields(schema, table string, data map[string]interface{}) map[string]interface{}
|
||||
mappingAndFilterData(schema, table string, data map[string]interface{}, filter bool) (map[string]interface{}, error)
|
||||
convertValue(fieldValue interface{}, fieldType string) interface{}
|
||||
rowsToResult(rows *sql.Rows) (Result, error)
|
||||
}
|
||||
@ -164,11 +167,12 @@ type Core struct {
|
||||
DB DB // DB interface object.
|
||||
group string // Configuration group name.
|
||||
debug *gtype.Bool // Enable debug mode for the database.
|
||||
cache *gcache.Cache // Cache manager.
|
||||
cache *gcache.Cache // Cache manager, SQL result cache only.
|
||||
schema *gtype.String // Custom schema for this object.
|
||||
dryrun *gtype.Bool // Dry run.
|
||||
prefix string // Table prefix.
|
||||
logger *glog.Logger // Logger.
|
||||
config *ConfigNode // Current config node.
|
||||
maxIdleConnCount int // Max idle connection count.
|
||||
maxOpenConnCount int // Max open connection count.
|
||||
maxConnLifetime time.Duration // Max TTL for a connection.
|
||||
@ -188,6 +192,7 @@ type Sql struct {
|
||||
Error error // Execution result.
|
||||
Start int64 // Start execution timestamp in milliseconds.
|
||||
End int64 // End execution timestamp in milliseconds.
|
||||
Group string // Group is the group name of the configuration that the sql is executed from.
|
||||
}
|
||||
|
||||
// TableField is the struct for table field.
|
||||
@ -228,13 +233,14 @@ type (
|
||||
)
|
||||
|
||||
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_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.
|
||||
insertOptionDefault = 0
|
||||
insertOptionReplace = 1
|
||||
insertOptionSave = 2
|
||||
insertOptionIgnore = 3
|
||||
defaultBatchNumber = 10 // Per count for batch insert/replace/save.
|
||||
defaultMaxIdleConnCount = 10 // Max idle connection count in pool.
|
||||
defaultMaxOpenConnCount = 100 // Max open connection count in pool.
|
||||
defaultMaxConnLifeTime = 30 // Max life time for per connection in pool in seconds.
|
||||
)
|
||||
|
||||
var (
|
||||
@ -260,8 +266,20 @@ var (
|
||||
// regularFieldNameRegPattern is the regular expression pattern for a string
|
||||
// which is a regular field name of table.
|
||||
regularFieldNameRegPattern = `^[\w\.\-]+$`
|
||||
|
||||
// internalCache is the memory cache for internal usage.
|
||||
internalCache = gcache.New()
|
||||
|
||||
// allDryRun sets dry-run feature for all database connections.
|
||||
// It is commonly used for command options for convenience.
|
||||
allDryRun = false
|
||||
)
|
||||
|
||||
func init() {
|
||||
// allDryRun is initialized from environment or command options.
|
||||
allDryRun = cmdenv.Get("gf.gdb.dryrun", false).Bool()
|
||||
}
|
||||
|
||||
// Register registers custom database driver to gdb.
|
||||
func Register(name string, driver Driver) error {
|
||||
driverMap[name] = driver
|
||||
@ -271,10 +289,10 @@ func Register(name string, driver Driver) error {
|
||||
// New creates and returns an ORM object with global configurations.
|
||||
// The parameter <name> specifies the configuration group name,
|
||||
// which is DEFAULT_GROUP_NAME in default.
|
||||
func New(name ...string) (db DB, err error) {
|
||||
group := configs.group
|
||||
if len(name) > 0 && name[0] != "" {
|
||||
group = name[0]
|
||||
func New(group ...string) (db DB, err error) {
|
||||
groupName := configs.group
|
||||
if len(group) > 0 && group[0] != "" {
|
||||
groupName = group[0]
|
||||
}
|
||||
configs.RLock()
|
||||
defer configs.RUnlock()
|
||||
@ -282,18 +300,20 @@ func New(name ...string) (db DB, err error) {
|
||||
if len(configs.config) < 1 {
|
||||
return nil, errors.New("empty database configuration")
|
||||
}
|
||||
if _, ok := configs.config[group]; ok {
|
||||
if node, err := getConfigNodeByGroup(group, true); err == nil {
|
||||
if _, ok := configs.config[groupName]; ok {
|
||||
if node, err := getConfigNodeByGroup(groupName, true); err == nil {
|
||||
c := &Core{
|
||||
group: group,
|
||||
group: groupName,
|
||||
debug: gtype.NewBool(),
|
||||
cache: gcache.New(),
|
||||
schema: gtype.NewString(),
|
||||
dryrun: gtype.NewBool(),
|
||||
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.
|
||||
config: node,
|
||||
maxIdleConnCount: defaultMaxIdleConnCount,
|
||||
maxOpenConnCount: defaultMaxOpenConnCount,
|
||||
maxConnLifetime: defaultMaxConnLifeTime, // Default max connection life time if user does not configure.
|
||||
}
|
||||
if v, ok := driverMap[node.Type]; ok {
|
||||
c.DB, err = v.New(c, node)
|
||||
@ -308,7 +328,7 @@ func New(name ...string) (db DB, err error) {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
return nil, errors.New(fmt.Sprintf(`database configuration node "%s" is not found`, group))
|
||||
return nil, errors.New(fmt.Sprintf(`database configuration node "%s" is not found`, groupName))
|
||||
}
|
||||
}
|
||||
|
||||
@ -426,11 +446,11 @@ func (c *Core) getSqlDb(master bool, schema ...string) (sqlDb *sql.DB, err error
|
||||
node = &n
|
||||
}
|
||||
// Cache the underlying connection pool object by node.
|
||||
v := c.cache.GetOrSetFuncLock(node.String(), func() interface{} {
|
||||
v, _ := internalCache.GetOrSetFuncLock(node.String(), func() (interface{}, error) {
|
||||
sqlDb, err = c.DB.Open(node)
|
||||
if err != nil {
|
||||
intlog.Printf("DB open failed: %v, %+v", err, node)
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
if c.maxIdleConnCount > 0 {
|
||||
sqlDb.SetMaxIdleConns(c.maxIdleConnCount)
|
||||
@ -449,7 +469,7 @@ func (c *Core) getSqlDb(master bool, schema ...string) (sqlDb *sql.DB, err error
|
||||
} else if node.MaxConnLifetime > 0 {
|
||||
sqlDb.SetConnMaxLifetime(node.MaxConnLifetime * time.Second)
|
||||
}
|
||||
return sqlDb
|
||||
return sqlDb, nil
|
||||
}, 0)
|
||||
if v != nil && sqlDb == nil {
|
||||
sqlDb = v.(*sql.DB)
|
||||
|
||||
@ -11,10 +11,12 @@ import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/internal/utils"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/internal/utils"
|
||||
|
||||
"github.com/gogf/gf/container/gvar"
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
"github.com/gogf/gf/text/gregex"
|
||||
@ -59,6 +61,7 @@ func (c *Core) DoQuery(link Link, sql string, args ...interface{}) (rows *sql.Ro
|
||||
Error: err,
|
||||
Start: mTime1,
|
||||
End: mTime2,
|
||||
Group: c.DB.GetGroup(),
|
||||
}
|
||||
c.writeSqlToLogger(s)
|
||||
} else {
|
||||
@ -102,6 +105,7 @@ func (c *Core) DoExec(link Link, sql string, args ...interface{}) (result sql.Re
|
||||
Error: err,
|
||||
Start: mTime1,
|
||||
End: mTime2,
|
||||
Group: c.DB.GetGroup(),
|
||||
}
|
||||
c.writeSqlToLogger(s)
|
||||
} else {
|
||||
@ -307,6 +311,11 @@ func (c *Core) Transaction(f func(tx *TX) error) (err error) {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err == nil {
|
||||
if e := recover(); e != nil {
|
||||
err = fmt.Errorf("%v", e)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
if e := tx.Rollback(); e != nil {
|
||||
err = e
|
||||
@ -331,7 +340,10 @@ func (c *Core) Transaction(f func(tx *TX) error) (err error) {
|
||||
//
|
||||
// The parameter <batch> specifies the batch operation count when given data is slice.
|
||||
func (c *Core) Insert(table string, data interface{}, batch ...int) (sql.Result, error) {
|
||||
return c.DB.DoInsert(nil, table, data, gINSERT_OPTION_DEFAULT, batch...)
|
||||
if len(batch) > 0 {
|
||||
return c.Model(table).Data(data).Batch(batch[0]).Insert()
|
||||
}
|
||||
return c.Model(table).Data(data).Insert()
|
||||
}
|
||||
|
||||
// InsertIgnore does "INSERT IGNORE INTO ..." statement for the table.
|
||||
@ -344,7 +356,10 @@ func (c *Core) Insert(table string, data interface{}, batch ...int) (sql.Result,
|
||||
//
|
||||
// The parameter <batch> specifies the batch operation count when given data is slice.
|
||||
func (c *Core) InsertIgnore(table string, data interface{}, batch ...int) (sql.Result, error) {
|
||||
return c.DB.DoInsert(nil, table, data, gINSERT_OPTION_IGNORE, batch...)
|
||||
if len(batch) > 0 {
|
||||
return c.Model(table).Data(data).Batch(batch[0]).InsertIgnore()
|
||||
}
|
||||
return c.Model(table).Data(data).InsertIgnore()
|
||||
}
|
||||
|
||||
// Replace does "REPLACE INTO ..." statement for the table.
|
||||
@ -360,7 +375,10 @@ func (c *Core) InsertIgnore(table string, data interface{}, batch ...int) (sql.R
|
||||
// If given data is type of slice, it then does batch replacing, and the optional parameter
|
||||
// <batch> specifies the batch operation count.
|
||||
func (c *Core) Replace(table string, data interface{}, batch ...int) (sql.Result, error) {
|
||||
return c.DB.DoInsert(nil, table, data, gINSERT_OPTION_REPLACE, batch...)
|
||||
if len(batch) > 0 {
|
||||
return c.Model(table).Data(data).Batch(batch[0]).Replace()
|
||||
}
|
||||
return c.Model(table).Data(data).Replace()
|
||||
}
|
||||
|
||||
// Save does "INSERT INTO ... ON DUPLICATE KEY UPDATE..." statement for the table.
|
||||
@ -375,11 +393,14 @@ func (c *Core) Replace(table string, data interface{}, batch ...int) (sql.Result
|
||||
// If given data is type of slice, it then does batch saving, and the optional parameter
|
||||
// <batch> specifies the batch operation count.
|
||||
func (c *Core) Save(table string, data interface{}, batch ...int) (sql.Result, error) {
|
||||
return c.DB.DoInsert(nil, table, data, gINSERT_OPTION_SAVE, batch...)
|
||||
if len(batch) > 0 {
|
||||
return c.Model(table).Data(data).Batch(batch[0]).Save()
|
||||
}
|
||||
return c.Model(table).Data(data).Save()
|
||||
}
|
||||
|
||||
// doInsert inserts or updates data for given table.
|
||||
//
|
||||
// This function is usually used for custom interface definition, you do not need call it manually.
|
||||
// The parameter <data> can be type of map/gmap/struct/*struct/[]map/[]struct, etc.
|
||||
// Eg:
|
||||
// Data(g.Map{"uid": 10000, "name":"john"})
|
||||
@ -407,8 +428,14 @@ func (c *Core) DoInsert(link Link, table string, data interface{}, option int, b
|
||||
switch reflectKind {
|
||||
case reflect.Slice, reflect.Array:
|
||||
return c.DB.DoBatchInsert(link, table, data, option, batch...)
|
||||
case reflect.Map, reflect.Struct:
|
||||
dataMap = DataToMapDeep(data)
|
||||
case reflect.Struct:
|
||||
if _, ok := data.(apiInterfaces); ok {
|
||||
return c.DB.DoBatchInsert(link, table, data, option, batch...)
|
||||
} else {
|
||||
dataMap = ConvertDataForTableRecord(data)
|
||||
}
|
||||
case reflect.Map:
|
||||
dataMap = ConvertDataForTableRecord(data)
|
||||
default:
|
||||
return result, errors.New(fmt.Sprint("unsupported data type:", reflectKind))
|
||||
}
|
||||
@ -425,11 +452,11 @@ func (c *Core) DoInsert(link Link, table string, data interface{}, option int, b
|
||||
values = append(values, "?")
|
||||
params = append(params, v)
|
||||
}
|
||||
if option == gINSERT_OPTION_SAVE {
|
||||
if option == insertOptionSave {
|
||||
for k, _ := range dataMap {
|
||||
// If it's SAVE operation,
|
||||
// do not automatically update the creating time.
|
||||
if utils.EqualFoldWithoutChars(k, gSOFT_FIELD_NAME_CREATE) {
|
||||
if c.isSoftCreatedFiledName(k) {
|
||||
continue
|
||||
}
|
||||
if len(updateStr) > 0 {
|
||||
@ -462,35 +489,48 @@ func (c *Core) DoInsert(link Link, table string, data interface{}, option int, b
|
||||
// BatchInsert batch inserts data.
|
||||
// The parameter <list> must be type of slice of map or struct.
|
||||
func (c *Core) BatchInsert(table string, list interface{}, batch ...int) (sql.Result, error) {
|
||||
return c.DB.DoBatchInsert(nil, table, list, gINSERT_OPTION_DEFAULT, batch...)
|
||||
if len(batch) > 0 {
|
||||
return c.Model(table).Data(list).Batch(batch[0]).Insert()
|
||||
}
|
||||
return c.Model(table).Data(list).Insert()
|
||||
}
|
||||
|
||||
// BatchInsert batch inserts data with ignore option.
|
||||
// BatchInsertIgnore batch inserts data with ignore option.
|
||||
// The parameter <list> must be type of slice of map or struct.
|
||||
func (c *Core) BatchInsertIgnore(table string, list interface{}, batch ...int) (sql.Result, error) {
|
||||
return c.DB.DoBatchInsert(nil, table, list, gINSERT_OPTION_IGNORE, batch...)
|
||||
if len(batch) > 0 {
|
||||
return c.Model(table).Data(list).Batch(batch[0]).InsertIgnore()
|
||||
}
|
||||
return c.Model(table).Data(list).InsertIgnore()
|
||||
}
|
||||
|
||||
// BatchReplace batch replaces data.
|
||||
// The parameter <list> must be type of slice of map or struct.
|
||||
func (c *Core) BatchReplace(table string, list interface{}, batch ...int) (sql.Result, error) {
|
||||
return c.DB.DoBatchInsert(nil, table, list, gINSERT_OPTION_REPLACE, batch...)
|
||||
if len(batch) > 0 {
|
||||
return c.Model(table).Data(list).Batch(batch[0]).Replace()
|
||||
}
|
||||
return c.Model(table).Data(list).Replace()
|
||||
}
|
||||
|
||||
// BatchSave batch replaces data.
|
||||
// The parameter <list> must be type of slice of map or struct.
|
||||
func (c *Core) BatchSave(table string, list interface{}, batch ...int) (sql.Result, error) {
|
||||
return c.DB.DoBatchInsert(nil, table, list, gINSERT_OPTION_SAVE, batch...)
|
||||
if len(batch) > 0 {
|
||||
return c.Model(table).Data(list).Batch(batch[0]).Save()
|
||||
}
|
||||
return c.Model(table).Data(list).Save()
|
||||
}
|
||||
|
||||
// DoBatchInsert batch inserts/replaces/saves data.
|
||||
// This function is usually used for custom interface definition, you do not need call it manually.
|
||||
func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option int, batch ...int) (result sql.Result, err error) {
|
||||
table = c.DB.QuotePrefixTableName(table)
|
||||
var (
|
||||
keys []string
|
||||
values []string
|
||||
params []interface{}
|
||||
listMap List
|
||||
keys []string // Field names.
|
||||
values []string // Value holder string array, like: (?,?,?)
|
||||
params []interface{} // Values that will be committed to underlying database driver.
|
||||
listMap List // The data list that passed from caller.
|
||||
)
|
||||
switch value := list.(type) {
|
||||
case Result:
|
||||
@ -515,10 +555,10 @@ func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option i
|
||||
case reflect.Slice, reflect.Array:
|
||||
listMap = make(List, rv.Len())
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
listMap[i] = DataToMapDeep(rv.Index(i).Interface())
|
||||
listMap[i] = ConvertDataForTableRecord(rv.Index(i).Interface())
|
||||
}
|
||||
case reflect.Map:
|
||||
listMap = List{DataToMapDeep(value)}
|
||||
listMap = List{ConvertDataForTableRecord(value)}
|
||||
case reflect.Struct:
|
||||
if v, ok := value.(apiInterfaces); ok {
|
||||
var (
|
||||
@ -526,11 +566,11 @@ func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option i
|
||||
list = make(List, len(array))
|
||||
)
|
||||
for i := 0; i < len(array); i++ {
|
||||
list[i] = DataToMapDeep(array[i])
|
||||
list[i] = ConvertDataForTableRecord(array[i])
|
||||
}
|
||||
listMap = list
|
||||
} else {
|
||||
listMap = List{DataToMapDeep(value)}
|
||||
listMap = List{ConvertDataForTableRecord(value)}
|
||||
}
|
||||
default:
|
||||
return result, errors.New(fmt.Sprint("unsupported list type:", kind))
|
||||
@ -559,11 +599,11 @@ func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option i
|
||||
operation = GetInsertOperationByOption(option)
|
||||
updateStr = ""
|
||||
)
|
||||
if option == gINSERT_OPTION_SAVE {
|
||||
if option == insertOptionSave {
|
||||
for _, k := range keys {
|
||||
// If it's SAVE operation,
|
||||
// do not automatically update the creating time.
|
||||
if utils.EqualFoldWithoutChars(k, gSOFT_FIELD_NAME_CREATE) {
|
||||
if c.isSoftCreatedFiledName(k) {
|
||||
continue
|
||||
}
|
||||
if len(updateStr) > 0 {
|
||||
@ -577,7 +617,7 @@ func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option i
|
||||
}
|
||||
updateStr = fmt.Sprintf("ON DUPLICATE KEY UPDATE %s", updateStr)
|
||||
}
|
||||
batchNum := gDEFAULT_BATCH_NUM
|
||||
batchNum := defaultBatchNumber
|
||||
if len(batch) > 0 && batch[0] > 0 {
|
||||
batchNum = batch[0]
|
||||
}
|
||||
@ -633,15 +673,11 @@ func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option i
|
||||
// "age IN(?,?)", 18, 50
|
||||
// User{ Id : 1, UserName : "john"}
|
||||
func (c *Core) Update(table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error) {
|
||||
newWhere, newArgs := formatWhere(c.DB, condition, args, false)
|
||||
if newWhere != "" {
|
||||
newWhere = " WHERE " + newWhere
|
||||
}
|
||||
return c.DB.DoUpdate(nil, table, data, newWhere, newArgs...)
|
||||
return c.Model(table).Data(data).Where(condition, args...).Update()
|
||||
}
|
||||
|
||||
// doUpdate does "UPDATE ... " statement for the table.
|
||||
// Also see Update.
|
||||
// This function is usually used for custom interface definition, you do not need call it manually.
|
||||
func (c *Core) DoUpdate(link Link, table string, data interface{}, condition string, args ...interface{}) (result sql.Result, err error) {
|
||||
table = c.DB.QuotePrefixTableName(table)
|
||||
var (
|
||||
@ -660,7 +696,7 @@ func (c *Core) DoUpdate(link Link, table string, data interface{}, condition str
|
||||
case reflect.Map, reflect.Struct:
|
||||
var (
|
||||
fields []string
|
||||
dataMap = DataToMapDeep(data)
|
||||
dataMap = ConvertDataForTableRecord(data)
|
||||
)
|
||||
for k, v := range dataMap {
|
||||
fields = append(fields, c.DB.QuoteWord(k)+"=?")
|
||||
@ -701,15 +737,11 @@ func (c *Core) DoUpdate(link Link, table string, data interface{}, condition str
|
||||
// "age IN(?,?)", 18, 50
|
||||
// User{ Id : 1, UserName : "john"}
|
||||
func (c *Core) Delete(table string, condition interface{}, args ...interface{}) (result sql.Result, err error) {
|
||||
newWhere, newArgs := formatWhere(c.DB, condition, args, false)
|
||||
if newWhere != "" {
|
||||
newWhere = " WHERE " + newWhere
|
||||
}
|
||||
return c.DB.DoDelete(nil, table, newWhere, newArgs...)
|
||||
return c.Model(table).Where(condition, args...).Delete()
|
||||
}
|
||||
|
||||
// DoDelete does "DELETE FROM ... " statement for the table.
|
||||
// Also see Delete.
|
||||
// This function is usually used for custom interface definition, you do not need call it manually.
|
||||
func (c *Core) DoDelete(link Link, table string, condition string, args ...interface{}) (result sql.Result, err error) {
|
||||
if link == nil {
|
||||
if link, err = c.DB.Master(); err != nil {
|
||||
@ -776,7 +808,7 @@ func (c *Core) MarshalJSON() ([]byte, error) {
|
||||
// writeSqlToLogger outputs the sql object to logger.
|
||||
// It is enabled when configuration "debug" is true.
|
||||
func (c *Core) writeSqlToLogger(v *Sql) {
|
||||
s := fmt.Sprintf("[%3d ms] %s", v.End-v.Start, v.Format)
|
||||
s := fmt.Sprintf("[%3d ms] [%s] %s", v.End-v.Start, v.Group, v.Format)
|
||||
if v.Error != nil {
|
||||
s += "\nError: " + v.Error.Error()
|
||||
c.logger.Error(s)
|
||||
@ -798,3 +830,22 @@ func (c *Core) HasTable(name string) (bool, error) {
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// isSoftCreatedFiledName checks and returns whether given filed name is an automatic-filled created time.
|
||||
func (c *Core) isSoftCreatedFiledName(fieldName string) bool {
|
||||
if fieldName == "" {
|
||||
return false
|
||||
}
|
||||
if config := c.DB.GetConfig(); config.CreatedAt != "" {
|
||||
if utils.EqualFoldWithoutChars(fieldName, config.CreatedAt) {
|
||||
return true
|
||||
}
|
||||
return gstr.InArray(append([]string{config.CreatedAt}, createdFiledNames...), fieldName)
|
||||
}
|
||||
for _, v := range createdFiledNames {
|
||||
if utils.EqualFoldWithoutChars(fieldName, v) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@ -39,6 +39,9 @@ type ConfigNode struct {
|
||||
DryRun bool // (Optional) Dry run, which does SELECT but no INSERT/UPDATE/DELETE statements.
|
||||
Weight int // (Optional) Weight for load balance calculating, it's useless if there's just one node.
|
||||
Charset string // (Optional, "utf8mb4" in default) Custom charset when operating on database.
|
||||
CreatedAt string // (Optional) The filed name of table for automatic-filled created datetime.
|
||||
UpdatedAt string // (Optional) The filed name of table for automatic-filled updated datetime.
|
||||
DeletedAt string // (Optional) The filed name of table for automatic-filled updated datetime.
|
||||
LinkInfo string `json:"link"` // (Optional) Custom link information, when it is used, configuration Host/Port/User/Pass/Name are ignored.
|
||||
MaxIdleConnCount int `json:"maxidle"` // (Optional) Max idle connection configuration for underlying connection pool.
|
||||
MaxOpenConnCount int `json:"maxopen"` // (Optional) Max open connection configuration for underlying connection pool.
|
||||
@ -115,6 +118,14 @@ func GetDefaultGroup() string {
|
||||
return configs.group
|
||||
}
|
||||
|
||||
// IsConfigured checks and returns whether the database configured.
|
||||
// It returns true if any configuration exists.
|
||||
func IsConfigured() bool {
|
||||
configs.RLock()
|
||||
defer configs.RUnlock()
|
||||
return len(configs.config) > 0
|
||||
}
|
||||
|
||||
// SetLogger sets the logger for orm.
|
||||
func (c *Core) SetLogger(logger *glog.Logger) {
|
||||
c.logger = logger
|
||||
@ -186,6 +197,10 @@ func (c *Core) SetDryRun(dryrun bool) {
|
||||
|
||||
// GetDryRun returns the DryRun value.
|
||||
func (c *Core) GetDryRun() bool {
|
||||
if allDryRun {
|
||||
// Globally set.
|
||||
return true
|
||||
}
|
||||
return c.dryrun.Val()
|
||||
}
|
||||
|
||||
@ -200,3 +215,8 @@ func (c *Core) SetSchema(schema string) {
|
||||
func (c *Core) GetSchema() string {
|
||||
return c.schema.Val()
|
||||
}
|
||||
|
||||
// GetConfig returns the current used node configuration.
|
||||
func (c *Core) GetConfig() *ConfigNode {
|
||||
return c.config
|
||||
}
|
||||
|
||||
@ -7,7 +7,10 @@
|
||||
package gdb
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/errors/gerror"
|
||||
"github.com/gogf/gf/util/gutil"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
|
||||
@ -88,12 +91,18 @@ func (c *Core) convertValue(fieldValue interface{}, fieldType string) interface{
|
||||
return gconv.Bool(fieldValue)
|
||||
|
||||
case "date":
|
||||
if t, ok := fieldValue.(time.Time); ok {
|
||||
return gtime.NewFromTime(t).Format("Y-m-d")
|
||||
}
|
||||
t, _ := gtime.StrToTime(gconv.String(fieldValue))
|
||||
return t.Format("Y-m-d")
|
||||
|
||||
case
|
||||
"datetime",
|
||||
"timestamp":
|
||||
if t, ok := fieldValue.(time.Time); ok {
|
||||
return gtime.NewFromTime(t)
|
||||
}
|
||||
t, _ := gtime.StrToTime(gconv.String(fieldValue))
|
||||
return t.String()
|
||||
|
||||
@ -138,15 +147,50 @@ func (c *Core) convertValue(fieldValue interface{}, fieldType string) interface{
|
||||
}
|
||||
|
||||
// filterFields removes all key-value pairs which are not the field of given table.
|
||||
func (c *Core) filterFields(schema, table string, data map[string]interface{}) map[string]interface{} {
|
||||
// It must use data copy here to avoid its changing the origin data map.
|
||||
newDataMap := make(map[string]interface{}, len(data))
|
||||
if fields, err := c.DB.TableFields(table, schema); err == nil {
|
||||
for k, v := range data {
|
||||
if _, ok := fields[k]; ok {
|
||||
newDataMap[k] = v
|
||||
func (c *Core) mappingAndFilterData(schema, table string, data map[string]interface{}, filter bool) (map[string]interface{}, error) {
|
||||
if fieldsMap, err := c.DB.TableFields(table, schema); err == nil {
|
||||
fieldsKeyMap := make(map[string]interface{}, len(fieldsMap))
|
||||
for k, _ := range fieldsMap {
|
||||
fieldsKeyMap[k] = nil
|
||||
}
|
||||
// Automatic data key to table field name mapping.
|
||||
var foundKey string
|
||||
for dataKey, dataValue := range data {
|
||||
if _, ok := fieldsKeyMap[dataKey]; !ok {
|
||||
foundKey, _ = gutil.MapPossibleItemByKey(fieldsKeyMap, dataKey)
|
||||
if foundKey != "" {
|
||||
data[foundKey] = dataValue
|
||||
delete(data, dataKey)
|
||||
} else if !filter {
|
||||
if schema != "" {
|
||||
return nil, gerror.Newf(`no column of name "%s" found for table "%s" in schema "%s"`, dataKey, table, schema)
|
||||
}
|
||||
return nil, gerror.Newf(`no column of name "%s" found for table "%s"`, dataKey, table)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Data filtering.
|
||||
if filter {
|
||||
for dataKey, _ := range data {
|
||||
if _, ok := fieldsMap[dataKey]; !ok {
|
||||
delete(data, dataKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return newDataMap
|
||||
return data, nil
|
||||
}
|
||||
|
||||
//// filterFields removes all key-value pairs which are not the field of given table.
|
||||
//func (c *Core) filterFields(schema, table string, data map[string]interface{}) map[string]interface{} {
|
||||
// // It must use data copy here to avoid its changing the origin data map.
|
||||
// newDataMap := make(map[string]interface{}, len(data))
|
||||
// if fields, err := c.DB.TableFields(table, schema); err == nil {
|
||||
// for k, v := range data {
|
||||
// if _, ok := fields[k]; ok {
|
||||
// newDataMap[k] = v
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// return newDataMap
|
||||
//}
|
||||
@ -198,47 +198,58 @@ func (d *DriverMssql) TableFields(table string, schema ...string) (fields map[st
|
||||
if len(schema) > 0 && schema[0] != "" {
|
||||
checkSchema = schema[0]
|
||||
}
|
||||
v := d.DB.GetCache().GetOrSetFunc(
|
||||
fmt.Sprintf(`mssql_table_fields_%s_%s`, table, checkSchema), func() interface{} {
|
||||
var result Result
|
||||
var link *sql.DB
|
||||
v, _ := internalCache.GetOrSetFunc(
|
||||
fmt.Sprintf(`mssql_table_fields_%s_%s`, table, checkSchema),
|
||||
func() (interface{}, error) {
|
||||
var (
|
||||
result Result
|
||||
link *sql.DB
|
||||
)
|
||||
link, err = d.DB.GetSlave(checkSchema)
|
||||
if err != nil {
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
result, err = d.DB.DoGetAll(link, fmt.Sprintf(`
|
||||
SELECT a.name Field,
|
||||
structureSql := fmt.Sprintf(`
|
||||
SELECT
|
||||
a.name Field,
|
||||
CASE b.name
|
||||
WHEN 'datetime' THEN 'datetime'
|
||||
WHEN 'numeric' THEN b.name + '(' + convert(varchar(20),a.xprec) + ',' + convert(varchar(20),a.xscale) + ')'
|
||||
WHEN 'char' THEN b.name + '(' + convert(varchar(20),a.length)+ ')'
|
||||
WHEN 'varchar' THEN b.name + '(' + convert(varchar(20),a.length)+ ')'
|
||||
ELSE b.name + '(' + convert(varchar(20),a.length)+ ')' END as TYPE,
|
||||
case when a.isnullable=1 then 'YES'else 'NO' end as [Null],
|
||||
case when exists(SELECT 1 FROM sysobjects where xtype='PK' and name in (
|
||||
SELECT name FROM sysindexes WHERE indid in(
|
||||
SELECT indid FROM sysindexkeys WHERE id = a.id AND colid=a.colid
|
||||
))) then 'PRI' else '' end AS [Key],
|
||||
case when COLUMNPROPERTY(a.id,a.name,'IsIdentity')=1 then 'auto_increment'else '' end Extra,
|
||||
isnull(e.text,'') as [Default],
|
||||
WHEN 'numeric' THEN b.name + '(' + convert(varchar(20), a.xprec) + ',' + convert(varchar(20), a.xscale) + ')'
|
||||
WHEN 'char' THEN b.name + '(' + convert(varchar(20), a.length)+ ')'
|
||||
WHEN 'varchar' THEN b.name + '(' + convert(varchar(20), a.length)+ ')'
|
||||
ELSE b.name + '(' + convert(varchar(20),a.length)+ ')' END AS Type,
|
||||
CASE WHEN a.isnullable=1 THEN 'YES' ELSE 'NO' end AS [Null],
|
||||
CASE WHEN exists (
|
||||
SELECT 1 FROM sysobjects WHERE xtype='PK' AND name IN (
|
||||
SELECT name FROM sysindexes WHERE indid IN (
|
||||
SELECT indid FROM sysindexkeys WHERE id = a.id AND colid=a.colid
|
||||
)
|
||||
)
|
||||
) THEN 'PRI' ELSE '' END AS [Key],
|
||||
CASE WHEN COLUMNPROPERTY(a.id,a.name,'IsIdentity')=1 THEN 'auto_increment' ELSE '' END Extra,
|
||||
isnull(e.text,'') AS [Default],
|
||||
isnull(g.[value],'') AS [Comment]
|
||||
FROM syscolumns a
|
||||
left join systypes b on a.xtype=b.xusertype
|
||||
inner join sysobjects d on a.id=d.id and d.xtype='U' and d.name<>'dtproperties'
|
||||
left join syscomments e on a.cdefault=e.id
|
||||
left join sys.extended_properties g on a.id=g.major_id and a.colid=g.minor_id
|
||||
left join sys.extended_properties f on d.id=f.major_id and f.minor_id =0
|
||||
where d.name='%s'
|
||||
order by a.id,a.colorder`, strings.ToUpper(table)))
|
||||
FROM syscolumns a
|
||||
LEFT JOIN systypes b ON a.xtype=b.xtype AND a.xusertype=b.xusertype
|
||||
INNER JOIN sysobjects d ON a.id=d.id AND d.xtype='U' AND d.name<>'dtproperties'
|
||||
LEFT JOIN syscomments e ON a.cdefault=e.id
|
||||
LEFT JOIN sys.extended_properties g ON a.id=g.major_id AND a.colid=g.minor_id
|
||||
LEFT JOIN sys.extended_properties f ON d.id=f.major_id AND f.minor_id =0
|
||||
WHERE d.name='%s'
|
||||
ORDER BY a.id,a.colorder`,
|
||||
strings.ToUpper(table),
|
||||
)
|
||||
structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql))
|
||||
result, err = d.DB.DoGetAll(link, structureSql)
|
||||
if err != nil {
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
fields = make(map[string]*TableField)
|
||||
for i, m := range result {
|
||||
fields[strings.ToLower(m["FIELD"].String())] = &TableField{
|
||||
fields[strings.ToLower(m["Field"].String())] = &TableField{
|
||||
Index: i,
|
||||
Name: strings.ToLower(m["FIELD"].String()),
|
||||
Type: strings.ToLower(m["TYPE"].String()),
|
||||
Name: strings.ToLower(m["Field"].String()),
|
||||
Type: strings.ToLower(m["Type"].String()),
|
||||
Null: m["Null"].Bool(),
|
||||
Key: m["Key"].String(),
|
||||
Default: m["Default"].Val(),
|
||||
@ -246,7 +257,7 @@ func (d *DriverMssql) TableFields(table string, schema ...string) (fields map[st
|
||||
Comment: m["Comment"].String(),
|
||||
}
|
||||
}
|
||||
return fields
|
||||
return fields, nil
|
||||
}, 0)
|
||||
if err == nil {
|
||||
fields = v.(map[string]*TableField)
|
||||
|
||||
@ -102,21 +102,23 @@ func (d *DriverMysql) TableFields(table string, schema ...string) (fields map[st
|
||||
if len(schema) > 0 && schema[0] != "" {
|
||||
checkSchema = schema[0]
|
||||
}
|
||||
v := d.cache.GetOrSetFunc(
|
||||
v, _ := internalCache.GetOrSetFunc(
|
||||
fmt.Sprintf(`mysql_table_fields_%s_%s`, table, checkSchema),
|
||||
func() interface{} {
|
||||
var result Result
|
||||
var link *sql.DB
|
||||
func() (interface{}, error) {
|
||||
var (
|
||||
result Result
|
||||
link *sql.DB
|
||||
)
|
||||
link, err = d.DB.GetSlave(checkSchema)
|
||||
if err != nil {
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
result, err = d.DB.DoGetAll(
|
||||
link,
|
||||
fmt.Sprintf(`SHOW FULL COLUMNS FROM %s`, d.DB.QuoteWord(table)),
|
||||
)
|
||||
if err != nil {
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
fields = make(map[string]*TableField)
|
||||
for i, m := range result {
|
||||
@ -131,7 +133,7 @@ func (d *DriverMysql) TableFields(table string, schema ...string) (fields map[st
|
||||
Comment: m["Comment"].String(),
|
||||
}
|
||||
}
|
||||
return fields
|
||||
return fields, nil
|
||||
}, 0)
|
||||
if err == nil {
|
||||
fields = v.(map[string]*TableField)
|
||||
|
||||
@ -78,44 +78,45 @@ func (d *DriverOracle) HandleSqlBeforeCommit(link Link, sql string, args []inter
|
||||
// parseSql does some replacement of the sql before commits it to underlying driver,
|
||||
// for support of oracle server.
|
||||
func (d *DriverOracle) parseSql(sql string) string {
|
||||
patten := `^\s*(?i)(SELECT)|(LIMIT\s*(\d+)\s*,\s*(\d+))`
|
||||
if gregex.IsMatchString(patten, sql) == false {
|
||||
var (
|
||||
patten = `^\s*(?i)(SELECT)|(LIMIT\s*(\d+)\s*,{0,1}\s*(\d*))`
|
||||
allMatch, _ = gregex.MatchAllString(patten, sql)
|
||||
)
|
||||
if len(allMatch) == 0 {
|
||||
return sql
|
||||
}
|
||||
|
||||
res, err := gregex.MatchAllString(patten, sql)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
var (
|
||||
index = 0
|
||||
keyword = strings.ToUpper(strings.TrimSpace(res[index][0]))
|
||||
keyword = strings.ToUpper(strings.TrimSpace(allMatch[index][0]))
|
||||
)
|
||||
index++
|
||||
switch keyword {
|
||||
case "SELECT":
|
||||
if len(res) < 2 || (strings.HasPrefix(res[index][0], "LIMIT") == false &&
|
||||
strings.HasPrefix(res[index][0], "limit") == false) {
|
||||
if len(allMatch) < 2 || strings.HasPrefix(allMatch[index][0], "LIMIT") == false {
|
||||
break
|
||||
}
|
||||
if gregex.IsMatchString("((?i)SELECT)(.+)((?i)LIMIT)", sql) == false {
|
||||
break
|
||||
}
|
||||
queryExpr, _ := gregex.MatchString("((?i)SELECT)(.+)((?i)LIMIT)", sql)
|
||||
if len(queryExpr) != 4 || strings.EqualFold(queryExpr[1], "SELECT") == false ||
|
||||
if len(queryExpr) != 4 ||
|
||||
strings.EqualFold(queryExpr[1], "SELECT") == false ||
|
||||
strings.EqualFold(queryExpr[3], "LIMIT") == false {
|
||||
break
|
||||
}
|
||||
first, limit := 0, 0
|
||||
for i := 1; i < len(res[index]); i++ {
|
||||
if len(strings.TrimSpace(res[index][i])) == 0 {
|
||||
for i := 1; i < len(allMatch[index]); i++ {
|
||||
if len(strings.TrimSpace(allMatch[index][i])) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(res[index][i], "LIMIT") || strings.HasPrefix(res[index][i], "limit") {
|
||||
first, _ = strconv.Atoi(res[index][i+1])
|
||||
limit, _ = strconv.Atoi(res[index][i+2])
|
||||
if strings.HasPrefix(allMatch[index][i], "LIMIT") {
|
||||
if allMatch[index][i+2] != "" {
|
||||
first, _ = strconv.Atoi(allMatch[index][i+1])
|
||||
limit, _ = strconv.Atoi(allMatch[index][i+2])
|
||||
} else {
|
||||
limit, _ = strconv.Atoi(allMatch[index][i+1])
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -157,18 +158,24 @@ func (d *DriverOracle) TableFields(table string, schema ...string) (fields map[s
|
||||
if len(schema) > 0 && schema[0] != "" {
|
||||
checkSchema = schema[0]
|
||||
}
|
||||
v := d.DB.GetCache().GetOrSetFunc(
|
||||
v, _ := internalCache.GetOrSetFunc(
|
||||
fmt.Sprintf(`oracle_table_fields_%s_%s`, table, checkSchema),
|
||||
func() interface{} {
|
||||
func() (interface{}, error) {
|
||||
result := (Result)(nil)
|
||||
result, err = d.DB.GetAll(fmt.Sprintf(`
|
||||
SELECT COLUMN_NAME AS FIELD, CASE DATA_TYPE
|
||||
WHEN 'NUMBER' THEN DATA_TYPE||'('||DATA_PRECISION||','||DATA_SCALE||')'
|
||||
WHEN 'FLOAT' THEN DATA_TYPE||'('||DATA_PRECISION||','||DATA_SCALE||')'
|
||||
ELSE DATA_TYPE||'('||DATA_LENGTH||')' END AS TYPE
|
||||
FROM USER_TAB_COLUMNS WHERE TABLE_NAME = '%s' ORDER BY COLUMN_ID`, strings.ToUpper(table)))
|
||||
structureSql := fmt.Sprintf(`
|
||||
SELECT
|
||||
COLUMN_NAME AS FIELD,
|
||||
CASE DATA_TYPE
|
||||
WHEN 'NUMBER' THEN DATA_TYPE||'('||DATA_PRECISION||','||DATA_SCALE||')'
|
||||
WHEN 'FLOAT' THEN DATA_TYPE||'('||DATA_PRECISION||','||DATA_SCALE||')'
|
||||
ELSE DATA_TYPE||'('||DATA_LENGTH||')' END AS TYPE
|
||||
FROM USER_TAB_COLUMNS WHERE TABLE_NAME = '%s' ORDER BY COLUMN_ID`,
|
||||
strings.ToUpper(table),
|
||||
)
|
||||
structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql))
|
||||
result, err = d.DB.GetAll(structureSql)
|
||||
if err != nil {
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
fields = make(map[string]*TableField)
|
||||
for i, m := range result {
|
||||
@ -178,7 +185,7 @@ func (d *DriverOracle) TableFields(table string, schema ...string) (fields map[s
|
||||
Type: strings.ToLower(m["TYPE"].String()),
|
||||
}
|
||||
}
|
||||
return fields
|
||||
return fields, nil
|
||||
}, 0)
|
||||
if err == nil {
|
||||
fields = v.(map[string]*TableField)
|
||||
@ -188,24 +195,26 @@ func (d *DriverOracle) TableFields(table string, schema ...string) (fields map[s
|
||||
|
||||
func (d *DriverOracle) getTableUniqueIndex(table string) (fields map[string]map[string]string, err error) {
|
||||
table = strings.ToUpper(table)
|
||||
v := d.DB.GetCache().GetOrSetFunc("table_unique_index_"+table, func() interface{} {
|
||||
res := (Result)(nil)
|
||||
res, err = d.DB.GetAll(fmt.Sprintf(`
|
||||
v, _ := internalCache.GetOrSetFunc(
|
||||
"table_unique_index_"+table,
|
||||
func() (interface{}, error) {
|
||||
res := (Result)(nil)
|
||||
res, err = d.DB.GetAll(fmt.Sprintf(`
|
||||
SELECT INDEX_NAME,COLUMN_NAME,CHAR_LENGTH FROM USER_IND_COLUMNS
|
||||
WHERE TABLE_NAME = '%s'
|
||||
AND INDEX_NAME IN(SELECT INDEX_NAME FROM USER_INDEXES WHERE TABLE_NAME='%s' AND UNIQUENESS='UNIQUE')
|
||||
ORDER BY INDEX_NAME,COLUMN_POSITION`, table, table))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
fields := make(map[string]map[string]string)
|
||||
for _, v := range res {
|
||||
mm := make(map[string]string)
|
||||
mm[v["COLUMN_NAME"].String()] = v["CHAR_LENGTH"].String()
|
||||
fields[v["INDEX_NAME"].String()] = mm
|
||||
}
|
||||
return fields
|
||||
}, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fields := make(map[string]map[string]string)
|
||||
for _, v := range res {
|
||||
mm := make(map[string]string)
|
||||
mm[v["COLUMN_NAME"].String()] = v["CHAR_LENGTH"].String()
|
||||
fields[v["INDEX_NAME"].String()] = mm
|
||||
}
|
||||
return fields, nil
|
||||
}, 0)
|
||||
if err == nil {
|
||||
fields = v.(map[string]map[string]string)
|
||||
}
|
||||
@ -231,7 +240,7 @@ func (d *DriverOracle) DoInsert(link Link, table string, data interface{}, optio
|
||||
case reflect.Map:
|
||||
fallthrough
|
||||
case reflect.Struct:
|
||||
dataMap = DataToMapDeep(data)
|
||||
dataMap = ConvertDataForTableRecord(data)
|
||||
default:
|
||||
return result, errors.New(fmt.Sprint("unsupported data type:", kind))
|
||||
}
|
||||
@ -239,7 +248,7 @@ func (d *DriverOracle) DoInsert(link Link, table string, data interface{}, optio
|
||||
indexs := make([]string, 0)
|
||||
indexMap := make(map[string]string)
|
||||
indexExists := false
|
||||
if option != gINSERT_OPTION_DEFAULT {
|
||||
if option != insertOptionDefault {
|
||||
index, err := d.getTableUniqueIndex(table)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -267,7 +276,7 @@ func (d *DriverOracle) DoInsert(link Link, table string, data interface{}, optio
|
||||
k = strings.ToUpper(k)
|
||||
|
||||
// 操作类型为REPLACE/SAVE时且存在唯一索引才使用merge,否则使用insert
|
||||
if (option == gINSERT_OPTION_REPLACE || option == gINSERT_OPTION_SAVE) && indexExists {
|
||||
if (option == insertOptionReplace || option == insertOptionSave) && indexExists {
|
||||
fields = append(fields, tableAlias1+"."+charL+k+charR)
|
||||
values = append(values, tableAlias2+"."+charL+k+charR)
|
||||
params = append(params, v)
|
||||
@ -293,18 +302,18 @@ func (d *DriverOracle) DoInsert(link Link, table string, data interface{}, optio
|
||||
}
|
||||
}
|
||||
|
||||
if indexExists && option != gINSERT_OPTION_DEFAULT {
|
||||
if indexExists && option != insertOptionDefault {
|
||||
switch option {
|
||||
case gINSERT_OPTION_REPLACE:
|
||||
case insertOptionReplace:
|
||||
fallthrough
|
||||
case gINSERT_OPTION_SAVE:
|
||||
case insertOptionSave:
|
||||
tmp := fmt.Sprintf(
|
||||
"MERGE INTO %s %s USING(SELECT %s FROM DUAL) %s ON(%s) WHEN MATCHED THEN UPDATE SET %s WHEN NOT MATCHED THEN INSERT (%s) VALUES(%s)",
|
||||
table, tableAlias1, strings.Join(subSqlStr, ","), tableAlias2,
|
||||
strings.Join(onStr, "AND"), strings.Join(updateStr, ","), strings.Join(fields, ","), strings.Join(values, ","),
|
||||
)
|
||||
return d.DB.DoExec(link, tmp, params...)
|
||||
case gINSERT_OPTION_IGNORE:
|
||||
case insertOptionIgnore:
|
||||
return d.DB.DoExec(link,
|
||||
fmt.Sprintf(
|
||||
"INSERT /*+ IGNORE_ROW_ON_DUPKEY_INDEX(%s(%s)) */ INTO %s(%s) VALUES(%s)",
|
||||
@ -351,12 +360,12 @@ func (d *DriverOracle) DoBatchInsert(link Link, table string, list interface{},
|
||||
case reflect.Array:
|
||||
listMap = make(List, rv.Len())
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
listMap[i] = DataToMapDeep(rv.Index(i).Interface())
|
||||
listMap[i] = ConvertDataForTableRecord(rv.Index(i).Interface())
|
||||
}
|
||||
case reflect.Map:
|
||||
fallthrough
|
||||
case reflect.Struct:
|
||||
listMap = List{Map(DataToMapDeep(list))}
|
||||
listMap = List{Map(ConvertDataForTableRecord(list))}
|
||||
default:
|
||||
return result, errors.New(fmt.Sprint("unsupported list type:", kind))
|
||||
}
|
||||
@ -382,7 +391,7 @@ func (d *DriverOracle) DoBatchInsert(link Link, table string, list interface{},
|
||||
valueHolderStr := strings.Join(holders, ",")
|
||||
|
||||
// 当操作类型非insert时调用单笔的insert功能
|
||||
if option != gINSERT_OPTION_DEFAULT {
|
||||
if option != insertOptionDefault {
|
||||
for _, v := range listMap {
|
||||
r, err := d.DB.DoInsert(link, table, v, option, 1)
|
||||
if err != nil {
|
||||
@ -400,7 +409,7 @@ func (d *DriverOracle) DoBatchInsert(link Link, table string, list interface{},
|
||||
}
|
||||
|
||||
// 构造批量写入数据格式(注意map的遍历是无序的)
|
||||
batchNum := gDEFAULT_BATCH_NUM
|
||||
batchNum := defaultBatchNumber
|
||||
if len(batch) > 0 {
|
||||
batchNum = batch[0]
|
||||
}
|
||||
|
||||
@ -107,21 +107,28 @@ func (d *DriverPgsql) TableFields(table string, schema ...string) (fields map[st
|
||||
if len(schema) > 0 && schema[0] != "" {
|
||||
checkSchema = schema[0]
|
||||
}
|
||||
v := d.DB.GetCache().GetOrSetFunc(
|
||||
fmt.Sprintf(`pgsql_table_fields_%s_%s`, table, checkSchema), func() interface{} {
|
||||
var result Result
|
||||
var link *sql.DB
|
||||
v, _ := internalCache.GetOrSetFunc(
|
||||
fmt.Sprintf(`pgsql_table_fields_%s_%s`, table, checkSchema),
|
||||
func() (interface{}, error) {
|
||||
var (
|
||||
result Result
|
||||
link *sql.DB
|
||||
)
|
||||
link, err = d.DB.GetSlave(checkSchema)
|
||||
if err != nil {
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
result, err = d.DB.DoGetAll(link, fmt.Sprintf(`
|
||||
SELECT a.attname AS field, t.typname AS type FROM pg_class c, pg_attribute a
|
||||
LEFT OUTER JOIN pg_description b ON a.attrelid=b.objoid AND a.attnum = b.objsubid,pg_type t
|
||||
WHERE c.relname = '%s' and a.attnum > 0 and a.attrelid = c.oid and a.atttypid = t.oid
|
||||
ORDER BY a.attnum`, strings.ToLower(table)))
|
||||
structureSql := fmt.Sprintf(`
|
||||
SELECT a.attname AS field, t.typname AS type FROM pg_class c, pg_attribute a
|
||||
LEFT OUTER JOIN pg_description b ON a.attrelid=b.objoid AND a.attnum = b.objsubid,pg_type t
|
||||
WHERE c.relname = '%s' and a.attnum > 0 and a.attrelid = c.oid and a.atttypid = t.oid
|
||||
ORDER BY a.attnum`,
|
||||
strings.ToLower(table),
|
||||
)
|
||||
structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql))
|
||||
result, err = d.DB.DoGetAll(link, structureSql)
|
||||
if err != nil {
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fields = make(map[string]*TableField)
|
||||
@ -132,7 +139,7 @@ func (d *DriverPgsql) TableFields(table string, schema ...string) (fields map[st
|
||||
Type: m["type"].String(),
|
||||
}
|
||||
}
|
||||
return fields
|
||||
return fields, nil
|
||||
}, 0)
|
||||
if err == nil {
|
||||
fields = v.(map[string]*TableField)
|
||||
|
||||
@ -98,17 +98,20 @@ func (d *DriverSqlite) TableFields(table string, schema ...string) (fields map[s
|
||||
if len(schema) > 0 && schema[0] != "" {
|
||||
checkSchema = schema[0]
|
||||
}
|
||||
v := d.DB.GetCache().GetOrSetFunc(
|
||||
fmt.Sprintf(`sqlite_table_fields_%s_%s`, table, checkSchema), func() interface{} {
|
||||
var result Result
|
||||
var link *sql.DB
|
||||
v, _ := internalCache.GetOrSetFunc(
|
||||
fmt.Sprintf(`sqlite_table_fields_%s_%s`, table, checkSchema),
|
||||
func() (interface{}, error) {
|
||||
var (
|
||||
result Result
|
||||
link *sql.DB
|
||||
)
|
||||
link, err = d.DB.GetSlave(checkSchema)
|
||||
if err != nil {
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
result, err = d.DB.DoGetAll(link, fmt.Sprintf(`PRAGMA TABLE_INFO(%s)`, table))
|
||||
if err != nil {
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
fields = make(map[string]*TableField)
|
||||
for i, m := range result {
|
||||
@ -118,7 +121,7 @@ func (d *DriverSqlite) TableFields(table string, schema ...string) (fields map[s
|
||||
Type: strings.ToLower(m["type"].String()),
|
||||
}
|
||||
}
|
||||
return fields
|
||||
return fields, nil
|
||||
}, 0)
|
||||
if err == nil {
|
||||
fields = v.(map[string]*TableField)
|
||||
|
||||
@ -11,6 +11,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/internal/empty"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"github.com/gogf/gf/internal/utils"
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
"github.com/gogf/gf/util/gutil"
|
||||
@ -87,9 +88,9 @@ func ListItemValuesUnique(list interface{}, key string, subKey ...interface{}) [
|
||||
func GetInsertOperationByOption(option int) string {
|
||||
var operator string
|
||||
switch option {
|
||||
case gINSERT_OPTION_REPLACE:
|
||||
case insertOptionReplace:
|
||||
operator = "REPLACE"
|
||||
case gINSERT_OPTION_IGNORE:
|
||||
case insertOptionIgnore:
|
||||
operator = "INSERT IGNORE"
|
||||
default:
|
||||
operator = "INSERT"
|
||||
@ -97,14 +98,56 @@ func GetInsertOperationByOption(option int) string {
|
||||
return operator
|
||||
}
|
||||
|
||||
// DataToMapDeep converts struct object to map type recursively.
|
||||
// ConvertDataForTableRecord is a very important function, which does converting for any data that
|
||||
// will be inserted into table as a record.
|
||||
//
|
||||
// The parameter <obj> should be type of *map/map/*struct/struct.
|
||||
// It supports inherit struct definition for struct.
|
||||
func ConvertDataForTableRecord(value interface{}) map[string]interface{} {
|
||||
var (
|
||||
rvValue reflect.Value
|
||||
rvKind reflect.Kind
|
||||
data = DataToMapDeep(value)
|
||||
)
|
||||
for k, v := range data {
|
||||
rvValue = reflect.ValueOf(v)
|
||||
rvKind = rvValue.Kind()
|
||||
for rvKind == reflect.Ptr {
|
||||
rvValue = rvValue.Elem()
|
||||
rvKind = rvValue.Kind()
|
||||
}
|
||||
switch rvKind {
|
||||
case reflect.Slice, reflect.Array, reflect.Map:
|
||||
// It should ignore the bytes type.
|
||||
if _, ok := v.([]byte); !ok {
|
||||
// Convert the value to JSON.
|
||||
data[k], _ = json.Marshal(v)
|
||||
}
|
||||
case reflect.Struct:
|
||||
switch v.(type) {
|
||||
case time.Time, *time.Time, gtime.Time, *gtime.Time:
|
||||
continue
|
||||
default:
|
||||
// Use string conversion in default.
|
||||
if s, ok := v.(apiString); ok {
|
||||
data[k] = s.String()
|
||||
} else {
|
||||
// Convert the value to JSON.
|
||||
data[k], _ = json.Marshal(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// DataToMapDeep converts <value> to map type recursively.
|
||||
// The parameter <value> should be type of *map/map/*struct/struct.
|
||||
// It supports inherit struct definition for struct.
|
||||
func DataToMapDeep(value interface{}) map[string]interface{} {
|
||||
if v, ok := value.(apiMapStrAny); ok {
|
||||
return v.MapStrAny()
|
||||
}
|
||||
|
||||
var (
|
||||
rvValue reflect.Value
|
||||
rvField reflect.Value
|
||||
@ -182,7 +225,7 @@ func DataToMapDeep(value interface{}) map[string]interface{} {
|
||||
// The underlying driver supports time.Time/*time.Time types.
|
||||
fieldValue := rvField.Interface()
|
||||
switch fieldValue.(type) {
|
||||
case time.Time, *time.Time:
|
||||
case time.Time, *time.Time, gtime.Time, *gtime.Time:
|
||||
data[name] = fieldValue
|
||||
default:
|
||||
// Use string conversion in default.
|
||||
@ -269,32 +312,40 @@ func doQuoteString(s, charLeft, charRight string) string {
|
||||
|
||||
// GetWhereConditionOfStruct returns the where condition sql and arguments by given struct pointer.
|
||||
// This function automatically retrieves primary or unique field and its attribute value as condition.
|
||||
func GetWhereConditionOfStruct(pointer interface{}) (where string, args []interface{}) {
|
||||
func GetWhereConditionOfStruct(pointer interface{}) (where string, args []interface{}, err error) {
|
||||
tagField, err := structs.TagFields(pointer, []string{ORM_TAG_FOR_STRUCT})
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
array := ([]string)(nil)
|
||||
for _, field := range structs.TagFields(pointer, []string{ORM_TAG_FOR_STRUCT}, true) {
|
||||
array = strings.Split(field.Tag, ",")
|
||||
for _, field := range tagField {
|
||||
array = strings.Split(field.TagValue, ",")
|
||||
if len(array) > 1 && gstr.InArray([]string{ORM_TAG_FOR_UNIQUE, ORM_TAG_FOR_PRIMARY}, array[1]) {
|
||||
return array[0], []interface{}{field.Value()}
|
||||
return array[0], []interface{}{field.Value()}, nil
|
||||
}
|
||||
if len(where) > 0 {
|
||||
where += " "
|
||||
}
|
||||
where += field.Tag + "=?"
|
||||
where += field.TagValue + "=?"
|
||||
args = append(args, field.Value())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetPrimaryKey retrieves and returns primary key field name from given struct.
|
||||
func GetPrimaryKey(pointer interface{}) string {
|
||||
func GetPrimaryKey(pointer interface{}) (string, error) {
|
||||
tagField, err := structs.TagFields(pointer, []string{ORM_TAG_FOR_STRUCT})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
array := ([]string)(nil)
|
||||
for _, field := range structs.TagFields(pointer, []string{ORM_TAG_FOR_STRUCT}, true) {
|
||||
array = strings.Split(field.Tag, ",")
|
||||
for _, field := range tagField {
|
||||
array = strings.Split(field.TagValue, ",")
|
||||
if len(array) > 1 && array[1] == ORM_TAG_FOR_PRIMARY {
|
||||
return array[0]
|
||||
return array[0], nil
|
||||
}
|
||||
}
|
||||
return ""
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// GetPrimaryKeyCondition returns a new where condition by primary field name.
|
||||
@ -317,8 +368,10 @@ func GetPrimaryKeyCondition(primary string, where ...interface{}) (newWhereCondi
|
||||
return where
|
||||
}
|
||||
if len(where) == 1 {
|
||||
rv := reflect.ValueOf(where[0])
|
||||
kind := rv.Kind()
|
||||
var (
|
||||
rv = reflect.ValueOf(where[0])
|
||||
kind = rv.Kind()
|
||||
)
|
||||
if kind == reflect.Ptr {
|
||||
rv = rv.Elem()
|
||||
kind = rv.Kind()
|
||||
@ -341,14 +394,14 @@ func GetPrimaryKeyCondition(primary string, where ...interface{}) (newWhereCondi
|
||||
// The internal handleArguments function might be called twice during the SQL procedure,
|
||||
// but do not worry about it, it's safe and efficient.
|
||||
func formatSql(sql string, args []interface{}) (newSql string, newArgs []interface{}) {
|
||||
sql = gstr.Trim(sql)
|
||||
sql = gstr.Replace(sql, "\n", " ")
|
||||
sql, _ = gregex.ReplaceString(`\s{2,}`, ` `, sql)
|
||||
// DO NOT do this as there may be multiple lines and comments in the sql.
|
||||
// sql = gstr.Trim(sql)
|
||||
// sql = gstr.Replace(sql, "\n", " ")
|
||||
// sql, _ = gregex.ReplaceString(`\s{2,}`, ` `, sql)
|
||||
return handleArguments(sql, args)
|
||||
}
|
||||
|
||||
// formatWhere formats where statement and its arguments.
|
||||
// TODO []interface{} type support for parameter <where> does not completed yet.
|
||||
func formatWhere(db DB, where interface{}, args []interface{}, omitEmpty bool) (newWhere string, newArgs []interface{}) {
|
||||
var (
|
||||
buffer = bytes.NewBuffer(nil)
|
||||
@ -430,21 +483,23 @@ func formatWhere(db DB, where interface{}, args []interface{}, omitEmpty bool) (
|
||||
}
|
||||
|
||||
// formatWhereInterfaces formats <where> as []interface{}.
|
||||
// TODO supporting for parameter <where> with []interface{} type is not completed yet.
|
||||
func formatWhereInterfaces(db DB, where []interface{}, buffer *bytes.Buffer, newArgs []interface{}) []interface{} {
|
||||
if len(where) == 0 {
|
||||
return newArgs
|
||||
}
|
||||
if len(where)%2 != 0 {
|
||||
buffer.WriteString(gstr.Join(gconv.Strings(where), ""))
|
||||
return newArgs
|
||||
}
|
||||
var str string
|
||||
var array []interface{}
|
||||
var holderCount int
|
||||
for i := 0; i < len(where); {
|
||||
if holderCount > 0 {
|
||||
array = gconv.Interfaces(where[i])
|
||||
newArgs = append(newArgs, array...)
|
||||
holderCount -= len(array)
|
||||
for i := 0; i < len(where); i += 2 {
|
||||
str = gconv.String(where[i])
|
||||
if buffer.Len() > 0 {
|
||||
buffer.WriteString(" AND " + db.QuoteWord(str) + "=?")
|
||||
} else {
|
||||
str = gconv.String(where[i])
|
||||
holderCount = gstr.Count(str, "?")
|
||||
buffer.WriteString(str)
|
||||
buffer.WriteString(db.QuoteWord(str) + "=?")
|
||||
}
|
||||
newArgs = append(newArgs, where[i+1])
|
||||
}
|
||||
return newArgs
|
||||
}
|
||||
@ -675,10 +730,14 @@ func FormatSqlWithArgs(sql string, args []interface{}) string {
|
||||
// mapToStruct maps the <data> to given struct.
|
||||
// Note that the given parameter <pointer> should be a pointer to s struct.
|
||||
func mapToStruct(data map[string]interface{}, pointer interface{}) error {
|
||||
tagNameMap, err := structs.TagMapName(pointer, []string{ORM_TAG_FOR_STRUCT})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// It retrieves and returns the mapping between orm tag and the struct attribute name.
|
||||
mapping := make(map[string]string)
|
||||
for tag, attr := range structs.TagMapName(pointer, []string{ORM_TAG_FOR_STRUCT}, true) {
|
||||
for tag, attr := range tagNameMap {
|
||||
mapping[strings.Split(tag, ",")[0]] = attr
|
||||
}
|
||||
return gconv.StructDeep(data, pointer, mapping)
|
||||
return gconv.Struct(data, pointer, mapping)
|
||||
}
|
||||
|
||||
@ -199,3 +199,10 @@ func (m *Model) Safe(safe ...bool) *Model {
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// Args sets custom arguments for model operation.
|
||||
func (m *Model) Args(args ...interface{}) *Model {
|
||||
model := m.getModel()
|
||||
model.extraArgs = append(model.extraArgs, args)
|
||||
return model
|
||||
}
|
||||
|
||||
@ -140,7 +140,7 @@ func (m *Model) Offset(offset int) *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.
|
||||
// Note that, it differs that the Limit function starts from 0 for "LIMIT" statement.
|
||||
func (m *Model) Page(page, limit int) *Model {
|
||||
model := m.getModel()
|
||||
if page <= 0 {
|
||||
|
||||
@ -9,16 +9,11 @@ package gdb
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/errors/gerror"
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
)
|
||||
|
||||
// Unscoped disables the soft deleting feature.
|
||||
func (m *Model) Unscoped() *Model {
|
||||
model := m.getModel()
|
||||
model.unscoped = true
|
||||
return model
|
||||
}
|
||||
|
||||
// Delete does "DELETE FROM ... " statement for the model.
|
||||
// The optional parameter <where> is the same as the parameter of Model.Where function,
|
||||
// see Model.Where.
|
||||
@ -32,8 +27,8 @@ func (m *Model) Delete(where ...interface{}) (result sql.Result, err error) {
|
||||
}
|
||||
}()
|
||||
var (
|
||||
fieldNameDelete = m.getSoftFieldNameDelete()
|
||||
conditionWhere, conditionExtra, conditionArgs = m.formatCondition(false)
|
||||
fieldNameDelete = m.getSoftFieldNameDeleted()
|
||||
conditionWhere, conditionExtra, conditionArgs = m.formatCondition(false, false)
|
||||
)
|
||||
// Soft deleting.
|
||||
if !m.unscoped && fieldNameDelete != "" {
|
||||
@ -45,5 +40,9 @@ func (m *Model) Delete(where ...interface{}) (result sql.Result, err error) {
|
||||
append([]interface{}{gtime.Now().String()}, conditionArgs...),
|
||||
)
|
||||
}
|
||||
return m.db.DoDelete(m.getLink(true), m.tables, conditionWhere+conditionExtra, conditionArgs...)
|
||||
conditionStr := conditionWhere + conditionExtra
|
||||
if !gstr.ContainsI(conditionStr, " WHERE ") {
|
||||
return nil, gerror.New("there should be WHERE condition statement for DELETE operation")
|
||||
}
|
||||
return m.db.DoDelete(m.getLink(true), m.tables, conditionStr, conditionArgs...)
|
||||
}
|
||||
|
||||
@ -10,6 +10,8 @@ import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/container/gset"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"github.com/gogf/gf/util/gutil"
|
||||
)
|
||||
|
||||
// Filter marks filtering the fields which does not exist in the fields of the operated table.
|
||||
@ -24,49 +26,66 @@ func (m *Model) Filter() *Model {
|
||||
}
|
||||
|
||||
// Fields sets the operation fields of the model, multiple fields joined using char ','.
|
||||
func (m *Model) Fields(fields string) *Model {
|
||||
model := m.getModel()
|
||||
model.fields = fields
|
||||
return model
|
||||
// The parameter <fieldNamesOrMapStruct> can be type of string/map/*map/struct/*struct.
|
||||
func (m *Model) Fields(fieldNamesOrMapStruct ...interface{}) *Model {
|
||||
length := len(fieldNamesOrMapStruct)
|
||||
if length == 0 {
|
||||
return m
|
||||
}
|
||||
switch {
|
||||
case length >= 2:
|
||||
model := m.getModel()
|
||||
model.fields = gstr.Join(m.mappingToTableFields(gconv.Strings(fieldNamesOrMapStruct)), ",")
|
||||
return model
|
||||
case length == 1:
|
||||
model := m.getModel()
|
||||
switch r := fieldNamesOrMapStruct[0].(type) {
|
||||
case string:
|
||||
model.fields = gstr.Join(m.mappingToTableFields([]string{r}), ",")
|
||||
case []string:
|
||||
model.fields = gstr.Join(m.mappingToTableFields(r), ",")
|
||||
default:
|
||||
model.fields = gstr.Join(m.mappingToTableFields(gutil.Keys(r)), ",")
|
||||
}
|
||||
return model
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// FieldsEx sets the excluded operation fields of the model, multiple fields joined using char ','.
|
||||
// Note that this function supports only single table operations.
|
||||
func (m *Model) FieldsEx(fields string) *Model {
|
||||
if gstr.Contains(m.tables, " ") {
|
||||
panic("function FieldsEx supports only single table operations")
|
||||
}
|
||||
tableFields, err := m.db.TableFields(m.tables)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if len(tableFields) == 0 {
|
||||
panic(fmt.Sprintf(`empty table fields for table "%s"`, m.tables))
|
||||
// The parameter <fieldNamesOrMapStruct> can be type of string/map/*map/struct/*struct.
|
||||
func (m *Model) FieldsEx(fieldNamesOrMapStruct ...interface{}) *Model {
|
||||
length := len(fieldNamesOrMapStruct)
|
||||
if length == 0 {
|
||||
return m
|
||||
}
|
||||
model := m.getModel()
|
||||
model.fieldsEx = fields
|
||||
fieldsExSet := gset.NewStrSetFrom(gstr.SplitAndTrim(fields, ","))
|
||||
fieldsArray := make([]string, len(tableFields))
|
||||
for k, v := range tableFields {
|
||||
fieldsArray[v.Index] = k
|
||||
}
|
||||
model.fields = ""
|
||||
for _, k := range fieldsArray {
|
||||
if fieldsExSet.Contains(k) {
|
||||
continue
|
||||
switch {
|
||||
case length >= 2:
|
||||
model.fieldsEx = gstr.Join(m.mappingToTableFields(gconv.Strings(fieldNamesOrMapStruct)), ",")
|
||||
return model
|
||||
case length == 1:
|
||||
switch r := fieldNamesOrMapStruct[0].(type) {
|
||||
case string:
|
||||
model.fieldsEx = gstr.Join(m.mappingToTableFields([]string{r}), ",")
|
||||
default:
|
||||
model.fieldsEx = gstr.Join(m.mappingToTableFields(gutil.Keys(r)), ",")
|
||||
}
|
||||
if len(model.fields) > 0 {
|
||||
model.fields += ","
|
||||
}
|
||||
model.fields += k
|
||||
return model
|
||||
}
|
||||
model.fields = model.db.QuoteString(model.fields)
|
||||
return model
|
||||
return m
|
||||
}
|
||||
|
||||
// Deprecated, use GetFieldsStr instead.
|
||||
// This function name confuses the user that it was a chaining function.
|
||||
func (m *Model) FieldsStr(prefix ...string) string {
|
||||
return m.GetFieldsStr(prefix...)
|
||||
}
|
||||
|
||||
// FieldsStr retrieves and returns all fields from the table, joined with char ','.
|
||||
// The optional parameter <prefix> specifies the prefix for each field, eg: FieldsStr("u.").
|
||||
func (m *Model) FieldsStr(prefix ...string) string {
|
||||
func (m *Model) GetFieldsStr(prefix ...string) string {
|
||||
prefixStr := ""
|
||||
if len(prefix) > 0 {
|
||||
prefixStr = prefix[0]
|
||||
@ -93,11 +112,17 @@ func (m *Model) FieldsStr(prefix ...string) string {
|
||||
return newFields
|
||||
}
|
||||
|
||||
// Deprecated, use GetFieldsExStr instead.
|
||||
// This function name confuses the user that it was a chaining function.
|
||||
func (m *Model) FieldsExStr(fields string, prefix ...string) string {
|
||||
return m.GetFieldsExStr(fields, prefix...)
|
||||
}
|
||||
|
||||
// FieldsExStr retrieves and returns fields which are not in parameter <fields> from the table,
|
||||
// joined with char ','.
|
||||
// The parameter <fields> specifies the fields that are excluded.
|
||||
// The optional parameter <prefix> specifies the prefix for each field, eg: FieldsExStr("id", "u.").
|
||||
func (m *Model) FieldsExStr(fields string, prefix ...string) string {
|
||||
func (m *Model) GetFieldsExStr(fields string, prefix ...string) string {
|
||||
prefixStr := ""
|
||||
if len(prefix) > 0 {
|
||||
prefixStr = prefix[0]
|
||||
|
||||
@ -56,8 +56,10 @@ func (m *Model) Data(data ...interface{}) *Model {
|
||||
case Map:
|
||||
model.data = params
|
||||
default:
|
||||
rv := reflect.ValueOf(params)
|
||||
kind := rv.Kind()
|
||||
var (
|
||||
rv = reflect.ValueOf(params)
|
||||
kind = rv.Kind()
|
||||
)
|
||||
if kind == reflect.Ptr {
|
||||
rv = rv.Elem()
|
||||
kind = rv.Kind()
|
||||
@ -66,11 +68,11 @@ func (m *Model) Data(data ...interface{}) *Model {
|
||||
case reflect.Slice, reflect.Array:
|
||||
list := make(List, rv.Len())
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
list[i] = DataToMapDeep(rv.Index(i).Interface())
|
||||
list[i] = ConvertDataForTableRecord(rv.Index(i).Interface())
|
||||
}
|
||||
model.data = list
|
||||
case reflect.Map:
|
||||
model.data = DataToMapDeep(data[0])
|
||||
model.data = ConvertDataForTableRecord(data[0])
|
||||
case reflect.Struct:
|
||||
if v, ok := data[0].(apiInterfaces); ok {
|
||||
var (
|
||||
@ -78,11 +80,11 @@ func (m *Model) Data(data ...interface{}) *Model {
|
||||
list = make(List, len(array))
|
||||
)
|
||||
for i := 0; i < len(array); i++ {
|
||||
list[i] = DataToMapDeep(array[i])
|
||||
list[i] = ConvertDataForTableRecord(array[i])
|
||||
}
|
||||
model.data = list
|
||||
} else {
|
||||
model.data = DataToMapDeep(data[0])
|
||||
model.data = ConvertDataForTableRecord(data[0])
|
||||
}
|
||||
default:
|
||||
model.data = data[0]
|
||||
@ -99,7 +101,7 @@ func (m *Model) Insert(data ...interface{}) (result sql.Result, err error) {
|
||||
if len(data) > 0 {
|
||||
return m.Data(data...).Insert()
|
||||
}
|
||||
return m.doInsertWithOption(gINSERT_OPTION_DEFAULT, data...)
|
||||
return m.doInsertWithOption(insertOptionDefault, data...)
|
||||
}
|
||||
|
||||
// InsertIgnore does "INSERT IGNORE INTO ..." statement for the model.
|
||||
@ -109,7 +111,7 @@ func (m *Model) InsertIgnore(data ...interface{}) (result sql.Result, err error)
|
||||
if len(data) > 0 {
|
||||
return m.Data(data...).Insert()
|
||||
}
|
||||
return m.doInsertWithOption(gINSERT_OPTION_IGNORE, data...)
|
||||
return m.doInsertWithOption(insertOptionIgnore, data...)
|
||||
}
|
||||
|
||||
// Replace does "REPLACE INTO ..." statement for the model.
|
||||
@ -119,7 +121,7 @@ func (m *Model) Replace(data ...interface{}) (result sql.Result, err error) {
|
||||
if len(data) > 0 {
|
||||
return m.Data(data...).Replace()
|
||||
}
|
||||
return m.doInsertWithOption(gINSERT_OPTION_REPLACE, data...)
|
||||
return m.doInsertWithOption(insertOptionReplace, data...)
|
||||
}
|
||||
|
||||
// Save does "INSERT INTO ... ON DUPLICATE KEY UPDATE..." statement for the model.
|
||||
@ -132,7 +134,7 @@ func (m *Model) Save(data ...interface{}) (result sql.Result, err error) {
|
||||
if len(data) > 0 {
|
||||
return m.Data(data...).Save()
|
||||
}
|
||||
return m.doInsertWithOption(gINSERT_OPTION_SAVE, data...)
|
||||
return m.doInsertWithOption(insertOptionSave, data...)
|
||||
}
|
||||
|
||||
// doInsertWithOption inserts data with option parameter.
|
||||
@ -147,13 +149,13 @@ func (m *Model) doInsertWithOption(option int, data ...interface{}) (result sql.
|
||||
}
|
||||
var (
|
||||
nowString = gtime.Now().String()
|
||||
fieldNameCreate = m.getSoftFieldNameCreate()
|
||||
fieldNameUpdate = m.getSoftFieldNameUpdate()
|
||||
fieldNameDelete = m.getSoftFieldNameDelete()
|
||||
fieldNameCreate = m.getSoftFieldNameCreated()
|
||||
fieldNameUpdate = m.getSoftFieldNameUpdated()
|
||||
fieldNameDelete = m.getSoftFieldNameDeleted()
|
||||
)
|
||||
// Batch operation.
|
||||
if list, ok := m.data.(List); ok {
|
||||
batch := gDEFAULT_BATCH_NUM
|
||||
batch := defaultBatchNumber
|
||||
if m.batch > 0 {
|
||||
batch = m.batch
|
||||
}
|
||||
@ -170,10 +172,14 @@ func (m *Model) doInsertWithOption(option int, data ...interface{}) (result sql.
|
||||
list[k] = v
|
||||
}
|
||||
}
|
||||
newData, err := m.filterDataForInsertOrUpdate(list)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m.db.DoBatchInsert(
|
||||
m.getLink(true),
|
||||
m.tables,
|
||||
m.filterDataForInsertOrUpdate(list),
|
||||
newData,
|
||||
option,
|
||||
batch,
|
||||
)
|
||||
@ -190,10 +196,14 @@ func (m *Model) doInsertWithOption(option int, data ...interface{}) (result sql.
|
||||
data[fieldNameUpdate] = nowString
|
||||
}
|
||||
}
|
||||
newData, err := m.filterDataForInsertOrUpdate(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m.db.DoInsert(
|
||||
m.getLink(true),
|
||||
m.tables,
|
||||
m.filterDataForInsertOrUpdate(data),
|
||||
newData,
|
||||
option,
|
||||
)
|
||||
}
|
||||
|
||||
@ -6,7 +6,21 @@
|
||||
|
||||
package gdb
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
)
|
||||
|
||||
// isSubQuery checks and returns whether given string a sub-query sql string.
|
||||
func isSubQuery(s string) bool {
|
||||
s = gstr.TrimLeft(s)
|
||||
if p := gstr.Pos(s, " "); p != -1 {
|
||||
if gstr.Equal(s[:p], "select") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// LeftJoin does "LEFT JOIN ... ON ..." statement on the model.
|
||||
// The parameter <table> can be joined table and its joined condition,
|
||||
@ -14,19 +28,32 @@ import "fmt"
|
||||
// Table("user").LeftJoin("user_detail", "user_detail.uid=user.uid")
|
||||
// Table("user", "u").LeftJoin("user_detail", "ud", "ud.uid=u.uid")
|
||||
func (m *Model) LeftJoin(table ...string) *Model {
|
||||
model := m.getModel()
|
||||
var (
|
||||
model = m.getModel()
|
||||
joinStr = ""
|
||||
)
|
||||
if len(table) > 0 {
|
||||
if isSubQuery(table[0]) {
|
||||
joinStr = "(" + table[0] + ")"
|
||||
} else {
|
||||
joinStr = m.db.QuotePrefixTableName(table[0])
|
||||
}
|
||||
}
|
||||
if len(table) > 2 {
|
||||
model.tables += fmt.Sprintf(
|
||||
" LEFT JOIN %s AS %s ON (%s)",
|
||||
m.db.QuotePrefixTableName(table[0]), m.db.QuoteWord(table[1]), table[2],
|
||||
joinStr, m.db.QuoteWord(table[1]), table[2],
|
||||
)
|
||||
} else if len(table) == 2 {
|
||||
model.tables += fmt.Sprintf(
|
||||
" LEFT JOIN %s ON (%s)",
|
||||
m.db.QuotePrefixTableName(table[0]), table[1],
|
||||
joinStr, table[1],
|
||||
)
|
||||
} else if len(table) == 1 {
|
||||
model.tables += fmt.Sprintf(
|
||||
" LEFT JOIN %s",
|
||||
joinStr,
|
||||
)
|
||||
} else {
|
||||
panic("invalid join table parameter")
|
||||
}
|
||||
return model
|
||||
}
|
||||
@ -37,19 +64,32 @@ func (m *Model) LeftJoin(table ...string) *Model {
|
||||
// Table("user").RightJoin("user_detail", "user_detail.uid=user.uid")
|
||||
// Table("user", "u").RightJoin("user_detail", "ud", "ud.uid=u.uid")
|
||||
func (m *Model) RightJoin(table ...string) *Model {
|
||||
model := m.getModel()
|
||||
var (
|
||||
model = m.getModel()
|
||||
joinStr = ""
|
||||
)
|
||||
if len(table) > 0 {
|
||||
if isSubQuery(table[0]) {
|
||||
joinStr = "(" + table[0] + ")"
|
||||
} else {
|
||||
joinStr = m.db.QuotePrefixTableName(table[0])
|
||||
}
|
||||
}
|
||||
if len(table) > 2 {
|
||||
model.tables += fmt.Sprintf(
|
||||
" RIGHT JOIN %s AS %s ON (%s)",
|
||||
m.db.QuotePrefixTableName(table[0]), m.db.QuoteWord(table[1]), table[2],
|
||||
joinStr, m.db.QuoteWord(table[1]), table[2],
|
||||
)
|
||||
} else if len(table) == 2 {
|
||||
model.tables += fmt.Sprintf(
|
||||
" RIGHT JOIN %s ON (%s)",
|
||||
m.db.QuotePrefixTableName(table[0]), table[1],
|
||||
joinStr, table[1],
|
||||
)
|
||||
} else if len(table) == 1 {
|
||||
model.tables += fmt.Sprintf(
|
||||
" RIGHT JOIN %s",
|
||||
joinStr,
|
||||
)
|
||||
} else {
|
||||
panic("invalid join table parameter")
|
||||
}
|
||||
return model
|
||||
}
|
||||
@ -60,19 +100,32 @@ func (m *Model) RightJoin(table ...string) *Model {
|
||||
// Table("user").InnerJoin("user_detail", "user_detail.uid=user.uid")
|
||||
// Table("user", "u").InnerJoin("user_detail", "ud", "ud.uid=u.uid")
|
||||
func (m *Model) InnerJoin(table ...string) *Model {
|
||||
model := m.getModel()
|
||||
var (
|
||||
model = m.getModel()
|
||||
joinStr = ""
|
||||
)
|
||||
if len(table) > 0 {
|
||||
if isSubQuery(table[0]) {
|
||||
joinStr = "(" + table[0] + ")"
|
||||
} else {
|
||||
joinStr = m.db.QuotePrefixTableName(table[0])
|
||||
}
|
||||
}
|
||||
if len(table) > 2 {
|
||||
model.tables += fmt.Sprintf(
|
||||
" INNER JOIN %s AS %s ON (%s)",
|
||||
m.db.QuotePrefixTableName(table[0]), m.db.QuoteWord(table[1]), table[2],
|
||||
joinStr, m.db.QuoteWord(table[1]), table[2],
|
||||
)
|
||||
} else if len(table) == 2 {
|
||||
model.tables += fmt.Sprintf(
|
||||
" INNER JOIN %s ON (%s)",
|
||||
m.db.QuotePrefixTableName(table[0]), table[1],
|
||||
joinStr, table[1],
|
||||
)
|
||||
} else if len(table) == 1 {
|
||||
model.tables += fmt.Sprintf(
|
||||
" INNER JOIN %s",
|
||||
joinStr,
|
||||
)
|
||||
} else {
|
||||
panic("invalid join table parameter")
|
||||
}
|
||||
return model
|
||||
}
|
||||
|
||||
@ -8,7 +8,10 @@ package gdb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/container/gset"
|
||||
"github.com/gogf/gf/container/gvar"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"reflect"
|
||||
)
|
||||
@ -43,7 +46,7 @@ func (m *Model) doGetAll(limit1 bool, where ...interface{}) (Result, error) {
|
||||
}
|
||||
var (
|
||||
softDeletingCondition = m.getConditionForSoftDeleting()
|
||||
conditionWhere, conditionExtra, conditionArgs = m.formatCondition(limit1)
|
||||
conditionWhere, conditionExtra, conditionArgs = m.formatCondition(limit1, false)
|
||||
)
|
||||
if !m.unscoped && softDeletingCondition != "" {
|
||||
if conditionWhere == "" {
|
||||
@ -53,12 +56,13 @@ func (m *Model) doGetAll(limit1 bool, where ...interface{}) (Result, error) {
|
||||
}
|
||||
conditionWhere += softDeletingCondition
|
||||
}
|
||||
|
||||
// DO NOT quote the m.fields where, in case of fields like:
|
||||
// DISTINCT t.user_id uid
|
||||
return m.doGetAllBySql(
|
||||
fmt.Sprintf(
|
||||
"SELECT %s FROM %s%s",
|
||||
m.fields,
|
||||
m.getFieldsFiltered(),
|
||||
m.tables,
|
||||
conditionWhere+conditionExtra,
|
||||
),
|
||||
@ -66,6 +70,56 @@ func (m *Model) doGetAll(limit1 bool, where ...interface{}) (Result, error) {
|
||||
)
|
||||
}
|
||||
|
||||
// getFieldsFiltered checks the fields and fieldsEx attributes, filters and returns the fields that will
|
||||
// really be committed to underlying database driver.
|
||||
func (m *Model) getFieldsFiltered() string {
|
||||
if m.fieldsEx == "" {
|
||||
// No filtering.
|
||||
if !gstr.Contains(m.fields, ".") && !gstr.Contains(m.fields, " ") {
|
||||
return m.db.QuoteString(m.fields)
|
||||
}
|
||||
return m.fields
|
||||
}
|
||||
var (
|
||||
fieldsArray []string
|
||||
fieldsExSet = gset.NewStrSetFrom(gstr.SplitAndTrim(m.fieldsEx, ","))
|
||||
)
|
||||
if m.fields != "*" {
|
||||
// Filter custom fields with fieldEx.
|
||||
fieldsArray = make([]string, 0, 8)
|
||||
for _, v := range gstr.SplitAndTrim(m.fields, ",") {
|
||||
fieldsArray = append(fieldsArray, v[gstr.PosR(v, "-")+1:])
|
||||
}
|
||||
} else {
|
||||
if gstr.Contains(m.tables, " ") {
|
||||
panic("function FieldsEx supports only single table operations")
|
||||
}
|
||||
// Filter table fields with fieldEx.
|
||||
tableFields, err := m.db.TableFields(m.tables)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if len(tableFields) == 0 {
|
||||
panic(fmt.Sprintf(`empty table fields for table "%s"`, m.tables))
|
||||
}
|
||||
fieldsArray = make([]string, len(tableFields))
|
||||
for k, v := range tableFields {
|
||||
fieldsArray[v.Index] = k
|
||||
}
|
||||
}
|
||||
newFields := ""
|
||||
for _, k := range fieldsArray {
|
||||
if fieldsExSet.Contains(k) {
|
||||
continue
|
||||
}
|
||||
if len(newFields) > 0 {
|
||||
newFields += ","
|
||||
}
|
||||
newFields += m.db.QuoteWord(k)
|
||||
}
|
||||
return newFields
|
||||
}
|
||||
|
||||
// Chunk iterates the query result with given size and callback function.
|
||||
func (m *Model) Chunk(limit int, callback func(result Result, err error) bool) {
|
||||
page := m.start
|
||||
@ -290,7 +344,7 @@ func (m *Model) Count(where ...interface{}) (int, error) {
|
||||
}
|
||||
var (
|
||||
softDeletingCondition = m.getConditionForSoftDeleting()
|
||||
conditionWhere, conditionExtra, conditionArgs = m.formatCondition(false)
|
||||
conditionWhere, conditionExtra, conditionArgs = m.formatCondition(false, true)
|
||||
)
|
||||
if !m.unscoped && softDeletingCondition != "" {
|
||||
if conditionWhere == "" {
|
||||
@ -381,23 +435,35 @@ func (m *Model) FindScan(pointer interface{}, where ...interface{}) error {
|
||||
// doGetAllBySql does the select statement on the database.
|
||||
func (m *Model) doGetAllBySql(sql string, args ...interface{}) (result Result, err error) {
|
||||
cacheKey := ""
|
||||
cacheObj := m.db.GetCache()
|
||||
// Retrieve from cache.
|
||||
if m.cacheEnabled && m.tx == nil {
|
||||
cacheKey = m.cacheName
|
||||
if len(cacheKey) == 0 {
|
||||
cacheKey = sql + "/" + gconv.String(args)
|
||||
cacheKey = sql + ", @PARAMS:" + gconv.String(args)
|
||||
}
|
||||
if v := m.db.GetCache().Get(cacheKey); v != nil {
|
||||
return v.(Result), nil
|
||||
if v, _ := cacheObj.GetVar(cacheKey); !v.IsNil() {
|
||||
if result, ok := v.Val().(Result); ok {
|
||||
// In-memory cache.
|
||||
return result, nil
|
||||
} else {
|
||||
// Other cache, it needs conversion.
|
||||
var result Result
|
||||
if err = json.Unmarshal(v.Bytes(), &result); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
result, err = m.db.DoGetAll(m.getLink(false), sql, m.mergeArguments(args)...)
|
||||
// Cache the result.
|
||||
if cacheKey != "" && err == nil {
|
||||
if m.cacheDuration < 0 {
|
||||
m.db.GetCache().Remove(cacheKey)
|
||||
cacheObj.Remove(cacheKey)
|
||||
} else {
|
||||
m.db.GetCache().Set(cacheKey, result, m.cacheDuration)
|
||||
cacheObj.Set(cacheKey, result, m.cacheDuration)
|
||||
}
|
||||
}
|
||||
return result, err
|
||||
|
||||
@ -15,58 +15,82 @@ import (
|
||||
"github.com/gogf/gf/util/gutil"
|
||||
)
|
||||
|
||||
const (
|
||||
gSOFT_FIELD_NAME_CREATE = "create_at"
|
||||
gSOFT_FIELD_NAME_UPDATE = "update_at"
|
||||
gSOFT_FIELD_NAME_DELETE = "delete_at"
|
||||
var (
|
||||
createdFiledNames = []string{"created_at", "create_at"} // Default filed names of table for automatic-filled created datetime.
|
||||
updatedFiledNames = []string{"updated_at", "update_at"} // Default filed names of table for automatic-filled updated datetime.
|
||||
deletedFiledNames = []string{"deleted_at", "delete_at"} // Default filed names of table for automatic-filled deleted datetime.
|
||||
)
|
||||
|
||||
// Unscoped disables the auto-update time feature for insert, update and delete options.
|
||||
func (m *Model) Unscoped() *Model {
|
||||
model := m.getModel()
|
||||
model.unscoped = true
|
||||
return model
|
||||
}
|
||||
|
||||
// getSoftFieldNameCreate checks and returns the field name for record creating time.
|
||||
// If there's no field name for storing creating time, it returns an empty string.
|
||||
// It checks the key with or without cases or chars '-'/'_'/'.'/' '.
|
||||
func (m *Model) getSoftFieldNameCreate(table ...string) string {
|
||||
func (m *Model) getSoftFieldNameCreated(table ...string) string {
|
||||
tableName := ""
|
||||
if len(table) > 0 {
|
||||
tableName = table[0]
|
||||
} else {
|
||||
tableName = m.getPrimaryTableName()
|
||||
}
|
||||
return m.getSoftFieldName(tableName, gSOFT_FIELD_NAME_CREATE)
|
||||
config := m.db.GetConfig()
|
||||
if config.CreatedAt != "" {
|
||||
return m.getSoftFieldName(tableName, append([]string{config.CreatedAt}, createdFiledNames...))
|
||||
}
|
||||
return m.getSoftFieldName(tableName, createdFiledNames)
|
||||
}
|
||||
|
||||
// getSoftFieldNameUpdate checks and returns the field name for record updating time.
|
||||
// If there's no field name for storing updating time, it returns an empty string.
|
||||
// It checks the key with or without cases or chars '-'/'_'/'.'/' '.
|
||||
func (m *Model) getSoftFieldNameUpdate(table ...string) (field string) {
|
||||
func (m *Model) getSoftFieldNameUpdated(table ...string) (field string) {
|
||||
tableName := ""
|
||||
if len(table) > 0 {
|
||||
tableName = table[0]
|
||||
} else {
|
||||
tableName = m.getPrimaryTableName()
|
||||
}
|
||||
return m.getSoftFieldName(tableName, gSOFT_FIELD_NAME_UPDATE)
|
||||
config := m.db.GetConfig()
|
||||
if config.UpdatedAt != "" {
|
||||
return m.getSoftFieldName(tableName, append([]string{config.UpdatedAt}, updatedFiledNames...))
|
||||
}
|
||||
return m.getSoftFieldName(tableName, updatedFiledNames)
|
||||
}
|
||||
|
||||
// getSoftFieldNameDelete checks and returns the field name for record deleting time.
|
||||
// If there's no field name for storing deleting time, it returns an empty string.
|
||||
// It checks the key with or without cases or chars '-'/'_'/'.'/' '.
|
||||
func (m *Model) getSoftFieldNameDelete(table ...string) (field string) {
|
||||
func (m *Model) getSoftFieldNameDeleted(table ...string) (field string) {
|
||||
tableName := ""
|
||||
if len(table) > 0 {
|
||||
tableName = table[0]
|
||||
} else {
|
||||
tableName = m.getPrimaryTableName()
|
||||
}
|
||||
return m.getSoftFieldName(tableName, gSOFT_FIELD_NAME_DELETE)
|
||||
config := m.db.GetConfig()
|
||||
if config.UpdatedAt != "" {
|
||||
return m.getSoftFieldName(tableName, append([]string{config.DeletedAt}, deletedFiledNames...))
|
||||
}
|
||||
return m.getSoftFieldName(tableName, deletedFiledNames)
|
||||
}
|
||||
|
||||
// getSoftFieldName retrieves and returns the field name of the table for possible key.
|
||||
func (m *Model) getSoftFieldName(table string, key string) (field string) {
|
||||
func (m *Model) getSoftFieldName(table string, keys []string) (field string) {
|
||||
fieldsMap, _ := m.db.TableFields(table)
|
||||
if len(fieldsMap) > 0 {
|
||||
field, _ = gutil.MapPossibleItemByKey(
|
||||
gconv.Map(fieldsMap), key,
|
||||
)
|
||||
for _, key := range keys {
|
||||
field, _ = gutil.MapPossibleItemByKey(
|
||||
gconv.Map(fieldsMap), key,
|
||||
)
|
||||
if field != "" {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -86,8 +110,8 @@ func (m *Model) getConditionForSoftDeleting() string {
|
||||
// Base table.
|
||||
match, _ := gregex.MatchString(`(.+?) [A-Z]+ JOIN`, m.tables)
|
||||
conditionArray.Append(m.getConditionOfTableStringForSoftDeleting(match[1]))
|
||||
// Multiple joined tables.
|
||||
matches, _ := gregex.MatchAllString(`JOIN (.+?) ON`, m.tables)
|
||||
// Multiple joined tables, exclude the sub query sql which contains char '(' and ')'.
|
||||
matches, _ := gregex.MatchAllString(`JOIN ([^()]+?) ON`, m.tables)
|
||||
for _, match := range matches {
|
||||
conditionArray.Append(m.getConditionOfTableStringForSoftDeleting(match[1]))
|
||||
}
|
||||
@ -103,7 +127,7 @@ func (m *Model) getConditionForSoftDeleting() string {
|
||||
return conditionArray.Join(" AND ")
|
||||
}
|
||||
// Only one table.
|
||||
if fieldName := m.getSoftFieldNameDelete(); fieldName != "" {
|
||||
if fieldName := m.getSoftFieldNameDeleted(); fieldName != "" {
|
||||
return fmt.Sprintf(`%s IS NULL`, m.db.QuoteWord(fieldName))
|
||||
}
|
||||
return ""
|
||||
@ -122,7 +146,7 @@ func (m *Model) getConditionOfTableStringForSoftDeleting(s string) string {
|
||||
} else {
|
||||
table = array2[0]
|
||||
}
|
||||
field = m.getSoftFieldNameDelete(table)
|
||||
field = m.getSoftFieldNameDeleted(table)
|
||||
if field == "" {
|
||||
return ""
|
||||
}
|
||||
@ -10,6 +10,7 @@ import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/errors/gerror"
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
@ -42,10 +43,10 @@ func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err erro
|
||||
}
|
||||
var (
|
||||
updateData = m.data
|
||||
fieldNameCreate = m.getSoftFieldNameCreate()
|
||||
fieldNameUpdate = m.getSoftFieldNameUpdate()
|
||||
fieldNameDelete = m.getSoftFieldNameDelete()
|
||||
conditionWhere, conditionExtra, conditionArgs = m.formatCondition(false)
|
||||
fieldNameCreate = m.getSoftFieldNameCreated()
|
||||
fieldNameUpdate = m.getSoftFieldNameUpdated()
|
||||
fieldNameDelete = m.getSoftFieldNameDeleted()
|
||||
conditionWhere, conditionExtra, conditionArgs = m.formatCondition(false, false)
|
||||
)
|
||||
// Automatically update the record updating time.
|
||||
if !m.unscoped && fieldNameUpdate != "" {
|
||||
@ -59,7 +60,7 @@ func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err erro
|
||||
}
|
||||
switch refKind {
|
||||
case reflect.Map, reflect.Struct:
|
||||
dataMap := DataToMapDeep(m.data)
|
||||
dataMap := ConvertDataForTableRecord(m.data)
|
||||
gutil.MapDelete(dataMap, fieldNameCreate, fieldNameUpdate, fieldNameDelete)
|
||||
if fieldNameUpdate != "" {
|
||||
dataMap[fieldNameUpdate] = gtime.Now().String()
|
||||
@ -73,11 +74,19 @@ func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err erro
|
||||
updateData = updates
|
||||
}
|
||||
}
|
||||
newData, err := m.filterDataForInsertOrUpdate(updateData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conditionStr := conditionWhere + conditionExtra
|
||||
if !gstr.ContainsI(conditionStr, " WHERE ") {
|
||||
return nil, gerror.New("there should be WHERE condition statement for UPDATE operation")
|
||||
}
|
||||
return m.db.DoUpdate(
|
||||
m.getLink(true),
|
||||
m.tables,
|
||||
m.filterDataForInsertOrUpdate(updateData),
|
||||
conditionWhere+conditionExtra,
|
||||
newData,
|
||||
conditionStr,
|
||||
m.mergeArguments(conditionArgs)...,
|
||||
)
|
||||
}
|
||||
|
||||
@ -13,6 +13,7 @@ import (
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"github.com/gogf/gf/util/gutil"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -26,29 +27,62 @@ func (m *Model) getModel() *Model {
|
||||
}
|
||||
}
|
||||
|
||||
// mappingToTableFields mappings and changes given field name to really table field name.
|
||||
func (m *Model) mappingToTableFields(fields []string) []string {
|
||||
var (
|
||||
foundKey = ""
|
||||
fieldsArray = gstr.SplitAndTrim(gstr.Join(fields, ","), ",")
|
||||
)
|
||||
|
||||
if fieldsMap, err := m.db.TableFields(m.tables); err == nil {
|
||||
fieldsKeyMap := make(map[string]interface{}, len(fieldsMap))
|
||||
for k, _ := range fieldsMap {
|
||||
fieldsKeyMap[k] = nil
|
||||
}
|
||||
for i, v := range fieldsArray {
|
||||
if _, ok := fieldsKeyMap[v]; !ok {
|
||||
if gstr.Contains(v, " ") || gstr.Contains(v, ".") {
|
||||
continue
|
||||
}
|
||||
foundKey, _ = gutil.MapPossibleItemByKey(fieldsKeyMap, v)
|
||||
if foundKey != "" {
|
||||
fieldsArray[i] = foundKey
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return fieldsArray
|
||||
}
|
||||
|
||||
// filterDataForInsertOrUpdate does filter feature with data for inserting/updating operations.
|
||||
// Note that, it does not filter list item, which is also type of map, for "omit empty" feature.
|
||||
func (m *Model) filterDataForInsertOrUpdate(data interface{}) interface{} {
|
||||
func (m *Model) filterDataForInsertOrUpdate(data interface{}) (interface{}, error) {
|
||||
var err error
|
||||
switch value := data.(type) {
|
||||
case List:
|
||||
for k, item := range value {
|
||||
value[k] = m.doFilterDataMapForInsertOrUpdate(item, false)
|
||||
value[k], err = m.doMappingAndFilterForInsertOrUpdateDataMap(item, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return value
|
||||
return value, nil
|
||||
|
||||
case Map:
|
||||
return m.doFilterDataMapForInsertOrUpdate(value, true)
|
||||
return m.doMappingAndFilterForInsertOrUpdateDataMap(value, true)
|
||||
|
||||
default:
|
||||
return data
|
||||
return data, nil
|
||||
}
|
||||
}
|
||||
|
||||
// doFilterDataMapForInsertOrUpdate does the filter features for map.
|
||||
// doMappingAndFilterForInsertOrUpdateDataMap does the filter features for map.
|
||||
// Note that, it does not filter list item, which is also type of map, for "omit empty" feature.
|
||||
func (m *Model) doFilterDataMapForInsertOrUpdate(data Map, allowOmitEmpty bool) Map {
|
||||
if m.filter {
|
||||
data = m.db.filterFields(m.schema, m.tables, data)
|
||||
func (m *Model) doMappingAndFilterForInsertOrUpdateDataMap(data Map, allowOmitEmpty bool) (Map, error) {
|
||||
var err error
|
||||
data, err = m.db.mappingAndFilterData(m.schema, m.tables, data, m.filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Remove key-value pairs of which the value is empty.
|
||||
if allowOmitEmpty && m.option&OPTION_OMITEMPTY > 0 {
|
||||
@ -103,7 +137,7 @@ func (m *Model) doFilterDataMapForInsertOrUpdate(data Map, allowOmitEmpty bool)
|
||||
delete(data, v)
|
||||
}
|
||||
}
|
||||
return data
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// getLink returns the underlying database link object with configured <linkType> attribute.
|
||||
@ -158,7 +192,7 @@ func (m *Model) getPrimaryKey() string {
|
||||
// Note that this function does not change any attribute value of the <m>.
|
||||
//
|
||||
// The parameter <limit1> specifies whether limits querying only one record if m.limit is not set.
|
||||
func (m *Model) formatCondition(limit1 bool) (conditionWhere string, conditionExtra string, conditionArgs []interface{}) {
|
||||
func (m *Model) formatCondition(limit1 bool, isCountStatement bool) (conditionWhere string, conditionExtra string, conditionArgs []interface{}) {
|
||||
if len(m.whereHolder) > 0 {
|
||||
for _, v := range m.whereHolder {
|
||||
switch v.operator {
|
||||
@ -225,18 +259,22 @@ func (m *Model) formatCondition(limit1 bool) (conditionWhere string, conditionEx
|
||||
conditionArgs = append(conditionArgs, havingArgs...)
|
||||
}
|
||||
}
|
||||
if m.limit != 0 {
|
||||
if m.start >= 0 {
|
||||
conditionExtra += fmt.Sprintf(" LIMIT %d,%d", m.start, m.limit)
|
||||
} else {
|
||||
conditionExtra += fmt.Sprintf(" LIMIT %d", m.limit)
|
||||
if !isCountStatement {
|
||||
if m.limit != 0 {
|
||||
if m.start >= 0 {
|
||||
conditionExtra += fmt.Sprintf(" LIMIT %d,%d", m.start, m.limit)
|
||||
} else {
|
||||
conditionExtra += fmt.Sprintf(" LIMIT %d", m.limit)
|
||||
}
|
||||
} else if limit1 {
|
||||
conditionExtra += " LIMIT 1"
|
||||
}
|
||||
|
||||
if m.offset >= 0 {
|
||||
conditionExtra += fmt.Sprintf(" OFFSET %d", m.offset)
|
||||
}
|
||||
} else if limit1 {
|
||||
conditionExtra += " LIMIT 1"
|
||||
}
|
||||
if m.offset >= 0 {
|
||||
conditionExtra += fmt.Sprintf(" OFFSET %d", m.offset)
|
||||
}
|
||||
|
||||
if m.lockInfo != "" {
|
||||
conditionExtra += " " + m.lockInfo
|
||||
}
|
||||
|
||||
@ -115,7 +115,6 @@ func (tx *TX) GetScan(objPointer interface{}, sql string, args ...interface{}) e
|
||||
default:
|
||||
return fmt.Errorf("element type should be type of struct/slice, unsupported: %v", k)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetValue queries and returns the field value from database.
|
||||
@ -154,7 +153,10 @@ func (tx *TX) GetCount(sql string, args ...interface{}) (int, error) {
|
||||
//
|
||||
// The parameter <batch> specifies the batch operation count when given data is slice.
|
||||
func (tx *TX) Insert(table string, data interface{}, batch ...int) (sql.Result, error) {
|
||||
return tx.db.DoInsert(tx.tx, table, data, gINSERT_OPTION_DEFAULT, batch...)
|
||||
if len(batch) > 0 {
|
||||
return tx.Model(table).Data(data).Batch(batch[0]).Insert()
|
||||
}
|
||||
return tx.Model(table).Data(data).Insert()
|
||||
}
|
||||
|
||||
// InsertIgnore does "INSERT IGNORE INTO ..." statement for the table.
|
||||
@ -167,7 +169,10 @@ func (tx *TX) Insert(table string, data interface{}, batch ...int) (sql.Result,
|
||||
//
|
||||
// The parameter <batch> specifies the batch operation count when given data is slice.
|
||||
func (tx *TX) InsertIgnore(table string, data interface{}, batch ...int) (sql.Result, error) {
|
||||
return tx.db.DoInsert(tx.tx, table, data, gINSERT_OPTION_IGNORE, batch...)
|
||||
if len(batch) > 0 {
|
||||
return tx.Model(table).Data(data).Batch(batch[0]).InsertIgnore()
|
||||
}
|
||||
return tx.Model(table).Data(data).InsertIgnore()
|
||||
}
|
||||
|
||||
// Replace does "REPLACE INTO ..." statement for the table.
|
||||
@ -183,7 +188,10 @@ func (tx *TX) InsertIgnore(table string, data interface{}, batch ...int) (sql.Re
|
||||
// If given data is type of slice, it then does batch replacing, and the optional parameter
|
||||
// <batch> specifies the batch operation count.
|
||||
func (tx *TX) Replace(table string, data interface{}, batch ...int) (sql.Result, error) {
|
||||
return tx.db.DoInsert(tx.tx, table, data, gINSERT_OPTION_REPLACE, batch...)
|
||||
if len(batch) > 0 {
|
||||
return tx.Model(table).Data(data).Batch(batch[0]).Replace()
|
||||
}
|
||||
return tx.Model(table).Data(data).Replace()
|
||||
}
|
||||
|
||||
// Save does "INSERT INTO ... ON DUPLICATE KEY UPDATE..." statement for the table.
|
||||
@ -198,31 +206,46 @@ func (tx *TX) Replace(table string, data interface{}, batch ...int) (sql.Result,
|
||||
// If given data is type of slice, it then does batch saving, and the optional parameter
|
||||
// <batch> specifies the batch operation count.
|
||||
func (tx *TX) Save(table string, data interface{}, batch ...int) (sql.Result, error) {
|
||||
return tx.db.DoInsert(tx.tx, table, data, gINSERT_OPTION_SAVE, batch...)
|
||||
if len(batch) > 0 {
|
||||
return tx.Model(table).Data(data).Batch(batch[0]).Save()
|
||||
}
|
||||
return tx.Model(table).Data(data).Save()
|
||||
}
|
||||
|
||||
// BatchInsert batch inserts data.
|
||||
// The parameter <list> must be type of slice of map or struct.
|
||||
func (tx *TX) BatchInsert(table string, list interface{}, batch ...int) (sql.Result, error) {
|
||||
return tx.db.DoBatchInsert(tx.tx, table, list, gINSERT_OPTION_DEFAULT, batch...)
|
||||
if len(batch) > 0 {
|
||||
return tx.Model(table).Data(list).Batch(batch[0]).Insert()
|
||||
}
|
||||
return tx.Model(table).Data(list).Insert()
|
||||
}
|
||||
|
||||
// BatchInsert batch inserts data with ignore option.
|
||||
// The parameter <list> must be type of slice of map or struct.
|
||||
func (tx *TX) BatchInsertIgnore(table string, list interface{}, batch ...int) (sql.Result, error) {
|
||||
return tx.db.DoBatchInsert(tx.tx, table, list, gINSERT_OPTION_IGNORE, batch...)
|
||||
if len(batch) > 0 {
|
||||
return tx.Model(table).Data(list).Batch(batch[0]).InsertIgnore()
|
||||
}
|
||||
return tx.Model(table).Data(list).InsertIgnore()
|
||||
}
|
||||
|
||||
// BatchReplace batch replaces data.
|
||||
// The parameter <list> must be type of slice of map or struct.
|
||||
func (tx *TX) BatchReplace(table string, list interface{}, batch ...int) (sql.Result, error) {
|
||||
return tx.db.DoBatchInsert(tx.tx, table, list, gINSERT_OPTION_REPLACE, batch...)
|
||||
if len(batch) > 0 {
|
||||
return tx.Model(table).Data(list).Batch(batch[0]).Replace()
|
||||
}
|
||||
return tx.Model(table).Data(list).Replace()
|
||||
}
|
||||
|
||||
// BatchSave batch replaces data.
|
||||
// The parameter <list> must be type of slice of map or struct.
|
||||
func (tx *TX) BatchSave(table string, list interface{}, batch ...int) (sql.Result, error) {
|
||||
return tx.db.DoBatchInsert(tx.tx, table, list, gINSERT_OPTION_SAVE, batch...)
|
||||
if len(batch) > 0 {
|
||||
return tx.Model(table).Data(list).Batch(batch[0]).Save()
|
||||
}
|
||||
return tx.Model(table).Data(list).Save()
|
||||
}
|
||||
|
||||
// Update does "UPDATE ... " statement for the table.
|
||||
@ -240,11 +263,7 @@ func (tx *TX) BatchSave(table string, list interface{}, batch ...int) (sql.Resul
|
||||
// "age IN(?,?)", 18, 50
|
||||
// User{ Id : 1, UserName : "john"}
|
||||
func (tx *TX) Update(table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error) {
|
||||
newWhere, newArgs := formatWhere(tx.db, condition, args, false)
|
||||
if newWhere != "" {
|
||||
newWhere = " WHERE " + newWhere
|
||||
}
|
||||
return tx.db.DoUpdate(tx.tx, table, data, newWhere, newArgs...)
|
||||
return tx.Model(table).Data(data).Where(condition, args...).Update()
|
||||
}
|
||||
|
||||
// Delete does "DELETE FROM ... " statement for the table.
|
||||
@ -259,9 +278,5 @@ func (tx *TX) Update(table string, data interface{}, condition interface{}, args
|
||||
// "age IN(?,?)", 18, 50
|
||||
// User{ Id : 1, UserName : "john"}
|
||||
func (tx *TX) Delete(table string, condition interface{}, args ...interface{}) (sql.Result, error) {
|
||||
newWhere, newArgs := formatWhere(tx.db, condition, args, false)
|
||||
if newWhere != "" {
|
||||
newWhere = " WHERE " + newWhere
|
||||
}
|
||||
return tx.db.DoDelete(tx.tx, table, newWhere, newArgs...)
|
||||
return tx.Model(table).Where(condition, args...).Delete()
|
||||
}
|
||||
|
||||
@ -32,11 +32,15 @@ import (
|
||||
// ScanList(&users, "User")
|
||||
// ScanList(&users, "UserDetail", "User", "uid:Uid")
|
||||
// ScanList(&users, "UserScores", "User", "uid:Uid")
|
||||
//
|
||||
// The parameters "User"/"UserDetail"/"UserScores" in the example codes specify the target attribute struct
|
||||
// that current result will be bound to.
|
||||
//
|
||||
// The "uid" in the example codes is the table field name of the result, and the "Uid" is the relational
|
||||
// struct attribute name. It automatically calculates the HasOne/HasMany relationship with given <relation>
|
||||
// parameter.
|
||||
// struct attribute name - not the attribute name of the bound to target. In the example codes, it's attribute
|
||||
// name "Uid" of "User" of entity "Entity". It automatically calculates the HasOne/HasMany relationship with
|
||||
// given <relation> parameter.
|
||||
//
|
||||
// See the example or unit testing cases for clear understanding for this function.
|
||||
func (r Result) ScanList(listPointer interface{}, attributeName string, relation ...string) (err error) {
|
||||
// Necessary checks for parameters.
|
||||
@ -177,7 +181,7 @@ func (r Result) ScanList(listPointer interface{}, attributeName string, relation
|
||||
}
|
||||
}
|
||||
if len(relationDataMap) > 0 && !relationValue.IsValid() {
|
||||
return fmt.Errorf(`invalid relation: %s, %s`, relation[0], relation[1])
|
||||
return fmt.Errorf(`invalid relation: "%s:%s"`, relation[0], relation[1])
|
||||
}
|
||||
switch attrKind {
|
||||
case reflect.Array, reflect.Slice:
|
||||
@ -192,7 +196,7 @@ func (r Result) ScanList(listPointer interface{}, attributeName string, relation
|
||||
}
|
||||
} else {
|
||||
// May be the attribute does not exist yet.
|
||||
return fmt.Errorf(`invalid relation: %s, %s`, relation[0], relation[1])
|
||||
return fmt.Errorf(`invalid relation: "%s:%s"`, relation[0], relation[1])
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf(`relationKey should not be empty as field "%s" is slice`, attributeName)
|
||||
@ -203,15 +207,25 @@ func (r Result) ScanList(listPointer interface{}, attributeName string, relation
|
||||
if len(relationDataMap) > 0 {
|
||||
relationField = relationValue.FieldByName(relationAttrName)
|
||||
if relationField.IsValid() {
|
||||
if err = gconv.Struct(relationDataMap[gconv.String(relationField.Interface())], e); err != nil {
|
||||
v := relationDataMap[gconv.String(relationField.Interface())]
|
||||
if v == nil {
|
||||
// There's no relational data.
|
||||
continue
|
||||
}
|
||||
if err = gconv.Struct(v, e); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// May be the attribute does not exist yet.
|
||||
return fmt.Errorf(`invalid relation: %s, %s`, relation[0], relation[1])
|
||||
return fmt.Errorf(`invalid relation: "%s:%s"`, relation[0], relation[1])
|
||||
}
|
||||
} else {
|
||||
if err = gconv.Struct(r[i], e); err != nil {
|
||||
v := r[i]
|
||||
if v == nil {
|
||||
// There's no relational data.
|
||||
continue
|
||||
}
|
||||
if err = gconv.Struct(v, e); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -222,15 +236,25 @@ func (r Result) ScanList(listPointer interface{}, attributeName string, relation
|
||||
if len(relationDataMap) > 0 {
|
||||
relationField = relationValue.FieldByName(relationAttrName)
|
||||
if relationField.IsValid() {
|
||||
if err = gconv.Struct(relationDataMap[gconv.String(relationField.Interface())], e); err != nil {
|
||||
v := relationDataMap[gconv.String(relationField.Interface())]
|
||||
if v == nil {
|
||||
// There's no relational data.
|
||||
continue
|
||||
}
|
||||
if err = gconv.Struct(v, e); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// May be the attribute does not exist yet.
|
||||
return fmt.Errorf(`invalid relation: %s, %s`, relation[0], relation[1])
|
||||
return fmt.Errorf(`invalid relation: "%s:%s"`, relation[0], relation[1])
|
||||
}
|
||||
} else {
|
||||
if err = gconv.Struct(r[i], e); err != nil {
|
||||
v := r[i]
|
||||
if v == nil {
|
||||
// There's no relational data.
|
||||
continue
|
||||
}
|
||||
if err = gconv.Struct(v, e); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,7 +17,152 @@ import (
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
)
|
||||
|
||||
func Test_Table_Relation(t *testing.T) {
|
||||
func Test_Table_Relation_One(t *testing.T) {
|
||||
var (
|
||||
tableUser = "user_" + gtime.TimestampMicroStr()
|
||||
tableUserDetail = "user_detail_" + gtime.TimestampMicroStr()
|
||||
tableUserScores = "user_scores_" + gtime.TimestampMicroStr()
|
||||
)
|
||||
if _, err := db.Exec(fmt.Sprintf(`
|
||||
CREATE TABLE %s (
|
||||
uid int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
name varchar(45) NOT NULL,
|
||||
PRIMARY KEY (uid)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
`, tableUser)); err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
defer dropTable(tableUser)
|
||||
|
||||
if _, err := db.Exec(fmt.Sprintf(`
|
||||
CREATE TABLE %s (
|
||||
uid int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
address varchar(45) NOT NULL,
|
||||
PRIMARY KEY (uid)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
`, tableUserDetail)); err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
defer dropTable(tableUserDetail)
|
||||
|
||||
if _, err := db.Exec(fmt.Sprintf(`
|
||||
CREATE TABLE %s (
|
||||
id int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
uid int(10) unsigned NOT NULL,
|
||||
score int(10) unsigned NOT NULL,
|
||||
course varchar(45) NOT NULL,
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
`, tableUserScores)); err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
defer dropTable(tableUserScores)
|
||||
|
||||
type EntityUser struct {
|
||||
Uid int `orm:"uid"`
|
||||
Name string `orm:"name"`
|
||||
}
|
||||
|
||||
type EntityUserDetail struct {
|
||||
Uid int `orm:"uid"`
|
||||
Address string `orm:"address"`
|
||||
}
|
||||
|
||||
type EntityUserScores struct {
|
||||
Id int `orm:"id"`
|
||||
Uid int `orm:"uid"`
|
||||
Score int `orm:"score"`
|
||||
Course string `orm:"course"`
|
||||
}
|
||||
|
||||
type Entity struct {
|
||||
User *EntityUser
|
||||
UserDetail *EntityUserDetail
|
||||
UserScores []*EntityUserScores
|
||||
}
|
||||
|
||||
// Initialize the data.
|
||||
var err error
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
err = db.Transaction(func(tx *gdb.TX) error {
|
||||
r, err := tx.Table(tableUser).Save(EntityUser{
|
||||
Name: "john",
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
uid, err := r.LastInsertId()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = tx.Table(tableUserDetail).Save(EntityUserDetail{
|
||||
Uid: int(uid),
|
||||
Address: "Beijing DongZhiMen #66",
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = tx.Table(tableUserScores).Save(g.Slice{
|
||||
EntityUserScores{Uid: int(uid), Score: 100, Course: "math"},
|
||||
EntityUserScores{Uid: int(uid), Score: 99, Course: "physics"},
|
||||
})
|
||||
return err
|
||||
})
|
||||
t.Assert(err, nil)
|
||||
})
|
||||
// Data check.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
r, err := db.Table(tableUser).All()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(r.Len(), 1)
|
||||
t.Assert(r[0]["uid"].Int(), 1)
|
||||
t.Assert(r[0]["name"].String(), "john")
|
||||
|
||||
r, err = db.Table(tableUserDetail).Where("uid", r[0]["uid"].Int()).All()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(r.Len(), 1)
|
||||
t.Assert(r[0]["uid"].Int(), 1)
|
||||
t.Assert(r[0]["address"].String(), `Beijing DongZhiMen #66`)
|
||||
|
||||
r, err = db.Table(tableUserScores).Where("uid", r[0]["uid"].Int()).All()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(r.Len(), 2)
|
||||
t.Assert(r[0]["uid"].Int(), 1)
|
||||
t.Assert(r[1]["uid"].Int(), 1)
|
||||
t.Assert(r[0]["course"].String(), `math`)
|
||||
t.Assert(r[1]["course"].String(), `physics`)
|
||||
})
|
||||
// Entity query.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var user Entity
|
||||
// SELECT * FROM `user` WHERE `name`='john'
|
||||
err := db.Table(tableUser).Scan(&user.User, "name", "john")
|
||||
t.Assert(err, nil)
|
||||
|
||||
// SELECT * FROM `user_detail` WHERE `uid`=1
|
||||
err = db.Table(tableUserDetail).Scan(&user.UserDetail, "uid", user.User.Uid)
|
||||
t.Assert(err, nil)
|
||||
|
||||
// SELECT * FROM `user_scores` WHERE `uid`=1
|
||||
err = db.Table(tableUserScores).Scan(&user.UserScores, "uid", user.User.Uid)
|
||||
t.Assert(err, nil)
|
||||
|
||||
t.Assert(user.User, EntityUser{
|
||||
Uid: 1,
|
||||
Name: "john",
|
||||
})
|
||||
t.Assert(user.UserDetail, EntityUserDetail{
|
||||
Uid: 1,
|
||||
Address: "Beijing DongZhiMen #66",
|
||||
})
|
||||
t.Assert(user.UserScores, []EntityUserScores{
|
||||
{Id: 1, Uid: 1, Course: "math", Score: 100},
|
||||
{Id: 2, Uid: 1, Course: "physics", Score: 99},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Table_Relation_Many(t *testing.T) {
|
||||
var (
|
||||
tableUser = "user_" + gtime.TimestampMicroStr()
|
||||
tableUserDetail = "user_detail_" + gtime.TimestampMicroStr()
|
||||
@ -204,9 +204,9 @@ CREATE TABLE %s (
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
model := db.Table(table1)
|
||||
gtest.Assert(model.getSoftFieldNameCreate(table2), "createat")
|
||||
gtest.Assert(model.getSoftFieldNameUpdate(table2), "updateat")
|
||||
gtest.Assert(model.getSoftFieldNameDelete(table2), "deleteat")
|
||||
gtest.Assert(model.getSoftFieldNameCreated(table2), "createat")
|
||||
gtest.Assert(model.getSoftFieldNameUpdated(table2), "updateat")
|
||||
gtest.Assert(model.getSoftFieldNameDeleted(table2), "deleteat")
|
||||
})
|
||||
}
|
||||
|
||||
@ -292,14 +292,23 @@ CREATE TABLE %s (
|
||||
}
|
||||
|
||||
// Fix issue: https://github.com/gogf/gf/issues/819
|
||||
func Test_Func_DataToMapDeep(t *testing.T) {
|
||||
func Test_Func_ConvertDataForTableRecord(t *testing.T) {
|
||||
type Test struct {
|
||||
ResetPasswordTokenAt mysql.NullTime `orm:"reset_password_token_at"`
|
||||
}
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := DataToMapDeep(new(Test))
|
||||
m := ConvertDataForTableRecord(new(Test))
|
||||
t.Assert(len(m), 1)
|
||||
t.AssertNE(m["reset_password_token_at"], nil)
|
||||
t.Assert(m["reset_password_token_at"], new(mysql.NullTime))
|
||||
})
|
||||
}
|
||||
|
||||
func Test_isSubQuery(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
t.Assert(isSubQuery("user"), false)
|
||||
t.Assert(isSubQuery("user.uid"), false)
|
||||
t.Assert(isSubQuery("u, user.uid"), false)
|
||||
t.Assert(isSubQuery("select 1"), true)
|
||||
})
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@ package gdb_test
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/container/garray"
|
||||
"github.com/gogf/gf/encoding/gparser"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -183,6 +184,119 @@ func Test_DB_Insert(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// Fix issue: https://github.com/gogf/gf/issues/819
|
||||
func Test_DB_Insert_WithStructAndSliceAttribute(t *testing.T) {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type Password struct {
|
||||
Salt string `json:"salt"`
|
||||
Pass string `json:"pass"`
|
||||
}
|
||||
data := g.Map{
|
||||
"id": 1,
|
||||
"passport": "t1",
|
||||
"password": &Password{"123", "456"},
|
||||
"nickname": []string{"A", "B", "C"},
|
||||
"create_time": gtime.Now().String(),
|
||||
}
|
||||
_, err := db.Insert(table, data)
|
||||
t.Assert(err, nil)
|
||||
|
||||
one, err := db.GetOne(fmt.Sprintf("SELECT * FROM %s WHERE id=?", table), 1)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(one["passport"], data["passport"])
|
||||
t.Assert(one["create_time"], data["create_time"])
|
||||
t.Assert(one["nickname"], gparser.MustToJson(data["nickname"]))
|
||||
})
|
||||
}
|
||||
|
||||
func Test_DB_Insert_KeyFieldNameMapping(t *testing.T) {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type User struct {
|
||||
Id int
|
||||
Passport string
|
||||
Password string
|
||||
Nickname string
|
||||
CreateTime string
|
||||
}
|
||||
data := User{
|
||||
Id: 1,
|
||||
Passport: "user_1",
|
||||
Password: "pass_1",
|
||||
Nickname: "name_1",
|
||||
CreateTime: "2020-10-10 12:00:01",
|
||||
}
|
||||
_, err := db.Insert(table, data)
|
||||
t.Assert(err, nil)
|
||||
|
||||
one, err := db.GetOne(fmt.Sprintf("SELECT * FROM %s WHERE id=?", table), 1)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(one["passport"], data.Passport)
|
||||
t.Assert(one["create_time"], data.CreateTime)
|
||||
t.Assert(one["nickname"], data.Nickname)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_DB_Upadte_KeyFieldNameMapping(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type User struct {
|
||||
Id int
|
||||
Passport string
|
||||
Password string
|
||||
Nickname string
|
||||
CreateTime string
|
||||
}
|
||||
data := User{
|
||||
Id: 1,
|
||||
Passport: "user_10",
|
||||
Password: "pass_10",
|
||||
Nickname: "name_10",
|
||||
CreateTime: "2020-10-10 12:00:01",
|
||||
}
|
||||
_, err := db.Update(table, data, "id=1")
|
||||
t.Assert(err, nil)
|
||||
|
||||
one, err := db.GetOne(fmt.Sprintf("SELECT * FROM %s WHERE id=?", table), 1)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(one["passport"], data.Passport)
|
||||
t.Assert(one["create_time"], data.CreateTime)
|
||||
t.Assert(one["nickname"], data.Nickname)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_DB_Insert_KeyFieldNameMapping_Error(t *testing.T) {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type User struct {
|
||||
Id int
|
||||
Passport string
|
||||
Password string
|
||||
Nickname string
|
||||
CreateTime string
|
||||
NoneExistField string
|
||||
}
|
||||
data := User{
|
||||
Id: 1,
|
||||
Passport: "user_1",
|
||||
Password: "pass_1",
|
||||
Nickname: "name_1",
|
||||
CreateTime: "2020-10-10 12:00:01",
|
||||
}
|
||||
_, err := db.Insert(table, data)
|
||||
t.AssertNE(err, nil)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_DB_InsertIgnore(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
@ -605,7 +719,7 @@ func Test_DB_Delete(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, err := db.Delete(table, nil)
|
||||
result, err := db.Delete(table, 1)
|
||||
t.Assert(err, nil)
|
||||
n, _ := result.RowsAffected()
|
||||
t.Assert(n, SIZE)
|
||||
@ -654,7 +768,7 @@ func Test_DB_Time(t *testing.T) {
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, err := db.Delete(table, nil)
|
||||
result, err := db.Delete(table, 1)
|
||||
t.Assert(err, nil)
|
||||
n, _ := result.RowsAffected()
|
||||
t.Assert(n, 2)
|
||||
|
||||
@ -11,6 +11,9 @@ import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/container/garray"
|
||||
"github.com/gogf/gf/container/gmap"
|
||||
"github.com/gogf/gf/debug/gdebug"
|
||||
"github.com/gogf/gf/encoding/gparser"
|
||||
"github.com/gogf/gf/os/gfile"
|
||||
"github.com/gogf/gf/util/gutil"
|
||||
"testing"
|
||||
"time"
|
||||
@ -52,20 +55,20 @@ func Test_Model_Insert(t *testing.T) {
|
||||
t.Assert(n, 1)
|
||||
|
||||
type User struct {
|
||||
Id int `gconv:"id"`
|
||||
Uid int `gconv:"uid"`
|
||||
Passport string `json:"passport"`
|
||||
Password string `gconv:"password"`
|
||||
Nickname string `gconv:"nickname"`
|
||||
CreateTime string `json:"create_time"`
|
||||
Id int `gconv:"id"`
|
||||
Uid int `gconv:"uid"`
|
||||
Passport string `json:"passport"`
|
||||
Password string `gconv:"password"`
|
||||
Nickname string `gconv:"nickname"`
|
||||
CreateTime *gtime.Time `json:"create_time"`
|
||||
}
|
||||
// Model inserting.
|
||||
result, err = db.Table(table).Filter().Data(User{
|
||||
Id: 3,
|
||||
Uid: 3,
|
||||
Passport: "t3",
|
||||
Password: "25d55ad283aa400af464c76d713c07ad",
|
||||
Nickname: "name_3",
|
||||
CreateTime: gtime.Now().String(),
|
||||
Id: 3,
|
||||
Uid: 3,
|
||||
Passport: "t3",
|
||||
Password: "25d55ad283aa400af464c76d713c07ad",
|
||||
Nickname: "name_3",
|
||||
}).Insert()
|
||||
t.Assert(err, nil)
|
||||
n, _ = result.RowsAffected()
|
||||
@ -80,7 +83,7 @@ func Test_Model_Insert(t *testing.T) {
|
||||
Passport: "t4",
|
||||
Password: "25d55ad283aa400af464c76d713c07ad",
|
||||
Nickname: "T4",
|
||||
CreateTime: gtime.Now().String(),
|
||||
CreateTime: gtime.Now(),
|
||||
}).Insert()
|
||||
t.Assert(err, nil)
|
||||
n, _ = result.RowsAffected()
|
||||
@ -94,7 +97,164 @@ func Test_Model_Insert(t *testing.T) {
|
||||
n, _ = result.RowsAffected()
|
||||
t.Assert(n, 3)
|
||||
})
|
||||
}
|
||||
|
||||
// Fix issue: https://github.com/gogf/gf/issues/819
|
||||
func Test_Model_Insert_WithStructAndSliceAttribute(t *testing.T) {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type Password struct {
|
||||
Salt string `json:"salt"`
|
||||
Pass string `json:"pass"`
|
||||
}
|
||||
data := g.Map{
|
||||
"id": 1,
|
||||
"passport": "t1",
|
||||
"password": &Password{"123", "456"},
|
||||
"nickname": []string{"A", "B", "C"},
|
||||
"create_time": gtime.Now().String(),
|
||||
}
|
||||
_, err := db.Table(table).Data(data).Insert()
|
||||
t.Assert(err, nil)
|
||||
|
||||
one, err := db.Table(table).One("id", 1)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(one["passport"], data["passport"])
|
||||
t.Assert(one["create_time"], data["create_time"])
|
||||
t.Assert(one["nickname"], gparser.MustToJson(data["nickname"]))
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_Insert_KeyFieldNameMapping(t *testing.T) {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type User struct {
|
||||
Id int
|
||||
Passport string
|
||||
Password string
|
||||
Nickname string
|
||||
CreateTime string
|
||||
}
|
||||
data := User{
|
||||
Id: 1,
|
||||
Passport: "user_1",
|
||||
Password: "pass_1",
|
||||
Nickname: "name_1",
|
||||
CreateTime: "2020-10-10 12:00:01",
|
||||
}
|
||||
_, err := db.Model(table).Data(data).Insert()
|
||||
t.Assert(err, nil)
|
||||
|
||||
one, err := db.Model(table).FindOne(1)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(one["passport"], data.Passport)
|
||||
t.Assert(one["create_time"], data.CreateTime)
|
||||
t.Assert(one["nickname"], data.Nickname)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_Update_KeyFieldNameMapping(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type User struct {
|
||||
Id int
|
||||
Passport string
|
||||
Password string
|
||||
Nickname string
|
||||
CreateTime string
|
||||
}
|
||||
data := User{
|
||||
Id: 1,
|
||||
Passport: "user_10",
|
||||
Password: "pass_10",
|
||||
Nickname: "name_10",
|
||||
CreateTime: "2020-10-10 12:00:01",
|
||||
}
|
||||
_, err := db.Model(table).Data(data).WherePri(1).Update()
|
||||
t.Assert(err, nil)
|
||||
|
||||
one, err := db.Model(table).FindOne(1)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(one["passport"], data.Passport)
|
||||
t.Assert(one["create_time"], data.CreateTime)
|
||||
t.Assert(one["nickname"], data.Nickname)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_Insert_KeyFieldNameMapping_Error(t *testing.T) {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type User struct {
|
||||
Id int
|
||||
Passport string
|
||||
Password string
|
||||
Nickname string
|
||||
CreateTime string
|
||||
NoneExistFiled string
|
||||
}
|
||||
data := User{
|
||||
Id: 1,
|
||||
Passport: "user_1",
|
||||
Password: "pass_1",
|
||||
Nickname: "name_1",
|
||||
CreateTime: "2020-10-10 12:00:01",
|
||||
}
|
||||
_, err := db.Model(table).Data(data).Insert()
|
||||
t.AssertNE(err, nil)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_Insert_Time(t *testing.T) {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
data := g.Map{
|
||||
"id": 1,
|
||||
"passport": "t1",
|
||||
"password": "p1",
|
||||
"nickname": "n1",
|
||||
"create_time": "2020-10-10 20:09:18.334",
|
||||
}
|
||||
_, err := db.Table(table).Data(data).Insert()
|
||||
t.Assert(err, nil)
|
||||
|
||||
one, err := db.Table(table).One("id", 1)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(one["passport"], data["passport"])
|
||||
t.Assert(one["create_time"], "2020-10-10 20:09:18")
|
||||
t.Assert(one["nickname"], data["nickname"])
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_BatchInsertWithArrayStruct(t *testing.T) {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
user := db.Table(table)
|
||||
array := garray.New()
|
||||
for i := 1; i <= SIZE; i++ {
|
||||
array.Append(g.Map{
|
||||
"id": i,
|
||||
"uid": i,
|
||||
"passport": fmt.Sprintf("t%d", i),
|
||||
"password": "25d55ad283aa400af464c76d713c07ad",
|
||||
"nickname": fmt.Sprintf("name_%d", i),
|
||||
"create_time": gtime.Now().String(),
|
||||
})
|
||||
}
|
||||
|
||||
result, err := user.Filter().Data(array).Insert()
|
||||
t.Assert(err, nil)
|
||||
n, _ := result.LastInsertId()
|
||||
t.Assert(n, SIZE)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_InsertIgnore(t *testing.T) {
|
||||
@ -229,7 +389,7 @@ func Test_Model_Update(t *testing.T) {
|
||||
defer dropTable(table)
|
||||
// UPDATE...LIMIT
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, err := db.Table(table).Data("nickname", "T100").Order("id desc").Limit(2).Update()
|
||||
result, err := db.Table(table).Data("nickname", "T100").Where(1).Order("id desc").Limit(2).Update()
|
||||
t.Assert(err, nil)
|
||||
n, _ := result.RowsAffected()
|
||||
t.Assert(n, 2)
|
||||
@ -605,6 +765,32 @@ func Test_Model_Count(t *testing.T) {
|
||||
t.Assert(err, nil)
|
||||
t.Assert(count, SIZE)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
count, err := db.Table(table).FieldsEx("id").Where("id>8").Count()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(count, 2)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
count, err := db.Table(table).Fields("distinct id,nickname").Where("id>8").Count()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(count, 2)
|
||||
})
|
||||
// COUNT...LIMIT...
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
count, err := db.Table(table).Page(1, 2).Count()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(count, SIZE)
|
||||
})
|
||||
//gtest.C(t, func(t *gtest.T) {
|
||||
// count, err := db.Table(table).Fields("id myid").Where("id>8").Count()
|
||||
// t.Assert(err, nil)
|
||||
// t.Assert(count, 2)
|
||||
//})
|
||||
//gtest.C(t, func(t *gtest.T) {
|
||||
// count, err := db.Table(table).As("u1").LeftJoin(table, "u2", "u2.id=u1.id").Fields("u2.id u2id").Where("u1.id>8").Count()
|
||||
// t.Assert(err, nil)
|
||||
// t.Assert(count, 2)
|
||||
//})
|
||||
}
|
||||
|
||||
func Test_Model_FindCount(t *testing.T) {
|
||||
@ -1024,6 +1210,21 @@ func Test_Model_Where(t *testing.T) {
|
||||
t.AssertGT(len(result), 0)
|
||||
t.Assert(result["id"].Int(), 3)
|
||||
})
|
||||
|
||||
// slice
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, err := db.Table(table).Where(g.Slice{"id", 3}).One()
|
||||
t.Assert(err, nil)
|
||||
t.AssertGT(len(result), 0)
|
||||
t.Assert(result["id"].Int(), 3)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, err := db.Table(table).Where(g.Slice{"id", 3, "nickname", "name_3"}).One()
|
||||
t.Assert(err, nil)
|
||||
t.AssertGT(len(result), 0)
|
||||
t.Assert(result["id"].Int(), 3)
|
||||
})
|
||||
|
||||
// slice parameter
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, err := db.Table(table).Where("id=? and nickname=?", g.Slice{3, "name_3"}).One()
|
||||
@ -1589,14 +1790,14 @@ func Test_Model_Delete(t *testing.T) {
|
||||
|
||||
// DELETE...LIMIT
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, err := db.Table(table).Limit(2).Delete()
|
||||
result, err := db.Table(table).Where(1).Limit(2).Delete()
|
||||
t.Assert(err, nil)
|
||||
n, _ := result.RowsAffected()
|
||||
t.Assert(n, 2)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, err := db.Table(table).Delete()
|
||||
result, err := db.Table(table).Where(1).Delete()
|
||||
t.Assert(err, nil)
|
||||
n, _ := result.RowsAffected()
|
||||
t.Assert(n, SIZE-2)
|
||||
@ -1856,7 +2057,7 @@ func Test_Model_Option_Where(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
r, err := db.Table(table).OmitEmpty().Data("nickname", 1).Where(g.Map{"id": 0, "passport": ""}).Update()
|
||||
r, err := db.Table(table).OmitEmpty().Data("nickname", 1).Where(g.Map{"id": 0, "passport": ""}).And(1).Update()
|
||||
t.Assert(err, nil)
|
||||
n, _ := r.RowsAffected()
|
||||
t.Assert(n, SIZE)
|
||||
@ -1935,6 +2136,19 @@ func Test_Model_FieldsEx(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_FieldsEx_WithReservedWords(t *testing.T) {
|
||||
table := "fieldsex_test_table"
|
||||
sqlTpcPath := gdebug.TestDataPath("reservedwords_table_tpl.sql")
|
||||
if _, err := db.Exec(fmt.Sprintf(gfile.GetContents(sqlTpcPath), table)); err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
defer dropTable(table)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
_, err := db.Table(table).FieldsEx("content").One()
|
||||
t.Assert(err, nil)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_FieldsStr(t *testing.T) {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
@ -2243,6 +2457,19 @@ func Test_Model_DryRun(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_Join_SubQuery(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
subQuery := fmt.Sprintf("select * from `%s`", table)
|
||||
r, err := db.Table(table, "t1").Fields("t2.id").LeftJoin(subQuery, "t2", "t2.id=t1.id").Array()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(len(r), SIZE)
|
||||
t.Assert(r[0], "1")
|
||||
t.Assert(r[SIZE-1], SIZE)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_Cache(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
@ -2395,6 +2622,101 @@ func Test_Model_Min_Max(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_Fields_AutoMapping(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
value, err := db.Table(table).Fields("ID").Where("id", 2).Value()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(value.Int(), 2)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
value, err := db.Table(table).Fields("NICK_NAME").Where("id", 2).Value()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(value.String(), "name_2")
|
||||
})
|
||||
// Map
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
one, err := db.Table(table).Fields(g.Map{
|
||||
"ID": 1,
|
||||
"NICK_NAME": 1,
|
||||
}).Where("id", 2).One()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(len(one), 2)
|
||||
t.Assert(one["id"], 2)
|
||||
t.Assert(one["nickname"], "name_2")
|
||||
})
|
||||
// Struct
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type T struct {
|
||||
ID int
|
||||
NICKNAME int
|
||||
}
|
||||
one, err := db.Table(table).Fields(&T{
|
||||
ID: 0,
|
||||
NICKNAME: 0,
|
||||
}).Where("id", 2).One()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(len(one), 2)
|
||||
t.Assert(one["id"], 2)
|
||||
t.Assert(one["nickname"], "name_2")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_FieldsEx_AutoMapping(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
// "id": i,
|
||||
// "passport": fmt.Sprintf(`user_%d`, i),
|
||||
// "password": fmt.Sprintf(`pass_%d`, i),
|
||||
// "nickname": fmt.Sprintf(`name_%d`, i),
|
||||
// "create_time": gtime.NewFromStr("2018-10-24 10:00:00").String(),
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
value, err := db.Table(table).FieldsEx("Passport, Password, NickName, CreateTime").Where("id", 2).Value()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(value.Int(), 2)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
value, err := db.Table(table).FieldsEx("ID, Passport, Password, CreateTime").Where("id", 2).Value()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(value.String(), "name_2")
|
||||
})
|
||||
// Map
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
one, err := db.Table(table).FieldsEx(g.Map{
|
||||
"Passport": 1,
|
||||
"Password": 1,
|
||||
"CreateTime": 1,
|
||||
}).Where("id", 2).One()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(len(one), 2)
|
||||
t.Assert(one["id"], 2)
|
||||
t.Assert(one["nickname"], "name_2")
|
||||
})
|
||||
// Struct
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type T struct {
|
||||
Passport int
|
||||
Password int
|
||||
CreateTime int
|
||||
}
|
||||
one, err := db.Table(table).FieldsEx(&T{
|
||||
Passport: 0,
|
||||
Password: 0,
|
||||
CreateTime: 0,
|
||||
}).Where("id", 2).One()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(len(one), 2)
|
||||
t.Assert(one["id"], 2)
|
||||
t.Assert(one["nickname"], "name_2")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_NullField(t *testing.T) {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
|
||||
@ -151,7 +151,7 @@ CREATE TABLE %s (
|
||||
}
|
||||
|
||||
func Test_SoftUpdateTime(t *testing.T) {
|
||||
table := "time_test_table"
|
||||
table := "time_test_table_" + gtime.TimestampNanoStr()
|
||||
if _, err := db.Exec(fmt.Sprintf(`
|
||||
CREATE TABLE %s (
|
||||
id int(11) NOT NULL,
|
||||
|
||||
@ -676,7 +676,7 @@ func Test_TX_Delete(t *testing.T) {
|
||||
if err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
if _, err := tx.Delete(table, nil); err != nil {
|
||||
if _, err := tx.Delete(table, 1); err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
if err := tx.Commit(); err != nil {
|
||||
@ -696,7 +696,7 @@ func Test_TX_Delete(t *testing.T) {
|
||||
if err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
if _, err := tx.Delete(table, nil); err != nil {
|
||||
if _, err := tx.Delete(table, 1); err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
if n, err := tx.Table(table).Count(); err != nil {
|
||||
@ -764,3 +764,31 @@ func Test_Transaction(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Transaction_Panic(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
err := db.Transaction(func(tx *gdb.TX) error {
|
||||
if _, err := tx.Replace(table, g.Map{
|
||||
"id": 1,
|
||||
"passport": "USER_1",
|
||||
"password": "PASS_1",
|
||||
"nickname": "NAME_1",
|
||||
"create_time": gtime.Now().String(),
|
||||
}); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
panic("error")
|
||||
return nil
|
||||
})
|
||||
t.AssertNE(err, nil)
|
||||
|
||||
if value, err := db.Table(table).Fields("nickname").Where("id", 1).Value(); err != nil {
|
||||
gtest.Error(err)
|
||||
} else {
|
||||
t.Assert(value.String(), "name_1")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -16,7 +16,6 @@ import (
|
||||
)
|
||||
|
||||
func Test_Types(t *testing.T) {
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
if _, err := db.Exec(fmt.Sprintf(`
|
||||
CREATE TABLE IF NOT EXISTS types (
|
||||
@ -32,8 +31,16 @@ func Test_Types(t *testing.T) {
|
||||
%s bool NOT NULL,
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
`, "`blob`", "`binary`", "`date`", "`time`",
|
||||
"`decimal`", "`double`", "`bit`", "`tinyint`", "`bool`")); err != nil {
|
||||
`,
|
||||
"`blob`",
|
||||
"`binary`",
|
||||
"`date`",
|
||||
"`time`",
|
||||
"`decimal`",
|
||||
"`double`",
|
||||
"`bit`",
|
||||
"`tinyint`",
|
||||
"`bool`")); err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
defer dropTable("types")
|
||||
@ -41,7 +48,7 @@ func Test_Types(t *testing.T) {
|
||||
"id": 1,
|
||||
"blob": "i love gf",
|
||||
"binary": []byte("abcdefgh"),
|
||||
"date": "2018-10-24",
|
||||
"date": "1880-10-24",
|
||||
"time": "10:00:01",
|
||||
"decimal": -123.456,
|
||||
"double": -123.456,
|
||||
|
||||
49
database/gdb/gdb_z_oracle_internal_test.go
Normal file
49
database/gdb/gdb_z_oracle_internal_test.go
Normal file
@ -0,0 +1,49 @@
|
||||
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gdb
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_Oracle_parseSql(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
o := new(DriverOracle)
|
||||
sql := `UPDATE user SET name='john'`
|
||||
newSql := o.parseSql(sql)
|
||||
t.Assert(newSql, sql)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
o := new(DriverOracle)
|
||||
sql := `SELECT * FROM user`
|
||||
newSql := o.parseSql(sql)
|
||||
t.Assert(newSql, sql)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
o := new(DriverOracle)
|
||||
sql := `SELECT * FROM user LIMIT 0, 10`
|
||||
newSql := o.parseSql(sql)
|
||||
t.Assert(newSql, `SELECT * FROM (SELECT GFORM.*, ROWNUM ROWNUM_ FROM (SELECT * FROM user ) GFORM WHERE ROWNUM <= 10) WHERE ROWNUM_ >= 0`)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
o := new(DriverOracle)
|
||||
sql := `SELECT * FROM user LIMIT 1`
|
||||
newSql := o.parseSql(sql)
|
||||
t.Assert(newSql, `SELECT * FROM (SELECT GFORM.*, ROWNUM ROWNUM_ FROM (SELECT * FROM user ) GFORM WHERE ROWNUM <= 1) WHERE ROWNUM_ >= 0`)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
o := new(DriverOracle)
|
||||
sql := `SELECT ENAME FROM USER_INFO WHERE ID=2 LIMIT 1`
|
||||
newSql := o.parseSql(sql)
|
||||
t.Assert(newSql, `SELECT * FROM (SELECT GFORM.*, ROWNUM ROWNUM_ FROM (SELECT ENAME FROM USER_INFO WHERE ID=2 ) GFORM WHERE ROWNUM <= 1) WHERE ROWNUM_ >= 0`)
|
||||
})
|
||||
}
|
||||
20
database/gdb/testdata/reservedwords_table_tpl.sql
vendored
Normal file
20
database/gdb/testdata/reservedwords_table_tpl.sql
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
CREATE TABLE %s (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`key` varchar(45) DEFAULT NULL,
|
||||
`category_id` int(10) unsigned NOT NULL,
|
||||
`user_id` int(10) unsigned NOT NULL,
|
||||
`title` varchar(255) NOT NULL,
|
||||
`content` mediumtext NOT NULL,
|
||||
`sort` int(10) unsigned DEFAULT '0',
|
||||
`brief` varchar(255) DEFAULT NULL,
|
||||
`thumb` varchar(255) DEFAULT NULL,
|
||||
`tags` varchar(900) DEFAULT NULL,
|
||||
`referer` varchar(255) DEFAULT NULL,
|
||||
`status` smallint(5) unsigned DEFAULT '0',
|
||||
`view_count` int(10) unsigned DEFAULT '0',
|
||||
`zan_count` int(10) unsigned DEFAULT NULL,
|
||||
`cai_count` int(10) unsigned DEFAULT NULL,
|
||||
`created_at` datetime DEFAULT NULL,
|
||||
`updated_at` datetime DEFAULT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
29
debug/gdebug/gdebug_grid.go
Normal file
29
debug/gdebug/gdebug_grid.go
Normal file
@ -0,0 +1,29 @@
|
||||
// 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 (
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var (
|
||||
// gridRegex is the regular expression object for parsing goroutine id from stack information.
|
||||
gridRegex = regexp.MustCompile(`^\w+\s+(\d+)\s+`)
|
||||
)
|
||||
|
||||
// GoroutineId retrieves and returns the current goroutine id from stack information.
|
||||
// Be very aware that, it is with low performance as it uses runtime.Stack function.
|
||||
// It is commonly used for debugging purpose.
|
||||
func GoroutineId() int {
|
||||
buf := make([]byte, 26)
|
||||
runtime.Stack(buf, false)
|
||||
match := gridRegex.FindSubmatch(buf)
|
||||
id, _ := strconv.Atoi(string(match[1]))
|
||||
return id
|
||||
}
|
||||
@ -68,6 +68,7 @@ func StackWithFilters(filters []string, skip ...int) string {
|
||||
file[0:len(goRootForFilter)] == goRootForFilter {
|
||||
continue
|
||||
}
|
||||
// Custom filtering.
|
||||
filtered = false
|
||||
for _, filter := range filters {
|
||||
if filter != "" && strings.Contains(file, filter) {
|
||||
|
||||
@ -9,32 +9,33 @@ package ghtml
|
||||
|
||||
import (
|
||||
"html"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
strip "github.com/grokify/html-strip-tags-go"
|
||||
)
|
||||
|
||||
// 过滤掉HTML标签,只返回text内容
|
||||
// 参考:http://php.net/manual/zh/function.strip-tags.php
|
||||
// StripTags strips HTML tags from content, and returns only text.
|
||||
// Referer: http://php.net/manual/zh/function.strip-tags.php
|
||||
func StripTags(s string) string {
|
||||
return strip.StripTags(s)
|
||||
}
|
||||
|
||||
// 本函数各方面都和SpecialChars一样,
|
||||
// 除了Entities会转换所有具有 HTML 实体的字符。
|
||||
// 参考:http://php.net/manual/zh/function.htmlentities.php
|
||||
// Entities encodes all HTML chars for content.
|
||||
// Referer: http://php.net/manual/zh/function.htmlentities.php
|
||||
func Entities(s string) string {
|
||||
return html.EscapeString(s)
|
||||
}
|
||||
|
||||
// Entities 的相反操作
|
||||
// 参考:http://php.net/manual/zh/function.html-entity-decode.php
|
||||
// EntitiesDecode decodes all HTML chars for content.
|
||||
// Referer: http://php.net/manual/zh/function.html-entity-decode.php
|
||||
func EntitiesDecode(s string) string {
|
||||
return html.UnescapeString(s)
|
||||
}
|
||||
|
||||
// 将html中的部分特殊标签转换为html转义标签
|
||||
// 参考:http://php.net/manual/zh/function.htmlspecialchars.php
|
||||
// SpecialChars encodes some special chars for content, these special chars are:
|
||||
// "&", "<", ">", `"`, "'".
|
||||
// Referer: http://php.net/manual/zh/function.htmlspecialchars.php
|
||||
func SpecialChars(s string) string {
|
||||
return strings.NewReplacer(
|
||||
"&", "&",
|
||||
@ -45,8 +46,9 @@ func SpecialChars(s string) string {
|
||||
).Replace(s)
|
||||
}
|
||||
|
||||
// 将html部分转义标签还原为html特殊标签
|
||||
// 参考:http://php.net/manual/zh/function.htmlspecialchars-decode.php
|
||||
// SpecialCharsDecode decodes some special chars for content, these special chars are:
|
||||
// "&", "<", ">", `"`, "'".
|
||||
// Referer: http://php.net/manual/zh/function.htmlspecialchars-decode.php
|
||||
func SpecialCharsDecode(s string) string {
|
||||
return strings.NewReplacer(
|
||||
"&", "&",
|
||||
@ -56,3 +58,46 @@ func SpecialCharsDecode(s string) string {
|
||||
"'", "'",
|
||||
).Replace(s)
|
||||
}
|
||||
|
||||
// SpecialCharsMapOrStruct automatically encodes string values/attributes for map/struct.
|
||||
func SpecialCharsMapOrStruct(mapOrStruct interface{}) error {
|
||||
var (
|
||||
reflectValue = reflect.ValueOf(mapOrStruct)
|
||||
reflectKind = reflectValue.Kind()
|
||||
)
|
||||
for reflectValue.IsValid() && (reflectKind == reflect.Ptr || reflectKind == reflect.Interface) {
|
||||
reflectValue = reflectValue.Elem()
|
||||
reflectKind = reflectValue.Kind()
|
||||
}
|
||||
switch reflectKind {
|
||||
case reflect.Map:
|
||||
var (
|
||||
mapKeys = reflectValue.MapKeys()
|
||||
mapValue reflect.Value
|
||||
)
|
||||
for _, key := range mapKeys {
|
||||
mapValue = reflectValue.MapIndex(key)
|
||||
switch mapValue.Kind() {
|
||||
case reflect.String:
|
||||
reflectValue.SetMapIndex(key, reflect.ValueOf(SpecialChars(mapValue.String())))
|
||||
case reflect.Interface:
|
||||
if mapValue.Elem().Kind() == reflect.String {
|
||||
reflectValue.SetMapIndex(key, reflect.ValueOf(SpecialChars(mapValue.Elem().String())))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case reflect.Struct:
|
||||
var (
|
||||
fieldValue reflect.Value
|
||||
)
|
||||
for i := 0; i < reflectValue.NumField(); i++ {
|
||||
fieldValue = reflectValue.Field(i)
|
||||
switch fieldValue.Kind() {
|
||||
case reflect.String:
|
||||
fieldValue.Set(reflect.ValueOf(SpecialChars(fieldValue.String())))
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -3,16 +3,18 @@
|
||||
// This 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 ghtml_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/encoding/ghtml"
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
)
|
||||
|
||||
func TestStripTags(t *testing.T) {
|
||||
func Test_StripTags(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
src := `<p>Test paragraph.</p><!-- Comment --> <a href="#fragment">Other text</a>`
|
||||
dst := `Test paragraph. Other text`
|
||||
@ -20,7 +22,7 @@ func TestStripTags(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestEntities(t *testing.T) {
|
||||
func Test_Entities(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
src := `A 'quote' "is" <b>bold</b>`
|
||||
dst := `A 'quote' "is" <b>bold</b>`
|
||||
@ -29,7 +31,7 @@ func TestEntities(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestSpecialChars(t *testing.T) {
|
||||
func Test_SpecialChars(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
src := `A 'quote' "is" <b>bold</b>`
|
||||
dst := `A 'quote' "is" <b>bold</b>`
|
||||
@ -37,3 +39,43 @@ func TestSpecialChars(t *testing.T) {
|
||||
t.Assert(ghtml.SpecialCharsDecode(dst), src)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_SpecialCharsMapOrStruct_Map(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
a := g.Map{
|
||||
"Title": "<h1>T</h1>",
|
||||
"Content": "<div>C</div>",
|
||||
}
|
||||
err := ghtml.SpecialCharsMapOrStruct(a)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(a["Title"], `<h1>T</h1>`)
|
||||
t.Assert(a["Content"], `<div>C</div>`)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
a := g.MapStrStr{
|
||||
"Title": "<h1>T</h1>",
|
||||
"Content": "<div>C</div>",
|
||||
}
|
||||
err := ghtml.SpecialCharsMapOrStruct(a)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(a["Title"], `<h1>T</h1>`)
|
||||
t.Assert(a["Content"], `<div>C</div>`)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_SpecialCharsMapOrStruct_Struct(t *testing.T) {
|
||||
type A struct {
|
||||
Title string
|
||||
Content string
|
||||
}
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
a := &A{
|
||||
Title: "<h1>T</h1>",
|
||||
Content: "<div>C</div>",
|
||||
}
|
||||
err := ghtml.SpecialCharsMapOrStruct(a)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(a.Title, `<h1>T</h1>`)
|
||||
t.Assert(a.Content, `<div>C</div>`)
|
||||
})
|
||||
}
|
||||
|
||||
@ -86,6 +86,7 @@ func (j *Json) setValue(pattern string, value interface{}, removed bool) error {
|
||||
}
|
||||
|
||||
case []interface{}:
|
||||
// A string key.
|
||||
if !gstr.IsNumeric(array[i]) {
|
||||
if i == length-1 {
|
||||
*pointer = map[string]interface{}{array[i]: value}
|
||||
@ -97,23 +98,24 @@ func (j *Json) setValue(pattern string, value interface{}, removed bool) error {
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
valn, err := strconv.Atoi(array[i])
|
||||
// Numeric index.
|
||||
valueNum, err := strconv.Atoi(array[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Leaf node.
|
||||
|
||||
if i == length-1 {
|
||||
if len((*pointer).([]interface{})) > valn {
|
||||
// Leaf node.
|
||||
if len((*pointer).([]interface{})) > valueNum {
|
||||
if removed && value == nil {
|
||||
// Deleting element.
|
||||
if pparent == nil {
|
||||
*pointer = append((*pointer).([]interface{})[:valn], (*pointer).([]interface{})[valn+1:]...)
|
||||
*pointer = append((*pointer).([]interface{})[:valueNum], (*pointer).([]interface{})[valueNum+1:]...)
|
||||
} else {
|
||||
j.setPointerWithValue(pparent, array[i-1], append((*pointer).([]interface{})[:valn], (*pointer).([]interface{})[valn+1:]...))
|
||||
j.setPointerWithValue(pparent, array[i-1], append((*pointer).([]interface{})[:valueNum], (*pointer).([]interface{})[valueNum+1:]...))
|
||||
}
|
||||
} else {
|
||||
(*pointer).([]interface{})[valn] = value
|
||||
(*pointer).([]interface{})[valueNum] = value
|
||||
}
|
||||
} else {
|
||||
if removed && value == nil {
|
||||
@ -124,19 +126,33 @@ func (j *Json) setValue(pattern string, value interface{}, removed bool) error {
|
||||
j.setPointerWithValue(pointer, array[i], value)
|
||||
} else {
|
||||
// It is not the root node.
|
||||
s := make([]interface{}, valn+1)
|
||||
s := make([]interface{}, valueNum+1)
|
||||
copy(s, (*pointer).([]interface{}))
|
||||
s[valn] = value
|
||||
s[valueNum] = value
|
||||
j.setPointerWithValue(pparent, array[i-1], s)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Branch node.
|
||||
if gstr.IsNumeric(array[i+1]) {
|
||||
n, _ := strconv.Atoi(array[i+1])
|
||||
if len((*pointer).([]interface{})) > valn {
|
||||
(*pointer).([]interface{})[valn] = make([]interface{}, n+1)
|
||||
pparent = pointer
|
||||
pointer = &(*pointer).([]interface{})[valn]
|
||||
pSlice := (*pointer).([]interface{})
|
||||
if len(pSlice) > valueNum {
|
||||
item := pSlice[valueNum]
|
||||
if s, ok := item.([]interface{}); ok {
|
||||
for i := 0; i < n-len(s); i++ {
|
||||
s = append(s, nil)
|
||||
}
|
||||
pparent = pointer
|
||||
pointer = &pSlice[valueNum]
|
||||
} else {
|
||||
if removed && value == nil {
|
||||
goto done
|
||||
}
|
||||
var v interface{} = make([]interface{}, n+1)
|
||||
pparent = j.setPointerWithValue(pointer, array[i], v)
|
||||
pointer = &v
|
||||
}
|
||||
} else {
|
||||
if removed && value == nil {
|
||||
goto done
|
||||
@ -146,14 +162,26 @@ func (j *Json) setValue(pattern string, value interface{}, removed bool) error {
|
||||
pointer = &v
|
||||
}
|
||||
} else {
|
||||
v := (*pointer).([]interface{})
|
||||
if len(v) > valn {
|
||||
pSlice := (*pointer).([]interface{})
|
||||
if len(pSlice) > valueNum {
|
||||
pparent = pointer
|
||||
pointer = &(*pointer).([]interface{})[valn]
|
||||
pointer = &(*pointer).([]interface{})[valueNum]
|
||||
} else {
|
||||
var v interface{} = make(map[string]interface{})
|
||||
pparent = j.setPointerWithValue(pointer, array[i], v)
|
||||
pointer = &v
|
||||
s := make([]interface{}, valueNum+1)
|
||||
copy(s, pSlice)
|
||||
s[valueNum] = make(map[string]interface{})
|
||||
if pparent != nil {
|
||||
// i > 0
|
||||
j.setPointerWithValue(pparent, array[i-1], s)
|
||||
pparent = pointer
|
||||
pointer = &s[valueNum]
|
||||
} else {
|
||||
// i = 0
|
||||
var v interface{} = s
|
||||
*pointer = v
|
||||
pparent = pointer
|
||||
pointer = &s[valueNum]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -177,19 +205,24 @@ func (j *Json) setValue(pattern string, value interface{}, removed bool) error {
|
||||
pparent = pointer
|
||||
}
|
||||
} else {
|
||||
var v interface{} = make(map[string]interface{})
|
||||
var v1, v2 interface{}
|
||||
if i == length-1 {
|
||||
v = map[string]interface{}{
|
||||
v1 = map[string]interface{}{
|
||||
array[i]: value,
|
||||
}
|
||||
} else {
|
||||
v1 = map[string]interface{}{
|
||||
array[i]: nil,
|
||||
}
|
||||
}
|
||||
if pparent != nil {
|
||||
pparent = j.setPointerWithValue(pparent, array[i-1], v)
|
||||
pparent = j.setPointerWithValue(pparent, array[i-1], v1)
|
||||
} else {
|
||||
*pointer = v
|
||||
*pointer = v1
|
||||
pparent = pointer
|
||||
}
|
||||
pointer = &v
|
||||
v2 = v1.(map[string]interface{})[array[i]]
|
||||
pointer = &v2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -315,6 +315,7 @@ func (j *Json) GetStruct(pattern string, pointer interface{}, mapping ...map[str
|
||||
}
|
||||
|
||||
// GetStructDeep does GetStruct recursively.
|
||||
// Deprecated, use GetStruct instead.
|
||||
func (j *Json) GetStructDeep(pattern string, pointer interface{}, mapping ...map[string]string) error {
|
||||
return gconv.StructDeep(j.Get(pattern), pointer, mapping...)
|
||||
}
|
||||
@ -325,6 +326,7 @@ func (j *Json) GetStructs(pattern string, pointer interface{}, mapping ...map[st
|
||||
}
|
||||
|
||||
// GetStructsDeep converts any slice to given struct slice recursively.
|
||||
// Deprecated, use GetStructs instead.
|
||||
func (j *Json) GetStructsDeep(pattern string, pointer interface{}, mapping ...map[string]string) error {
|
||||
return gconv.StructsDeep(j.Get(pattern), pointer, mapping...)
|
||||
}
|
||||
@ -394,6 +396,7 @@ func (j *Json) ToStruct(pointer interface{}, mapping ...map[string]string) error
|
||||
|
||||
// ToStructDeep converts current Json object to specified object recursively.
|
||||
// The <pointer> should be a pointer type of *struct.
|
||||
// Deprecated, use ToStruct instead.
|
||||
func (j *Json) ToStructDeep(pointer interface{}, mapping ...map[string]string) error {
|
||||
j.mu.RLock()
|
||||
defer j.mu.RUnlock()
|
||||
|
||||
@ -10,9 +10,10 @@ import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"reflect"
|
||||
|
||||
"github.com/gogf/gf/internal/json"
|
||||
|
||||
"github.com/gogf/gf/encoding/gini"
|
||||
"github.com/gogf/gf/encoding/gtoml"
|
||||
"github.com/gogf/gf/encoding/gxml"
|
||||
@ -54,8 +55,10 @@ func NewWithTag(data interface{}, tags string, safe ...bool) *Json {
|
||||
}
|
||||
}
|
||||
default:
|
||||
rv := reflect.ValueOf(data)
|
||||
kind := rv.Kind()
|
||||
var (
|
||||
rv = reflect.ValueOf(data)
|
||||
kind = rv.Kind()
|
||||
)
|
||||
if kind == reflect.Ptr {
|
||||
rv = rv.Elem()
|
||||
kind = rv.Kind()
|
||||
@ -71,9 +74,7 @@ func NewWithTag(data interface{}, tags string, safe ...bool) *Json {
|
||||
}
|
||||
case reflect.Map, reflect.Struct:
|
||||
i := interface{}(nil)
|
||||
// Note that it uses Map function implementing the converting.
|
||||
// Note that it here should not use MapDeep function if you really know what it means.
|
||||
i = gconv.Map(data, tags)
|
||||
i = gconv.MapDeep(data, tags)
|
||||
j = &Json{
|
||||
p: &i,
|
||||
c: byte(gDEFAULT_SPLIT_CHAR),
|
||||
@ -188,22 +189,60 @@ func LoadContent(data interface{}, safe ...bool) (*Json, error) {
|
||||
if len(content) == 0 {
|
||||
return New(nil, safe...), nil
|
||||
}
|
||||
return doLoadContent(checkDataType(content), content, safe...)
|
||||
return LoadContentType(checkDataType(content), content, safe...)
|
||||
}
|
||||
|
||||
// LoadContentType creates a Json object from given type and content,
|
||||
// supporting data content type as follows:
|
||||
// JSON, XML, INI, YAML and TOML.
|
||||
func LoadContentType(dataType string, data interface{}, safe ...bool) (*Json, error) {
|
||||
content := gconv.Bytes(data)
|
||||
if len(content) == 0 {
|
||||
return New(nil, safe...), nil
|
||||
}
|
||||
//ignore UTF8-BOM
|
||||
if content[0] == 0xEF && content[1] == 0xBB && content[2] == 0xBF {
|
||||
content = content[3:]
|
||||
}
|
||||
return doLoadContent(dataType, content, safe...)
|
||||
}
|
||||
|
||||
// IsValidDataType checks and returns whether given <dataType> a valid data type for loading.
|
||||
func IsValidDataType(dataType string) bool {
|
||||
if dataType == "" {
|
||||
return false
|
||||
}
|
||||
if dataType[0] == '.' {
|
||||
dataType = dataType[1:]
|
||||
}
|
||||
switch dataType {
|
||||
case "json", "js", "xml", "yaml", "yml", "toml", "ini":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// checkDataType automatically checks and returns the data type for <content>.
|
||||
// Note that it uses regular expression for loose checking, you can use LoadXXX/LoadContentType
|
||||
// functions to load the content for certain content type.
|
||||
func checkDataType(content []byte) string {
|
||||
if json.Valid(content) {
|
||||
return "json"
|
||||
} else if gregex.IsMatch(`^<.+>[\S\s]+<.+>$`, content) {
|
||||
} else if gregex.IsMatch(`^<.+>[\S\s]+<.+>\s*$`, content) {
|
||||
return "xml"
|
||||
} else if gregex.IsMatch(`^[\s\t]*[\w\-]+\s*:\s*.+`, content) || gregex.IsMatch(`\n[\s\t]*[\w\-]+\s*:\s*.+`, content) {
|
||||
} else if !gregex.IsMatch(`[\n\r]*[\s\t\w\-\."]+\s*=\s*"""[\s\S]+"""`, content) && !gregex.IsMatch(`[\n\r]*[\s\t\w\-\."]+\s*=\s*'''[\s\S]+'''`, content) &&
|
||||
((gregex.IsMatch(`^[\n\r]*[\w\-\s\t]+\s*:\s*".+"`, content) || gregex.IsMatch(`^[\n\r]*[\w\-\s\t]+\s*:\s*\w+`, content)) ||
|
||||
(gregex.IsMatch(`[\n\r]+[\w\-\s\t]+\s*:\s*".+"`, content) || gregex.IsMatch(`[\n\r]+[\w\-\s\t]+\s*:\s*\w+`, content))) {
|
||||
return "yml"
|
||||
} else if (gregex.IsMatch(`^[\s\t\[*\]].?*[\w\-]+\s*=\s*.+`, content) || gregex.IsMatch(`\n[\s\t\[*\]]*[\w\-]+\s*=\s*.+`, content)) && gregex.IsMatch(`\n[\s\t]*[\w\-]+\s*=*\"*.+\"`, content) == false && gregex.IsMatch(`^[\s\t]*[\w\-]+\s*=*\"*.+\"`, content) == false {
|
||||
return "ini"
|
||||
} else if gregex.IsMatch(`^[\s\t]*[\w\-\."]+\s*=\s*.+`, content) || gregex.IsMatch(`\n[\s\t]*[\w\-\."]+\s*=\s*.+`, content) {
|
||||
} else if !gregex.IsMatch(`^[\s\t\n\r]*;.+`, content) &&
|
||||
!gregex.IsMatch(`[\s\t\n\r]+;.+`, content) &&
|
||||
!gregex.IsMatch(`[\n\r]+[\s\t\w\-]+\.[\s\t\w\-]+\s*=\s*.+`, content) &&
|
||||
(gregex.IsMatch(`[\n\r]*[\s\t\w\-\."]+\s*=\s*".+"`, content) || gregex.IsMatch(`[\n\r]*[\s\t\w\-\."]+\s*=\s*\w+`, content)) {
|
||||
return "toml"
|
||||
} else if gregex.IsMatch(`\[[\w\.]+\]`, content) &&
|
||||
(gregex.IsMatch(`[\n\r]*[\s\t\w\-\."]+\s*=\s*".+"`, content) || gregex.IsMatch(`[\n\r]*[\s\t\w\-\."]+\s*=\s*\w+`, content)) {
|
||||
// Must contain "[xxx]" section.
|
||||
return "ini"
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
|
||||
@ -419,7 +419,7 @@ func Test_Basic(t *testing.T) {
|
||||
j = gjson.New(`[1,2,3]`)
|
||||
err = j.Remove("0.3")
|
||||
t.Assert(err, nil)
|
||||
t.Assert(len(j.Get("0").([]interface{})), 3)
|
||||
t.Assert(j.Get("0"), 1)
|
||||
|
||||
j = gjson.New(`[1,2,3]`)
|
||||
err = j.Remove("0.a")
|
||||
|
||||
133
encoding/gjson/gjson_z_unit_internal_test.go
Normal file
133
encoding/gjson/gjson_z_unit_internal_test.go
Normal file
@ -0,0 +1,133 @@
|
||||
// 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
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_checkDataType(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
data := []byte(`
|
||||
bb = """
|
||||
dig := dig; END;"""
|
||||
`)
|
||||
t.Assert(checkDataType(data), "toml")
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
data := []byte(`
|
||||
# 模板引擎目录
|
||||
viewpath = "/home/www/templates/"
|
||||
# MySQL数据库配置
|
||||
[redis]
|
||||
dd = 11
|
||||
[redis]
|
||||
disk = "127.0.0.1:6379,0"
|
||||
cache = "127.0.0.1:6379,1"
|
||||
`)
|
||||
t.Assert(checkDataType(data), "toml")
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
data := []byte(`
|
||||
"gf.gvalid.rule.required" = "The :attribute field is required"
|
||||
"gf.gvalid.rule.required-if" = "The :attribute field is required"
|
||||
"gf.gvalid.rule.required-unless" = "The :attribute field is required"
|
||||
"gf.gvalid.rule.required-with" = "The :attribute field is required"
|
||||
"gf.gvalid.rule.required-with-all" = "The :attribute field is required"
|
||||
"gf.gvalid.rule.required-without" = "The :attribute field is required"
|
||||
"gf.gvalid.rule.required-without-all" = "The :attribute field is required"
|
||||
"gf.gvalid.rule.date" = "The :attribute value is not a valid date"
|
||||
"gf.gvalid.rule.date-format" = "The :attribute value does not match the format :format"
|
||||
"gf.gvalid.rule.email" = "The :attribute value must be a valid email address"
|
||||
"gf.gvalid.rule.phone" = "The :attribute value must be a valid phone number"
|
||||
"gf.gvalid.rule.telephone" = "The :attribute value must be a valid telephone number"
|
||||
"gf.gvalid.rule.passport" = "The :attribute value is not a valid passport format"
|
||||
"gf.gvalid.rule.password" = "The :attribute value is not a valid passport format"
|
||||
"gf.gvalid.rule.password2" = "The :attribute value is not a valid passport format"
|
||||
"gf.gvalid.rule.password3" = "The :attribute value is not a valid passport format"
|
||||
"gf.gvalid.rule.postcode" = "The :attribute value is not a valid passport format"
|
||||
"gf.gvalid.rule.resident-id" = "The :attribute value is not a valid resident id number"
|
||||
"gf.gvalid.rule.bank-card" = "The :attribute value must be a valid bank card number"
|
||||
"gf.gvalid.rule.qq" = "The :attribute value must be a valid QQ number"
|
||||
"gf.gvalid.rule.ip" = "The :attribute value must be a valid IP address"
|
||||
"gf.gvalid.rule.ipv4" = "The :attribute value must be a valid IPv4 address"
|
||||
"gf.gvalid.rule.ipv6" = "The :attribute value must be a valid IPv6 address"
|
||||
"gf.gvalid.rule.mac" = "The :attribute value must be a valid MAC address"
|
||||
"gf.gvalid.rule.url" = "The :attribute value must be a valid URL address"
|
||||
"gf.gvalid.rule.domain" = "The :attribute value must be a valid domain format"
|
||||
"gf.gvalid.rule.length" = "The :attribute value length must be between :min and :max"
|
||||
"gf.gvalid.rule.min-length" = "The :attribute value length must be equal or greater than :min"
|
||||
"gf.gvalid.rule.max-length" = "The :attribute value length must be equal or lesser than :max"
|
||||
"gf.gvalid.rule.between" = "The :attribute value must be between :min and :max"
|
||||
"gf.gvalid.rule.min" = "The :attribute value must be equal or greater than :min"
|
||||
"gf.gvalid.rule.max" = "The :attribute value must be equal or lesser than :max"
|
||||
"gf.gvalid.rule.json" = "The :attribute value must be a valid JSON string"
|
||||
"gf.gvalid.rule.xml" = "The :attribute value must be a valid XML string"
|
||||
"gf.gvalid.rule.array" = "The :attribute value must be an array"
|
||||
"gf.gvalid.rule.integer" = "The :attribute value must be an integer"
|
||||
"gf.gvalid.rule.float" = "The :attribute value must be a float"
|
||||
"gf.gvalid.rule.boolean" = "The :attribute value field must be true or false"
|
||||
"gf.gvalid.rule.same" = "The :attribute value must be the same as field :field"
|
||||
"gf.gvalid.rule.different" = "The :attribute value must be different from field :field"
|
||||
"gf.gvalid.rule.in" = "The :attribute value is not in acceptable range"
|
||||
"gf.gvalid.rule.not-in" = "The :attribute value is not in acceptable range"
|
||||
"gf.gvalid.rule.regex" = "The :attribute value is invalid"
|
||||
`)
|
||||
//fmt.Println(gregex.IsMatch(`^[\s\t\n\r]*[\w\-]+\s*:\s*".+"`, data))
|
||||
//fmt.Println(gregex.IsMatch(`^[\s\t\n\r]*[\w\-]+\s*:\s*\w+`, data))
|
||||
//fmt.Println(gregex.IsMatch(`[\s\t\n\r]+[\w\-]+\s*:\s*".+"`, data))
|
||||
//fmt.Println(gregex.IsMatch(`[\n\r]+[\w\-\s\t]+\s*:\s*\w+`, data))
|
||||
//fmt.Println(gregex.MatchString(`[\n\r]+[\w\-\s\t]+\s*:\s*\w+`, string(data)))
|
||||
t.Assert(checkDataType(data), "toml")
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
data := []byte(`
|
||||
[default]
|
||||
db.engine = mysql
|
||||
db.max.idle.conns = 5
|
||||
db.max.open.conns = 100
|
||||
allow_ips =
|
||||
api.key =
|
||||
api.secret =
|
||||
enable_tls = false
|
||||
concurrency.queue = 500
|
||||
auth_secret = 63358e6f3daf0e5775ec3fb4d2516b01d41530bf30960aa76972f6ce7e08552f
|
||||
ca_file =
|
||||
cert_file =
|
||||
key_file =
|
||||
host_port = 8088
|
||||
log_path = /Users/zhaosuji/go/src/git.medlinker.com/foundations/gocron/log
|
||||
#k8s-api地址(只提供内网访问)
|
||||
k8s-inner-api = http://127.0.0.1:8081/kube/add
|
||||
conf_dir = ./config
|
||||
app_conf = ./config/app.ini
|
||||
`)
|
||||
t.Assert(checkDataType(data), "ini")
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
data := []byte(`
|
||||
# API Server
|
||||
[server]
|
||||
address = ":8199"
|
||||
|
||||
# Jenkins
|
||||
[jenkins]
|
||||
url = "https://jenkins-swimlane.com"
|
||||
nodeJsStaticBuildCmdTpl = """
|
||||
npm i --registry=https://registry.npm.taobao.org
|
||||
wget http://consul.infra:8500/v1/kv/app_{{.SwimlaneName}}/{{.RepoName}}/.env.qa?raw=true -O ./env.qa
|
||||
npm run build:qa
|
||||
"""
|
||||
`)
|
||||
t.Assert(checkDataType(data), "toml")
|
||||
})
|
||||
}
|
||||
@ -215,8 +215,7 @@ func Test_Load_Ini(t *testing.T) {
|
||||
|
||||
;注释
|
||||
|
||||
[addr]
|
||||
#注释
|
||||
[addr]
|
||||
ip = 127.0.0.1
|
||||
port=9001
|
||||
enable=true
|
||||
|
||||
@ -47,3 +47,53 @@ func Test_Load_NewWithTag(t *testing.T) {
|
||||
t.Assert(j.Get("addr-json"), nil)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Load_New_CustomStruct(t *testing.T) {
|
||||
type Base struct {
|
||||
Id int
|
||||
}
|
||||
type User struct {
|
||||
Base
|
||||
Name string
|
||||
}
|
||||
user := new(User)
|
||||
user.Id = 1
|
||||
user.Name = "john"
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
j := gjson.New(user)
|
||||
t.AssertNE(j, nil)
|
||||
|
||||
s, err := j.ToJsonString()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(s == `{"Id":1,"Name":"john"}` || s == `{"Name":"john","Id":1}`, true)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Load_New_HierarchicalStruct(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type Me struct {
|
||||
Name string `json:"name"`
|
||||
Score int `json:"score"`
|
||||
Children []Me `json:"children"`
|
||||
}
|
||||
me := Me{
|
||||
Name: "john",
|
||||
Score: 100,
|
||||
Children: []Me{
|
||||
{
|
||||
Name: "Bean",
|
||||
Score: 99,
|
||||
},
|
||||
{
|
||||
Name: "Sam",
|
||||
Score: 98,
|
||||
},
|
||||
},
|
||||
}
|
||||
j := gjson.New(me)
|
||||
t.Assert(j.Remove("children.0.score"), nil)
|
||||
t.Assert(j.Remove("children.1.score"), nil)
|
||||
t.Assert(j.MustToJsonString(), `{"children":[{"children":null,"name":"Bean"},{"children":null,"name":"Sam"}],"name":"john","score":100}`)
|
||||
})
|
||||
}
|
||||
|
||||
@ -8,6 +8,9 @@ package gjson_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/encoding/gjson"
|
||||
@ -31,17 +34,13 @@ func Test_Set1(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_Set2(t *testing.T) {
|
||||
e := []byte(`[[null,1]]`)
|
||||
p := gjson.New([]string{"a"})
|
||||
p.Set("0.1", 1)
|
||||
if c, err := p.ToJson(); err == nil {
|
||||
|
||||
if bytes.Compare(c, e) != 0 {
|
||||
t.Error("expect:", string(e))
|
||||
}
|
||||
} else {
|
||||
t.Error(err)
|
||||
}
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
e := `[[null,1]]`
|
||||
p := gjson.New([]string{"a"})
|
||||
p.Set("0.1", 1)
|
||||
s := p.MustToJsonString()
|
||||
t.Assert(s, e)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Set3(t *testing.T) {
|
||||
@ -51,7 +50,6 @@ func Test_Set3(t *testing.T) {
|
||||
"k1": "v1",
|
||||
})
|
||||
if c, err := p.ToJson(); err == nil {
|
||||
|
||||
if bytes.Compare(c, e) != 0 {
|
||||
t.Error("expect:", string(e))
|
||||
}
|
||||
@ -227,3 +225,107 @@ func Test_Set14(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Set15(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
j := gjson.New(nil)
|
||||
|
||||
t.Assert(j.Set("root.0.k1", "v1"), nil)
|
||||
t.Assert(j.Set("root.1.k2", "v2"), nil)
|
||||
t.Assert(j.Set("k", "v"), nil)
|
||||
|
||||
s, err := j.ToJsonString()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(
|
||||
gstr.Contains(s, `"root":[{"k1":"v1"},{"k2":"v2"}`) ||
|
||||
gstr.Contains(s, `"root":[{"k2":"v2"},{"k1":"v1"}`),
|
||||
true,
|
||||
)
|
||||
t.Assert(
|
||||
gstr.Contains(s, `{"k":"v"`) ||
|
||||
gstr.Contains(s, `"k":"v"}`),
|
||||
true,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Set16(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
j := gjson.New(nil)
|
||||
|
||||
t.Assert(j.Set("processors.0.set.0value", "1"), nil)
|
||||
t.Assert(j.Set("processors.0.set.0field", "2"), nil)
|
||||
t.Assert(j.Set("description", "3"), nil)
|
||||
|
||||
s, err := j.ToJsonString()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(
|
||||
gstr.Contains(s, `"processors":[{"set":{"0field":"2","0value":"1"}}]`) ||
|
||||
gstr.Contains(s, `"processors":[{"set":{"0value":"1","0field":"2"}}]`),
|
||||
true,
|
||||
)
|
||||
t.Assert(
|
||||
gstr.Contains(s, `{"description":"3"`) || gstr.Contains(s, `"description":"3"}`),
|
||||
true,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Set17(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
j := gjson.New(nil)
|
||||
|
||||
t.Assert(j.Set("0.k1", "v1"), nil)
|
||||
t.Assert(j.Set("1.k2", "v2"), nil)
|
||||
// overwrite the previous slice.
|
||||
t.Assert(j.Set("k", "v"), nil)
|
||||
|
||||
s, err := j.ToJsonString()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(s, `{"k":"v"}`)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Set18(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
j := gjson.New(nil)
|
||||
|
||||
t.Assert(j.Set("0.1.k1", "v1"), nil)
|
||||
t.Assert(j.Set("0.2.k2", "v2"), nil)
|
||||
s, err := j.ToJsonString()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(s, `[[null,{"k1":"v1"},{"k2":"v2"}]]`)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Set19(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
j := gjson.New(nil)
|
||||
|
||||
t.Assert(j.Set("0.1.1.k1", "v1"), nil)
|
||||
t.Assert(j.Set("0.2.1.k2", "v2"), nil)
|
||||
s, err := j.ToJsonString()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(s, `[[null,[null,{"k1":"v1"}],[null,{"k2":"v2"}]]]`)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Set20(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
j := gjson.New(nil)
|
||||
|
||||
t.Assert(j.Set("k1", "v1"), nil)
|
||||
t.Assert(j.Set("k2", g.Slice{1, 2, 3}), nil)
|
||||
t.Assert(j.Set("k2.1", 20), nil)
|
||||
t.Assert(j.Set("k2.2", g.Map{"k3": "v3"}), nil)
|
||||
s, err := j.ToJsonString()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(gstr.InArray(
|
||||
g.SliceStr{
|
||||
`{"k1":"v1","k2":[1,20,{"k3":"v3"}]}`,
|
||||
`{"k2":[1,20,{"k3":"v3"}],"k1":"v1"}`,
|
||||
},
|
||||
s,
|
||||
), true)
|
||||
})
|
||||
}
|
||||
|
||||
@ -203,7 +203,7 @@ func Test_ToStruct1(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_ToStructDeep(t *testing.T) {
|
||||
func Test_ToStruct(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type Item struct {
|
||||
Title string `json:"title"`
|
||||
@ -231,10 +231,73 @@ func Test_ToStructDeep(t *testing.T) {
|
||||
t.Assert(j.GetBool("items"), false)
|
||||
t.Assert(j.GetArray("items"), nil)
|
||||
m := new(M)
|
||||
err = j.ToStructDeep(m)
|
||||
err = j.ToStruct(m)
|
||||
t.Assert(err, nil)
|
||||
t.AssertNE(m.Me, nil)
|
||||
t.Assert(m.Me["day"], "20009")
|
||||
t.Assert(m.Items, nil)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_ToStruct_Complicated(t *testing.T) {
|
||||
type CertInfo struct {
|
||||
UserRealName string `json:"userRealname,omitempty"`
|
||||
IdentType string `json:"identType,omitempty"`
|
||||
IdentNo string `json:"identNo,omitempty"`
|
||||
CompanyName string `json:"companyName,omitempty"`
|
||||
Website string `json:"website,omitempty"`
|
||||
RegisterNo string `json:"registerNo,omitempty"`
|
||||
AreaCode string `json:"areaCode,omitempty"`
|
||||
Address string `json:"address,omitempty"`
|
||||
CommunityCreditCode string `json:"communityCreditCode,omitempty"`
|
||||
PhoneNumber string `json:"phoneNumber,omitempty"`
|
||||
AreaName string `json:"areaName,omitempty"`
|
||||
PhoneAreaCode string `json:"phoneAreaCode,omitempty"`
|
||||
OperateRange string `json:"operateRange,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
LegalPersonName string `json:"legalPersonName,omitempty"`
|
||||
OrgCode string `json:"orgCode,omitempty"`
|
||||
BusinessLicense string `json:"businessLicense,omitempty"`
|
||||
FilePath1 string `json:"filePath1,omitempty"`
|
||||
MobileNo string `json:"mobileNo,omitempty"`
|
||||
CardName string `json:"cardName,omitempty"`
|
||||
BankMobileNo string `json:"bankMobileNo,omitempty"`
|
||||
BankCode string `json:"bankCode,omitempty"`
|
||||
BankCard string `json:"bankCard,omitempty"`
|
||||
}
|
||||
|
||||
type CertList struct {
|
||||
StatusCode uint `json:"statusCode,string"`
|
||||
SrcType uint `json:"srcType,string"`
|
||||
CertID string `json:"certId"`
|
||||
CardType string `json:"cardType,omitempty"`
|
||||
CertInfo CertInfo `json:"certInfo"`
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
UserLevel uint `json:"userLevel,string,omitempty"`
|
||||
CertList []CertList `json:"certList"`
|
||||
}
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
jsonContent := `{
|
||||
"certList":[
|
||||
{"certId":"2023313","certInfo":"{\"address\":\"xxxxxxx\",\"phoneNumber\":\"15084890\",\"companyName\":\"dddd\",\"communityCreditCode\":\"91110111MBE1G2B\",\"operateRange\":\"fff\",\"registerNo\":\"91110111MA00G2B\",\"legalPersonName\":\"rrr\"}","srcType":"1","statusCode":"2"},
|
||||
{"certId":"2023314","certInfo":"{\"identNo\":\"342224196507051\",\"userRealname\":\"xxxx\",\"identType\":\"01\"}","srcType":"8","statusCode":"0"},
|
||||
{"certId":"2023322","certInfo":"{\"businessLicense\":\"91110111MA00BE1G\",\"companyName\":\"sssss\",\"communityCreditCode\":\"91110111MA00BE1\"}","srcType":"2","statusCode":"0"}
|
||||
]
|
||||
}`
|
||||
j, err := gjson.LoadContent(jsonContent)
|
||||
t.Assert(err, nil)
|
||||
var response = new(Response)
|
||||
err = j.ToStruct(response)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(len(response.CertList), 3)
|
||||
t.Assert(response.CertList[0].CertID, 2023313)
|
||||
t.Assert(response.CertList[1].CertID, 2023314)
|
||||
t.Assert(response.CertList[2].CertID, 2023322)
|
||||
t.Assert(response.CertList[0].CertInfo.PhoneNumber, "15084890")
|
||||
t.Assert(response.CertList[1].CertInfo.IdentNo, "342224196507051")
|
||||
t.Assert(response.CertList[2].CertInfo.BusinessLicense, "91110111MA00BE1G")
|
||||
})
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@ import (
|
||||
"github.com/gogf/gf/encoding/gjson"
|
||||
)
|
||||
|
||||
// New creates a Parser object with any variable type of <data>, but <data> should be a map or
|
||||
// New creates a Parser object with any variable type of <data>, but <data> should be a map, struct or
|
||||
// slice for data access reason, or it will make no sense.
|
||||
//
|
||||
// The parameter <safe> specifies whether using this Json object in concurrent-safe context, which
|
||||
|
||||
@ -26,6 +26,12 @@ type ApiCause interface {
|
||||
Cause() error
|
||||
}
|
||||
|
||||
// ApiLevel is the interface for Current/Next feature.
|
||||
type ApiLevel interface {
|
||||
Current() error
|
||||
Next() error
|
||||
}
|
||||
|
||||
// New creates and returns an error which is formatted from given text.
|
||||
func New(text string) error {
|
||||
if text == "" {
|
||||
@ -120,3 +126,27 @@ func Stack(err error) string {
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Current creates and returns the current level error.
|
||||
// It returns nil if current level error is nil.
|
||||
func Current(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if e, ok := err.(ApiLevel); ok {
|
||||
return e.Current()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Next returns the next level error.
|
||||
// It returns nil if current level error or the next level error is nil.
|
||||
func Next(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if e, ok := err.(ApiLevel); ok {
|
||||
return e.Next()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -27,6 +27,7 @@ const (
|
||||
|
||||
var (
|
||||
// goRootForFilter is used for stack filtering purpose.
|
||||
// Mainly for development environment.
|
||||
goRootForFilter = runtime.GOROOT()
|
||||
)
|
||||
|
||||
@ -36,8 +37,11 @@ func init() {
|
||||
}
|
||||
}
|
||||
|
||||
// Error implements the interface of Error, it returns the error as string.
|
||||
// Error implements the interface of Error, it returns all the error as string.
|
||||
func (err *Error) Error() string {
|
||||
if err == nil {
|
||||
return ""
|
||||
}
|
||||
if err.text != "" {
|
||||
if err.error != nil {
|
||||
return err.text + ": " + err.error.Error()
|
||||
@ -49,6 +53,9 @@ func (err *Error) Error() string {
|
||||
|
||||
// Cause returns the root cause error.
|
||||
func (err *Error) Cause() error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
loop := err
|
||||
for loop != nil {
|
||||
if loop.error != nil {
|
||||
@ -66,8 +73,8 @@ func (err *Error) Cause() error {
|
||||
|
||||
// Format formats the frame according to the fmt.Formatter interface.
|
||||
//
|
||||
// %v, %s : Print the error string;
|
||||
// %-v, %-s : Print current error string;
|
||||
// %v, %s : Print all the error string;
|
||||
// %-v, %-s : Print current level error string;
|
||||
// %+s : Print full stack error list;
|
||||
// %+v : Print the error string and full stack error list;
|
||||
func (err *Error) Format(s fmt.State, verb rune) {
|
||||
@ -120,6 +127,28 @@ func (err *Error) Stack() string {
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
// Current creates and returns the current level error.
|
||||
// It returns nil if current level error is nil.
|
||||
func (err *Error) Current() error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return &Error{
|
||||
error: nil,
|
||||
stack: err.stack,
|
||||
text: err.text,
|
||||
}
|
||||
}
|
||||
|
||||
// Next returns the next level error.
|
||||
// It returns nil if current level error or the next level error is nil.
|
||||
func (err *Error) Next() error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return err.error
|
||||
}
|
||||
|
||||
// formatSubStack formats the stack for error.
|
||||
func formatSubStack(st stack, buffer *bytes.Buffer) {
|
||||
index := 1
|
||||
|
||||
@ -127,3 +127,31 @@ func Test_Stack(t *testing.T) {
|
||||
//fmt.Printf("%+v", err)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Current(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
err := errors.New("1")
|
||||
err = gerror.Wrap(err, "2")
|
||||
err = gerror.Wrap(err, "3")
|
||||
t.Assert(err.Error(), "3: 2: 1")
|
||||
t.Assert(gerror.Current(err).Error(), "3")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Next(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
err := errors.New("1")
|
||||
err = gerror.Wrap(err, "2")
|
||||
err = gerror.Wrap(err, "3")
|
||||
t.Assert(err.Error(), "3: 2: 1")
|
||||
|
||||
err = gerror.Next(err)
|
||||
t.Assert(err.Error(), "2: 1")
|
||||
|
||||
err = gerror.Next(err)
|
||||
t.Assert(err.Error(), "1")
|
||||
|
||||
err = gerror.Next(err)
|
||||
t.Assert(err, nil)
|
||||
})
|
||||
}
|
||||
|
||||
@ -39,8 +39,15 @@ func Throw(exception interface{}) {
|
||||
gutil.Throw(exception)
|
||||
}
|
||||
|
||||
// TryCatch does the try...catch... mechanism.
|
||||
func TryCatch(try func(), catch ...func(exception interface{})) {
|
||||
// Try implements try... logistics using internal panic...recover.
|
||||
// It returns error if any exception occurs, or else it returns nil.
|
||||
func Try(try func()) (err error) {
|
||||
return gutil.Try(try)
|
||||
}
|
||||
|
||||
// TryCatch implements try...catch... logistics using internal panic...recover.
|
||||
// It automatically calls function <catch> if any exception occurs ans passes the exception as an error.
|
||||
func TryCatch(try func(), catch ...func(exception error)) {
|
||||
gutil.TryCatch(try, catch...)
|
||||
}
|
||||
|
||||
|
||||
@ -10,17 +10,14 @@ import (
|
||||
"github.com/gogf/gf/os/glog"
|
||||
)
|
||||
|
||||
// SetDebug disables/enables debug level for logging component globally.
|
||||
func SetDebug(debug bool) {
|
||||
glog.SetDebug(debug)
|
||||
}
|
||||
|
||||
// SetLogLevel sets the logging level globally.
|
||||
// Deprecated, use functions of package glog or g.Log() instead.
|
||||
func SetLogLevel(level int) {
|
||||
glog.SetLevel(level)
|
||||
}
|
||||
|
||||
// GetLogLevel returns the global logging level.
|
||||
// Deprecated, use functions of package glog or g.Log() instead.
|
||||
func GetLogLevel() int {
|
||||
return glog.GetLevel()
|
||||
}
|
||||
|
||||
@ -6,7 +6,17 @@
|
||||
|
||||
package g
|
||||
|
||||
import "github.com/gogf/gf/net/ghttp"
|
||||
import (
|
||||
"github.com/gogf/gf/internal/intlog"
|
||||
"github.com/gogf/gf/net/ghttp"
|
||||
)
|
||||
|
||||
// SetEnabled enables/disables the GoFrame internal logging manually.
|
||||
// Note that this function is not concurrent safe, be aware of the DATA RACE,
|
||||
// which means you should call this function in your boot but not the runtime.
|
||||
func SetDebug(enabled bool) {
|
||||
intlog.SetEnabled(enabled)
|
||||
}
|
||||
|
||||
// SetServerGraceful enables/disables graceful reload feature of http Web Server.
|
||||
// This feature is disabled in default.
|
||||
|
||||
@ -31,25 +31,26 @@ func Database(name ...string) gdb.DB {
|
||||
}
|
||||
instanceKey := fmt.Sprintf("%s.%s", gFRAME_CORE_COMPONENT_NAME_DATABASE, group)
|
||||
db := instances.GetOrSetFuncLock(instanceKey, func() interface{} {
|
||||
// Configuration already exists.
|
||||
if gdb.GetConfig(group) != nil {
|
||||
db, err := gdb.Instance(group)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return db
|
||||
}
|
||||
var m map[string]interface{}
|
||||
var (
|
||||
configMap map[string]interface{}
|
||||
configNodeKey string
|
||||
)
|
||||
// It firstly searches the configuration of the instance name.
|
||||
nodeKey, _ := gutil.MapPossibleItemByKey(Config().GetMap("."), gDATABASE_NODE_NAME)
|
||||
if nodeKey == "" {
|
||||
nodeKey = gDATABASE_NODE_NAME
|
||||
if Config().Available() {
|
||||
configNodeKey, _ = gutil.MapPossibleItemByKey(Config().GetMap("."), gDATABASE_NODE_NAME)
|
||||
if configNodeKey == "" {
|
||||
configNodeKey = gDATABASE_NODE_NAME
|
||||
}
|
||||
configMap = Config().GetMap(configNodeKey)
|
||||
}
|
||||
if m = Config().GetMap(nodeKey); len(m) == 0 {
|
||||
if len(configMap) == 0 && !gdb.IsConfigured() {
|
||||
panic(fmt.Sprintf(`database init failed: "%s" node not found, is config file or configuration missing?`, gDATABASE_NODE_NAME))
|
||||
}
|
||||
if len(configMap) == 0 {
|
||||
configMap = make(map[string]interface{})
|
||||
}
|
||||
// Parse <m> as map-slice and adds it to gdb's global configurations.
|
||||
for group, groupConfig := range m {
|
||||
for g, groupConfig := range configMap {
|
||||
cg := gdb.ConfigGroup{}
|
||||
switch value := groupConfig.(type) {
|
||||
case []interface{}:
|
||||
@ -64,37 +65,51 @@ func Database(name ...string) gdb.DB {
|
||||
}
|
||||
}
|
||||
if len(cg) > 0 {
|
||||
intlog.Printf("%s, %#v", group, cg)
|
||||
gdb.SetConfigGroup(group, cg)
|
||||
if gdb.GetConfig(group) == nil {
|
||||
intlog.Printf("add configuration for group: %s, %#v", g, cg)
|
||||
gdb.SetConfigGroup(g, cg)
|
||||
} else {
|
||||
intlog.Printf("ignore configuration as it already exists for group: %s, %#v", g, cg)
|
||||
intlog.Printf("%s, %#v", g, cg)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Parse <m> as a single node configuration,
|
||||
// which is the default group configuration.
|
||||
if node := parseDBConfigNode(m); node != nil {
|
||||
if node := parseDBConfigNode(configMap); node != nil {
|
||||
cg := gdb.ConfigGroup{}
|
||||
if node.LinkInfo != "" || node.Host != "" {
|
||||
cg = append(cg, *node)
|
||||
}
|
||||
|
||||
if len(cg) > 0 {
|
||||
intlog.Printf("%s, %#v", gdb.DEFAULT_GROUP_NAME, cg)
|
||||
gdb.SetConfigGroup(gdb.DEFAULT_GROUP_NAME, cg)
|
||||
if gdb.GetConfig(group) == nil {
|
||||
intlog.Printf("add configuration for group: %s, %#v", gdb.DEFAULT_GROUP_NAME, cg)
|
||||
gdb.SetConfigGroup(gdb.DEFAULT_GROUP_NAME, cg)
|
||||
} else {
|
||||
intlog.Printf("ignore configuration as it already exists for group: %s, %#v", gdb.DEFAULT_GROUP_NAME, cg)
|
||||
intlog.Printf("%s, %#v", gdb.DEFAULT_GROUP_NAME, cg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new ORM object with given configurations.
|
||||
if db, err := gdb.New(name...); err == nil {
|
||||
// Initialize logger for ORM.
|
||||
var m map[string]interface{}
|
||||
m = Config().GetMap(fmt.Sprintf("%s.%s", nodeKey, gLOGGER_NODE_NAME))
|
||||
if len(m) == 0 {
|
||||
m = Config().GetMap(nodeKey)
|
||||
}
|
||||
if len(m) > 0 {
|
||||
if err := db.GetLogger().SetConfigWithMap(m); err != nil {
|
||||
panic(err)
|
||||
if Config().Available() {
|
||||
// Initialize logger for ORM.
|
||||
var loggerConfigMap map[string]interface{}
|
||||
loggerConfigMap = Config().GetMap(fmt.Sprintf("%s.%s", configNodeKey, gLOGGER_NODE_NAME))
|
||||
if len(loggerConfigMap) == 0 {
|
||||
loggerConfigMap = Config().GetMap(configNodeKey)
|
||||
}
|
||||
if len(loggerConfigMap) > 0 {
|
||||
if err := db.GetLogger().SetConfigWithMap(loggerConfigMap); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return db
|
||||
} else {
|
||||
// It panics often because it dose not find its configuration for given group.
|
||||
panic(err)
|
||||
}
|
||||
return nil
|
||||
|
||||
4
go.mod
4
go.mod
@ -5,17 +5,15 @@ go 1.11
|
||||
require (
|
||||
github.com/BurntSushi/toml v0.3.1
|
||||
github.com/clbanning/mxj v1.8.5-0.20200714211355-ff02cfb8ea28
|
||||
github.com/fsnotify/fsnotify v1.4.7
|
||||
github.com/fsnotify/fsnotify v1.4.9
|
||||
github.com/go-sql-driver/mysql v1.5.0
|
||||
github.com/gomodule/redigo v2.0.0+incompatible
|
||||
github.com/gorilla/websocket v1.4.1
|
||||
github.com/gqcn/structs v1.1.1
|
||||
github.com/grokify/html-strip-tags-go v0.0.0-20190921062105-daaa06bf1aaf
|
||||
github.com/json-iterator/go v1.1.10
|
||||
github.com/mattn/go-runewidth v0.0.9 // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.1
|
||||
golang.org/x/net v0.0.0-20200602114024-627f9648deb9
|
||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae // indirect
|
||||
golang.org/x/text v0.3.2
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c
|
||||
)
|
||||
|
||||
@ -32,7 +32,31 @@ func T(content string, language ...string) string {
|
||||
return defaultManager.T(content, language...)
|
||||
}
|
||||
|
||||
// Translate translates <content> with configured language.
|
||||
// Tf is alias of TranslateFormat for convenience.
|
||||
func Tf(format string, values ...interface{}) string {
|
||||
return defaultManager.TranslateFormat(format, values...)
|
||||
}
|
||||
|
||||
// Tfl is alias of TranslateFormatLang for convenience.
|
||||
func Tfl(language string, format string, values ...interface{}) string {
|
||||
return defaultManager.TranslateFormatLang(language, format, values...)
|
||||
}
|
||||
|
||||
// TranslateFormat translates, formats and returns the <format> with configured language
|
||||
// and given <values>.
|
||||
func TranslateFormat(format string, values ...interface{}) string {
|
||||
return defaultManager.TranslateFormat(format, values...)
|
||||
}
|
||||
|
||||
// TranslateFormatLang translates, formats and returns the <format> with configured language
|
||||
// and given <values>. The parameter <language> specifies custom translation language ignoring
|
||||
// configured language. If <language> is given empty string, it uses the default configured
|
||||
// language for the translation.
|
||||
func TranslateFormatLang(language string, format string, values ...interface{}) string {
|
||||
return defaultManager.TranslateFormatLang(format, language, values...)
|
||||
}
|
||||
|
||||
// Translate translates <content> with configured language and returns the translated content.
|
||||
// The parameter <language> specifies custom translation language ignoring configured language.
|
||||
func Translate(content string, language ...string) string {
|
||||
return defaultManager.Translate(content, language...)
|
||||
|
||||
@ -124,6 +124,30 @@ func (m *Manager) T(content string, language ...string) string {
|
||||
return m.Translate(content, language...)
|
||||
}
|
||||
|
||||
// Tf is alias of TranslateFormat for convenience.
|
||||
func (m *Manager) Tf(format string, values ...interface{}) string {
|
||||
return m.TranslateFormat(format, values...)
|
||||
}
|
||||
|
||||
// Tfl is alias of TranslateFormatLang for convenience.
|
||||
func (m *Manager) Tfl(language string, format string, values ...interface{}) string {
|
||||
return m.TranslateFormatLang(language, format, values...)
|
||||
}
|
||||
|
||||
// TranslateFormat translates, formats and returns the <format> with configured language
|
||||
// and given <values>.
|
||||
func (m *Manager) TranslateFormat(format string, values ...interface{}) string {
|
||||
return fmt.Sprintf(m.Translate(format), values...)
|
||||
}
|
||||
|
||||
// TranslateFormatLang translates, formats and returns the <format> with configured language
|
||||
// and given <values>. The parameter <language> specifies custom translation language ignoring
|
||||
// configured language. If <language> is given empty string, it uses the default configured
|
||||
// language for the translation.
|
||||
func (m *Manager) TranslateFormatLang(language string, format string, values ...interface{}) string {
|
||||
return fmt.Sprintf(m.Translate(format, language), values...)
|
||||
}
|
||||
|
||||
// Translate translates <content> with configured language.
|
||||
// The parameter <language> specifies custom translation language ignoring configured language.
|
||||
func (m *Manager) Translate(content string, language ...string) string {
|
||||
|
||||
@ -73,6 +73,28 @@ func Test_Basic(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_TranslateFormat(t *testing.T) {
|
||||
// Tf
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
i18n := gi18n.New(gi18n.Options{
|
||||
Path: gdebug.TestDataPath("i18n"),
|
||||
})
|
||||
i18n.SetLanguage("none")
|
||||
t.Assert(i18n.Tf("{#hello}{#world} %d", 2020), "{#hello}{#world} 2020")
|
||||
|
||||
i18n.SetLanguage("ja")
|
||||
t.Assert(i18n.Tf("{#hello}{#world} %d", 2020), "こんにちは世界 2020")
|
||||
})
|
||||
// Tfl
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
i18n := gi18n.New(gi18n.Options{
|
||||
Path: gdebug.TestDataPath("i18n"),
|
||||
})
|
||||
t.Assert(i18n.Tfl("ja", "{#hello}{#world} %d", 2020), "こんにちは世界 2020")
|
||||
t.Assert(i18n.Tfl("zh-CN", "{#hello}{#world} %d", 2020), "你好世界 2020")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_DefaultManager(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
err := gi18n.SetPath(gdebug.TestDataPath("i18n"))
|
||||
|
||||
@ -33,6 +33,7 @@ func IsEmpty(value interface{}) bool {
|
||||
if value == nil {
|
||||
return true
|
||||
}
|
||||
// It firstly checks the variable as common types using assertion, and then reflection.
|
||||
switch value := value.(type) {
|
||||
case int:
|
||||
return value == 0
|
||||
@ -66,15 +67,34 @@ func IsEmpty(value interface{}) bool {
|
||||
return len(value) == 0
|
||||
case []rune:
|
||||
return len(value) == 0
|
||||
case []int:
|
||||
return len(value) == 0
|
||||
case []string:
|
||||
return len(value) == 0
|
||||
case []float32:
|
||||
return len(value) == 0
|
||||
case []float64:
|
||||
return len(value) == 0
|
||||
case map[string]interface{}:
|
||||
return len(value) == 0
|
||||
default:
|
||||
// Common interfaces checks.
|
||||
if f, ok := value.(apiString); ok {
|
||||
if f == nil {
|
||||
return true
|
||||
}
|
||||
return f.String() == ""
|
||||
}
|
||||
if f, ok := value.(apiInterfaces); ok {
|
||||
if f == nil {
|
||||
return true
|
||||
}
|
||||
return len(f.Interfaces()) == 0
|
||||
}
|
||||
if f, ok := value.(apiMapStrAny); ok {
|
||||
if f == nil {
|
||||
return true
|
||||
}
|
||||
return len(f.MapStrAny()) == 0
|
||||
}
|
||||
// Finally using reflect.
|
||||
|
||||
@ -33,9 +33,12 @@ func init() {
|
||||
}
|
||||
|
||||
// SetEnabled enables/disables the internal logging manually.
|
||||
// Note that this function is not current safe, be aware of the DATA RACE.
|
||||
// Note that this function is not concurrent safe, be aware of the DATA RACE.
|
||||
func SetEnabled(enabled bool) {
|
||||
isGFDebug = enabled
|
||||
// If they're the same, it does not write the <isGFDebug> but only reading operation.
|
||||
if isGFDebug != enabled {
|
||||
isGFDebug = enabled
|
||||
}
|
||||
}
|
||||
|
||||
// IsEnabled checks and returns whether current process is in GF development.
|
||||
|
||||
@ -5,14 +5,46 @@
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// Package structs provides functions for struct conversion.
|
||||
//
|
||||
// Inspired and improved from: https://github.com/fatih/structs
|
||||
package structs
|
||||
|
||||
import "github.com/gqcn/structs"
|
||||
import (
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// Field is alias of structs.Field.
|
||||
// Field contains information of a struct field .
|
||||
type Field struct {
|
||||
*structs.Field
|
||||
// Retrieved tag name. There might be more than one tags in the field,
|
||||
value reflect.Value
|
||||
field reflect.StructField
|
||||
// Retrieved tag value. There might be more than one tags in the field,
|
||||
// but only one can be retrieved according to calling function rules.
|
||||
Tag string
|
||||
TagValue string
|
||||
}
|
||||
|
||||
// Tag returns the value associated with key in the tag string. If there is no
|
||||
// such key in the tag, Tag returns the empty string.
|
||||
func (f *Field) Tag(key string) string {
|
||||
return f.field.Tag.Get(key)
|
||||
}
|
||||
|
||||
// Value returns the underlying value of the field. It panics if the field
|
||||
// is not exported.
|
||||
func (f *Field) Value() interface{} {
|
||||
return f.value.Interface()
|
||||
}
|
||||
|
||||
// IsEmbedded returns true if the given field is an anonymous field (embedded)
|
||||
func (f *Field) IsEmbedded() bool {
|
||||
return f.field.Anonymous
|
||||
}
|
||||
|
||||
// IsExported returns true if the given field is exported.
|
||||
func (f *Field) IsExported() bool {
|
||||
return f.field.PkgPath == ""
|
||||
}
|
||||
|
||||
// Name returns the name of the given field
|
||||
func (f *Field) Name() string {
|
||||
return f.field.Name
|
||||
}
|
||||
|
||||
@ -6,73 +6,54 @@
|
||||
|
||||
package structs
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/gqcn/structs"
|
||||
)
|
||||
|
||||
// MapField retrieves struct field as map[name/tag]*Field from <pointer>, and returns the map.
|
||||
//
|
||||
// The parameter <pointer> should be type of struct/*struct.
|
||||
//
|
||||
// The parameter <priority> specifies the priority tag array for retrieving from high to low.
|
||||
//
|
||||
// The parameter <recursive> specifies whether retrieving the struct field recursively.
|
||||
//
|
||||
// Note that it only retrieves the exported attributes with first letter up-case from struct.
|
||||
func MapField(pointer interface{}, priority []string, recursive bool) map[string]*Field {
|
||||
var (
|
||||
fields []*structs.Field
|
||||
fieldMap = make(map[string]*Field)
|
||||
)
|
||||
if v, ok := pointer.(reflect.Value); ok {
|
||||
fields = structs.Fields(v.Interface())
|
||||
} else {
|
||||
fields = structs.Fields(pointer)
|
||||
func MapField(pointer interface{}, priority []string) (map[string]*Field, error) {
|
||||
fields, err := getFieldValues(pointer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var (
|
||||
tag = ""
|
||||
name = ""
|
||||
tagValue = ""
|
||||
mapField = make(map[string]*Field)
|
||||
)
|
||||
for _, field := range fields {
|
||||
name = field.Name()
|
||||
// Only retrieve exported attributes.
|
||||
if name[0] < byte('A') || name[0] > byte('Z') {
|
||||
if !field.IsExported() {
|
||||
continue
|
||||
}
|
||||
fieldMap[name] = &Field{
|
||||
Field: field,
|
||||
Tag: tag,
|
||||
}
|
||||
tag = ""
|
||||
tagValue = ""
|
||||
for _, p := range priority {
|
||||
tag = field.Tag(p)
|
||||
if tag != "" {
|
||||
tagValue = field.Tag(p)
|
||||
if tagValue != "" && tagValue != "-" {
|
||||
break
|
||||
}
|
||||
}
|
||||
if tag != "" {
|
||||
fieldMap[tag] = &Field{
|
||||
Field: field,
|
||||
Tag: tag,
|
||||
}
|
||||
}
|
||||
if recursive {
|
||||
rv := reflect.ValueOf(field.Value())
|
||||
kind := rv.Kind()
|
||||
if kind == reflect.Ptr {
|
||||
rv = rv.Elem()
|
||||
kind = rv.Kind()
|
||||
}
|
||||
if kind == reflect.Struct {
|
||||
for k, v := range MapField(rv, priority, true) {
|
||||
if _, ok := fieldMap[k]; !ok {
|
||||
fieldMap[k] = v
|
||||
tempField := field
|
||||
tempField.TagValue = tagValue
|
||||
if tagValue != "" {
|
||||
mapField[tagValue] = tempField
|
||||
} else {
|
||||
if field.IsEmbedded() {
|
||||
m, err := MapField(field.value, priority)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for k, v := range m {
|
||||
if _, ok := mapField[k]; !ok {
|
||||
tempV := v
|
||||
mapField[k] = tempV
|
||||
}
|
||||
}
|
||||
} else {
|
||||
mapField[field.Name()] = tempField
|
||||
}
|
||||
}
|
||||
}
|
||||
return fieldMap
|
||||
return mapField, nil
|
||||
}
|
||||
|
||||
@ -7,119 +7,147 @@
|
||||
package structs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
|
||||
"github.com/gqcn/structs"
|
||||
)
|
||||
|
||||
// TagFields retrieves struct tags as []*Field from <pointer>, and returns it.
|
||||
//
|
||||
// The parameter <pointer> should be type of struct/*struct.
|
||||
//
|
||||
// The parameter <recursive> specifies whether retrieving the struct field recursively.
|
||||
//
|
||||
// Note that it only retrieves the exported attributes with first letter up-case from struct.
|
||||
func TagFields(pointer interface{}, priority []string, recursive bool) []*Field {
|
||||
return doTagFields(pointer, priority, recursive, map[string]struct{}{})
|
||||
}
|
||||
|
||||
// doTagFields retrieves the tag and corresponding attribute name from <pointer>. It also filters repeated
|
||||
// tag internally.
|
||||
// The parameter <pointer> should be type of struct/*struct.
|
||||
func doTagFields(pointer interface{}, priority []string, recursive bool, tagMap map[string]struct{}) []*Field {
|
||||
var fields []*structs.Field
|
||||
if v, ok := pointer.(reflect.Value); ok {
|
||||
fields = structs.Fields(v.Interface())
|
||||
} else {
|
||||
var (
|
||||
rv = reflect.ValueOf(pointer)
|
||||
kind = rv.Kind()
|
||||
)
|
||||
if kind == reflect.Ptr {
|
||||
rv = rv.Elem()
|
||||
kind = rv.Kind()
|
||||
}
|
||||
// If pointer is type of **struct and nil, then automatically create a temporary struct,
|
||||
// which is used for structs.Fields.
|
||||
if kind == reflect.Ptr && (!rv.IsValid() || rv.IsNil()) {
|
||||
fields = structs.Fields(reflect.New(rv.Type().Elem()).Elem().Interface())
|
||||
} else {
|
||||
fields = structs.Fields(pointer)
|
||||
}
|
||||
}
|
||||
var (
|
||||
tag = ""
|
||||
name = ""
|
||||
)
|
||||
tagFields := make([]*Field, 0)
|
||||
for _, field := range fields {
|
||||
name = field.Name()
|
||||
// Only retrieve exported attributes.
|
||||
if name[0] < byte('A') || name[0] > byte('Z') {
|
||||
continue
|
||||
}
|
||||
tag = ""
|
||||
for _, p := range priority {
|
||||
tag = field.Tag(p)
|
||||
if tag != "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
if tag != "" {
|
||||
// Filter repeated tag.
|
||||
if _, ok := tagMap[tag]; ok {
|
||||
continue
|
||||
}
|
||||
tagFields = append(tagFields, &Field{
|
||||
Field: field,
|
||||
Tag: tag,
|
||||
})
|
||||
}
|
||||
if recursive {
|
||||
var (
|
||||
rv = reflect.ValueOf(field.Value())
|
||||
kind = rv.Kind()
|
||||
)
|
||||
if kind == reflect.Ptr {
|
||||
rv = rv.Elem()
|
||||
kind = rv.Kind()
|
||||
}
|
||||
if kind == reflect.Struct {
|
||||
tagFields = append(tagFields, doTagFields(rv, priority, recursive, tagMap)...)
|
||||
}
|
||||
}
|
||||
}
|
||||
return tagFields
|
||||
func TagFields(pointer interface{}, priority []string) ([]*Field, error) {
|
||||
return getFieldValuesByTagPriority(pointer, priority, map[string]struct{}{})
|
||||
}
|
||||
|
||||
// TagMapName retrieves struct tags as map[tag]attribute from <pointer>, and returns it.
|
||||
//
|
||||
// The parameter <pointer> should be type of struct/*struct.
|
||||
//
|
||||
// The parameter <recursive> specifies whether retrieving the struct field recursively.
|
||||
//
|
||||
// Note that it only retrieves the exported attributes with first letter up-case from struct.
|
||||
func TagMapName(pointer interface{}, priority []string, recursive bool) map[string]string {
|
||||
fields := TagFields(pointer, priority, recursive)
|
||||
tagMap := make(map[string]string, len(fields))
|
||||
for _, v := range fields {
|
||||
tagMap[v.Tag] = v.Name()
|
||||
func TagMapName(pointer interface{}, priority []string) (map[string]string, error) {
|
||||
fields, err := TagFields(pointer, priority)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tagMap
|
||||
tagMap := make(map[string]string, len(fields))
|
||||
for _, field := range fields {
|
||||
tagMap[field.TagValue] = field.Name()
|
||||
}
|
||||
return tagMap, nil
|
||||
}
|
||||
|
||||
// TagMapField retrieves struct tags as map[tag]*Field from <pointer>, and returns it.
|
||||
//
|
||||
// The parameter <pointer> should be type of struct/*struct.
|
||||
//
|
||||
// The parameter <recursive> specifies whether retrieving the struct field recursively.
|
||||
//
|
||||
// Note that it only retrieves the exported attributes with first letter up-case from struct.
|
||||
func TagMapField(pointer interface{}, priority []string, recursive bool) map[string]*Field {
|
||||
fields := TagFields(pointer, priority, recursive)
|
||||
tagMap := make(map[string]*Field, len(fields))
|
||||
for _, v := range fields {
|
||||
tagMap[v.Tag] = v
|
||||
func TagMapField(pointer interface{}, priority []string) (map[string]*Field, error) {
|
||||
fields, err := TagFields(pointer, priority)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tagMap
|
||||
tagMap := make(map[string]*Field, len(fields))
|
||||
for _, field := range fields {
|
||||
tagField := field
|
||||
tagMap[field.TagValue] = tagField
|
||||
}
|
||||
return tagMap, nil
|
||||
}
|
||||
|
||||
func getFieldValues(value interface{}) ([]*Field, error) {
|
||||
var (
|
||||
reflectValue reflect.Value
|
||||
reflectKind reflect.Kind
|
||||
)
|
||||
if v, ok := value.(reflect.Value); ok {
|
||||
reflectValue = v
|
||||
reflectKind = reflectValue.Kind()
|
||||
} else {
|
||||
reflectValue = reflect.ValueOf(value)
|
||||
reflectKind = reflectValue.Kind()
|
||||
}
|
||||
|
||||
if reflectKind == reflect.Ptr {
|
||||
if !reflectValue.IsValid() || reflectValue.IsNil() {
|
||||
// If pointer is type of *struct and nil, then automatically create a temporary struct.
|
||||
reflectValue = reflect.New(reflectValue.Type().Elem()).Elem()
|
||||
reflectKind = reflectValue.Kind()
|
||||
} else {
|
||||
// If pointer is type of **struct and nil, then automatically create a temporary struct.
|
||||
var (
|
||||
pointedValue = reflectValue.Elem()
|
||||
pointedValueKind = pointedValue.Kind()
|
||||
)
|
||||
if pointedValueKind == reflect.Ptr && (!pointedValue.IsValid() || pointedValue.IsNil()) {
|
||||
reflectValue = reflect.New(pointedValue.Type().Elem()).Elem()
|
||||
reflectKind = reflectValue.Kind()
|
||||
} else {
|
||||
reflectValue = pointedValue
|
||||
reflectKind = pointedValueKind
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for reflectKind == reflect.Ptr {
|
||||
reflectValue = reflectValue.Elem()
|
||||
reflectKind = reflectValue.Kind()
|
||||
}
|
||||
if reflectKind != reflect.Struct {
|
||||
return nil, errors.New("given value should be type of struct/*struct")
|
||||
}
|
||||
var (
|
||||
structType = reflectValue.Type()
|
||||
length = reflectValue.NumField()
|
||||
fields = make([]*Field, length)
|
||||
)
|
||||
for i := 0; i < length; i++ {
|
||||
fields[i] = &Field{
|
||||
value: reflectValue.Field(i),
|
||||
field: structType.Field(i),
|
||||
}
|
||||
}
|
||||
return fields, nil
|
||||
}
|
||||
|
||||
func getFieldValuesByTagPriority(pointer interface{}, priority []string, tagMap map[string]struct{}) ([]*Field, error) {
|
||||
fields, err := getFieldValues(pointer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var (
|
||||
tagValue = ""
|
||||
tagFields = make([]*Field, 0)
|
||||
)
|
||||
for _, field := range fields {
|
||||
// Only retrieve exported attributes.
|
||||
if !field.IsExported() {
|
||||
continue
|
||||
}
|
||||
tagValue = ""
|
||||
for _, p := range priority {
|
||||
tagValue = field.Tag(p)
|
||||
if tagValue != "" && tagValue != "-" {
|
||||
break
|
||||
}
|
||||
}
|
||||
if tagValue != "" {
|
||||
// Filter repeated tag.
|
||||
if _, ok := tagMap[tagValue]; ok {
|
||||
continue
|
||||
}
|
||||
tagField := field
|
||||
tagField.TagValue = tagValue
|
||||
tagFields = append(tagFields, tagField)
|
||||
}
|
||||
// If this is an embedded attribute, it retrieves the tags recursively.
|
||||
if field.IsEmbedded() {
|
||||
if subTagFields, err := getFieldValuesByTagPriority(field.value, priority, tagMap); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
tagFields = append(tagFields, subTagFields...)
|
||||
}
|
||||
}
|
||||
}
|
||||
return tagFields, nil
|
||||
}
|
||||
|
||||
@ -1,73 +0,0 @@
|
||||
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package structs_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/internal/structs"
|
||||
|
||||
"github.com/gogf/gf/frame/g"
|
||||
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
)
|
||||
|
||||
func Test_Basic(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type User struct {
|
||||
Id int
|
||||
Name string `params:"name"`
|
||||
Pass string `my-tag1:"pass1" my-tag2:"pass2" params:"pass"`
|
||||
}
|
||||
var user User
|
||||
t.Assert(structs.TagMapName(user, []string{"params"}, true), g.Map{"name": "Name", "pass": "Pass"})
|
||||
t.Assert(structs.TagMapName(&user, []string{"params"}, true), g.Map{"name": "Name", "pass": "Pass"})
|
||||
|
||||
t.Assert(structs.TagMapName(&user, []string{"params", "my-tag1"}, true), g.Map{"name": "Name", "pass": "Pass"})
|
||||
t.Assert(structs.TagMapName(&user, []string{"my-tag1", "params"}, true), g.Map{"name": "Name", "pass1": "Pass"})
|
||||
t.Assert(structs.TagMapName(&user, []string{"my-tag2", "params"}, true), g.Map{"name": "Name", "pass2": "Pass"})
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type Base struct {
|
||||
Pass1 string `params:"password1"`
|
||||
Pass2 string `params:"password2"`
|
||||
}
|
||||
type UserWithBase struct {
|
||||
Id int
|
||||
Name string
|
||||
Base `params:"base"`
|
||||
}
|
||||
user := new(UserWithBase)
|
||||
t.Assert(structs.TagMapName(user, []string{"params"}, true), g.Map{
|
||||
"base": "Base",
|
||||
"password1": "Pass1",
|
||||
"password2": "Pass2",
|
||||
})
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type Base struct {
|
||||
Pass1 string `params:"password1"`
|
||||
Pass2 string `params:"password2"`
|
||||
}
|
||||
type UserWithBase1 struct {
|
||||
Id int
|
||||
Name string
|
||||
Base
|
||||
}
|
||||
type UserWithBase2 struct {
|
||||
Id int
|
||||
Name string
|
||||
Pass Base
|
||||
}
|
||||
user1 := new(UserWithBase1)
|
||||
user2 := new(UserWithBase2)
|
||||
t.Assert(structs.TagMapName(user1, []string{"params"}, true), g.Map{"password1": "Pass1", "password2": "Pass2"})
|
||||
t.Assert(structs.TagMapName(user2, []string{"params"}, true), g.Map{"password1": "Pass1", "password2": "Pass2"})
|
||||
})
|
||||
}
|
||||
35
internal/structs/structs_z_bench_test.go
Normal file
35
internal/structs/structs_z_bench_test.go
Normal file
@ -0,0 +1,35 @@
|
||||
// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package structs_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/internal/structs"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
Id int
|
||||
Name string `params:"name"`
|
||||
Pass string `my-tag1:"pass1" my-tag2:"pass2" params:"pass"`
|
||||
}
|
||||
|
||||
var (
|
||||
user = new(User)
|
||||
userNilPointer *User
|
||||
)
|
||||
|
||||
func Benchmark_TagFields(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
structs.TagFields(user, []string{"params", "my-tag1"})
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_TagFields_NilPointer(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
structs.TagFields(&userNilPointer, []string{"params", "my-tag1"})
|
||||
}
|
||||
}
|
||||
126
internal/structs/structs_z_unit_test.go
Normal file
126
internal/structs/structs_z_unit_test.go
Normal file
@ -0,0 +1,126 @@
|
||||
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package structs_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/internal/structs"
|
||||
|
||||
"github.com/gogf/gf/frame/g"
|
||||
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
)
|
||||
|
||||
func Test_Basic(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type User struct {
|
||||
Id int
|
||||
Name string `params:"name"`
|
||||
Pass string `my-tag1:"pass1" my-tag2:"pass2" params:"pass"`
|
||||
}
|
||||
var user User
|
||||
m, _ := structs.TagMapName(user, []string{"params"})
|
||||
t.Assert(m, g.Map{"name": "Name", "pass": "Pass"})
|
||||
m, _ = structs.TagMapName(&user, []string{"params"})
|
||||
t.Assert(m, g.Map{"name": "Name", "pass": "Pass"})
|
||||
|
||||
m, _ = structs.TagMapName(&user, []string{"params", "my-tag1"})
|
||||
t.Assert(m, g.Map{"name": "Name", "pass": "Pass"})
|
||||
m, _ = structs.TagMapName(&user, []string{"my-tag1", "params"})
|
||||
t.Assert(m, g.Map{"name": "Name", "pass1": "Pass"})
|
||||
m, _ = structs.TagMapName(&user, []string{"my-tag2", "params"})
|
||||
t.Assert(m, g.Map{"name": "Name", "pass2": "Pass"})
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type Base struct {
|
||||
Pass1 string `params:"password1"`
|
||||
Pass2 string `params:"password2"`
|
||||
}
|
||||
type UserWithBase struct {
|
||||
Id int
|
||||
Name string
|
||||
Base `params:"base"`
|
||||
}
|
||||
user := new(UserWithBase)
|
||||
m, _ := structs.TagMapName(user, []string{"params"})
|
||||
t.Assert(m, g.Map{
|
||||
"base": "Base",
|
||||
"password1": "Pass1",
|
||||
"password2": "Pass2",
|
||||
})
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type Base struct {
|
||||
Pass1 string `params:"password1"`
|
||||
Pass2 string `params:"password2"`
|
||||
}
|
||||
type UserWithEmbeddedAttribute struct {
|
||||
Id int
|
||||
Name string
|
||||
Base
|
||||
}
|
||||
type UserWithoutEmbeddedAttribute struct {
|
||||
Id int
|
||||
Name string
|
||||
Pass Base
|
||||
}
|
||||
user1 := new(UserWithEmbeddedAttribute)
|
||||
user2 := new(UserWithoutEmbeddedAttribute)
|
||||
m, _ := structs.TagMapName(user1, []string{"params"})
|
||||
t.Assert(m, g.Map{"password1": "Pass1", "password2": "Pass2"})
|
||||
m, _ = structs.TagMapName(user2, []string{"params"})
|
||||
t.Assert(m, g.Map{})
|
||||
})
|
||||
}
|
||||
|
||||
func Test_StructOfNilPointer(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type User struct {
|
||||
Id int
|
||||
Name string `params:"name"`
|
||||
Pass string `my-tag1:"pass1" my-tag2:"pass2" params:"pass"`
|
||||
}
|
||||
var user *User
|
||||
m, _ := structs.TagMapName(user, []string{"params"})
|
||||
t.Assert(m, g.Map{"name": "Name", "pass": "Pass"})
|
||||
m, _ = structs.TagMapName(&user, []string{"params"})
|
||||
t.Assert(m, g.Map{"name": "Name", "pass": "Pass"})
|
||||
|
||||
m, _ = structs.TagMapName(&user, []string{"params", "my-tag1"})
|
||||
t.Assert(m, g.Map{"name": "Name", "pass": "Pass"})
|
||||
m, _ = structs.TagMapName(&user, []string{"my-tag1", "params"})
|
||||
t.Assert(m, g.Map{"name": "Name", "pass1": "Pass"})
|
||||
m, _ = structs.TagMapName(&user, []string{"my-tag2", "params"})
|
||||
t.Assert(m, g.Map{"name": "Name", "pass2": "Pass"})
|
||||
})
|
||||
}
|
||||
|
||||
func Test_MapField(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type User struct {
|
||||
Id int
|
||||
Name string `params:"name"`
|
||||
Pass string `my-tag1:"pass1" my-tag2:"pass2" params:"pass"`
|
||||
}
|
||||
var user *User
|
||||
m, _ := structs.MapField(user, []string{"params"})
|
||||
t.Assert(len(m), 3)
|
||||
_, ok := m["Id"]
|
||||
t.Assert(ok, true)
|
||||
_, ok = m["Name"]
|
||||
t.Assert(ok, false)
|
||||
_, ok = m["name"]
|
||||
t.Assert(ok, true)
|
||||
_, ok = m["Pass"]
|
||||
t.Assert(ok, false)
|
||||
_, ok = m["pass"]
|
||||
t.Assert(ok, true)
|
||||
})
|
||||
}
|
||||
@ -7,16 +7,9 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
// replaceCharReg is the regular expression object for replacing chars in key.
|
||||
// It is used for function EqualFoldWithoutChars.
|
||||
replaceCharReg, _ = regexp.Compile(`[\-\.\_\s]+`)
|
||||
)
|
||||
|
||||
// IsLetterUpper checks whether the given byte b is in upper case.
|
||||
func IsLetterUpper(b byte) bool {
|
||||
if b >= byte('A') && b <= byte('Z') {
|
||||
@ -83,11 +76,19 @@ func ReplaceByMap(origin string, replaces map[string]string) string {
|
||||
return origin
|
||||
}
|
||||
|
||||
// RemoveSymbols removes all symbols from string and lefts only numbers and letters.
|
||||
func RemoveSymbols(s string) string {
|
||||
var b []byte
|
||||
for _, c := range s {
|
||||
if (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') {
|
||||
b = append(b, byte(c))
|
||||
}
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// EqualFoldWithoutChars checks string <s1> and <s2> equal case-insensitively,
|
||||
// with/without chars '-'/'_'/'.'/' '.
|
||||
func EqualFoldWithoutChars(s1, s2 string) bool {
|
||||
return strings.EqualFold(
|
||||
replaceCharReg.ReplaceAllString(s1, ""),
|
||||
replaceCharReg.ReplaceAllString(s2, ""),
|
||||
)
|
||||
return strings.EqualFold(RemoveSymbols(s1), RemoveSymbols(s2))
|
||||
}
|
||||
|
||||
29
internal/utils/utils_z_bench_test.go
Normal file
29
internal/utils/utils_z_bench_test.go
Normal file
@ -0,0 +1,29 @@
|
||||
// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package utils_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/internal/utils"
|
||||
"regexp"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var (
|
||||
replaceCharReg, _ = regexp.Compile(`[\-\.\_\s]+`)
|
||||
)
|
||||
|
||||
func Benchmark_RemoveSymbols(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
utils.RemoveSymbols(`-a-b._a c1!@#$%^&*()_+:";'.,'01`)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_RegularReplaceChars(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
replaceCharReg.ReplaceAllString(`-a-b._a c1!@#$%^&*()_+:";'.,'01`, "")
|
||||
}
|
||||
}
|
||||
@ -63,3 +63,9 @@ func Test_ReadCloser(t *testing.T) {
|
||||
t.Assert(r, []byte{1, 2, 3, 4})
|
||||
})
|
||||
}
|
||||
|
||||
func Test_RemoveSymbols(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
t.Assert(utils.RemoveSymbols(`-a-b._a c1!@#$%^&*()_+:";'.,'01`), `abac101`)
|
||||
})
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user