Compare commits

..

150 Commits

Author SHA1 Message Date
dd5cd31ef5 add automatic data key to field name mapping feature for package gdb 2020-10-17 18:16:13 +08:00
de92e804fe Merge branch 'master' of https://github.com/gogf/gf 2020-10-17 17:17:27 +08:00
7725d9aaaf add Current/Next function for package gerror 2020-10-17 17:17:10 +08:00
bd3e25adea Merge pull request #946 from yuancjun/patch-1
Update ghttp_server_config.go
2020-10-15 20:41:07 +08:00
0b1d49af4b upgrade fsnotify to v1.4.9 2020-10-15 20:14:35 +08:00
7efa9e351e remove default initialization function and add lazy initialization feature for package gfsnotify 2020-10-15 20:07:21 +08:00
0b0141954b Update ghttp_server_config.go 2020-10-15 18:04:32 +08:00
82b531fbfb add domain support for domain of ghttp.Server 2020-10-14 21:18:11 +08:00
67fb626339 add configuration SessionCookieOutput for ghttp.Server 2020-10-14 20:52:26 +08:00
9044d5f05d fix issue in custom session id lost for ghttp.Request 2020-10-13 20:34:31 +08:00
47663aa1f1 fix issue of EOF in ghttp.Request when no data posted using form-data 2020-10-13 20:12:54 +08:00
63c0aab19c improve package gtime 2020-10-12 23:32:32 +08:00
261216f5e4 improve structure sql for package gdb 2020-10-12 23:22:56 +08:00
f88b799d67 add more unit testing case for package gdb 2020-10-11 22:21:03 +08:00
9c48dd172c add auto-json support for slice/struct attribute for data inserting of package gdb 2020-10-10 17:29:38 +08:00
09ce105eee improve gdb.Model.Fields/FieldsEx for package gdb 2020-10-10 14:00:10 +08:00
651aa895f8 improve function addWordBoundariesToNumbers for package gstr 2020-10-10 13:38:28 +08:00
0509e41198 recover number for unit testing case of package gconv 2020-10-10 00:04:55 +08:00
1b40d6a53a do tx.Rollback if there's panic in gdb.Transaction 2020-10-09 23:42:33 +08:00
f9189d48d1 remove type ValueFunc from package gcache 2020-10-09 23:36:39 +08:00
849874a247 improve adapter definition for package gcache 2020-10-09 20:59:49 +08:00
3f6510bae7 fix issue in gconv.Struct 2020-09-29 23:47:37 +08:00
a585a26c39 improve custom rule function type for package gvalid 2020-09-29 23:25:20 +08:00
9943966a86 improva function formatSql for package gdb 2020-09-29 22:53:44 +08:00
3617e51c01 improve gdb.Model.ScanList 2020-09-28 23:52:02 +08:00
c931032f08 comment update for gdb.Model.Unscoped 2020-09-27 23:37:40 +08:00
37b286eaa4 improve performance for gconv.Maps;comment update for package gcache 2020-09-27 22:35:02 +08:00
619287c273 improve cache feature for package gdb 2020-09-27 00:15:11 +08:00
aeb9b68298 improve adapter feature for package gcache 2020-09-26 22:44:07 +08:00
bae8f6315b comment updat 2020-09-26 21:13:09 +08:00
a6f70f8935 comment updat 2020-09-26 21:00:28 +08:00
acca6f4009 add adapter feature for package gcache 2020-09-26 20:47:29 +08:00
727fdd2391 improve unit testing case for association feature for package gdb 2020-09-25 08:33:22 +08:00
1d174e00c0 improve package internal/intlog 2020-09-24 23:46:19 +08:00
a5e3e2f5ba change g.SetDebug function to control the debugging information for framework 2020-09-24 23:40:44 +08:00
da43c2d52f improve example for package gview 2020-09-22 20:12:34 +08:00
262f27748c improve custom validation rule feature for package gvalid 2020-09-22 08:45:22 +08:00
a29aef7e1c improve package gvalid for custom rule 2020-09-21 23:51:30 +08:00
1671391195 comment updates 2020-09-21 22:56:48 +08:00
28b0d59c61 improve package gcfg/gjson for data loading 2020-09-21 21:30:58 +08:00
86433cef25 improve gtime.New, gconv.String 2020-09-18 23:59:49 +08:00
e96ccd5f71 v1.13.6 2020-09-17 23:49:03 +08:00
50a087bb3d Merge branch 'master' of https://github.com/gogf/gf 2020-09-17 23:41:23 +08:00
1ec049c52f improve gf server for routes check while starting 2020-09-17 23:27:10 +08:00
ec38805542 Merge pull request #908 from cai1111/patch-2
Update gdb_driver_mssql.go
2020-09-17 19:23:25 +08:00
4c8517d075 Update gdb_driver_mssql.go 2020-09-17 11:06:24 +08:00
edf06da6ea add struct which implements Interfaces function parameter support for gdb.Model.Insert/Save 2020-09-13 11:21:10 +08:00
eb43a2040e add ghttp.Client.RedirectLimit and gfile.ScanDirFileFunc 2020-09-09 22:26:46 +08:00
9d0ecc7d3e add more unit testing case for package gdb 2020-09-07 20:25:59 +08:00
ad943c5e02 improve gjson.New by using gconv.MapDeep for map/struct 2020-09-07 19:44:11 +08:00
bdb4fd0d25 donator updates 2020-09-07 19:10:04 +08:00
2440e05457 fix issue in limit operations for database driver oracle 2020-09-03 22:38:07 +08:00
1337c6c0d1 add sub query sql support for join functions 2020-09-03 21:57:58 +08:00
957689e07c up 2020-09-02 23:22:16 +08:00
3952d74f87 up 2020-09-02 21:36:21 +08:00
6dc4b81693 add max recursive depth for directory scanning for function gfile.Scan* 2020-09-02 21:25:02 +08:00
9cd953b7be improve function FieldsEx by filtering fields from custom fields specified by function Fields for package gdb 2020-09-02 20:37:02 +08:00
631810dda2 add function String for package gmap 2020-09-02 19:53:58 +08:00
8c12bc5506 change panic to internal logging for package glog 2020-09-01 21:22:19 +08:00
d4091a4826 improve some transaction operations by directly calling model operations, making their implements logic the same 2020-08-31 15:57:04 +08:00
a7c269886b improve support for dynamic database configurations in codes 2020-08-31 15:39:27 +08:00
f54593037b improve CURD functions for package gdb 2020-08-31 00:59:42 +08:00
0415cf6a08 fix issue in nil gtime attribute for model entity for package gdb 2020-08-31 00:39:12 +08:00
87c22d32b0 version updates 2020-08-26 14:49:53 +08:00
27dd15b403 improve initialization for logger of package glog 2020-08-26 14:37:02 +08:00
dd452c19ce add example for custom uploading file name for ghttp.Server 2020-08-24 23:05:30 +08:00
acc0846cf3 fix issue in unit testing case of package gvalid 2020-08-24 22:26:12 +08:00
5a6738841f travis ci update 2020-08-22 00:21:24 +08:00
bea451c9d6 improve date type support for package gdb; improve datetime string for gtime.StrToTime; improve function gtime.New for more types 2020-08-21 23:41:12 +08:00
676e904ec6 improve package gvalid 2020-08-21 00:00:00 +08:00
49aa5c61bc improve package gvalid 2020-08-20 23:37:02 +08:00
a2272b852c improve package gvalid 2020-08-20 23:28:58 +08:00
1fa77630f9 improve package gvalid 2020-08-20 23:28:10 +08:00
a841c4cc05 improve package gvalid 2020-08-20 23:25:36 +08:00
1874808e3b improve unit testing case for package gcache 2020-08-19 21:37:26 +08:00
149b67916b README updates 2020-08-19 21:10:40 +08:00
7fb6f58162 README updates 2020-08-19 21:09:25 +08:00
a309114a18 donator updates 2020-08-15 21:58:03 +08:00
35cbde9530 comment update for package gstr 2020-08-15 21:28:24 +08:00
b9211b182a comment update for function ghttp.Request. 2020-08-15 12:35:56 +08:00
81ec499ae9 Merge pull request #867 from pingyeaa/master
Solve the problem of invalid modification of request parameters
2020-08-15 12:33:24 +08:00
f2e276eabd add function Test_Params_Modify for ghttp_unit_param_test.go 2020-08-14 20:02:52 +08:00
f6dbaba1f8 add function ReloadParam for ghttp_request.go 2020-08-14 18:34:43 +08:00
65dcff052a add function ReloadParam for ghttp_request.go 2020-08-14 18:19:48 +08:00
e3861567c7 add function ReloadParam for ghttp_request.go 2020-08-14 16:22:37 +08:00
e89a20c725 add ReloadParam func to ghttp_request.go 2020-08-14 15:19:56 +08:00
a8acc6bd28 update unit testing case for package gdb 2020-08-14 00:51:22 +08:00
eb5efc735e improve unit testing case of ScanList for package gdb 2020-08-13 23:45:22 +08:00
737af527cd improve *Struct functions for ghttp.Request 2020-08-13 23:29:00 +08:00
875d2b7e63 donator updates 2020-08-13 19:25:51 +08:00
bf1cb0e1bd donator updates 2020-08-13 19:23:31 +08:00
2bfeb1b06c README updates 2020-08-13 18:59:13 +08:00
820e4302b7 improve function *Struct for ghttp.Request 2020-08-13 18:51:59 +08:00
84d761b418 add function AddWithRecover for package grpool 2020-08-12 23:53:05 +08:00
e5e27f2ac4 add function GetRouterMap for ghtt.Request 2020-08-12 21:01:17 +08:00
efca9b18a8 improve content type checks for package gjson 2020-08-12 20:53:47 +08:00
607821ecbc add function LoadContentType for package gjson 2020-08-12 20:38:48 +08:00
7cc1b239d4 add functions Update/UpdateExpire/GetExpire for package gcache 2020-08-12 20:13:13 +08:00
efa8de34da Merge pull request #860 from XWR940711/master
add func gcache.SetVar/gcache.SetExpire/gcacge.GetExpire.
2020-08-12 19:19:19 +08:00
bcf45e3c5a CI-glog失败重测 2020-08-12 10:32:40 +08:00
0a3cd1d2ab 修正方法代码.使用Benchmark测试. 2020-08-12 09:57:47 +08:00
3e621856c8 change the port range for unit testing cases of ghttp.Server 2020-08-12 09:10:50 +08:00
32e4d64ddb improve package gjson for automatic content type checking 2020-08-12 00:11:25 +08:00
46e45ca84b improve package gjson for automatic content type checking 2020-08-11 23:46:21 +08:00
fcb13bd8ee improve package gjson for automatic content type checking 2020-08-11 23:36:40 +08:00
eacad9b453 improve package gjson for automatic content type checking 2020-08-11 23:22:09 +08:00
ed479e2a13 improve package gjson for automatic content type checking 2020-08-11 23:20:22 +08:00
19937cb75d improve MapDeep for package gconv 2020-08-11 20:13:47 +08:00
a95093222c add GetRemoteIp for ghttp.Request; add *Var feature for ghttp.Client 2020-08-11 15:23:42 +08:00
7b599d1882 Merge branch 'master' of https://github.com/gogf/gf 2020-08-10 23:04:28 +08:00
4d501fd2f4 improve function Set for package gjson 2020-08-10 23:03:35 +08:00
c3d3053ded improve GetOrSetFunc for package gcache 2020-08-09 21:28:31 +08:00
14d5fd3e11 add translation format feature for package gi18n 2020-08-08 16:46:52 +08:00
9f79453334 improve ghttp.Server by closing the request and response body to release the file descriptor in time 2020-08-08 11:09:58 +08:00
c7f1c881c0 Merge pull request #850 from coderzhuang/master
fix: Access-Control-Request-Headers
2020-08-08 09:59:45 +08:00
c39c032b4e Merge pull request #844 from qinyuguang/scan
gcmd.Scan supports read line that contains whitespace
2020-08-08 09:58:32 +08:00
fa96c18308 add function MapOmitEmpty for package gutil 2020-08-08 09:53:32 +08:00
d90145a47f add goroutine id retrieving feature for package gdebug 2020-08-08 09:42:24 +08:00
4871f86346 adjust TestCache_Expire_SetVar Test Code. 2020-08-07 16:26:26 +08:00
919eaf1e9a adjust TestCache_Expire_SetVar Test Code. 2020-08-07 14:05:21 +08:00
8a84ca16d1 add func gcache.SetVar/gcache.SetExpire/gcacge.GetExpire.
add test TestCache_Expire_SetVar.
2020-08-07 13:24:41 +08:00
12b4fdd692 fix: Access-Control-Request-Headers 2020-08-04 11:57:42 +08:00
3d8451d5d0 comment update for gdb.Model.Page 2020-08-04 09:22:21 +08:00
cf88f28519 gcmd.Scanf supports read line that contains whitespace 2020-08-03 21:00:02 +08:00
15d99eee46 add more unit testing case for ghttp.Server 2020-08-03 20:49:19 +08:00
6d68277db8 improve cookie feature for ghttp.Server 2020-08-03 20:00:00 +08:00
3e3b5557f7 readme updates 2020-08-03 18:58:43 +08:00
ba1a9d9f8e gcmd.Scan supports read line that contains whitespace 2020-08-01 02:13:42 +08:00
3e1a7953ec Merge branch 'master' of https://github.com/gogf/gf 2020-07-31 09:57:45 +08:00
073fb2d717 improve sql logging structure for package gdb 2020-07-31 09:57:26 +08:00
7f33021184 Merge pull request #825 from chenall/gjson-support-utf8-with-bom
fix configfile with UTF8-BOM issue
2020-07-31 00:08:05 +08:00
b396096721 improve dry-run feature by adding global dry-run variable reading from environment or command options 2020-07-30 23:08:27 +08:00
0a5c6d832f add configration group name for logging content for package gdb 2020-07-30 23:00:20 +08:00
d279566114 Merge pull request #769 from fxk2006/master 2020-07-30 22:55:39 +08:00
2693cbb136 add more unit testing case for ghttp.Server 2020-07-30 22:45:50 +08:00
f7a9be4292 improve package glog for rotation feature 2020-07-30 21:09:45 +08:00
a926033b66 move Separator from const to variable for package gfile 2020-07-30 20:57:08 +08:00
dbcdd06b19 Merge branch 'master' of https://github.com/gogf/gf 2020-07-30 20:49:32 +08:00
ff5dab5c70 improve package glog for rotation feature 2020-07-30 20:49:11 +08:00
84b576418f Merge pull request #835 from XWR940711/master
add func gtimer.Entry.Reset()
2020-07-30 20:20:50 +08:00
253b124903 fix issue in database configuration for package gind 2020-07-30 20:18:18 +08:00
33dc5ddf79 fix issue in database configuration for package gind 2020-07-30 18:52:27 +08:00
a456fa537c fix logger configure issue for package gdb 2020-07-30 17:16:29 +08:00
9e1fb93e08 adjust gtimer.Entry.Reset() Test Code. 2020-07-30 11:00:19 +08:00
5d24f702be adjust gtimer.Entry.Reset() Test Code. 2020-07-30 10:37:48 +08:00
6cc4747965 add func gtimer.Entry.Reset() 2020-07-29 11:22:46 +08:00
437fc04620 improve testCfg_With_UTF8_BOM unit_test 2020-07-25 14:07:33 +08:00
937f8e6919 fix configfile with UTF8-BOM issue 2020-07-25 10:57:40 +08:00
6e08eebcbe 修改sql打印debug信息,增加数据库配置的group名称,用于区分sql来源,尤其是多数据库配置的时候 2020-06-28 16:58:43 +08:00
d9422d00ac 修改sql打印debug信息,增加数据库配置的group名称,用于区分sql来源,尤其是多数据库配置的时候 2020-06-28 16:50:13 +08:00
157 changed files with 4914 additions and 1471 deletions

View File

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

View File

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

View File

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

View File

@ -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("哈喽世界!")
})

View File

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

View File

@ -1,3 +1,3 @@
{{define "container"}}
<h1>CONTAINER</h1>
<h1>{{.container}}</h1>
{{end}}

View File

@ -1,3 +1,3 @@
{{define "footer"}}
<h1>FOOTER</h1>
<h1>{{.footer}}</h1>
{{end}}

View File

@ -1,3 +1,3 @@
{{define "header"}}
<h1>HEADER</h1>
<h1>{{.header}}</h1>
{{end}}

View File

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

View File

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

View File

@ -5,6 +5,7 @@ go:
- "1.12.x"
- "1.13.x"
- "1.14.x"
- "1.15.x"
branches:
only:

View File

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

View File

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

View File

@ -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) 留言。
# 贡献

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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"
@ -154,7 +156,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,7 +166,7 @@ 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.
@ -188,6 +190,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.
@ -260,8 +263,17 @@ var (
// regularFieldNameRegPattern is the regular expression pattern for a string
// which is a regular field name of table.
regularFieldNameRegPattern = `^[\w\.\-]+$`
// 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 +283,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,10 +294,10 @@ 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(),
@ -308,7 +320,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 +438,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, _ := gcache.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 +461,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)

View File

@ -11,10 +11,11 @@ import (
"database/sql"
"errors"
"fmt"
"github.com/gogf/gf/internal/utils"
"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 +60,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 +104,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 +310,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 +339,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 +355,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 +374,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 +392,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 +427,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))
}
@ -462,35 +488,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 +554,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 +565,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))
@ -633,15 +672,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 +695,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 +736,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 +807,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)

View File

@ -115,6 +115,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 +194,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()
}

View File

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

View File

@ -15,6 +15,7 @@ import (
"database/sql"
"errors"
"fmt"
"github.com/gogf/gf/os/gcache"
"strconv"
"strings"
@ -198,47 +199,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, _ := gcache.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 +258,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)

View File

@ -11,6 +11,7 @@ import (
"errors"
"fmt"
"github.com/gogf/gf/internal/intlog"
"github.com/gogf/gf/os/gcache"
"github.com/gogf/gf/text/gregex"
"github.com/gogf/gf/text/gstr"
@ -102,21 +103,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, _ := gcache.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 +134,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)

View File

@ -16,6 +16,7 @@ import (
"errors"
"fmt"
"github.com/gogf/gf/internal/intlog"
"github.com/gogf/gf/os/gcache"
"github.com/gogf/gf/text/gstr"
"reflect"
"strconv"
@ -78,44 +79,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 +159,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, _ := gcache.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 +186,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 +196,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, _ := gcache.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 +241,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))
}
@ -351,12 +361,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))
}

View File

@ -16,6 +16,7 @@ import (
"errors"
"fmt"
"github.com/gogf/gf/internal/intlog"
"github.com/gogf/gf/os/gcache"
"github.com/gogf/gf/text/gstr"
"strings"
@ -107,21 +108,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, _ := gcache.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 +140,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)

View File

@ -15,6 +15,7 @@ import (
"errors"
"fmt"
"github.com/gogf/gf/internal/intlog"
"github.com/gogf/gf/os/gcache"
"github.com/gogf/gf/os/gfile"
"github.com/gogf/gf/text/gstr"
"strings"
@ -98,17 +99,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, _ := gcache.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 +122,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)

View File

@ -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"
@ -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.
@ -317,8 +360,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,9 +386,10 @@ 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)
}

View File

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

View File

@ -12,13 +12,6 @@ import (
"github.com/gogf/gf/os/gtime"
)
// 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.

View File

@ -24,49 +24,35 @@ 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
func (m *Model) Fields(fields ...string) *Model {
if len(fields) > 0 {
model := m.getModel()
model.fields = gstr.Join(fields, ",")
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")
func (m *Model) FieldsEx(fields ...string) *Model {
if len(fields) > 0 {
model := m.getModel()
model.fieldsEx = gstr.Join(fields, ",")
return model
}
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))
}
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
}
if len(model.fields) > 0 {
model.fields += ","
}
model.fields += k
}
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 +79,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]

View File

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

View File

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

View File

@ -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"
)
@ -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,53 @@ 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.
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 += 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
@ -381,23 +432,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

View File

@ -21,6 +21,13 @@ const (
gSOFT_FIELD_NAME_DELETE = "delete_at"
)
// 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 '-'/'_'/'.'/' '.
@ -86,8 +93,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]))
}

View File

@ -59,7 +59,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,10 +73,14 @@ func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err erro
updateData = updates
}
}
newData, err := m.filterDataForInsertOrUpdate(updateData)
if err != nil {
return nil, err
}
return m.db.DoUpdate(
m.getLink(true),
m.tables,
m.filterDataForInsertOrUpdate(updateData),
newData,
conditionWhere+conditionExtra,
m.mergeArguments(conditionArgs)...,
)

View File

@ -28,27 +28,33 @@ func (m *Model) getModel() *Model {
// 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 +109,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.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -11,6 +11,7 @@ import (
"fmt"
"github.com/gogf/gf/container/garray"
"github.com/gogf/gf/container/gmap"
"github.com/gogf/gf/encoding/gparser"
"github.com/gogf/gf/util/gutil"
"testing"
"time"
@ -52,20 +53,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 +81,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 +95,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) {
@ -2243,6 +2401,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)

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

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

View File

@ -215,8 +215,7 @@ func Test_Load_Ini(t *testing.T) {
;注释
[addr]
#注释
[addr]
ip = 127.0.0.1
port=9001
enable=true

View File

@ -47,3 +47,25 @@ 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)
})
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

3
go.mod
View File

@ -5,7 +5,7 @@ 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
@ -15,7 +15,6 @@ require (
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
)

View File

@ -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(format string, language string, values ...interface{}) string {
return defaultManager.TranslateFormatLang(format, language, 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(format string, language 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...)

View File

@ -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(format string, language string, values ...interface{}) string {
return m.TranslateFormatLang(format, language, 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(format string, language 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 {

View File

@ -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("{#hello}{#world} %d", "ja", 2020), "こんにちは世界 2020")
t.Assert(i18n.TFL("{#hello}{#world} %d", "zh-CN", 2020), "你好世界 2020")
})
}
func Test_DefaultManager(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
err := gi18n.SetPath(gdebug.TestDataPath("i18n"))

View File

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

View File

@ -6,6 +6,8 @@
package ghttp
import "github.com/gogf/gf/container/gvar"
// Get is a convenience method for sending GET request.
// NOTE that remembers CLOSING the response object when it'll never be used.
func Get(url string, data ...interface{}) (*ClientResponse, error) {
@ -185,3 +187,69 @@ func TraceBytes(url string, data ...interface{}) []byte {
func RequestBytes(method string, url string, data ...interface{}) []byte {
return NewClient().RequestBytes(method, url, data...)
}
// GetVar sends a GET request, retrieves and converts the result content to specified pointer.
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, et
func GetVar(url string, data ...interface{}) *gvar.Var {
return RequestVar("GET", url, data...)
}
// PutVar sends a PUT request, retrieves and converts the result content to specified pointer.
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, et
func PutVar(url string, data ...interface{}) *gvar.Var {
return RequestVar("PUT", url, data...)
}
// PostVar sends a POST request, retrieves and converts the result content to specified pointer.
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, et
func PostVar(url string, data ...interface{}) *gvar.Var {
return RequestVar("POST", url, data...)
}
// DeleteVar sends a DELETE request, retrieves and converts the result content to specified pointer.
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, et
func DeleteVar(url string, data ...interface{}) *gvar.Var {
return RequestVar("DELETE", url, data...)
}
// HeadVar sends a HEAD request, retrieves and converts the result content to specified pointer.
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, et
func HeadVar(url string, data ...interface{}) *gvar.Var {
return RequestVar("HEAD", url, data...)
}
// PatchVar sends a PATCH request, retrieves and converts the result content to specified pointer.
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, et
func PatchVar(url string, data ...interface{}) *gvar.Var {
return RequestVar("PATCH", url, data...)
}
// ConnectVar sends a CONNECT request, retrieves and converts the result content to specified pointer.
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, et
func ConnectVar(url string, data ...interface{}) *gvar.Var {
return RequestVar("CONNECT", url, data...)
}
// OptionsVar sends a OPTIONS request, retrieves and converts the result content to specified pointer.
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, et
func OptionsVar(url string, data ...interface{}) *gvar.Var {
return RequestVar("OPTIONS", url, data...)
}
// TraceVar sends a TRACE request, retrieves and converts the result content to specified pointer.
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, et
func TraceVar(url string, data ...interface{}) *gvar.Var {
return RequestVar("TRACE", url, data...)
}
// RequestVar sends request using given HTTP method and data, retrieves converts the result
// to specified pointer. It reads and closes the response object internally automatically.
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, et
func RequestVar(method string, url string, data ...interface{}) *gvar.Var {
response, err := DoRequest(method, url, data...)
if err != nil {
return gvar.New(nil)
}
defer response.Close()
return gvar.New(response.ReadAll())
}

View File

@ -149,3 +149,14 @@ func (c *Client) Proxy(proxyURL string) *Client {
newClient.SetProxy(proxyURL)
return c
}
// RedirectLimit is a chaining function,
// which sets the redirect limit the number of jumps for the request.
func (c *Client) RedirectLimit(redirectLimit int) *Client {
newClient := c
if c.parent == nil {
newClient = c.Clone()
}
newClient.SetRedirectLimit(redirectLimit)
return c
}

View File

@ -0,0 +1,77 @@
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package ghttp
import (
"github.com/gogf/gf/container/gvar"
)
// GetVar sends a GET request, retrieves and converts the result content to specified pointer.
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, etc.
func (c *Client) GetVar(url string, data ...interface{}) *gvar.Var {
return c.RequestVar("GET", url, data...)
}
// PutVar sends a PUT request, retrieves and converts the result content to specified pointer.
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, etc.
func (c *Client) PutVar(url string, data ...interface{}) *gvar.Var {
return c.RequestVar("PUT", url, data...)
}
// PostVar sends a POST request, retrieves and converts the result content to specified pointer.
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, etc.
func (c *Client) PostVar(url string, data ...interface{}) *gvar.Var {
return c.RequestVar("POST", url, data...)
}
// DeleteVar sends a DELETE request, retrieves and converts the result content to specified pointer.
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, etc.
func (c *Client) DeleteVar(url string, data ...interface{}) *gvar.Var {
return c.RequestVar("DELETE", url, data...)
}
// HeadVar sends a HEAD request, retrieves and converts the result content to specified pointer.
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, etc.
func (c *Client) HeadVar(url string, data ...interface{}) *gvar.Var {
return c.RequestVar("HEAD", url, data...)
}
// PatchVar sends a PATCH request, retrieves and converts the result content to specified pointer.
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, etc.
func (c *Client) PatchVar(url string, data ...interface{}) *gvar.Var {
return c.RequestVar("PATCH", url, data...)
}
// ConnectVar sends a CONNECT request, retrieves and converts the result content to specified pointer.
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, etc.
func (c *Client) ConnectVar(url string, data ...interface{}) *gvar.Var {
return c.RequestVar("CONNECT", url, data...)
}
// OptionsVar sends a OPTIONS request, retrieves and converts the result content to specified pointer.
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, etc.
func (c *Client) OptionsVar(url string, data ...interface{}) *gvar.Var {
return c.RequestVar("OPTIONS", url, data...)
}
// TraceVar sends a TRACE request, retrieves and converts the result content to specified pointer.
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, etc.
func (c *Client) TraceVar(url string, data ...interface{}) *gvar.Var {
return c.RequestVar("TRACE", url, data...)
}
// RequestVar sends request using given HTTP method and data, retrieves converts the result
// to specified pointer. It reads and closes the response object internally automatically.
// The parameter <pointer> can be type of: struct/*struct/**struct/[]struct/[]*struct/*[]struct, etc.
func (c *Client) RequestVar(method string, url string, data ...interface{}) *gvar.Var {
response, err := c.DoRequest(method, url, data...)
if err != nil {
return gvar.New(nil)
}
defer response.Close()
return gvar.New(response.ReadAll())
}

View File

@ -9,14 +9,15 @@ package ghttp
import (
"context"
"fmt"
"net/http"
"strings"
"time"
"github.com/gogf/gf/internal/intlog"
"github.com/gogf/gf/os/gres"
"github.com/gogf/gf/os/gsession"
"github.com/gogf/gf/os/gview"
"github.com/gogf/gf/util/guid"
"net/http"
"strings"
"time"
"github.com/gogf/gf/os/gtime"
"github.com/gogf/gf/text/gregex"
@ -155,6 +156,7 @@ func (r *Request) IsAjaxRequest() bool {
}
// GetClientIp returns the client ip of this request without port.
// Note that this ip address might be modified by client header.
func (r *Request) GetClientIp() string {
if len(r.clientIp) == 0 {
realIps := r.Header.Get("X-Forwarded-For")
@ -178,17 +180,21 @@ func (r *Request) GetClientIp() string {
r.clientIp = r.Header.Get("X-Real-IP")
}
if r.clientIp == "" || strings.EqualFold("unknown", realIps) {
array, _ := gregex.MatchString(`(.+):(\d+)`, r.RemoteAddr)
if len(array) > 1 {
r.clientIp = array[1]
} else {
r.clientIp = r.RemoteAddr
}
r.clientIp = r.GetRemoteIp()
}
}
return r.clientIp
}
// GetRemoteIp returns the ip from RemoteAddr.
func (r *Request) GetRemoteIp() string {
array, _ := gregex.MatchString(`(.+):(\d+)`, r.RemoteAddr)
if len(array) > 1 {
return array[1]
}
return r.RemoteAddr
}
// GetUrl returns current URL of this request.
func (r *Request) GetUrl() string {
scheme := "http"
@ -217,3 +223,13 @@ func (r *Request) GetReferer() string {
func (r *Request) GetError() error {
return r.error
}
// ReloadParam is used for modifying request parameter.
// Sometimes, we want to modify request parameters through middleware, but directly modifying Request.Body
// is invalid, so it clears the parsed* marks to make the parameters re-parsed.
func (r *Request) ReloadParam() {
r.parsedBody = false
r.parsedForm = false
r.parsedQuery = false
r.bodyContent = nil
}

View File

@ -276,6 +276,10 @@ func (r *Request) parseBody() {
return
}
r.parsedBody = true
// There's no data posted.
if r.ContentLength == 0 {
return
}
if body := r.GetBody(); len(body) > 0 {
// Trim space/new line characters.
body = bytes.TrimSpace(body)
@ -306,6 +310,10 @@ func (r *Request) parseForm() {
return
}
r.parsedForm = true
// There's no data posted.
if r.ContentLength == 0 {
return
}
if contentType := r.Header.Get("Content-Type"); contentType != "" {
var err error
if gstr.Contains(contentType, "multipart/") {

View File

@ -189,7 +189,11 @@ func (r *Request) GetFormMapStrVar(kvMap ...map[string]interface{}) map[string]*
// The optional parameter <mapping> is used to specify the key to attribute mapping.
func (r *Request) GetFormStruct(pointer interface{}, mapping ...map[string]string) error {
r.parseForm()
return gconv.StructDeep(r.formMap, pointer, mapping...)
m := r.formMap
if m == nil {
m = map[string]interface{}{}
}
return gconv.StructDeep(m, pointer, mapping...)
}
// GetFormToStruct is alias of GetFormStruct. See GetFormStruct.

View File

@ -193,7 +193,11 @@ func (r *Request) GetQueryMapStrVar(kvMap ...map[string]interface{}) map[string]
// attribute mapping.
func (r *Request) GetQueryStruct(pointer interface{}, mapping ...map[string]string) error {
r.parseQuery()
return gconv.StructDeep(r.GetQueryMap(), pointer, mapping...)
m := r.GetQueryMap()
if m == nil {
m = map[string]interface{}{}
}
return gconv.StructDeep(m, pointer, mapping...)
}
// GetQueryToStruct is alias of GetQueryStruct. See GetQueryStruct.

View File

@ -267,7 +267,11 @@ func (r *Request) GetRequestMapStrVar(kvMap ...map[string]interface{}) map[strin
// the parameter <pointer> is a pointer to the struct object.
// The optional parameter <mapping> is used to specify the key to attribute mapping.
func (r *Request) GetRequestStruct(pointer interface{}, mapping ...map[string]string) error {
return gconv.StructDeep(r.GetRequestMap(), pointer, mapping...)
m := r.GetRequestMap()
if m == nil {
m = map[string]interface{}{}
}
return gconv.StructDeep(m, pointer, mapping...)
}
// GetRequestToStruct is alias of GetRequestStruct. See GetRequestStruct.

View File

@ -8,6 +8,18 @@ package ghttp
import "github.com/gogf/gf/container/gvar"
// GetRouterMap retrieves and returns a copy of router map.
func (r *Request) GetRouterMap() map[string]string {
if r.routerMap != nil {
m := make(map[string]string, len(r.routerMap))
for k, v := range r.routerMap {
m[k] = v
}
return m
}
return nil
}
// GetRouterValue retrieves and returns the router value with given key name <key>.
// It returns <def> if <key> does not exist.
func (r *Request) GetRouterValue(key string, def ...interface{}) interface{} {

View File

@ -55,7 +55,7 @@ func (r *Response) DefaultCORSOptions() CORSOptions {
array := gstr.SplitAndTrim(headers, ",")
for _, header := range array {
if _, ok := defaultAllowHeadersMap[header]; !ok {
options.AllowHeaders += header + ","
options.AllowHeaders += "," + header
}
}
}

View File

@ -267,11 +267,6 @@ func (s *Server) Start() error {
return errors.New("[ghttp] server is already running")
}
// If there's no route registered and no static service enabled,
// it then returns an error of invalid usage of server.
if len(s.routesMap) == 0 && !s.config.FileServerEnabled {
return errors.New(`[ghttp] there's no route set or static feature enabled, did you forget import the router?`)
}
// Logging path setting check.
if s.config.LogPath != "" {
if err := s.config.Logger.SetPath(s.config.LogPath); err != nil {
@ -316,6 +311,12 @@ func (s *Server) Start() error {
// Check the group routes again.
s.handlePreBindItems()
// If there's no route registered and no static service enabled,
// it then returns an error of invalid usage of server.
if len(s.routesMap) == 0 && !s.config.FileServerEnabled {
return errors.New(`[ghttp] there's no route set or static feature enabled, did you forget import the router?`)
}
// Start the HTTP server.
reloaded := false
fdMapStr := genv.Get(gADMIN_ACTION_RELOAD_ENVKEY)

View File

@ -92,7 +92,7 @@ type ServerConfig struct {
// size of the request body.
//
// It can be configured in configuration file using string like: 1m, 10m, 500kb etc.
// It's 1024 bytes in default.
// It's 10240 bytes in default.
MaxHeaderBytes int
// KeepAlive enables HTTP keep-alive.
@ -157,6 +157,9 @@ type ServerConfig struct {
// SessionIdName specifies the session id name.
SessionIdName string
// SessionCookieOutput specifies whether automatic outputting session id to cookie.
SessionCookieOutput bool
// SessionPath specifies the session storage directory path for storing session files.
// It only makes sense if the session storage is type of file storage.
SessionPath string
@ -231,50 +234,56 @@ type ServerConfig struct {
Graceful bool
}
// Config creates and returns a ServerConfig object with default configurations.
// Deprecated. Use NewConfig instead.
func Config() ServerConfig {
return NewConfig()
}
// NewConfig creates and returns a ServerConfig object with default configurations.
// Note that, do not define this default configuration to local package variable, as there're
// some pointer attributes that may be shared in different servers.
func Config() ServerConfig {
func NewConfig() ServerConfig {
return ServerConfig{
Address: "",
HTTPSAddr: "",
Handler: nil,
ReadTimeout: 60 * time.Second,
WriteTimeout: 0, // No timeout.
IdleTimeout: 60 * time.Second,
MaxHeaderBytes: 10240, // 10KB
KeepAlive: true,
IndexFiles: []string{"index.html", "index.htm"},
IndexFolder: false,
ServerAgent: "GF HTTP Server",
ServerRoot: "",
StaticPaths: make([]staticPathItem, 0),
FileServerEnabled: false,
CookieMaxAge: time.Hour * 24 * 365,
CookiePath: "/",
CookieDomain: "",
SessionMaxAge: time.Hour * 24,
SessionIdName: "gfsessionid",
SessionPath: gsession.DefaultStorageFilePath,
Logger: glog.New(),
LogStdout: true,
ErrorStack: true,
ErrorLogEnabled: true,
ErrorLogPattern: "error-{Ymd}.log",
AccessLogEnabled: false,
AccessLogPattern: "access-{Ymd}.log",
DumpRouterMap: true,
ClientMaxBodySize: 8 * 1024 * 1024, // 8MB
FormParsingMemory: 1024 * 1024, // 1MB
Rewrites: make(map[string]string),
Graceful: false,
Address: "",
HTTPSAddr: "",
Handler: nil,
ReadTimeout: 60 * time.Second,
WriteTimeout: 0, // No timeout.
IdleTimeout: 60 * time.Second,
MaxHeaderBytes: 10240, // 10KB
KeepAlive: true,
IndexFiles: []string{"index.html", "index.htm"},
IndexFolder: false,
ServerAgent: "GF HTTP Server",
ServerRoot: "",
StaticPaths: make([]staticPathItem, 0),
FileServerEnabled: false,
CookieMaxAge: time.Hour * 24 * 365,
CookiePath: "/",
CookieDomain: "",
SessionMaxAge: time.Hour * 24,
SessionIdName: "gfsessionid",
SessionPath: gsession.DefaultStorageFilePath,
SessionCookieOutput: true,
Logger: glog.New(),
LogStdout: true,
ErrorStack: true,
ErrorLogEnabled: true,
ErrorLogPattern: "error-{Ymd}.log",
AccessLogEnabled: false,
AccessLogPattern: "access-{Ymd}.log",
DumpRouterMap: true,
ClientMaxBodySize: 8 * 1024 * 1024, // 8MB
FormParsingMemory: 1024 * 1024, // 1MB
Rewrites: make(map[string]string),
Graceful: false,
}
}
// ConfigFromMap creates and returns a ServerConfig object with given map and
// default configuration object.
func ConfigFromMap(m map[string]interface{}) (ServerConfig, error) {
config := Config()
config := NewConfig()
if err := gconv.Struct(m, &config); err != nil {
return config, err
}

View File

@ -27,6 +27,11 @@ func (s *Server) SetSessionStorage(storage gsession.Storage) {
s.config.SessionStorage = storage
}
// SetSessionCookieOutput sets the SetSessionCookieOutput for server.
func (s *Server) SetSessionCookieOutput(enabled bool) {
s.config.SessionCookieOutput = enabled
}
// GetSessionMaxAge returns the SessionMaxAge of server.
func (s *Server) GetSessionMaxAge() time.Duration {
return s.config.SessionMaxAge

View File

@ -9,28 +9,17 @@ package ghttp
import (
"net/http"
"time"
"github.com/gogf/gf/os/gtime"
)
// Cookie for HTTP COOKIE management.
type Cookie struct {
data map[string]CookieItem // Underlying cookie items.
path string // The default cookie path.
domain string // The default cookie domain
maxage time.Duration // The default cookie maxage.
server *Server // Belonged HTTP server
request *Request // Belonged HTTP request.
response *Response // Belonged HTTP response.
}
// CookieItem is cookie item stored in Cookie management object.
type CookieItem struct {
value string // Cookie value.
domain string // Cookie domain.
path string // Cookie path.
expireAt int64 // Cookie expiration timestamp.
httpOnly bool
data map[string]*http.Cookie // Underlying cookie items.
path string // The default cookie path.
domain string // The default cookie domain
maxAge time.Duration // The default cookie max age.
server *Server // Belonged HTTP server
request *Request // Belonged HTTP request.
response *Response // Belonged HTTP response.
}
// GetCookie creates or retrieves a cookie object with given request.
@ -48,21 +37,20 @@ func GetCookie(r *Request) *Cookie {
// init does lazy initialization for cookie object.
func (c *Cookie) init() {
if c.data == nil {
c.data = make(map[string]CookieItem)
c.path = c.request.Server.GetCookiePath()
c.domain = c.request.Server.GetCookieDomain()
c.maxage = c.request.Server.GetCookieMaxAge()
c.response = c.request.Response
// DO NOT ADD ANY DEFAULT COOKIE DOMAIN!
//if c.domain == "" {
// c.domain = c.request.GetHost()
//}
for _, v := range c.request.Cookies() {
c.data[v.Name] = CookieItem{
v.Value, v.Domain, v.Path, int64(v.Expires.Second()), v.HttpOnly,
}
}
if c.data != nil {
return
}
c.data = make(map[string]*http.Cookie)
c.path = c.request.Server.GetCookiePath()
c.domain = c.request.Server.GetCookieDomain()
c.maxAge = c.request.Server.GetCookieMaxAge()
c.response = c.request.Response
// DO NOT ADD ANY DEFAULT COOKIE DOMAIN!
//if c.domain == "" {
// c.domain = c.request.GetHost()
//}
for _, v := range c.request.Cookies() {
c.data[v.Name] = v
}
}
@ -71,7 +59,7 @@ func (c *Cookie) Map() map[string]string {
c.init()
m := make(map[string]string)
for k, v := range c.data {
m[k] = v.value
m[k] = v.Value
}
return m
}
@ -80,7 +68,7 @@ func (c *Cookie) Map() map[string]string {
func (c *Cookie) Contains(key string) bool {
c.init()
if r, ok := c.data[key]; ok {
if r.expireAt >= 0 {
if r.Expires.IsZero() || r.Expires.After(time.Now()) {
return true
}
}
@ -89,7 +77,7 @@ func (c *Cookie) Contains(key string) bool {
// Set sets cookie item with default domain, path and expiration age.
func (c *Cookie) Set(key, value string) {
c.SetCookie(key, value, c.domain, c.path, c.server.GetCookieMaxAge())
c.SetCookie(key, value, c.domain, c.path, c.maxAge)
}
// SetCookie sets cookie item given given domain, path and expiration age.
@ -101,11 +89,25 @@ func (c *Cookie) SetCookie(key, value, domain, path string, maxAge time.Duration
if len(httpOnly) > 0 {
isHttpOnly = httpOnly[0]
}
c.data[key] = CookieItem{
value, domain, path, gtime.Timestamp() + int64(maxAge.Seconds()), isHttpOnly,
c.data[key] = &http.Cookie{
Name: key,
Value: value,
Path: path,
Domain: domain,
Expires: time.Now().Add(maxAge),
HttpOnly: isHttpOnly,
}
}
// SetHttpCookie sets cookie with *http.Cookie.
func (c *Cookie) SetHttpCookie(cookie *http.Cookie) {
c.init()
if cookie.Expires.IsZero() {
cookie.Expires = time.Now().Add(c.maxAge)
}
c.data[cookie.Name] = cookie
}
// GetSessionId retrieves and returns the session id from cookie.
func (c *Cookie) GetSessionId() string {
return c.Get(c.server.GetSessionIdName())
@ -121,8 +123,8 @@ func (c *Cookie) SetSessionId(id string) {
func (c *Cookie) Get(key string, def ...string) string {
c.init()
if r, ok := c.data[key]; ok {
if r.expireAt >= 0 {
return r.value
if r.Expires.IsZero() || r.Expires.After(time.Now()) {
return r.Value
}
}
if len(def) > 0 {
@ -148,22 +150,12 @@ func (c *Cookie) Flush() {
if len(c.data) == 0 {
return
}
for k, v := range c.data {
// Cookie item matches expire != 0 means it is set in this request,
for _, v := range c.data {
// If cookie item is v.Expires.IsZero() means it is set in this request,
// which should be outputted to client.
if v.expireAt == 0 {
if v.Expires.IsZero() {
continue
}
http.SetCookie(
c.response.Writer,
&http.Cookie{
Name: k,
Value: v.value,
Domain: v.domain,
Path: v.path,
Expires: time.Unix(v.expireAt, 0),
HttpOnly: v.httpOnly,
},
)
http.SetCookie(c.response.Writer, v)
}
}

View File

@ -73,6 +73,13 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Close the session, which automatically update the TTL
// of the session if it exists.
request.Session.Close()
// Close the request and response body
// to release the file descriptor in time.
request.Request.Body.Close()
if request.Request.Response != nil {
request.Request.Response.Body.Close()
}
}()
// ============================================================
@ -154,7 +161,9 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Automatically set the session id to cookie
// if it creates a new session id in this request.
if request.Session.IsDirty() && request.Session.Id() != request.GetSessionId() {
if s.config.SessionCookieOutput &&
request.Session.IsDirty() &&
request.Session.Id() != request.GetSessionId() {
request.Cookie.SetSessionId(request.Session.Id())
}
// Output the cookie content to client.

View File

@ -24,14 +24,19 @@ const (
// EnablePProf enables PProf feature for server.
func (s *Server) EnablePProf(pattern ...string) {
s.Domain(gDEFAULT_DOMAIN).EnablePProf(pattern...)
}
// EnablePProf enables PProf feature for server of specified domain.
func (d *Domain) EnablePProf(pattern ...string) {
p := gDEFAULT_PPROF_PATTERN
if len(pattern) > 0 && pattern[0] != "" {
p = pattern[0]
}
up := &utilPProf{}
_, _, uri, _ := s.parsePattern(p)
_, _, uri, _ := d.server.parsePattern(p)
uri = strings.TrimRight(uri, "/")
s.Group(uri, func(group *RouterGroup) {
d.Group(uri, func(group *RouterGroup) {
group.ALL("/*action", up.Index)
group.ALL("/cmdline", up.Cmdline)
group.ALL("/profile", up.Profile)

View File

@ -44,13 +44,15 @@ func (s *Server) getHandlersWithCache(r *Request) (parsedItems []*handlerParsedI
}
}
// Search and cache the router handlers.
value := s.serveCache.GetOrSetFunc(s.serveHandlerKey(method, r.URL.Path, r.GetHost()), func() interface{} {
parsedItems, hasHook, hasServe = s.searchHandlers(method, r.URL.Path, r.GetHost())
if parsedItems != nil {
return &handlerCacheItem{parsedItems, hasHook, hasServe}
}
return nil
}, gROUTE_CACHE_DURATION)
value, _ := s.serveCache.GetOrSetFunc(
s.serveHandlerKey(method, r.URL.Path, r.GetHost()),
func() (interface{}, error) {
parsedItems, hasHook, hasServe = s.searchHandlers(method, r.URL.Path, r.GetHost())
if parsedItems != nil {
return &handlerCacheItem{parsedItems, hasHook, hasServe}, nil
}
return nil, nil
}, gROUTE_CACHE_DURATION)
if value != nil {
item := value.(*handlerCacheItem)
return item.parsedItems, item.hasHook, item.hasServe

View File

@ -8,6 +8,7 @@ package ghttp_test
import (
"fmt"
"net/http"
"testing"
"time"
@ -33,6 +34,52 @@ func Test_Cookie(t *testing.T) {
s.Start()
defer s.Shutdown()
time.Sleep(100 * time.Millisecond)
gtest.C(t, func(t *gtest.T) {
client := ghttp.NewClient()
client.SetBrowserMode(true)
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
r1, e1 := client.Get("/set?k=key1&v=100")
if r1 != nil {
defer r1.Close()
}
t.Assert(e1, nil)
t.Assert(r1.ReadAllString(), "")
t.Assert(client.GetContent("/set?k=key2&v=200"), "")
t.Assert(client.GetContent("/get?k=key1"), "100")
t.Assert(client.GetContent("/get?k=key2"), "200")
t.Assert(client.GetContent("/get?k=key3"), "")
t.Assert(client.GetContent("/remove?k=key1"), "")
t.Assert(client.GetContent("/remove?k=key3"), "")
t.Assert(client.GetContent("/remove?k=key4"), "")
t.Assert(client.GetContent("/get?k=key1"), "")
t.Assert(client.GetContent("/get?k=key2"), "200")
})
}
func Test_SetHttpCookie(t *testing.T) {
p, _ := ports.PopRand()
s := g.Server(p)
s.BindHandler("/set", func(r *ghttp.Request) {
r.Cookie.SetHttpCookie(&http.Cookie{
Name: r.GetString("k"),
Value: r.GetString("v"),
})
})
s.BindHandler("/get", func(r *ghttp.Request) {
r.Response.Write(r.Cookie.Get(r.GetString("k")))
})
s.BindHandler("/remove", func(r *ghttp.Request) {
r.Cookie.Remove(r.GetString("k"))
})
s.SetPort(p)
s.SetDumpRouterMap(false)
s.Start()
defer s.Shutdown()
time.Sleep(100 * time.Millisecond)
gtest.C(t, func(t *gtest.T) {
client := ghttp.NewClient()

View File

@ -17,7 +17,7 @@ var (
func init() {
genv.Set("UNDER_TEST", "1")
for i := 8000; i <= 9000; i++ {
for i := 7000; i <= 8000; i++ {
ports.Append(i)
}
}

View File

@ -0,0 +1,41 @@
// 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.
// static service testing.
package ghttp_test
import (
"fmt"
"testing"
"time"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
"github.com/gogf/gf/test/gtest"
)
func TestRequest_GetRemoteIp(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
p, _ := ports.PopRand()
s := g.Server(p)
s.BindHandler("/", func(r *ghttp.Request) {
r.Response.Write(r.GetRemoteIp())
})
s.SetDumpRouterMap(false)
s.SetPort(p)
s.Start()
defer s.Shutdown()
time.Sleep(100 * time.Millisecond)
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
t.Assert(client.GetContent("/"), "127.0.0.1")
})
}

View File

@ -7,10 +7,14 @@
package ghttp_test
import (
"bytes"
"fmt"
"io/ioutil"
"testing"
"time"
"github.com/gogf/gf/encoding/gjson"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
"github.com/gogf/gf/test/gtest"
@ -517,3 +521,39 @@ func Test_Params_GetRequestMap(t *testing.T) {
)
})
}
func Test_Params_Modify(t *testing.T) {
p, _ := ports.PopRand()
s := g.Server(p)
s.BindHandler("/param/modify", func(r *ghttp.Request) {
param := r.GetMap()
param["id"] = 2
paramBytes, err := gjson.Encode(param)
if err != nil {
r.Response.Write(err)
return
}
r.Request.Body = ioutil.NopCloser(bytes.NewReader(paramBytes))
r.ReloadParam()
r.Response.Write(r.GetMap())
})
s.SetPort(p)
s.SetDumpRouterMap(false)
s.Start()
defer s.Shutdown()
time.Sleep(100 * time.Millisecond)
gtest.C(t, func(t *gtest.T) {
prefix := fmt.Sprintf("http://127.0.0.1:%d", p)
client := ghttp.NewClient()
client.SetPrefix(prefix)
t.Assert(
client.PostContent(
"/param/modify",
`{"id":1}`,
),
`{"id":2}`,
)
})
}

View File

@ -73,6 +73,33 @@ func Test_Router_Basic2(t *testing.T) {
})
}
func Test_Router_Value(t *testing.T) {
p, _ := ports.PopRand()
s := g.Server(p)
s.BindHandler("/{hash}", func(r *ghttp.Request) {
r.Response.Write(r.GetRouterString("hash"))
})
s.BindHandler("/{hash}.{type}", func(r *ghttp.Request) {
r.Response.Write(r.GetRouterString("type"))
})
s.BindHandler("/{hash}.{type}.map", func(r *ghttp.Request) {
r.Response.Write(r.GetRouterMap()["type"])
})
s.SetPort(p)
s.SetDumpRouterMap(false)
s.Start()
defer s.Shutdown()
time.Sleep(100 * time.Millisecond)
gtest.C(t, func(t *gtest.T) {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
t.Assert(client.GetContent("/data"), "data")
t.Assert(client.GetContent("/data.json"), "json")
t.Assert(client.GetContent("/data.json.map"), "json")
})
}
// HTTP method register.
func Test_Router_Method(t *testing.T) {
p, _ := ports.PopRand()

View File

@ -97,7 +97,7 @@ func (o *GroupObjRest) Head(r *ghttp.Request) {
r.Response.Header().Set("head-ok", "1")
}
func Test_Router_GroupRest(t *testing.T) {
func Test_Router_GroupRest1(t *testing.T) {
p, _ := ports.PopRand()
s := g.Server(p)
g := s.Group("/api")
@ -172,3 +172,80 @@ func Test_Router_GroupRest(t *testing.T) {
t.Assert(resp4.Header.Get("head-ok"), "1")
})
}
func Test_Router_GroupRest2(t *testing.T) {
p, _ := ports.PopRand()
s := g.Server(p)
s.Group("/api", func(group *ghttp.RouterGroup) {
ctl := new(GroupCtlRest)
obj := new(GroupObjRest)
group.REST("/ctl", ctl)
group.REST("/obj", obj)
group.REST("/{.struct}/{.method}", ctl)
group.REST("/{.struct}/{.method}", obj)
})
s.SetPort(p)
s.SetDumpRouterMap(false)
s.Start()
defer s.Shutdown()
time.Sleep(100 * time.Millisecond)
gtest.C(t, func(t *gtest.T) {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
t.Assert(client.GetContent("/api/ctl"), "1Controller Get2")
t.Assert(client.PutContent("/api/ctl"), "1Controller Put2")
t.Assert(client.PostContent("/api/ctl"), "1Controller Post2")
t.Assert(client.DeleteContent("/api/ctl"), "1Controller Delete2")
t.Assert(client.PatchContent("/api/ctl"), "1Controller Patch2")
t.Assert(client.OptionsContent("/api/ctl"), "1Controller Options2")
resp1, err := client.Head("/api/ctl")
if err == nil {
defer resp1.Close()
}
t.Assert(err, nil)
t.Assert(resp1.Header.Get("head-ok"), "1")
t.Assert(client.GetContent("/api/obj"), "1Object Get2")
t.Assert(client.PutContent("/api/obj"), "1Object Put2")
t.Assert(client.PostContent("/api/obj"), "1Object Post2")
t.Assert(client.DeleteContent("/api/obj"), "1Object Delete2")
t.Assert(client.PatchContent("/api/obj"), "1Object Patch2")
t.Assert(client.OptionsContent("/api/obj"), "1Object Options2")
resp2, err := client.Head("/api/obj")
if err == nil {
defer resp2.Close()
}
t.Assert(err, nil)
t.Assert(resp2.Header.Get("head-ok"), "1")
t.Assert(client.GetContent("/api/group-ctl-rest"), "Not Found")
t.Assert(client.GetContent("/api/group-ctl-rest/get"), "1Controller Get2")
t.Assert(client.PutContent("/api/group-ctl-rest/put"), "1Controller Put2")
t.Assert(client.PostContent("/api/group-ctl-rest/post"), "1Controller Post2")
t.Assert(client.DeleteContent("/api/group-ctl-rest/delete"), "1Controller Delete2")
t.Assert(client.PatchContent("/api/group-ctl-rest/patch"), "1Controller Patch2")
t.Assert(client.OptionsContent("/api/group-ctl-rest/options"), "1Controller Options2")
resp3, err := client.Head("/api/group-ctl-rest/head")
if err == nil {
defer resp3.Close()
}
t.Assert(err, nil)
t.Assert(resp3.Header.Get("head-ok"), "1")
t.Assert(client.GetContent("/api/group-obj-rest"), "Not Found")
t.Assert(client.GetContent("/api/group-obj-rest/get"), "1Object Get2")
t.Assert(client.PutContent("/api/group-obj-rest/put"), "1Object Put2")
t.Assert(client.PostContent("/api/group-obj-rest/post"), "1Object Post2")
t.Assert(client.DeleteContent("/api/group-obj-rest/delete"), "1Object Delete2")
t.Assert(client.PatchContent("/api/group-obj-rest/patch"), "1Object Patch2")
t.Assert(client.OptionsContent("/api/group-obj-rest/options"), "1Object Options2")
resp4, err := client.Head("/api/group-obj-rest/head")
if err == nil {
defer resp4.Close()
}
t.Assert(err, nil)
t.Assert(resp4.Header.Get("head-ok"), "1")
})
}

View File

@ -157,3 +157,38 @@ func Test_Router_Hook_Multi(t *testing.T) {
t.Assert(client.GetContent("/multi-hook"), "12show")
})
}
func Test_Router_Hook_ExitAll(t *testing.T) {
p, _ := ports.PopRand()
s := g.Server(p)
s.BindHandler("/test", func(r *ghttp.Request) {
r.Response.Write("test")
})
s.Group("/hook", func(group *ghttp.RouterGroup) {
group.Middleware(func(r *ghttp.Request) {
r.Response.Write("1")
r.Middleware.Next()
})
group.ALL("/test", func(r *ghttp.Request) {
r.Response.Write("2")
})
})
s.BindHookHandler("/hook/*", ghttp.HOOK_BEFORE_SERVE, func(r *ghttp.Request) {
r.Response.Write("hook")
r.ExitAll()
})
s.SetPort(p)
s.SetDumpRouterMap(false)
s.Start()
defer s.Shutdown()
time.Sleep(100 * time.Millisecond)
gtest.C(t, func(t *gtest.T) {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
t.Assert(client.GetContent("/test"), "test")
t.Assert(client.GetContent("/hook/test"), "hook")
})
}

View File

@ -152,3 +152,47 @@ func Test_Session_StorageFile(t *testing.T) {
t.Assert(client.GetContent("/get?k=key1"), "")
})
}
func Test_Session_Custom_Id(t *testing.T) {
var (
sessionId = "1234567890"
key = "key"
value = "value"
p, _ = ports.PopRand()
s = g.Server(p)
)
s.BindHandler("/id", func(r *ghttp.Request) {
if err := r.Session.SetId(sessionId); err != nil {
r.Response.WriteExit(err.Error())
}
if err := r.Session.Set(key, value); err != nil {
r.Response.WriteExit(err.Error())
}
r.Response.WriteExit(r.Session.Id())
})
s.BindHandler("/value", func(r *ghttp.Request) {
r.Response.WriteExit(r.Session.Get(key))
})
s.SetPort(p)
s.SetDumpRouterMap(false)
s.Start()
defer s.Shutdown()
time.Sleep(100 * time.Millisecond)
gtest.C(t, func(t *gtest.T) {
client := g.Client()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
r, err := client.Get("/id")
t.Assert(err, nil)
defer r.Close()
t.Assert(r.ReadAllString(), sessionId)
t.Assert(r.GetCookie(s.GetSessionIdName()), sessionId)
})
gtest.C(t, func(t *gtest.T) {
client := g.Client()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
client.SetHeader(s.GetSessionIdName(), sessionId)
t.Assert(client.GetContent("/value"), value)
})
}

View File

@ -0,0 +1,86 @@
// 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 ghttp_test
import (
"fmt"
"github.com/gogf/gf/frame/g"
)
func ExampleClient_Header() {
var (
url = "http://127.0.0.1:8999/header"
header = g.MapStrStr{
"Span-Id": "0.1",
"Trace-Id": "123456789",
}
)
content := g.Client().Header(header).PostContent(url, g.Map{
"id": 10000,
"name": "john",
})
fmt.Println(content)
// Output:
// Span-Id: 0.1, Trace-Id: 123456789
}
func ExampleClient_HeaderRaw() {
var (
url = "http://127.0.0.1:8999/header"
headerRaw = `
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3950.0 Safari/537.36
Span-Id: 0.1
Trace-Id: 123456789
`
)
content := g.Client().HeaderRaw(headerRaw).PostContent(url, g.Map{
"id": 10000,
"name": "john",
})
fmt.Println(content)
// Output:
// Span-Id: 0.1, Trace-Id: 123456789
}
func ExampleClient_Cookie() {
var (
url = "http://127.0.0.1:8999/cookie"
cookie = g.MapStrStr{
"SessionId": "123",
}
)
content := g.Client().Cookie(cookie).PostContent(url, g.Map{
"id": 10000,
"name": "john",
})
fmt.Println(content)
// Output:
// SessionId: 123
}
func ExampleClient_ContentJson() {
var (
url = "http://127.0.0.1:8999/json"
jsonStr = `{"id":10000,"name":"john"}`
jsonMap = g.Map{
"id": 10000,
"name": "john",
}
)
// Post using JSON string.
fmt.Println(g.Client().ContentJson().PostContent(url, jsonStr))
// Post using JSON map.
fmt.Println(g.Client().ContentJson().PostContent(url, jsonMap))
// Output:
// Content-Type: application/json, id: 10000
// Content-Type: application/json, id: 10000
}

View File

@ -0,0 +1,89 @@
// 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 ghttp_test
import (
"fmt"
"github.com/gogf/gf/frame/g"
)
func ExampleClient_Get() {
url := "http://127.0.0.1:8999"
// Send with string parameter along with URL.
r1, err := g.Client().Get(url + "?id=10000&name=john")
if err != nil {
panic(err)
}
defer r1.Close()
fmt.Println(r1.ReadAllString())
// Send with string parameter in request body.
r2, err := g.Client().Get(url, "id=10000&name=john")
if err != nil {
panic(err)
}
defer r2.Close()
fmt.Println(r2.ReadAllString())
// Send with map parameter.
r3, err := g.Client().Get(url, g.Map{
"id": 10000,
"name": "john",
})
if err != nil {
panic(err)
}
defer r3.Close()
fmt.Println(r3.ReadAllString())
// Output:
// GET: query: 10000, john
// GET: query: 10000, john
// GET: query: 10000, john
}
func ExampleClient_GetBytes() {
url := "http://127.0.0.1:8999"
fmt.Println(string(g.Client().GetBytes(url, g.Map{
"id": 10000,
"name": "john",
})))
// Output:
// GET: query: 10000, john
}
func ExampleClient_GetContent() {
url := "http://127.0.0.1:8999"
fmt.Println(g.Client().GetContent(url, g.Map{
"id": 10000,
"name": "john",
}))
// Output:
// GET: query: 10000, john
}
func ExampleClient_GetVar() {
type User struct {
Id int
Name string
}
var (
user *User
url = "http://127.0.0.1:8999/var/json"
)
err := g.Client().GetVar(url).Scan(&user)
if err != nil {
panic(err)
}
fmt.Println(user)
// Output:
// &{1 john}
}

View File

@ -0,0 +1,95 @@
// 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 ghttp_test
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
"time"
)
func init() {
p := 8999
s := g.Server(p)
// HTTP method handlers.
s.Group("/", func(group *ghttp.RouterGroup) {
group.GET("/", func(r *ghttp.Request) {
r.Response.Writef(
"GET: query: %d, %s",
r.GetQueryInt("id"),
r.GetQueryString("name"),
)
})
group.PUT("/", func(r *ghttp.Request) {
r.Response.Writef(
"PUT: form: %d, %s",
r.GetFormInt("id"),
r.GetFormString("name"),
)
})
group.POST("/", func(r *ghttp.Request) {
r.Response.Writef(
"POST: form: %d, %s",
r.GetFormInt("id"),
r.GetFormString("name"),
)
})
group.DELETE("/", func(r *ghttp.Request) {
r.Response.Writef(
"DELETE: form: %d, %s",
r.GetFormInt("id"),
r.GetFormString("name"),
)
})
group.HEAD("/", func(r *ghttp.Request) {
r.Response.Write("head")
})
group.OPTIONS("/", func(r *ghttp.Request) {
r.Response.Write("options")
})
})
// Client chaining operations handlers.
s.Group("/", func(group *ghttp.RouterGroup) {
group.ALL("/header", func(r *ghttp.Request) {
r.Response.Writef(
"Span-Id: %s, Trace-Id: %s",
r.Header.Get("Span-Id"),
r.Header.Get("Trace-Id"),
)
})
group.ALL("/cookie", func(r *ghttp.Request) {
r.Response.Writef(
"SessionId: %s",
r.Cookie.Get("SessionId"),
)
})
group.ALL("/json", func(r *ghttp.Request) {
r.Response.Writef(
"Content-Type: %s, id: %d",
r.Header.Get("Content-Type"),
r.GetInt("id"),
)
})
})
// Other testing handlers.
s.Group("/var", func(group *ghttp.RouterGroup) {
group.ALL("/json", func(r *ghttp.Request) {
r.Response.Write(`{"id":1,"name":"john"}`)
})
group.ALL("/jsons", func(r *ghttp.Request) {
r.Response.Write(`[{"id":1,"name":"john"}, {"id":2,"name":"smith"}]`)
})
})
s.SetAccessLogEnabled(false)
s.SetDumpRouterMap(false)
s.SetPort(p)
err := s.Start()
if err != nil {
panic(err)
}
time.Sleep(time.Millisecond * 500)
}

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