Compare commits

...

74 Commits

Author SHA1 Message Date
6a80091fef version updates 2021-05-03 00:00:29 +08:00
742653ce75 improve Model function for struct parameter that can retrieve table name tag from 2021-05-02 23:28:24 +08:00
a8c3d07d9f improve with feature for package gdb 2021-05-02 22:35:47 +08:00
df1ef5db78 add example for package gdb 2021-05-02 17:57:00 +08:00
4b15ab5e99 improve OrderRandom function for package gdb 2021-05-02 16:42:34 +08:00
cdc97e9b2b improve logging for transaction feature for package gdb 2021-05-02 15:58:28 +08:00
bd84b97614 add more Where*/Min/Max/Avg/Sum/CountColumn/Increment/Decrement/OrderAsc/OrderDesc/OrderRandom functions and associated unit testing cases for package gdb 2021-05-02 12:17:06 +08:00
d7eb1cca07 add nested transaction feature for package gdb 2021-05-02 09:35:54 +08:00
5856f74d83 up 2021-05-02 08:10:35 +08:00
563509c4a6 route map dumping updates 2021-05-01 09:04:16 +08:00
d0753fa527 Merge branch 'develop' 2021-04-26 20:38:16 +08:00
524e3bedc7 improve empty value validation for required-with* patterns for package gvalid 2021-04-26 20:37:36 +08:00
30b60754e3 README update 2021-04-16 10:32:55 +08:00
88c49dc2cb README update 2021-04-16 10:27:39 +08:00
bb3dcc2622 Merge branch 'master' of https://github.com/gogf/gf 2021-04-16 10:22:02 +08:00
5cca0640d9 README update 2021-04-16 10:21:42 +08:00
fb48ceeeee Merge pull request #1214 from ansionfor/add-graceful-timeout
add graceful reload timeout config
2021-04-10 08:23:54 +08:00
90f4bba8fd gofmt格式化 2021-04-07 20:52:38 +08:00
802187abc4 add graceful reload timeout 2021-04-07 16:06:37 +08:00
49dd17c047 remove maxIdleTime feature due to be compatible with old Golang version <= v1.14 2021-04-04 12:13:51 +08:00
a3b94c24de fix issue #854; add maxIdleTime configuration and comments update for package gdb; version updates 2021-04-04 12:01:22 +08:00
ac4485dc84 comment update for package gsession 2021-04-02 18:00:50 +08:00
4d1e25cdab change internal constant varibale names for package gqueue 2021-04-02 16:16:54 +08:00
ec00e99477 comments update for package gtime 2021-04-01 09:41:14 +08:00
a73dca3e50 Merge pull request #1196 from yanllllk/yys 2021-04-01 09:27:59 +08:00
yys
ed71d2b2ac add test 2021-03-31 14:07:28 +08:00
3911c2e0a2 README updates 2021-03-30 18:00:44 +08:00
1b6765db50 README updates 2021-03-30 17:44:57 +08:00
345fb69521 README updates 2021-03-30 17:43:44 +08:00
83a6abc70f README updates 2021-03-30 17:42:00 +08:00
cfef726205 README updates 2021-03-30 17:41:10 +08:00
de0f9e728c README updates 2021-03-30 17:39:56 +08:00
d9754a532b version updates 2021-03-30 17:20:13 +08:00
dc51023c61 upgrade github.com/olekukonko/tablewriter from 0.0.1 to 0.0.5 2021-03-30 16:54:52 +08:00
87ce5c5419 Merge branch 'master' of https://github.com/gogf/gf 2021-03-30 16:48:19 +08:00
c4fc9f9618 fix issue #1204: improve validation support for time value, eg: gtime.Time/*gtime.Time, time.Time/*time.Time. 2021-03-30 16:06:20 +08:00
8d7aa5db83 Merge pull request #1205 from Zh1Cheung/master
bug:The parameter of the New method is a pointer
2021-03-30 15:39:31 +08:00
9c70fe3be8 add more unit testing case for package gvalid 2021-03-30 15:25:26 +08:00
78027d2ec6 deprecated comments update 2021-03-30 13:43:08 +08:00
d4e4b9addf improve package gcfg for automatically checking and adding path of package main 2021-03-29 18:05:47 +08:00
b2acd22f58 improve package gi18n 2021-03-29 17:36:51 +08:00
ae5ecb5bfa update unit testing case for package gtrace 2021-03-29 16:12:14 +08:00
6f1340ce36 upgrade otel from v0.18.0 to v0.19.0 2021-03-29 16:00:56 +08:00
473fdba14f add replace in go.mod for otel 2021-03-29 15:51:03 +08:00
93a01a1aaf bug:The parameter of the New method is a pointer 2021-03-27 16:21:28 +08:00
yys
7c51ff4707 gtime 修改 2021-03-25 13:38:59 +08:00
bb6883213f fix issue #1202 2021-03-24 22:04:04 +08:00
54395b39a6 fix issue #1202 2021-03-24 21:19:23 +08:00
6deb817fd0 fix issue in unit testing case for package gpage 2021-03-23 17:58:09 +08:00
482e093331 add map/[]map converting support for gconv.Scan;improve gconv.MaptoMaps 2021-03-23 17:53:20 +08:00
042f903157 Merge branch 'master' of https://github.com/gogf/gf 2021-03-23 14:23:47 +08:00
c423ad4830 fix issue in gfile.MainPkgPath;improve fields filter for package gdb 2021-03-23 14:21:54 +08:00
958b109a12 fix removing cookie issue 2021-03-19 19:16:21 +08:00
113fffdd69 Merge pull request #1199 from yangyanqing/yangyanqing/fix-zte-chinese 2021-03-19 17:08:31 +08:00
4aaf09fded improve gfile.MainPkgPath 2021-03-19 16:35:55 +08:00
2f3df76f37 fix issue #1190 2021-03-19 15:37:33 +08:00
cf7706b16d ZTE 的中文名应该是“中兴通讯”,不是“中兴科技” 2021-03-19 11:45:17 +08:00
36963c05a2 fix issue #1190 2021-03-19 11:35:12 +08:00
48d840a1b8 ad go.sum 2021-03-18 15:29:42 +08:00
e3ebc908bb improve package gconv 2021-03-18 15:21:05 +08:00
9ed8d8c113 rename max content log size name for gtrace from maxlogsize to maxcontentlogsize 2021-03-18 11:22:44 +08:00
9eab5daf19 update required minimum go version from 1.11 to 1.14 2021-03-18 11:19:36 +08:00
6a24b595f0 upgrade otel from 0.16.0 to 0.18.0 2021-03-18 10:39:23 +08:00
150f237f13 fix issue in incorrect parameter sequence in package gi18n;improve package gcfg for detailed error printing 2021-03-16 14:39:01 +08:00
yys
17233084f1 为gtime添加scanner和valuer 2021-03-13 14:07:22 +08:00
41f2138b39 fix issue of overflow in grand.D 2021-03-11 23:29:39 +08:00
6376b8aaa6 remove session storage file removing feature for package gsession 2021-03-11 21:21:47 +08:00
58362ad143 add grand.D for random time.Duration;add checking and removing session files for package gsession 2021-03-11 20:05:08 +08:00
d72d23c2eb change browser mode from boolean markable variable to cookiejar for ghttp.Client 2021-03-11 18:58:13 +08:00
7702c5bfde add internal logging for package gdb/gredis 2021-03-11 11:39:49 +08:00
20f2a6c003 add recursive validation feature of struct attribute for package gvalid for #1165 2021-03-10 23:28:34 +08:00
0d4c1c47d5 improve package grand 2021-03-10 21:19:11 +08:00
4d32733790 improve ghttp.Client for #1179 2021-03-09 22:54:38 +08:00
0e58b6e95b README updates 2021-03-08 23:27:48 +08:00
131 changed files with 3198 additions and 1570 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -9,10 +9,11 @@ import (
// 使用原生gredis.New操作redis但是注意需要自己调用Close方法关闭redis链接池
func main() {
redis := gredis.New(gredis.Config{
config := &gredis.Config{
Host: "127.0.0.1",
Port: 6379,
})
}
redis := gredis.New(config)
defer redis.Close()
redis.Do("SET", "k", "v")
v, _ := redis.Do("GET", "k")

View File

@ -8,7 +8,6 @@ import (
func main() {
s := g.Server()
s.SetSessionCookieMaxAge(0)
s.Group("/", func(group *ghttp.RouterGroup) {
group.GET("/set", func(r *ghttp.Request) {
r.Session.Set("time", gtime.Timestamp())

1
.gitignore vendored
View File

@ -14,5 +14,4 @@ bin/
cbuild
**/.DS_Store
.vscode/
go.sum
.example/other/

View File

@ -4,11 +4,9 @@ arch: arm64-graviton2
language: go
go:
- "1.11.x"
- "1.12.x"
- "1.13.x"
- "1.14.x"
- "1.15.x"
- "1.16.x"
branches:
only:

View File

@ -9,11 +9,8 @@
English | [简体中文](README_ZH.MD)
`GF(GoFrame)` is a modular, powerful, high-performance and enterprise-class application development framework
of Golang. Providing a series of core components and dozens of practical modules, such as:
cache, logging, containers, timer, resource, validator, database orm, etc.
Supporting web server integrated with router, cookie, session, middleware, logger, configure,
template, https, hooks, rewrites and many more features.
`GoFrame` is a modular, powerful, high-performance and enterprise-class application development framework
of Golang.
> If you're a newbie to `Go`, you may consider `GoFrame` easy and great as `Laravel` in `PHP`, `SpringBoot` in `Java` or `Django` in `Python`.
@ -53,17 +50,10 @@ The `Web` component performance of `GoFrame`, please refer to third-party projec
# Documentation
* 中文官网: https://goframe.org
* GoDoc API: https://godoc.org/github.com/gogf/gf
* 中文官网: [https://goframe.org](https://goframe.org/display/gf)
* GoDoc API: [https://pkg.go.dev/github.com/gogf/gf](https://pkg.go.dev/github.com/gogf/gf)
# Discussion
- QQ Group[116707870](//shang.qq.com/wpa/qunwpa?idkey=195f91eceeb5d7fa76009b7cd5a4641f70bf4897b7f5a520635eb26ff17adfe7)
- WX GroupAdd friend`389961817` in WeChat, commenting `GF`
- Issueshttps://github.com/gogf/gf/issues
> It's recommended learning `GoFrame` through its awesome source codes and API reference.
# License
`GF` is licensed under the [MIT License](LICENSE), 100% free and open-source, forever.

View File

@ -8,24 +8,21 @@
[English](README.MD) | 简体中文
`GF(Go Frame)`是一款模块化、高性能、企业级的Go基础开发框架。
实现了比较完善的基础设施建设以及开发工具链,提供了常用的基础开发模块,
如:缓存、日志、队列、数组、集合、容器、定时器、命令行、内存锁、对象池、
配置管理、资源管理、数据校验、数据编码、定时任务、数据库ORM、TCP/UDP组件、进程管理/通信等等。
并提供了Web服务开发的系列核心组件Router、Cookie、Session、Middleware、服务注册、模板引擎等等
支持热重启、热更新、域名绑定、TLS/HTTPS、Rewrite等特性。
`GoFrame`是一款模块化、高性能、企业级的Go基础开发框架。
> 如果您初识`Go`语言,您可以将`GoFrame`类似于`PHP`中的`Laravel`, `Java`中的`SpringBoot`或者`Python`中的`Django`。
# 特点
* 模块化、松耦合设计;
* 模块丰富、开箱即用
* 简便易用、易于维护;
* 高代码质量、高单元测试覆盖率;
* 社区活跃,大牛谦逊低调脾气好;
* 详尽的开发文档及示例;
* 完善的本地中文化支持;
* 设计为团队及企业使用;
* 模块化、松耦合
* 模块丰富、开箱即用
* 简洁易用、快速接入
* 文档详尽、易于维护
* 自顶向下、体系化设计
* 统一框架、统一组件、降低选择成本
* 开发规范、设计模式、代码分层模型
* 强大便捷的开发工具链
* 完善的本地中文化支持
* 设计为团队及企业使用
# 地址
- **主库**https://github.com/gogf/gf
@ -68,16 +65,10 @@ golang版本 >= 1.11
# 文档
开发文档:https://goframe.org
官方站点:[https://goframe.org](https://goframe.org/display/gf)
接口文档https://godoc.org/github.com/gogf/gf
接口文档:[https://pkg.go.dev/github.com/gogf/gf](https://pkg.go.dev/github.com/gogf/gf)
# 帮助
- QQ交流群[116707870](//shang.qq.com/wpa/qunwpa?idkey=195f91eceeb5d7fa76009b7cd5a4641f70bf4897b7f5a520635eb26ff17adfe7)
- WX交流群微信添加`389961817`备注`GF`
- 主库ISSUEhttps://github.com/gogf/gf/issues
> 建议通过阅读`GoFrame`的源码以及API文档深度学习`GoFrame`,了解更多的精妙设计。
# 协议
@ -86,7 +77,7 @@ golang版本 >= 1.11
# 用户
- [腾讯科技](https://www.tencent.com/)
- [中兴科技](https://www.zte.com.cn/china/)
- [中兴通讯](https://www.zte.com.cn/china/)
- [蚂蚁金服](https://www.antfin.com/)
- [医联科技](https://www.medlinker.com/)
- [库币科技](https://www.kucoin.io/)

View File

@ -35,10 +35,8 @@ type Queue struct {
}
const (
// Size for queue buffer.
gDEFAULT_QUEUE_SIZE = 10000
// Max batch size per-fetching from list.
gDEFAULT_MAX_BATCH_SIZE = 10
defaultQueueSize = 10000 // Size for queue buffer.
defaultBatchSize = 10 // Max batch size per-fetching from list.
)
// New returns an empty queue object.
@ -54,7 +52,7 @@ func New(limit ...int) *Queue {
} else {
q.list = glist.New(true)
q.events = make(chan struct{}, math.MaxInt32)
q.C = make(chan interface{}, gDEFAULT_QUEUE_SIZE)
q.C = make(chan interface{}, defaultQueueSize)
go q.asyncLoopFromListToChannel()
}
return q
@ -72,8 +70,8 @@ func (q *Queue) asyncLoopFromListToChannel() {
<-q.events
for !q.closed.Val() {
if length := q.list.Len(); length > 0 {
if length > gDEFAULT_MAX_BATCH_SIZE {
length = gDEFAULT_MAX_BATCH_SIZE
if length > defaultBatchSize {
length = defaultBatchSize
}
for _, v := range q.list.PopFronts(length) {
// When q.C is closed, it will panic here, especially q.C is being blocked for writing.
@ -101,7 +99,7 @@ func (q *Queue) Push(v interface{}) {
q.C <- v
} else {
q.list.PushBack(v)
if len(q.events) < gDEFAULT_QUEUE_SIZE {
if len(q.events) < defaultQueueSize {
q.events <- struct{}{}
}
}
@ -124,7 +122,7 @@ func (q *Queue) Close() {
if q.limit > 0 {
close(q.C)
}
for i := 0; i < gDEFAULT_MAX_BATCH_SIZE; i++ {
for i := 0; i < defaultBatchSize; i++ {
q.Pop()
}
}

View File

@ -71,13 +71,6 @@ func (v *Var) MapToMap(pointer interface{}, mapping ...map[string]string) (err e
return gconv.MapToMap(v.Val(), pointer, mapping...)
}
// MapToMapDeep converts any map type variable <params> to another map type variable
// <pointer> recursively.
// See gconv.MapToMapDeep.
func (v *Var) MapToMapDeep(pointer interface{}, mapping ...map[string]string) (err error) {
return gconv.MapToMapDeep(v.Val(), pointer, mapping...)
}
// MapToMaps converts any map type variable <params> to another map type variable <pointer>.
// See gconv.MapToMaps.
func (v *Var) MapToMaps(pointer interface{}, mapping ...map[string]string) (err error) {

View File

@ -32,11 +32,12 @@ type DB interface {
// Model creation.
// ===========================================================================
// Table function is deprecated, use Model instead.
// The DB interface is designed not only for
// relational databases but also for NoSQL databases in the future. The name
// "Table" is not proper for that purpose any more.
// Deprecated, use Model instead.
Table(table ...string) *Model
// Also see Core.Table.
Table(tableNameOrStruct ...interface{}) *Model
// Model creates and returns a new ORM model from given schema.
// The parameter `table` can be more than one table names, and also alias name, like:
@ -46,144 +47,152 @@ type DB interface {
// Model("user, user_detail")
// Model("user u, user_detail ud")
// 2. Model name with alias: Model("user", "u")
Model(table ...string) *Model
// Also see Core.Model.
Model(tableNameOrStruct ...interface{}) *Model
// Schema creates and returns a schema.
// Also see Core.Schema.
Schema(schema string) *Schema
// With creates and returns an ORM model based on meta data of given object.
With(object interface{}) *Model
// Also see Core.With.
With(objects ...interface{}) *Model
// Open creates a raw connection object for database with given node configuration.
// Note that it is not recommended using the this function manually.
// Also see DriverMysql.Open.
Open(config *ConfigNode) (*sql.DB, error)
// Ctx is a chaining function, which creates and returns a new DB that is a shallow copy
// of current DB object and with given context in it.
// Note that this returned DB object can be used only once, so do not assign it to
// a global or package variable for long using.
// Also see Core.Ctx.
Ctx(ctx context.Context) DB
// ===========================================================================
// Query APIs.
// ===========================================================================
Query(sql string, args ...interface{}) (*sql.Rows, error)
Exec(sql string, args ...interface{}) (sql.Result, error)
Prepare(sql string, execOnMaster ...bool) (*Stmt, error)
Query(sql string, args ...interface{}) (*sql.Rows, error) // See Core.Query.
Exec(sql string, args ...interface{}) (sql.Result, error) // See Core.Exec.
Prepare(sql string, execOnMaster ...bool) (*Stmt, error) // See Core.Prepare.
// ===========================================================================
// Common APIs for CURD.
// ===========================================================================
Insert(table string, data interface{}, batch ...int) (sql.Result, error)
InsertIgnore(table string, data interface{}, batch ...int) (sql.Result, error)
Replace(table string, data interface{}, batch ...int) (sql.Result, error)
Save(table string, data interface{}, batch ...int) (sql.Result, error)
Insert(table string, data interface{}, batch ...int) (sql.Result, error) // See Core.Insert.
InsertIgnore(table string, data interface{}, batch ...int) (sql.Result, error) // See Core.InsertIgnore.
Replace(table string, data interface{}, batch ...int) (sql.Result, error) // See Core.Replace.
Save(table string, data interface{}, batch ...int) (sql.Result, error) // See Core.Save.
BatchInsert(table string, list interface{}, batch ...int) (sql.Result, error)
BatchReplace(table string, list interface{}, batch ...int) (sql.Result, error)
BatchSave(table string, list interface{}, batch ...int) (sql.Result, error)
BatchInsert(table string, list interface{}, batch ...int) (sql.Result, error) // See Core.BatchInsert.
BatchReplace(table string, list interface{}, batch ...int) (sql.Result, error) // See Core.BatchReplace.
BatchSave(table string, list interface{}, batch ...int) (sql.Result, error) // See Core.BatchSave.
Update(table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error)
Delete(table string, condition interface{}, args ...interface{}) (sql.Result, error)
Update(table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error) // See Core.Update.
Delete(table string, condition interface{}, args ...interface{}) (sql.Result, error) // See Core.Delete.
// ===========================================================================
// Internal APIs for CURD, which can be overwrote for custom CURD implements.
// ===========================================================================
DoQuery(link Link, sql string, args ...interface{}) (rows *sql.Rows, err error)
DoGetAll(link Link, sql string, args ...interface{}) (result Result, err error)
DoExec(link Link, sql string, args ...interface{}) (result sql.Result, err error)
DoPrepare(link Link, sql string) (*Stmt, error)
DoInsert(link Link, table string, data interface{}, option int, batch ...int) (result sql.Result, err error)
DoBatchInsert(link Link, table string, list interface{}, option int, batch ...int) (result sql.Result, err error)
DoUpdate(link Link, table string, data interface{}, condition string, args ...interface{}) (result sql.Result, err error)
DoDelete(link Link, table string, condition string, args ...interface{}) (result sql.Result, err error)
DoQuery(link Link, sql string, args ...interface{}) (rows *sql.Rows, err error) // See Core.DoQuery.
DoGetAll(link Link, sql string, args ...interface{}) (result Result, err error) // See Core.DoGetAll.
DoExec(link Link, sql string, args ...interface{}) (result sql.Result, err error) // See Core.DoExec.
DoPrepare(link Link, sql string) (*Stmt, error) // See Core.DoPrepare.
DoInsert(link Link, table string, data interface{}, option int, batch ...int) (result sql.Result, err error) // See Core.DoInsert.
DoBatchInsert(link Link, table string, list interface{}, option int, batch ...int) (result sql.Result, err error) // See Core.DoBatchInsert.
DoUpdate(link Link, table string, data interface{}, condition string, args ...interface{}) (result sql.Result, err error) // See Core.DoUpdate.
DoDelete(link Link, table string, condition string, args ...interface{}) (result sql.Result, err error) // See Core.DoDelete.
// ===========================================================================
// Query APIs for convenience purpose.
// ===========================================================================
GetAll(sql string, args ...interface{}) (Result, error)
GetOne(sql string, args ...interface{}) (Record, error)
GetValue(sql string, args ...interface{}) (Value, error)
GetArray(sql string, args ...interface{}) ([]Value, error)
GetCount(sql string, args ...interface{}) (int, error)
GetStruct(objPointer interface{}, sql string, args ...interface{}) error
GetStructs(objPointerSlice interface{}, sql string, args ...interface{}) error
GetScan(objPointer interface{}, sql string, args ...interface{}) error
GetAll(sql string, args ...interface{}) (Result, error) // See Core.GetAll.
GetOne(sql string, args ...interface{}) (Record, error) // See Core.GetOne.
GetValue(sql string, args ...interface{}) (Value, error) // See Core.GetValue.
GetArray(sql string, args ...interface{}) ([]Value, error) // See Core.GetArray.
GetCount(sql string, args ...interface{}) (int, error) // See Core.GetCount.
GetStruct(objPointer interface{}, sql string, args ...interface{}) error // See Core.GetStruct.
GetStructs(objPointerSlice interface{}, sql string, args ...interface{}) error // See Core.GetStructs.
GetScan(objPointer interface{}, sql string, args ...interface{}) error // See Core.GetScan.
// ===========================================================================
// Master/Slave specification support.
// ===========================================================================
Master() (*sql.DB, error)
Slave() (*sql.DB, error)
Master() (*sql.DB, error) // See Core.Master.
Slave() (*sql.DB, error) // See Core.Slave.
// ===========================================================================
// Ping-Pong.
// ===========================================================================
PingMaster() error
PingSlave() error
PingMaster() error // See Core.PingMaster.
PingSlave() error // See Core.PingSlave.
// ===========================================================================
// Transaction.
// ===========================================================================
Begin() (*TX, error)
Transaction(f func(tx *TX) error) (err error)
Begin() (*TX, error) // See Core.Begin.
Transaction(f func(tx *TX) error) (err error) // See Core.Transaction.
// ===========================================================================
// Configuration methods.
// ===========================================================================
GetCache() *gcache.Cache
SetDebug(debug bool)
GetDebug() bool
SetSchema(schema string)
GetSchema() string
GetPrefix() string
GetGroup() string
SetDryRun(dryrun bool)
GetDryRun() bool
SetLogger(logger *glog.Logger)
GetLogger() *glog.Logger
GetConfig() *ConfigNode
SetMaxIdleConnCount(n int)
SetMaxOpenConnCount(n int)
SetMaxConnLifetime(d time.Duration)
GetCache() *gcache.Cache // See Core.GetCache.
SetDebug(debug bool) // See Core.SetDebug.
GetDebug() bool // See Core.GetDebug.
SetSchema(schema string) // See Core.SetSchema.
GetSchema() string // See Core.GetSchema.
GetPrefix() string // See Core.GetPrefix.
GetGroup() string // See Core.GetGroup.
SetDryRun(enabled bool) // See Core.SetDryRun.
GetDryRun() bool // See Core.GetDryRun.
SetLogger(logger *glog.Logger) // See Core.SetLogger.
GetLogger() *glog.Logger // See Core.GetLogger.
GetConfig() *ConfigNode // See Core.GetConfig.
SetMaxIdleConnCount(n int) // See Core.SetMaxIdleConnCount.
SetMaxOpenConnCount(n int) // See Core.SetMaxOpenConnCount.
SetMaxConnLifeTime(d time.Duration) // See Core.SetMaxConnLifeTime.
// ===========================================================================
// Utility methods.
// ===========================================================================
GetCtx() context.Context
GetChars() (charLeft string, charRight string)
GetMaster(schema ...string) (*sql.DB, error)
GetSlave(schema ...string) (*sql.DB, error)
QuoteWord(s string) string
QuoteString(s string) string
QuotePrefixTableName(table string) string
Tables(schema ...string) (tables []string, err error)
TableFields(table string, schema ...string) (map[string]*TableField, error)
HasTable(name string) (bool, error)
FilteredLinkInfo() string
GetCtx() context.Context // See Core.GetCtx.
GetChars() (charLeft string, charRight string) // See Core.GetChars.
GetMaster(schema ...string) (*sql.DB, error) // See Core.GetMaster.
GetSlave(schema ...string) (*sql.DB, error) // See Core.GetSlave.
QuoteWord(s string) string // See Core.QuoteWord.
QuoteString(s string) string // See Core.QuoteString.
QuotePrefixTableName(table string) string // See Core.QuotePrefixTableName.
Tables(schema ...string) (tables []string, err error) // See Core.Tables.
TableFields(link Link, table string, schema ...string) (map[string]*TableField, error) // See Core.TableFields.
HasTable(name string) (bool, error) // See Core.HasTable.
FilteredLinkInfo() string // See Core.FilteredLinkInfo.
// HandleSqlBeforeCommit is a hook function, which deals with the sql string before
// it's committed to underlying driver. The parameter `link` specifies the current
// database connection operation object. You can modify the sql string `sql` and its
// arguments `args` as you wish before they're committed to driver.
// Also see Core.HandleSqlBeforeCommit.
HandleSqlBeforeCommit(link Link, sql string, args []interface{}) (string, []interface{})
// ===========================================================================
// Internal methods, for internal usage purpose, you do not need consider it.
// ===========================================================================
mappingAndFilterData(schema, table string, data map[string]interface{}, filter bool) (map[string]interface{}, error)
convertFieldValueToLocalValue(fieldValue interface{}, fieldType string) interface{}
convertRowsToResult(rows *sql.Rows) (Result, error)
mappingAndFilterData(schema, table string, data map[string]interface{}, filter bool) (map[string]interface{}, error) // See Core.mappingAndFilterData.
convertFieldValueToLocalValue(fieldValue interface{}, fieldType string) interface{} // See Core.convertFieldValueToLocalValue.
convertRowsToResult(rows *sql.Rows) (Result, error) // See Core.convertRowsToResult.
addSqlToTracing(ctx context.Context, sql *Sql) // See Core.addSqlToTracing.
writeSqlToLogger(v *Sql) // See Core.writeSqlToLogger.
}
// Core is the base struct for database management.
@ -299,6 +308,9 @@ var (
// internalCache is the memory cache for internal usage.
internalCache = gcache.New()
// tableFieldsMap caches the table information retrived from database.
tableFieldsMap = gmap.New(true)
// allDryRun sets dry-run feature for all database connections.
// It is commonly used for command options for convenience.
allDryRun = false
@ -471,11 +483,26 @@ func (c *Core) getSqlDb(master bool, schema ...string) (sqlDb *sql.DB, err error
}
// Cache the underlying connection pool object by node.
v, _ := internalCache.GetOrSetFuncLock(node.String(), func() (interface{}, error) {
intlog.Printf(
`open new connection, master:%#v, config:%#v, node:%#v`,
master, c.config, node,
)
defer func() {
if err != nil {
intlog.Printf(`open new connection failed: %v, %#v`, err, node)
} else {
intlog.Printf(
`open new connection success, master:%#v, config:%#v, node:%#v`,
master, c.config, node,
)
}
}()
sqlDb, err = c.db.Open(node)
if err != nil {
intlog.Printf("DB open failed: %v, %+v", err, node)
return nil, err
}
if c.config.MaxIdleConnCount > 0 {
sqlDb.SetMaxIdleConns(c.config.MaxIdleConnCount)
} else {
@ -486,13 +513,13 @@ func (c *Core) getSqlDb(master bool, schema ...string) (sqlDb *sql.DB, err error
} else {
sqlDb.SetMaxOpenConns(defaultMaxOpenConnCount)
}
if c.config.MaxConnLifetime > 0 {
if c.config.MaxConnLifeTime > 0 {
// Automatically checks whether MaxConnLifetime is configured using string like: "30s", "60s", etc.
// Or else it is configured just using number, which means value in seconds.
if c.config.MaxConnLifetime > time.Second {
sqlDb.SetConnMaxLifetime(c.config.MaxConnLifetime)
if c.config.MaxConnLifeTime > time.Second {
sqlDb.SetConnMaxLifetime(c.config.MaxConnLifeTime)
} else {
sqlDb.SetConnMaxLifetime(c.config.MaxConnLifetime * time.Second)
sqlDb.SetConnMaxLifetime(c.config.MaxConnLifeTime * time.Second)
}
} else {
sqlDb.SetConnMaxLifetime(defaultMaxConnLifeTime)

View File

@ -113,7 +113,6 @@ func (c *Core) DoQuery(link Link, sql string, args ...interface{}) (rows *sql.Ro
if c.GetConfig().QueryTimeout > 0 {
ctx, _ = context.WithTimeout(ctx, c.GetConfig().QueryTimeout)
}
mTime1 := gtime.TimestampMilli()
rows, err = link.QueryContext(ctx, sql, args...)
mTime2 := gtime.TimestampMilli()
@ -277,7 +276,7 @@ func (c *Core) GetOne(sql string, args ...interface{}) (Record, error) {
}
// GetArray queries and returns data values as slice from database.
// Note that if there're multiple columns in the result, it returns just one column values randomly.
// Note that if there are multiple columns in the result, it returns just one column values randomly.
func (c *Core) GetArray(sql string, args ...interface{}) ([]Value, error) {
all, err := c.db.DoGetAll(nil, sql, args...)
if err != nil {
@ -382,13 +381,13 @@ func (c *Core) Begin() (*TX, error) {
if master, err := c.db.Master(); err != nil {
return nil, err
} else {
ctx := c.db.GetCtx()
if c.GetConfig().TranTimeout > 0 {
var cancelFunc context.CancelFunc
ctx, cancelFunc = context.WithTimeout(ctx, c.GetConfig().TranTimeout)
defer cancelFunc()
}
if tx, err := master.BeginTx(ctx, nil); err == nil {
//ctx := c.db.GetCtx()
//if c.GetConfig().TranTimeout > 0 {
// var cancelFunc context.CancelFunc
// ctx, cancelFunc = context.WithTimeout(ctx, c.GetConfig().TranTimeout)
// defer cancelFunc()
//}
if tx, err := master.Begin(); err == nil {
return &TX{
db: c.db,
tx: tx,
@ -813,13 +812,19 @@ func (c *Core) DoUpdate(link Link, table string, data interface{}, condition str
switch value := v.(type) {
case *Counter:
if value.Value != 0 {
column := c.db.QuoteWord(value.Field)
column := k
if value.Field != "" {
column = c.db.QuoteWord(value.Field)
}
fields = append(fields, fmt.Sprintf("%s=%s+?", column, column))
params = append(params, value.Value)
}
case Counter:
if value.Value != 0 {
column := c.db.QuoteWord(value.Field)
column := k
if value.Field != "" {
column = c.db.QuoteWord(value.Field)
}
fields = append(fields, fmt.Sprintf("%s=%s+?", column, column))
params = append(params, value.Value)
}
@ -830,7 +835,6 @@ func (c *Core) DoUpdate(link Link, table string, data interface{}, condition str
fields = append(fields, c.db.QuoteWord(k)+"=?")
params = append(params, v)
}
}
}
updates = strings.Join(fields, ",")

View File

@ -42,7 +42,7 @@ type ConfigNode struct {
LinkInfo string `json:"link"` // (Optional) Custom link information, when it is used, configuration Host/Port/User/Pass/Name are ignored.
MaxIdleConnCount int `json:"maxIdle"` // (Optional) Max idle connection configuration for underlying connection pool.
MaxOpenConnCount int `json:"maxOpen"` // (Optional) Max open connection configuration for underlying connection pool.
MaxConnLifetime time.Duration `json:"maxLifetime"` // (Optional) Max connection TTL configuration for underlying connection pool.
MaxConnLifeTime time.Duration `json:"maxLifeTime"` // (Optional) Max amount of time a connection may be idle before being closed.
QueryTimeout time.Duration `json:"queryTimeout"` // (Optional) Max query time for per dql.
ExecTimeout time.Duration `json:"execTimeout"` // (Optional) Max exec time for dml.
TranTimeout time.Duration `json:"tranTimeout"` // (Optional) Max exec time time for a transaction.
@ -141,20 +141,39 @@ func (c *Core) GetLogger() *glog.Logger {
return c.logger
}
// SetMaxIdleConnCount sets the max idle connection count for underlying connection pool.
// SetMaxIdleConnCount sets the maximum number of connections in the idle
// connection pool.
//
// If MaxOpenConns is greater than 0 but less than the new MaxIdleConns,
// then the new MaxIdleConns will be reduced to match the MaxOpenConns limit.
//
// If n <= 0, no idle connections are retained.
//
// The default max idle connections is currently 2. This may change in
// a future release.
func (c *Core) SetMaxIdleConnCount(n int) {
c.config.MaxIdleConnCount = n
}
// SetMaxOpenConnCount sets the max open connection count for underlying connection pool.
// SetMaxOpenConnCount sets the maximum number of open connections to the database.
//
// If MaxIdleConns is greater than 0 and the new MaxOpenConns is less than
// MaxIdleConns, then MaxIdleConns will be reduced to match the new
// MaxOpenConns limit.
//
// If n <= 0, then there is no limit on the number of open connections.
// The default is 0 (unlimited).
func (c *Core) SetMaxOpenConnCount(n int) {
c.config.MaxOpenConnCount = n
}
// SetMaxConnLifetime sets the connection TTL for underlying connection pool.
// If parameter `d` <= 0, it means the connection never expires.
func (c *Core) SetMaxConnLifetime(d time.Duration) {
c.config.MaxConnLifetime = d
// SetMaxConnLifeTime sets the maximum amount of time a connection may be reused.
//
// Expired connections may be closed lazily before reuse.
//
// If d <= 0, connections are not closed due to a connection's age.
func (c *Core) SetMaxConnLifeTime(d time.Duration) {
c.config.MaxConnLifeTime = d
}
// String returns the node as string.
@ -165,7 +184,7 @@ func (node *ConfigNode) String() string {
node.Name, node.Type, node.Role, node.Charset, node.Debug,
node.MaxIdleConnCount,
node.MaxOpenConnCount,
node.MaxConnLifetime,
node.MaxConnLifeTime,
node.LinkInfo,
)
}

View File

@ -149,7 +149,7 @@ func (c *Core) convertFieldValueToLocalValue(fieldValue interface{}, fieldType s
// mappingAndFilterData automatically mappings the map key to table field and removes
// all key-value pairs that are not the field of given table.
func (c *Core) mappingAndFilterData(schema, table string, data map[string]interface{}, filter bool) (map[string]interface{}, error) {
if fieldsMap, err := c.db.TableFields(table, schema); err == nil {
if fieldsMap, err := c.db.TableFields(nil, table, schema); err == nil {
fieldsKeyMap := make(map[string]interface{}, len(fieldsMap))
for k, _ := range fieldsMap {
fieldsKeyMap[k] = nil

View File

@ -14,8 +14,8 @@ import (
"github.com/gogf/gf/net/gtrace"
"github.com/gogf/gf/os/gcmd"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/label"
"go.opentelemetry.io/otel/trace"
)
@ -59,33 +59,33 @@ func (c *Core) addSqlToTracing(ctx context.Context, sql *Sql) {
if sql.Error != nil {
span.SetStatus(codes.Error, fmt.Sprintf(`%+v`, sql.Error))
}
labels := make([]label.KeyValue, 0)
labels := make([]attribute.KeyValue, 0)
labels = append(labels, gtrace.CommonLabels()...)
labels = append(labels,
label.String(tracingAttrDbType, c.db.GetConfig().Type),
attribute.String(tracingAttrDbType, c.db.GetConfig().Type),
)
if c.db.GetConfig().Host != "" {
labels = append(labels, label.String(tracingAttrDbHost, c.db.GetConfig().Host))
labels = append(labels, attribute.String(tracingAttrDbHost, c.db.GetConfig().Host))
}
if c.db.GetConfig().Port != "" {
labels = append(labels, label.String(tracingAttrDbPort, c.db.GetConfig().Port))
labels = append(labels, attribute.String(tracingAttrDbPort, c.db.GetConfig().Port))
}
if c.db.GetConfig().Name != "" {
labels = append(labels, label.String(tracingAttrDbName, c.db.GetConfig().Name))
labels = append(labels, attribute.String(tracingAttrDbName, c.db.GetConfig().Name))
}
if c.db.GetConfig().User != "" {
labels = append(labels, label.String(tracingAttrDbUser, c.db.GetConfig().User))
labels = append(labels, attribute.String(tracingAttrDbUser, c.db.GetConfig().User))
}
if filteredLinkInfo := c.db.FilteredLinkInfo(); filteredLinkInfo != "" {
labels = append(labels, label.String(tracingAttrDbLink, c.db.FilteredLinkInfo()))
labels = append(labels, attribute.String(tracingAttrDbLink, c.db.FilteredLinkInfo()))
}
if group := c.db.GetGroup(); group != "" {
labels = append(labels, label.String(tracingAttrDbGroup, group))
labels = append(labels, attribute.String(tracingAttrDbGroup, group))
}
span.SetAttributes(labels...)
span.AddEvent(tracingEventDbExecution, trace.WithAttributes(
label.String(tracingEventDbExecutionSql, sql.Format),
label.String(tracingEventDbExecutionCost, fmt.Sprintf(`%d ms`, sql.End-sql.Start)),
label.String(tracingEventDbExecutionType, sql.Type),
attribute.String(tracingEventDbExecutionSql, sql.Format),
attribute.String(tracingEventDbExecutionCost, fmt.Sprintf(`%d ms`, sql.End-sql.Start)),
attribute.String(tracingEventDbExecutionType, sql.Type),
))
}

View File

@ -203,7 +203,9 @@ func (d *DriverMssql) Tables(schema ...string) (tables []string, err error) {
}
// TableFields retrieves and returns the fields information of specified table of current schema.
func (d *DriverMssql) TableFields(table string, schema ...string) (fields map[string]*TableField, err error) {
//
// Also see DriverMysql.TableFields.
func (d *DriverMssql) TableFields(link Link, table string, schema ...string) (fields map[string]*TableField, err error) {
charL, charR := d.GetChars()
table = gstr.Trim(table, charL+charR)
if gstr.Contains(table, " ") {
@ -213,18 +215,21 @@ func (d *DriverMssql) TableFields(table string, schema ...string) (fields map[st
if len(schema) > 0 && schema[0] != "" {
checkSchema = schema[0]
}
v, _ := internalCache.GetOrSetFunc(
fmt.Sprintf(`mssql_table_fields_%s_%s@group:%s`, table, checkSchema, d.GetGroup()),
func() (interface{}, error) {
var (
result Result
link *sql.DB
)
tableFieldsCacheKey := fmt.Sprintf(
`mssql_table_fields_%s_%s@group:%s`,
table, checkSchema, d.GetGroup(),
)
v := tableFieldsMap.GetOrSetFuncLock(tableFieldsCacheKey, func() interface{} {
var (
result Result
)
if link == nil {
link, err = d.db.GetSlave(checkSchema)
if err != nil {
return nil, err
return nil
}
structureSql := fmt.Sprintf(`
}
structureSql := fmt.Sprintf(`
SELECT
a.name Field,
CASE b.name
@ -252,29 +257,29 @@ 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, err
strings.ToUpper(table),
)
structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql))
result, err = d.db.DoGetAll(link, structureSql)
if err != nil {
return nil
}
fields = make(map[string]*TableField)
for i, m := range result {
fields[strings.ToLower(m["Field"].String())] = &TableField{
Index: i,
Name: strings.ToLower(m["Field"].String()),
Type: strings.ToLower(m["Type"].String()),
Null: m["Null"].Bool(),
Key: m["Key"].String(),
Default: m["Default"].Val(),
Extra: m["Extra"].String(),
Comment: m["Comment"].String(),
}
fields = make(map[string]*TableField)
for i, m := range result {
fields[strings.ToLower(m["Field"].String())] = &TableField{
Index: i,
Name: strings.ToLower(m["Field"].String()),
Type: strings.ToLower(m["Type"].String()),
Null: m["Null"].Bool(),
Key: m["Key"].String(),
Default: m["Default"].Val(),
Extra: m["Extra"].String(),
Comment: m["Comment"].String(),
}
}
return fields, nil
}, 0)
if err == nil {
}
return fields
})
if v != nil {
fields = v.(map[string]*TableField)
}
return

View File

@ -102,13 +102,16 @@ func (d *DriverMysql) Tables(schema ...string) (tables []string, err error) {
// TableFields retrieves and returns the fields information of specified table of current
// schema.
//
// The parameter `link` is optional, if given nil it automatically retrieves a raw sql connection
// as its link to proceed necessary sql query.
//
// Note that it returns a map containing the field name and its corresponding fields.
// As a map is unsorted, the TableField struct has a "Index" field marks its sequence in
// the fields.
//
// It's using cache feature to enhance the performance, which is never expired util the
// process restarts.
func (d *DriverMysql) TableFields(table string, schema ...string) (fields map[string]*TableField, err error) {
func (d *DriverMysql) TableFields(link Link, table string, schema ...string) (fields map[string]*TableField, err error) {
charL, charR := d.GetChars()
table = gstr.Trim(table, charL+charR)
if gstr.Contains(table, " ") {
@ -118,40 +121,43 @@ func (d *DriverMysql) TableFields(table string, schema ...string) (fields map[st
if len(schema) > 0 && schema[0] != "" {
checkSchema = schema[0]
}
v, _ := internalCache.GetOrSetFunc(
fmt.Sprintf(`mysql_table_fields_%s_%s@group:%s`, table, checkSchema, d.GetGroup()),
func() (interface{}, error) {
var (
result Result
link *sql.DB
)
tableFieldsCacheKey := fmt.Sprintf(
`mysql_table_fields_%s_%s@group:%s`,
table, checkSchema, d.GetGroup(),
)
v := tableFieldsMap.GetOrSetFuncLock(tableFieldsCacheKey, func() interface{} {
var (
result Result
)
if link == nil {
link, err = d.db.GetSlave(checkSchema)
if err != nil {
return nil, err
return nil
}
result, err = d.db.DoGetAll(
link,
fmt.Sprintf(`SHOW FULL COLUMNS FROM %s`, d.db.QuoteWord(table)),
)
if err != 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
}
fields = make(map[string]*TableField)
for i, m := range result {
fields[m["Field"].String()] = &TableField{
Index: i,
Name: m["Field"].String(),
Type: m["Type"].String(),
Null: m["Null"].Bool(),
Key: m["Key"].String(),
Default: m["Default"].Val(),
Extra: m["Extra"].String(),
Comment: m["Comment"].String(),
}
fields = make(map[string]*TableField)
for i, m := range result {
fields[m["Field"].String()] = &TableField{
Index: i,
Name: m["Field"].String(),
Type: m["Type"].String(),
Null: m["Null"].Bool(),
Key: m["Key"].String(),
Default: m["Default"].Val(),
Extra: m["Extra"].String(),
Comment: m["Comment"].String(),
}
}
return fields, nil
}, 0)
if err == nil {
}
return fields
})
if v != nil {
fields = v.(map[string]*TableField)
}
return

View File

@ -179,7 +179,9 @@ func (d *DriverOracle) Tables(schema ...string) (tables []string, err error) {
}
// TableFields retrieves and returns the fields information of specified table of current schema.
func (d *DriverOracle) TableFields(table string, schema ...string) (fields map[string]*TableField, err error) {
//
// Also see DriverMysql.TableFields.
func (d *DriverOracle) TableFields(link Link, table string, schema ...string) (fields map[string]*TableField, err error) {
charL, charR := d.GetChars()
table = gstr.Trim(table, charL+charR)
if gstr.Contains(table, " ") {
@ -189,11 +191,14 @@ func (d *DriverOracle) TableFields(table string, schema ...string) (fields map[s
if len(schema) > 0 && schema[0] != "" {
checkSchema = schema[0]
}
v, _ := internalCache.GetOrSetFunc(
fmt.Sprintf(`oracle_table_fields_%s_%s@group:%s`, table, checkSchema, d.GetGroup()),
func() (interface{}, error) {
result := (Result)(nil)
structureSql := fmt.Sprintf(`
tableFieldsCacheKey := fmt.Sprintf(
`oracle_table_fields_%s_%s@group:%s`,
table, checkSchema, d.GetGroup(),
)
v := tableFieldsMap.GetOrSetFuncLock(tableFieldsCacheKey, func() interface{} {
var (
result Result
structureSql = fmt.Sprintf(`
SELECT
COLUMN_NAME AS FIELD,
CASE DATA_TYPE
@ -203,22 +208,29 @@ SELECT
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)
)
structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql))
if link == nil {
link, err = d.db.GetSlave(checkSchema)
if err != nil {
return nil, err
return nil
}
fields = make(map[string]*TableField)
for i, m := range result {
fields[strings.ToLower(m["FIELD"].String())] = &TableField{
Index: i,
Name: strings.ToLower(m["FIELD"].String()),
Type: strings.ToLower(m["TYPE"].String()),
}
}
result, err = d.db.DoGetAll(link, structureSql)
if err != nil {
return nil
}
fields = make(map[string]*TableField)
for i, m := range result {
fields[strings.ToLower(m["FIELD"].String())] = &TableField{
Index: i,
Name: strings.ToLower(m["FIELD"].String()),
Type: strings.ToLower(m["TYPE"].String()),
}
return fields, nil
}, 0)
if err == nil {
}
return fields
})
if v != nil {
fields = v.(map[string]*TableField)
}
return

View File

@ -111,7 +111,9 @@ func (d *DriverPgsql) Tables(schema ...string) (tables []string, err error) {
}
// TableFields retrieves and returns the fields information of specified table of current schema.
func (d *DriverPgsql) TableFields(table string, schema ...string) (fields map[string]*TableField, err error) {
//
// Also see DriverMysql.TableFields.
func (d *DriverPgsql) TableFields(link Link, table string, schema ...string) (fields map[string]*TableField, err error) {
charL, charR := d.GetChars()
table = gstr.Trim(table, charL+charR)
if gstr.Contains(table, " ") {
@ -122,41 +124,43 @@ func (d *DriverPgsql) TableFields(table string, schema ...string) (fields map[st
if len(schema) > 0 && schema[0] != "" {
checkSchema = schema[0]
}
v, _ := internalCache.GetOrSetFunc(
fmt.Sprintf(`pgsql_table_fields_%s_%s@group:%s`, table, checkSchema, d.GetGroup()),
func() (interface{}, error) {
var (
result Result
link *sql.DB
)
link, err = d.db.GetSlave(checkSchema)
if err != nil {
return nil, err
}
structureSql := fmt.Sprintf(`
tableFieldsCacheKey := fmt.Sprintf(
`pgsql_table_fields_%s_%s@group:%s`,
table, checkSchema, d.GetGroup(),
)
v := tableFieldsMap.GetOrSetFuncLock(tableFieldsCacheKey, func() interface{} {
var (
result Result
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)
)
structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql))
if link == nil {
link, err = d.db.GetSlave(checkSchema)
if err != nil {
return nil, err
return nil
}
fields = make(map[string]*TableField)
for i, m := range result {
fields[m["field"].String()] = &TableField{
Index: i,
Name: m["field"].String(),
Type: m["type"].String(),
}
}
result, err = d.db.DoGetAll(link, structureSql)
if err != nil {
return nil
}
fields = make(map[string]*TableField)
for i, m := range result {
fields[m["field"].String()] = &TableField{
Index: i,
Name: m["field"].String(),
Type: m["type"].String(),
}
return fields, nil
}, 0)
if err == nil {
}
return fields
})
if v != nil {
fields = v.(map[string]*TableField)
}
return

View File

@ -93,7 +93,9 @@ func (d *DriverSqlite) Tables(schema ...string) (tables []string, err error) {
}
// TableFields retrieves and returns the fields information of specified table of current schema.
func (d *DriverSqlite) TableFields(table string, schema ...string) (fields map[string]*TableField, err error) {
//
// Also see DriverMysql.TableFields.
func (d *DriverSqlite) TableFields(link Link, table string, schema ...string) (fields map[string]*TableField, err error) {
charL, charR := d.GetChars()
table = gstr.Trim(table, charL+charR)
if gstr.Contains(table, " ") {
@ -103,32 +105,35 @@ func (d *DriverSqlite) TableFields(table string, schema ...string) (fields map[s
if len(schema) > 0 && schema[0] != "" {
checkSchema = schema[0]
}
v, _ := internalCache.GetOrSetFunc(
fmt.Sprintf(`sqlite_table_fields_%s_%s@group:%s`, table, checkSchema, d.GetGroup()),
func() (interface{}, error) {
var (
result Result
link *sql.DB
)
tableFieldsCacheKey := fmt.Sprintf(
`sqlite_table_fields_%s_%s@group:%s`,
table, checkSchema, d.GetGroup(),
)
v := tableFieldsMap.GetOrSetFuncLock(tableFieldsCacheKey, func() interface{} {
var (
result Result
)
if link == nil {
link, err = d.db.GetSlave(checkSchema)
if err != nil {
return nil, err
return nil
}
result, err = d.db.DoGetAll(link, fmt.Sprintf(`PRAGMA TABLE_INFO(%s)`, table))
if err != nil {
return nil, err
}
result, err = d.db.DoGetAll(link, fmt.Sprintf(`PRAGMA TABLE_INFO(%s)`, table))
if err != nil {
return nil
}
fields = make(map[string]*TableField)
for i, m := range result {
fields[strings.ToLower(m["name"].String())] = &TableField{
Index: i,
Name: strings.ToLower(m["name"].String()),
Type: strings.ToLower(m["type"].String()),
}
fields = make(map[string]*TableField)
for i, m := range result {
fields[strings.ToLower(m["name"].String())] = &TableField{
Index: i,
Name: strings.ToLower(m["name"].String()),
Type: strings.ToLower(m["type"].String()),
}
}
return fields, nil
}, 0)
if err == nil {
}
return fields
})
if v != nil {
fields = v.(map[string]*TableField)
}
return

View File

@ -235,7 +235,7 @@ func DataToMapDeep(value interface{}) map[string]interface{} {
name = ""
fieldTag = rtField.Tag
for _, tag := range structTagPriority {
if s := fieldTag.Get(tag); s != "" {
if s := fieldTag.Get(tag); s != "" && gregex.IsMatchString(regularFieldNameWithoutDotRegPattern, s) {
name = s
break
}

View File

@ -39,6 +39,7 @@ type Model struct {
data interface{} // Data for operation, which can be type of map/[]map/struct/*struct/string, etc.
batch int // Batch number for batch Insert/Replace/Save operations.
filter bool // Filter data and where key-value pairs according to the fields of the table.
distinct string // Force the query to only return distinct results.
lockInfo string // Lock for update or in shared lock.
cacheEnabled bool // Enable sql result cache feature.
cacheDuration time.Duration // Cache TTL duration.
@ -69,53 +70,66 @@ const (
// Table is alias of Core.Model.
// See Core.Model.
// Deprecated, use Model instead.
func (c *Core) Table(table ...string) *Model {
return c.db.Model(table...)
func (c *Core) Table(tableNameOrStruct ...interface{}) *Model {
return c.db.Model(tableNameOrStruct...)
}
// Model creates and returns a new ORM model from given schema.
// The parameter `table` can be more than one table names, and also alias name, like:
// The parameter `tableNameOrStruct` can be more than one table names, and also alias name, like:
// 1. Model names:
// Model("user")
// Model("user u")
// Model("user, user_detail")
// Model("user u, user_detail ud")
// 2. Model name with alias: Model("user", "u")
func (c *Core) Model(table ...string) *Model {
tables := ""
if len(table) > 1 {
tables = fmt.Sprintf(
`%s AS %s`, c.db.QuotePrefixTableName(table[0]), c.db.QuoteWord(table[1]),
func (c *Core) Model(tableNameOrStruct ...interface{}) *Model {
var (
tableStr = ""
tableName = ""
tableNames = make([]string, len(tableNameOrStruct))
)
for k, v := range tableNameOrStruct {
if s, ok := v.(string); ok {
tableNames[k] = s
} else if tableName = getTableNameFromOrmTag(v); tableName != "" {
tableNames[k] = tableName
}
}
if len(tableNames) > 1 {
tableStr = fmt.Sprintf(
`%s AS %s`, c.db.QuotePrefixTableName(tableNames[0]), c.db.QuoteWord(tableNames[1]),
)
} else if len(table) == 1 {
tables = c.db.QuotePrefixTableName(table[0])
} else if len(tableNames) == 1 {
tableStr = c.db.QuotePrefixTableName(tableNames[0])
}
return &Model{
db: c.db,
tablesInit: tables,
tables: tables,
tablesInit: tableStr,
tables: tableStr,
fields: "*",
start: -1,
offset: -1,
option: OptionAllowEmpty,
filter: true,
}
}
// With creates and returns an ORM model based on meta data of given object.
func (c *Core) With(object interface{}) *Model {
return c.db.Model().With(object)
func (c *Core) With(objects ...interface{}) *Model {
return c.db.Model().With(objects...)
}
// Table is alias of tx.Model.
// Deprecated, use Model instead.
func (tx *TX) Table(table ...string) *Model {
return tx.Model(table...)
func (tx *TX) Table(tableNameOrStruct ...interface{}) *Model {
return tx.Model(tableNameOrStruct...)
}
// Model acts like Core.Model except it operates on transaction.
// See Core.Model.
func (tx *TX) Model(table ...string) *Model {
model := tx.db.Model(table...)
func (tx *TX) Model(tableNameOrStruct ...interface{}) *Model {
model := tx.db.Model(tableNameOrStruct...)
model.db = tx.db
model.tx = tx
return model

View File

@ -7,6 +7,7 @@
package gdb
import (
"fmt"
"strings"
)
@ -58,7 +59,125 @@ func (m *Model) WherePri(where interface{}, args ...interface{}) *Model {
return m.Where(newWhere[0], newWhere[1:]...)
}
// WhereBetween builds `xxx BETWEEN x AND y` statement.
func (m *Model) WhereBetween(column string, min, max interface{}) *Model {
return m.Where(fmt.Sprintf(`%s BETWEEN ? AND ?`, m.db.QuoteWord(column)), min, max)
}
// WhereLike builds `xxx LIKE x` statement.
func (m *Model) WhereLike(column string, like interface{}) *Model {
return m.Where(fmt.Sprintf(`%s LIKE ?`, m.db.QuoteWord(column)), like)
}
// WhereIn builds `xxx IN (x)` statement.
func (m *Model) WhereIn(column string, in interface{}) *Model {
return m.Where(fmt.Sprintf(`%s IN (?)`, m.db.QuoteWord(column)), in)
}
// WhereNull builds `xxx IS NULL` statement.
func (m *Model) WhereNull(columns ...string) *Model {
model := m
for _, column := range columns {
model = m.Where(fmt.Sprintf(`%s IS NULL`, m.db.QuoteWord(column)))
}
return model
}
// WhereNotBetween builds `xxx NOT BETWEEN x AND y` statement.
func (m *Model) WhereNotBetween(column string, min, max interface{}) *Model {
return m.Where(fmt.Sprintf(`%s NOT BETWEEN ? AND ?`, m.db.QuoteWord(column)), min, max)
}
// WhereNotLike builds `xxx NOT LIKE x` statement.
func (m *Model) WhereNotLike(column string, like interface{}) *Model {
return m.Where(fmt.Sprintf(`%s NOT LIKE ?`, m.db.QuoteWord(column)), like)
}
// WhereNotIn builds `xxx NOT IN (x)` statement.
func (m *Model) WhereNotIn(column string, in interface{}) *Model {
return m.Where(fmt.Sprintf(`%s NOT IN (?)`, m.db.QuoteWord(column)), in)
}
// WhereNotNull builds `xxx IS NOT NULL` statement.
func (m *Model) WhereNotNull(columns ...string) *Model {
model := m
for _, column := range columns {
model = m.Where(fmt.Sprintf(`%s IS NOT NULL`, m.db.QuoteWord(column)))
}
return model
}
// WhereOr adds "OR" condition to the where statement.
func (m *Model) WhereOr(where interface{}, args ...interface{}) *Model {
model := m.getModel()
if model.whereHolder == nil {
model.whereHolder = make([]*whereHolder, 0)
}
model.whereHolder = append(model.whereHolder, &whereHolder{
operator: whereHolderOr,
where: where,
args: args,
})
return model
}
// WhereOrBetween builds `xxx BETWEEN x AND y` statement in `OR` conditions.
func (m *Model) WhereOrBetween(column string, min, max interface{}) *Model {
return m.WhereOr(fmt.Sprintf(`%s BETWEEN ? AND ?`, m.db.QuoteWord(column)), min, max)
}
// WhereOrLike builds `xxx LIKE x` statement in `OR` conditions.
func (m *Model) WhereOrLike(column string, like interface{}) *Model {
return m.WhereOr(fmt.Sprintf(`%s LIKE ?`, m.db.QuoteWord(column)), like)
}
// WhereOrIn builds `xxx IN (x)` statement in `OR` conditions.
func (m *Model) WhereOrIn(column string, in interface{}) *Model {
return m.WhereOr(fmt.Sprintf(`%s IN (?)`, m.db.QuoteWord(column)), in)
}
// WhereOrNull builds `xxx IS NULL` statement in `OR` conditions.
func (m *Model) WhereOrNull(columns ...string) *Model {
model := m
for _, column := range columns {
model = m.WhereOr(fmt.Sprintf(`%s IS NULL`, m.db.QuoteWord(column)))
}
return model
}
// WhereOrNotBetween builds `xxx NOT BETWEEN x AND y` statement in `OR` conditions.
func (m *Model) WhereOrNotBetween(column string, min, max interface{}) *Model {
return m.WhereOr(fmt.Sprintf(`%s NOT BETWEEN ? AND ?`, m.db.QuoteWord(column)), min, max)
}
// WhereOrNotLike builds `xxx NOT LIKE x` statement in `OR` conditions.
func (m *Model) WhereOrNotLike(column string, like interface{}) *Model {
return m.WhereOr(fmt.Sprintf(`%s NOT LIKE ?`, m.db.QuoteWord(column)), like)
}
// WhereOrNotIn builds `xxx NOT IN (x)` statement.
func (m *Model) WhereOrNotIn(column string, in interface{}) *Model {
return m.WhereOr(fmt.Sprintf(`%s NOT IN (?)`, m.db.QuoteWord(column)), in)
}
// WhereOrNotNull builds `xxx IS NOT NULL` statement in `OR` conditions.
func (m *Model) WhereOrNotNull(columns ...string) *Model {
model := m
for _, column := range columns {
model = m.WhereOr(fmt.Sprintf(`%s IS NOT NULL`, m.db.QuoteWord(column)))
}
return model
}
// Group sets the "GROUP BY" statement for the model.
func (m *Model) Group(groupBy string) *Model {
model := m.getModel()
model.groupBy = m.db.QuoteString(groupBy)
return model
}
// And adds "AND" condition to the where statement.
// Deprecated, use Where instead.
func (m *Model) And(where interface{}, args ...interface{}) *Model {
model := m.getModel()
if model.whereHolder == nil {
@ -73,43 +192,58 @@ func (m *Model) And(where interface{}, args ...interface{}) *Model {
}
// Or adds "OR" condition to the where statement.
// Deprecated, use WhereOr instead.
func (m *Model) Or(where interface{}, args ...interface{}) *Model {
model := m.getModel()
if model.whereHolder == nil {
model.whereHolder = make([]*whereHolder, 0)
}
model.whereHolder = append(model.whereHolder, &whereHolder{
operator: whereHolderOr,
where: where,
args: args,
})
return model
}
// Group sets the "GROUP BY" statement for the model.
func (m *Model) Group(groupBy string) *Model {
model := m.getModel()
model.groupBy = m.db.QuoteString(groupBy)
return model
return m.WhereOr(where, args...)
}
// GroupBy is alias of Model.Group.
// See Model.Group.
// Deprecated.
// Deprecated, use Group instead.
func (m *Model) GroupBy(groupBy string) *Model {
return m.Group(groupBy)
}
// Order sets the "ORDER BY" statement for the model.
func (m *Model) Order(orderBy ...string) *Model {
if len(orderBy) == 0 {
return m
}
model := m.getModel()
model.orderBy = m.db.QuoteString(strings.Join(orderBy, " "))
return model
}
// OrderAsc sets the "ORDER BY xxx ASC" statement for the model.
func (m *Model) OrderAsc(column string) *Model {
if len(column) == 0 {
return m
}
model := m.getModel()
model.orderBy = m.db.QuoteWord(column) + " ASC"
return model
}
// OrderDesc sets the "ORDER BY xxx DESC" statement for the model.
func (m *Model) OrderDesc(column string) *Model {
if len(column) == 0 {
return m
}
model := m.getModel()
model.orderBy = m.db.QuoteWord(column) + " DESC"
return model
}
// OrderRandom sets the "ORDER BY RANDOM()" statement for the model.
func (m *Model) OrderRandom() *Model {
model := m.getModel()
model.orderBy = "RAND()"
return model
}
// OrderBy is alias of Model.Order.
// See Model.Order.
// Deprecated.
// Deprecated, use Order instead.
func (m *Model) OrderBy(orderBy string) *Model {
return m.Order(orderBy)
}
@ -138,6 +272,13 @@ func (m *Model) Offset(offset int) *Model {
return model
}
// Distinct forces the query to only return distinct results.
func (m *Model) Distinct() *Model {
model := m.getModel()
model.distinct = "DISTINCT "
return model
}
// Page sets the paging number for the model.
// The parameter `page` is started from 1 for paging.
// Note that, it differs that the Limit function starts from 0 for "LIMIT" statement.
@ -153,7 +294,7 @@ func (m *Model) Page(page, limit int) *Model {
// ForPage is alias of Model.Page.
// See Model.Page.
// Deprecated.
// Deprecated, use Page instead.
func (m *Model) ForPage(page, limit int) *Model {
return m.Page(page, limit)
}

View File

@ -14,17 +14,6 @@ import (
"github.com/gogf/gf/util/gutil"
)
// Filter marks filtering the fields which does not exist in the fields of the operated table.
// Note that this function supports only single table operations.
func (m *Model) Filter() *Model {
if gstr.Contains(m.tables, " ") {
panic("function Filter supports only single table operations")
}
model := m.getModel()
model.filter = true
return model
}
// Fields sets the operation fields of the model, multiple fields joined using char ','.
// The parameter `fieldNamesOrMapStruct` can be type of string/map/*map/struct/*struct.
func (m *Model) Fields(fieldNamesOrMapStruct ...interface{}) *Model {
@ -36,18 +25,18 @@ func (m *Model) Fields(fieldNamesOrMapStruct ...interface{}) *Model {
// String slice.
case length >= 2:
model := m.getModel()
model.fields = gstr.Join(m.mappingAndFilterToTableFields(gconv.Strings(fieldNamesOrMapStruct)), ",")
model.fields = gstr.Join(m.mappingAndFilterToTableFields(gconv.Strings(fieldNamesOrMapStruct), true), ",")
return model
// It need type asserting.
case length == 1:
model := m.getModel()
switch r := fieldNamesOrMapStruct[0].(type) {
case string:
model.fields = gstr.Join(m.mappingAndFilterToTableFields([]string{r}), ",")
model.fields = gstr.Join(m.mappingAndFilterToTableFields([]string{r}, false), ",")
case []string:
model.fields = gstr.Join(m.mappingAndFilterToTableFields(r), ",")
model.fields = gstr.Join(m.mappingAndFilterToTableFields(r, true), ",")
default:
model.fields = gstr.Join(m.mappingAndFilterToTableFields(gutil.Keys(r)), ",")
model.fields = gstr.Join(m.mappingAndFilterToTableFields(gutil.Keys(r), true), ",")
}
return model
}
@ -65,36 +54,49 @@ func (m *Model) FieldsEx(fieldNamesOrMapStruct ...interface{}) *Model {
model := m.getModel()
switch {
case length >= 2:
model.fieldsEx = gstr.Join(m.mappingAndFilterToTableFields(gconv.Strings(fieldNamesOrMapStruct)), ",")
model.fieldsEx = gstr.Join(m.mappingAndFilterToTableFields(gconv.Strings(fieldNamesOrMapStruct), true), ",")
return model
case length == 1:
switch r := fieldNamesOrMapStruct[0].(type) {
case string:
model.fieldsEx = gstr.Join(m.mappingAndFilterToTableFields([]string{r}), ",")
model.fieldsEx = gstr.Join(m.mappingAndFilterToTableFields([]string{r}, false), ",")
case []string:
model.fieldsEx = gstr.Join(m.mappingAndFilterToTableFields(r), ",")
model.fieldsEx = gstr.Join(m.mappingAndFilterToTableFields(r, true), ",")
default:
model.fieldsEx = gstr.Join(m.mappingAndFilterToTableFields(gutil.Keys(r)), ",")
model.fieldsEx = gstr.Join(m.mappingAndFilterToTableFields(gutil.Keys(r), true), ",")
}
return model
}
return m
}
// Filter marks filtering the fields which does not exist in the fields of the operated table.
// Note that this function supports only single table operations.
// Deprecated, filter feature is automatically enabled from GoFrame v1.16.0, it is so no longer used.
func (m *Model) Filter() *Model {
if gstr.Contains(m.tables, " ") {
panic("function Filter supports only single table operations")
}
model := m.getModel()
model.filter = true
return model
}
// 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.").
// 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 ','.
// GetFieldsStr retrieves and returns all fields from the table, joined with char ','.
// The optional parameter `prefix` specifies the prefix for each field, eg: FieldsStr("u.").
func (m *Model) GetFieldsStr(prefix ...string) string {
prefixStr := ""
if len(prefix) > 0 {
prefixStr = prefix[0]
}
tableFields, err := m.db.TableFields(m.tables)
tableFields, err := m.TableFields(m.tables)
if err != nil {
panic(err)
}
@ -116,13 +118,16 @@ func (m *Model) GetFieldsStr(prefix ...string) string {
return newFields
}
// 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.").
// 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,
// GetFieldsExStr 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.").
@ -131,7 +136,7 @@ func (m *Model) GetFieldsExStr(fields string, prefix ...string) string {
if len(prefix) > 0 {
prefixStr = prefix[0]
}
tableFields, err := m.db.TableFields(m.tables)
tableFields, err := m.TableFields(m.tables)
if err != nil {
panic(err)
}
@ -159,7 +164,7 @@ func (m *Model) GetFieldsExStr(fields string, prefix ...string) string {
// HasField determine whether the field exists in the table.
func (m *Model) HasField(field string) (bool, error) {
tableFields, err := m.db.TableFields(m.tables)
tableFields, err := m.TableFields(m.tables)
if err != nil {
return false, err
}

View File

@ -109,6 +109,18 @@ func (m *Model) Insert(data ...interface{}) (result sql.Result, err error) {
return m.doInsertWithOption(insertOptionDefault)
}
// InsertAndGetId performs action Insert and returns the last insert id that automatically generated.
func (m *Model) InsertAndGetId(data ...interface{}) (lastInsertId int64, err error) {
if len(data) > 0 {
return m.Data(data...).InsertAndGetId()
}
result, err := m.doInsertWithOption(insertOptionDefault)
if err != nil {
return 0, err
}
return result.LastInsertId()
}
// InsertIgnore does "INSERT IGNORE INTO ..." statement for the model.
// The optional parameter `data` is the same as the parameter of Model.Data function,
// see Model.Data.

View File

@ -19,7 +19,7 @@ import (
// Select is alias of Model.All.
// See Model.All.
// Deprecated.
// Deprecated, use All instead.
func (m *Model) Select(where ...interface{}) (Result, error) {
return m.All(where...)
}
@ -50,7 +50,8 @@ func (m *Model) doGetAll(limit1 bool, where ...interface{}) (Result, error) {
// DISTINCT t.user_id uid
return m.doGetAllBySql(
fmt.Sprintf(
"SELECT %s FROM %s%s",
"SELECT %s%s FROM %s%s",
m.distinct,
m.getFieldsFiltered(),
m.tables,
conditionWhere+conditionExtra,
@ -84,7 +85,7 @@ func (m *Model) getFieldsFiltered() string {
panic("function FieldsEx supports only single table operations")
}
// Filter table fields with fieldEx.
tableFields, err := m.db.TableFields(m.tables)
tableFields, err := m.TableFields(m.tables)
if err != nil {
panic(err)
}
@ -182,7 +183,7 @@ func (m *Model) Value(fieldsAndWhere ...interface{}) (Value, error) {
}
// Array queries and returns data values as slice from database.
// Note that if there're multiple columns in the result, it returns just one column values randomly.
// Note that if there are multiple columns in the result, it returns just one column values randomly.
//
// If the optional parameter `fieldsAndWhere` is given, the fieldsAndWhere[0] is the selected fields
// and fieldsAndWhere[1:] is treated as where condition fields.
@ -334,7 +335,7 @@ func (m *Model) Count(where ...interface{}) (int, error) {
if m.fields != "" && m.fields != "*" {
// DO NOT quote the m.fields here, in case of fields like:
// DISTINCT t.user_id uid
countFields = fmt.Sprintf(`COUNT(%s)`, m.fields)
countFields = fmt.Sprintf(`COUNT(%s%s)`, m.distinct, m.fields)
}
conditionWhere, conditionExtra, conditionArgs := m.formatCondition(false, true)
s := fmt.Sprintf("SELECT %s FROM %s%s", countFields, m.tables, conditionWhere+conditionExtra)
@ -353,6 +354,62 @@ func (m *Model) Count(where ...interface{}) (int, error) {
return 0, nil
}
// CountColumn does "SELECT COUNT(x) FROM ..." statement for the model.
func (m *Model) CountColumn(column string) (int, error) {
if len(column) == 0 {
return 0, nil
}
return m.Fields(column).Count()
}
// Min does "SELECT MIN(x) FROM ..." statement for the model.
func (m *Model) Min(column string) (float64, error) {
if len(column) == 0 {
return 0, nil
}
value, err := m.Fields(fmt.Sprintf(`MIN(%s)`, m.db.QuoteWord(column))).Value()
if err != nil {
return 0, err
}
return value.Float64(), err
}
// Max does "SELECT MAX(x) FROM ..." statement for the model.
func (m *Model) Max(column string) (float64, error) {
if len(column) == 0 {
return 0, nil
}
value, err := m.Fields(fmt.Sprintf(`MAX(%s)`, m.db.QuoteWord(column))).Value()
if err != nil {
return 0, err
}
return value.Float64(), err
}
// Avg does "SELECT AVG(x) FROM ..." statement for the model.
func (m *Model) Avg(column string) (float64, error) {
if len(column) == 0 {
return 0, nil
}
value, err := m.Fields(fmt.Sprintf(`AVG(%s)`, m.db.QuoteWord(column))).Value()
if err != nil {
return 0, err
}
return value.Float64(), err
}
// Sum does "SELECT SUM(x) FROM ..." statement for the model.
func (m *Model) Sum(column string) (float64, error) {
if len(column) == 0 {
return 0, nil
}
value, err := m.Fields(fmt.Sprintf(`SUM(%s)`, m.db.QuoteWord(column))).Value()
if err != nil {
return 0, err
}
return value.Float64(), err
}
// FindOne retrieves and returns a single Record by Model.WherePri and Model.One.
// Also see Model.WherePri and Model.One.
func (m *Model) FindOne(where ...interface{}) (Record, error) {

View File

@ -93,7 +93,7 @@ func (m *Model) getSoftFieldNameDeleted(table ...string) (field string) {
// getSoftFieldName retrieves and returns the field name of the table for possible key.
func (m *Model) getSoftFieldName(table string, keys []string) (field string) {
fieldsMap, _ := m.db.TableFields(table)
fieldsMap, _ := m.TableFields(table)
if len(fieldsMap) > 0 {
for _, key := range keys {
field, _ = gutil.MapPossibleItemByKey(

View File

@ -89,3 +89,19 @@ func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err erro
m.mergeArguments(conditionArgs)...,
)
}
// Increment increments a column's value by a given amount.
func (m *Model) Increment(column string, amount float64) (sql.Result, error) {
return m.getModel().Data(column, &Counter{
Field: column,
Value: amount,
}).Update()
}
// Decrement decrements a column's value by a given amount.
func (m *Model) Decrement(column string, amount float64) (sql.Result, error) {
return m.getModel().Data(column, &Counter{
Field: column,
Value: -amount,
}).Update()
}

View File

@ -18,6 +18,25 @@ import (
"time"
)
// TableFields retrieves and returns the fields information of specified table of current
// schema.
//
// Also see DriverMysql.TableFields.
func (m *Model) TableFields(table string, schema ...string) (fields map[string]*TableField, err error) {
var (
link Link
)
if m.tx != nil {
link = m.tx.tx
} else {
link, err = m.db.GetSlave(schema...)
if err != nil {
return
}
}
return m.db.TableFields(link, table, schema...)
}
// getModel creates and returns a cloned model of current model if `safe` is true, or else it returns
// the current model.
func (m *Model) getModel() *Model {
@ -29,8 +48,11 @@ func (m *Model) getModel() *Model {
}
// mappingAndFilterToTableFields mappings and changes given field name to really table field name.
func (m *Model) mappingAndFilterToTableFields(fields []string) []string {
fieldsMap, err := m.db.TableFields(m.tables)
// Eg:
// ID -> id
// NICK_Name -> nickname
func (m *Model) mappingAndFilterToTableFields(fields []string, filter bool) []string {
fieldsMap, err := m.TableFields(m.tables)
if err != nil || len(fieldsMap) == 0 {
return fields
}
@ -52,6 +74,8 @@ func (m *Model) mappingAndFilterToTableFields(fields []string) []string {
// Eg: id, name
if foundKey, _ := gutil.MapPossibleItemByKey(fieldsKeyMap, field); foundKey != "" {
outputFieldsArray = append(outputFieldsArray, foundKey)
} else if !filter {
outputFieldsArray = append(outputFieldsArray, field)
}
}
} else {
@ -183,7 +207,7 @@ func (m *Model) getLink(master bool) Link {
// "user", "user u", "user as u, user_detail as ud".
func (m *Model) getPrimaryKey() string {
table := gstr.SplitAndTrim(m.tables, " ")[0]
tableFields, err := m.db.TableFields(table)
tableFields, err := m.TableFields(table)
if err != nil {
return ""
}

View File

@ -32,13 +32,17 @@ import (
// db.With(User{}.UserDetail).With(User{}.UserDetail).Scan(xxx)
// Or:
// db.With(UserDetail{}).With(UserDetail{}).Scan(xxx)
func (m *Model) With(object interface{}) *Model {
// Or:
// db.With(UserDetail{}, UserDetail{}).Scan(xxx)
func (m *Model) With(objects ...interface{}) *Model {
model := m.getModel()
if m.tables == "" {
m.tables = m.db.QuotePrefixTableName(getTableNameFromOrmTag(object))
return model
for _, object := range objects {
if m.tables == "" {
m.tables = m.db.QuotePrefixTableName(getTableNameFromOrmTag(object))
return model
}
model.withArray = append(model.withArray, object)
}
model.withArray = append(model.withArray, object)
return model
}

View File

@ -9,6 +9,8 @@ package gdb
import (
"database/sql"
"fmt"
"github.com/gogf/gf/os/gtime"
"github.com/gogf/gf/util/gconv"
"reflect"
"github.com/gogf/gf/text/gregex"
@ -16,19 +18,139 @@ import (
// TX is the struct for transaction management.
type TX struct {
db DB
tx *sql.Tx
master *sql.DB
db DB // db is the current gdb database manager.
tx *sql.Tx // tx is the raw and underlying transaction manager.
master *sql.DB // master is the raw and underlying database manager.
transactionCount int // transactionCount marks the times that Begins.
}
// Commit commits the transaction.
const (
transactionPointerPrefix = "transaction"
)
// Commit commits current transaction.
// Note that it releases previous saved transaction point if it's in a nested transaction procedure,
// or else it commits the hole transaction.
func (tx *TX) Commit() error {
return tx.tx.Commit()
if tx.transactionCount > 0 {
tx.transactionCount--
_, err := tx.Exec("RELEASE SAVEPOINT " + tx.transactionKey())
return err
}
var (
sqlStr = "COMMIT"
mTime1 = gtime.TimestampMilli()
err = tx.tx.Commit()
mTime2 = gtime.TimestampMilli()
sqlObj = &Sql{
Sql: sqlStr,
Type: "TX.Commit",
Args: nil,
Format: sqlStr,
Error: err,
Start: mTime1,
End: mTime2,
Group: tx.db.GetGroup(),
}
)
tx.db.addSqlToTracing(tx.db.GetCtx(), sqlObj)
if tx.db.GetDebug() {
tx.db.writeSqlToLogger(sqlObj)
}
return err
}
// Rollback aborts the transaction.
// Rollback aborts current transaction.
// Note that it aborts current transaction if it's in a nested transaction procedure,
// or else it aborts the hole transaction.
func (tx *TX) Rollback() error {
return tx.tx.Rollback()
if tx.transactionCount > 0 {
tx.transactionCount--
_, err := tx.Exec("ROLLBACK TO SAVEPOINT " + tx.transactionKey())
return err
}
var (
sqlStr = "ROLLBACK"
mTime1 = gtime.TimestampMilli()
err = tx.tx.Rollback()
mTime2 = gtime.TimestampMilli()
sqlObj = &Sql{
Sql: sqlStr,
Type: "TX.Rollback",
Args: nil,
Format: sqlStr,
Error: err,
Start: mTime1,
End: mTime2,
Group: tx.db.GetGroup(),
}
)
tx.db.addSqlToTracing(tx.db.GetCtx(), sqlObj)
if tx.db.GetDebug() {
tx.db.writeSqlToLogger(sqlObj)
}
return err
}
// Begin starts a nested transaction procedure.
func (tx *TX) Begin() error {
_, err := tx.Exec("SAVEPOINT " + tx.transactionKey())
if err != nil {
return err
}
tx.transactionCount++
return nil
}
// SavePoint performs `SAVEPOINT xxx` SQL statement that saves transaction at current point.
// The parameter `point` specifies the point name that will be saved to server.
func (tx *TX) SavePoint(point string) error {
_, err := tx.Exec("SAVEPOINT " + tx.db.QuoteWord(point))
return err
}
// RollbackTo performs `ROLLBACK TO SAVEPOINT xxx` SQL statement that rollbacks to specified saved transaction.
// The parameter `point` specifies the point name that was saved previously.
func (tx *TX) RollbackTo(point string) error {
_, err := tx.Exec("ROLLBACK TO SAVEPOINT " + tx.db.QuoteWord(point))
return err
}
// transactionKey forms and returns the transaction key at current save point.
func (tx *TX) transactionKey() string {
return tx.db.QuoteWord(transactionPointerPrefix + gconv.String(tx.transactionCount))
}
// Transaction wraps the transaction logic using function `f`.
// It rollbacks the transaction and returns the error from function `f` if
// it returns non-nil error. It commits the transaction and returns nil if
// function `f` returns nil.
//
// Note that, you should not Commit or Rollback the transaction in function `f`
// as it is automatically handled by this function.
func (tx *TX) Transaction(f func(tx *TX) error) (err error) {
err = tx.Begin()
if err != nil {
return err
}
defer func() {
if err == nil {
if e := recover(); e != nil {
err = fmt.Errorf("%v", e)
}
}
if err != nil {
if e := tx.Rollback(); e != nil {
err = e
}
} else {
if e := tx.Commit(); e != nil {
err = e
}
}
}()
err = f(tx)
return
}
// Query does query operation on transaction.
@ -221,7 +343,7 @@ func (tx *TX) BatchInsert(table string, list interface{}, batch ...int) (sql.Res
return tx.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 (tx *TX) BatchInsertIgnore(table string, list interface{}, batch ...int) (sql.Result, error) {
if len(batch) > 0 {

View File

@ -6,22 +6,22 @@
package gdb
// Deprecated.
// Deprecated, use Json instead.
func (r Record) ToJson() string {
return r.Json()
}
// Deprecated.
// Deprecated, use Xml instead.
func (r Record) ToXml(rootTag ...string) string {
return r.Xml(rootTag...)
}
// Deprecated.
// Deprecated, use Map instead.
func (r Record) ToMap() Map {
return r.Map()
}
// Deprecated.
// Deprecated, use Struct instead.
func (r Record) ToStruct(pointer interface{}) error {
return r.Struct(pointer)
}

View File

@ -189,5 +189,5 @@ func (r Result) RecordKeyUint(key string) map[uint]Record {
// Structs converts `r` to struct slice.
// Note that the parameter `pointer` should be type of *[]struct/*[]*struct.
func (r Result) Structs(pointer interface{}) (err error) {
return gconv.StructsTag(r, pointer, OrmTagForStruct)
return gconv.StructsTag(r.List(), pointer, OrmTagForStruct)
}

View File

@ -6,52 +6,52 @@
package gdb
// Deprecated.
// Deprecated, use Json instead.
func (r Result) ToJson() string {
return r.Json()
}
// Deprecated.
// Deprecated, use Xml instead.
func (r Result) ToXml(rootTag ...string) string {
return r.Xml(rootTag...)
}
// Deprecated.
// Deprecated, use List instead.
func (r Result) ToList() List {
return r.List()
}
// Deprecated.
// Deprecated, use MapKeyStr instead.
func (r Result) ToStringMap(key string) map[string]Map {
return r.MapKeyStr(key)
}
// Deprecated.
// Deprecated, use MapKetInt instead.
func (r Result) ToIntMap(key string) map[int]Map {
return r.MapKeyInt(key)
}
// Deprecated.
// Deprecated, use MapKeyUint instead.
func (r Result) ToUintMap(key string) map[uint]Map {
return r.MapKeyUint(key)
}
// Deprecated.
// Deprecated, use RecordKeyStr instead.
func (r Result) ToStringRecord(key string) map[string]Record {
return r.RecordKeyStr(key)
}
// Deprecated.
// Deprecated, use RecordKetInt instead.
func (r Result) ToIntRecord(key string) map[int]Record {
return r.RecordKeyInt(key)
}
// Deprecated.
// Deprecated, use RecordKetUint instead.
func (r Result) ToUintRecord(key string) map[uint]Record {
return r.RecordKeyUint(key)
}
// Deprecated.
// Deprecated, use Structs instead.
func (r Result) ToStructs(pointer interface{}) (err error) {
return r.Structs(pointer)
}

View File

@ -52,7 +52,7 @@ func init() {
Weight: 1,
MaxIdleConnCount: 10,
MaxOpenConnCount: 10,
MaxConnLifetime: 600,
MaxConnLifeTime: 600,
}
nodePrefix := configNode
nodePrefix.Prefix = TableNamePrefix1

View File

@ -18,7 +18,7 @@ func Test_Table_Relation_With_Scan(t *testing.T) {
var (
tableUser = "user"
tableUserDetail = "user_detail"
tableUserScores = "user_scores"
tableUserScores = "user_score"
)
if _, err := db.Exec(fmt.Sprintf(`
CREATE TABLE IF NOT EXISTS %s (
@ -60,8 +60,8 @@ PRIMARY KEY (id)
Address string `json:"address"`
}
type UserScores struct {
gmeta.Meta `orm:"table:user_scores"`
type UserScore struct {
gmeta.Meta `orm:"table:user_score"`
Id int `json:"id"`
Uid int `json:"uid"`
Score int `json:"score"`
@ -69,34 +69,61 @@ PRIMARY KEY (id)
type User struct {
gmeta.Meta `orm:"table:user"`
Id int `json:"id"`
Name string `json:"name"`
UserDetail *UserDetail `orm:"with:uid=id"`
UserScores []*UserScores `orm:"with:uid=id"`
Id int `json:"id"`
Name string `json:"name"`
UserDetail *UserDetail `orm:"with:uid=id"`
UserScores []*UserScore `orm:"with:uid=id"`
}
// Initialize the data.
var err error
gtest.C(t, func(t *gtest.T) {
for i := 1; i <= 5; i++ {
// User.
user := User{
Name: fmt.Sprintf(`name_%d`, i),
}
lastInsertId, err := db.Model(user).Data(user).OmitEmpty().InsertAndGetId()
t.AssertNil(err)
// Detail.
userDetail := UserDetail{
Uid: int(lastInsertId),
Address: fmt.Sprintf(`address_%d`, lastInsertId),
}
_, err = db.Model(userDetail).Data(userDetail).OmitEmpty().Insert()
t.AssertNil(err)
// Scores.
for j := 1; j <= 5; j++ {
userScore := UserScore{
Uid: int(lastInsertId),
Score: j,
}
_, err = db.Model(userScore).Data(userScore).OmitEmpty().Insert()
t.AssertNil(err)
}
}
})
for i := 1; i <= 5; i++ {
// User.
_, err = db.Insert(tableUser, g.Map{
"id": i,
"name": fmt.Sprintf(`name_%d`, i),
})
gtest.Assert(err, nil)
user := User{
Name: fmt.Sprintf(`name_%d`, i),
}
lastInsertId, err := db.Model(user).Data(user).OmitEmpty().InsertAndGetId()
gtest.AssertNil(err)
// Detail.
_, err = db.Insert(tableUserDetail, g.Map{
"uid": i,
"address": fmt.Sprintf(`address_%d`, i),
})
gtest.Assert(err, nil)
userDetail := UserDetail{
Uid: int(lastInsertId),
Address: fmt.Sprintf(`address_%d`, lastInsertId),
}
_, err = db.Model(userDetail).Data(userDetail).Insert()
gtest.AssertNil(err)
// Scores.
for j := 1; j <= 5; j++ {
_, err = db.Insert(tableUserScores, g.Map{
"uid": i,
"score": j,
})
gtest.Assert(err, nil)
userScore := UserScore{
Uid: int(lastInsertId),
Score: j,
}
_, err = db.Model(userScore).Data(userScore).Insert()
gtest.AssertNil(err)
}
}
gtest.C(t, func(t *gtest.T) {
@ -139,7 +166,7 @@ PRIMARY KEY (id)
var user *User
err := db.With(User{}).
With(UserDetail{}).
With(UserScores{}).
With(UserScore{}).
Where("id", 4).
Scan(&user)
t.AssertNil(err)

View File

@ -45,7 +45,7 @@ func init() {
Weight: 1,
MaxIdleConnCount: 10,
MaxOpenConnCount: 10,
MaxConnLifetime: 600,
MaxConnLifeTime: 600,
}
AddConfigNode(DefaultGroupName, configNode)
// Default db.

View File

@ -274,30 +274,31 @@ func Test_DB_Upadte_KeyFieldNameMapping(t *testing.T) {
})
}
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)
})
}
// This is no longer used as the filter feature is automatically enabled from GoFrame v1.16.0.
//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()

View File

@ -240,30 +240,31 @@ func Test_Model_Update_KeyFieldNameMapping(t *testing.T) {
})
}
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)
})
}
// This is no longer used as the filter feature is automatically enabled from GoFrame v1.16.0.
//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()
@ -2778,6 +2779,11 @@ func Test_Model_Distinct(t *testing.T) {
t.AssertNil(err)
t.Assert(len(all), 2)
})
gtest.C(t, func(t *gtest.T) {
count, err := db.Model(table).Where("id > 1").Distinct().Count()
t.AssertNil(err)
t.Assert(count, 9)
})
}
func Test_Model_Min_Max(t *testing.T) {
@ -3321,3 +3327,292 @@ func Test_Model_Fields_AutoFilterInJoinStatement(t *testing.T) {
t.Assert(one["number"].String(), "n")
})
}
func Test_Model_WhereIn(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).WhereIn("id", g.Slice{1, 2, 3, 4}).WhereIn("id", g.Slice{3, 4, 5}).OrderAsc("id").All()
t.AssertNil(err)
t.Assert(len(result), 2)
t.Assert(result[0]["id"], 3)
t.Assert(result[1]["id"], 4)
})
}
func Test_Model_WhereNotIn(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).WhereNotIn("id", g.Slice{1, 2, 3, 4}).WhereNotIn("id", g.Slice{3, 4, 5}).OrderAsc("id").All()
t.AssertNil(err)
t.Assert(len(result), 5)
t.Assert(result[0]["id"], 6)
t.Assert(result[1]["id"], 7)
})
}
func Test_Model_WhereOrIn(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).WhereOrIn("id", g.Slice{1, 2, 3, 4}).WhereOrIn("id", g.Slice{3, 4, 5}).OrderAsc("id").All()
t.AssertNil(err)
t.Assert(len(result), 5)
t.Assert(result[0]["id"], 1)
t.Assert(result[4]["id"], 5)
})
}
func Test_Model_WhereOrNotIn(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).WhereOrNotIn("id", g.Slice{1, 2, 3, 4}).WhereOrNotIn("id", g.Slice{3, 4, 5}).OrderAsc("id").All()
t.AssertNil(err)
t.Assert(len(result), 8)
t.Assert(result[0]["id"], 1)
t.Assert(result[4]["id"], 7)
})
}
func Test_Model_WhereBetween(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).WhereBetween("id", 1, 4).WhereBetween("id", 3, 5).OrderAsc("id").All()
t.AssertNil(err)
t.Assert(len(result), 2)
t.Assert(result[0]["id"], 3)
t.Assert(result[1]["id"], 4)
})
}
func Test_Model_WhereNotBetween(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).WhereNotBetween("id", 2, 8).WhereNotBetween("id", 3, 100).OrderAsc("id").All()
t.AssertNil(err)
t.Assert(len(result), 1)
t.Assert(result[0]["id"], 1)
})
}
func Test_Model_WhereOrBetween(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).WhereOrBetween("id", 1, 4).WhereOrBetween("id", 3, 5).OrderDesc("id").All()
t.AssertNil(err)
t.Assert(len(result), 5)
t.Assert(result[0]["id"], 5)
t.Assert(result[4]["id"], 1)
})
}
func Test_Model_WhereOrNotBetween(t *testing.T) {
table := createInitTable()
defer dropTable(table)
//db.SetDebug(true)
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).WhereOrNotBetween("id", 1, 4).WhereOrNotBetween("id", 3, 5).OrderDesc("id").All()
t.AssertNil(err)
t.Assert(len(result), 8)
t.Assert(result[0]["id"], 10)
t.Assert(result[4]["id"], 6)
})
}
func Test_Model_WhereLike(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).WhereLike("nickname", "name%").OrderAsc("id").All()
t.AssertNil(err)
t.Assert(len(result), TableSize)
t.Assert(result[0]["id"], 1)
t.Assert(result[TableSize-1]["id"], TableSize)
})
}
func Test_Model_WhereNotLike(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).WhereNotLike("nickname", "name%").OrderAsc("id").All()
t.AssertNil(err)
t.Assert(len(result), 0)
})
}
func Test_Model_WhereOrLike(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).WhereOrLike("nickname", "namexxx%").WhereOrLike("nickname", "name%").OrderAsc("id").All()
t.AssertNil(err)
t.Assert(len(result), TableSize)
t.Assert(result[0]["id"], 1)
t.Assert(result[TableSize-1]["id"], TableSize)
})
}
func Test_Model_WhereOrNotLike(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).WhereOrNotLike("nickname", "namexxx%").WhereOrNotLike("nickname", "name%").OrderAsc("id").All()
t.AssertNil(err)
t.Assert(len(result), TableSize)
t.Assert(result[0]["id"], 1)
t.Assert(result[TableSize-1]["id"], TableSize)
})
}
func Test_Model_WhereNull(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).WhereNull("nickname").WhereNull("passport").OrderAsc("id").All()
t.AssertNil(err)
t.Assert(len(result), 0)
})
}
func Test_Model_WhereNotNull(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).WhereNotNull("nickname").WhereNotNull("passport").OrderAsc("id").All()
t.AssertNil(err)
t.Assert(len(result), TableSize)
t.Assert(result[0]["id"], 1)
t.Assert(result[TableSize-1]["id"], TableSize)
})
}
func Test_Model_WhereOrNull(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).WhereOrNull("nickname").WhereOrNull("passport").OrderAsc("id").OrderRandom().All()
t.AssertNil(err)
t.Assert(len(result), 0)
})
}
func Test_Model_WhereOrNotNull(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).WhereOrNotNull("nickname").WhereOrNotNull("passport").OrderAsc("id").All()
t.AssertNil(err)
t.Assert(len(result), TableSize)
t.Assert(result[0]["id"], 1)
t.Assert(result[TableSize-1]["id"], TableSize)
})
}
func Test_Model_Min_Max_Avg_Sum(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).Min("id")
t.AssertNil(err)
t.Assert(result, 1)
})
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).Max("id")
t.AssertNil(err)
t.Assert(result, TableSize)
})
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).Avg("id")
t.AssertNil(err)
t.Assert(result, 5.5)
})
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).Sum("id")
t.AssertNil(err)
t.Assert(result, 55)
})
}
func Test_Model_CountColumn(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).CountColumn("id")
t.AssertNil(err)
t.Assert(result, TableSize)
})
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).WhereIn("id", g.Slice{1, 2, 3}).CountColumn("id")
t.AssertNil(err)
t.Assert(result, 3)
})
}
func Test_Model_InsertAndGetId(t *testing.T) {
table := createTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
id, err := db.Model(table).Data(g.Map{
"id": 1,
"passport": "user_1",
"password": "pass_1",
"nickname": "name_1",
}).InsertAndGetId()
t.AssertNil(err)
t.Assert(id, 1)
})
gtest.C(t, func(t *gtest.T) {
id, err := db.Model(table).Data(g.Map{
"passport": "user_2",
"password": "pass_2",
"nickname": "name_2",
}).InsertAndGetId()
t.AssertNil(err)
t.Assert(id, 2)
})
}
func Test_Model_Increment_Decrement(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).Where("id", 1).Increment("id", 100)
t.AssertNil(err)
rows, _ := result.RowsAffected()
t.Assert(rows, 1)
})
gtest.C(t, func(t *gtest.T) {
result, err := db.Model(table).Where("id", 101).Decrement("id", 10)
t.AssertNil(err)
rows, _ := result.RowsAffected()
t.Assert(rows, 1)
})
gtest.C(t, func(t *gtest.T) {
count, err := db.Model(table).Where("id", 91).Count()
t.AssertNil(err)
t.Assert(count, 1)
})
}

View File

@ -8,7 +8,6 @@ package gdb_test
import (
"database/sql"
"github.com/gogf/gf/database/gdb"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/os/gtime"
"github.com/gogf/gf/test/gtest"
@ -363,6 +362,30 @@ func Test_Model_Scan_CustomType_Time(t *testing.T) {
})
}
func Test_Model_Scan_CustomType_String(t *testing.T) {
type MyString string
type MyStringSt struct {
Passport MyString
}
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
st := new(MyStringSt)
err := db.Model(table).Fields("Passport").WherePri(1).Scan(st)
t.AssertNil(err)
t.Assert(st.Passport, "user_1")
})
gtest.C(t, func(t *gtest.T) {
var sts []MyStringSt
err := db.Model(table).Fields("Passport").Order("id asc").Scan(&sts)
t.AssertNil(err)
t.Assert(len(sts), TableSize)
t.Assert(sts[0].Passport, "user_1")
})
}
type User struct {
Id int
Passport string
@ -374,11 +397,11 @@ type User struct {
func (user *User) UnmarshalValue(value interface{}) error {
switch result := value.(type) {
case map[string]interface{}:
user.Id = result["id"].(gdb.Value).Int()
user.Passport = result["passport"].(gdb.Value).String()
user.Id = result["id"].(int)
user.Passport = result["passport"].(string)
user.Password = ""
user.Nickname = result["nickname"].(gdb.Value).String()
user.CreateTime = result["create_time"].(gdb.Value).GTime()
user.Nickname = result["nickname"].(string)
user.CreateTime = gtime.New(result["create_time"])
return nil
default:
return gconv.Struct(value, user)

View File

@ -789,3 +789,137 @@ func Test_Transaction_Panic(t *testing.T) {
}
})
}
func Test_Transaction_Nested_Begin_Rollback_Commit(t *testing.T) {
table := createTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
tx, err := db.Begin()
t.AssertNil(err)
// tx begin.
err = tx.Begin()
t.AssertNil(err)
// tx rollback.
_, err = tx.Model(table).Data(g.Map{
"id": 1,
"passport": "user_1",
"password": "pass_1",
"nickname": "name_1",
}).Insert()
err = tx.Rollback()
t.AssertNil(err)
// tx commit.
_, err = tx.Model(table).Data(g.Map{
"id": 2,
"passport": "user_2",
"password": "pass_2",
"nickname": "name_2",
}).Insert()
err = tx.Commit()
t.AssertNil(err)
// check data.
all, err := db.Model(table).All()
t.AssertNil(err)
t.Assert(len(all), 1)
t.Assert(all[0]["id"], 2)
})
}
func Test_Transaction_Nested_TX_Transaction(t *testing.T) {
table := createTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
var err error
err = db.Transaction(func(tx *gdb.TX) error {
// commit
err = tx.Transaction(func(tx *gdb.TX) error {
err = tx.Transaction(func(tx *gdb.TX) error {
err = tx.Transaction(func(tx *gdb.TX) error {
err = tx.Transaction(func(tx *gdb.TX) error {
err = tx.Transaction(func(tx *gdb.TX) error {
_, err = tx.Model(table).Data(g.Map{
"id": 1,
"passport": "USER_1",
"password": "PASS_1",
"nickname": "NAME_1",
"create_time": gtime.Now().String(),
}).Insert()
t.AssertNil(err)
return err
})
t.AssertNil(err)
return err
})
t.AssertNil(err)
return err
})
t.AssertNil(err)
return err
})
t.AssertNil(err)
return err
})
t.AssertNil(err)
// rollback
err = tx.Transaction(func(tx *gdb.TX) error {
_, err = tx.Model(table).Data(g.Map{
"id": 2,
"passport": "USER_2",
"password": "PASS_2",
"nickname": "NAME_2",
"create_time": gtime.Now().String(),
}).Insert()
t.AssertNil(err)
panic("error")
return err
})
t.AssertNE(err, nil)
return nil
})
t.AssertNil(err)
all, err := db.Model(table).All()
t.AssertNil(err)
t.Assert(len(all), 1)
t.Assert(all[0]["id"], 1)
})
}
func Test_Transaction_Nested_SavePoint_RollbackTo(t *testing.T) {
table := createTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
tx, err := db.Begin()
t.AssertNil(err)
// tx save point.
_, err = tx.Model(table).Data(g.Map{
"id": 1,
"passport": "user_1",
"password": "pass_1",
"nickname": "name_1",
}).Insert()
err = tx.SavePoint("MyPoint")
t.AssertNil(err)
_, err = tx.Model(table).Data(g.Map{
"id": 2,
"passport": "user_2",
"password": "pass_2",
"nickname": "name_2",
}).Insert()
// tx rollback to.
err = tx.RollbackTo("MyPoint")
t.AssertNil(err)
// tx commit.
err = tx.Commit()
t.AssertNil(err)
// check data.
all, err := db.Model(table).All()
t.AssertNil(err)
t.Assert(len(all), 1)
t.Assert(all[0]["id"], 1)
})
}

View File

@ -16,6 +16,7 @@ package gredis
import (
"context"
"fmt"
"github.com/gogf/gf/internal/intlog"
"time"
"github.com/gogf/gf/container/gmap"
@ -113,6 +114,7 @@ func New(config *Config) *Redis {
if err != nil {
return nil, err
}
intlog.Printf(`open new connection, config:%+v`, config)
// AUTH
if len(config.Pass) > 0 {
if _, err := c.Do("AUTH", config.Pass); err != nil {
@ -188,7 +190,7 @@ func (r *Redis) Conn() *Conn {
}
// Alias of Conn, see Conn.
// Deprecated.
// Deprecated, use Conn instead.
func (r *Redis) GetConn() *Conn {
return r.Conn()
}

View File

@ -14,8 +14,8 @@ import (
"github.com/gogf/gf/net/gtrace"
"github.com/gogf/gf/os/gcmd"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/label"
"go.opentelemetry.io/otel/trace"
)
@ -68,14 +68,14 @@ func (c *Conn) addTracingItem(item *tracingItem) {
}
span.SetAttributes(gtrace.CommonLabels()...)
span.SetAttributes(
label.String(tracingAttrRedisHost, c.redis.config.Host),
label.Int(tracingAttrRedisPort, c.redis.config.Port),
label.Int(tracingAttrRedisDb, c.redis.config.Db),
attribute.String(tracingAttrRedisHost, c.redis.config.Host),
attribute.Int(tracingAttrRedisPort, c.redis.config.Port),
attribute.Int(tracingAttrRedisDb, c.redis.config.Db),
)
jsonBytes, _ := json.Marshal(item.arguments)
span.AddEvent(tracingEventRedisExecution, trace.WithAttributes(
label.String(tracingEventRedisExecutionCommand, item.commandName),
label.String(tracingEventRedisExecutionCost, fmt.Sprintf(`%d ms`, item.costMilli)),
label.String(tracingEventRedisExecutionArguments, string(jsonBytes)),
attribute.String(tracingEventRedisExecutionCommand, item.commandName),
attribute.String(tracingEventRedisExecutionCost, fmt.Sprintf(`%d ms`, item.costMilli)),
attribute.String(tracingEventRedisExecutionArguments, string(jsonBytes)),
))
}

View File

@ -354,13 +354,6 @@ func (j *Json) GetMapToMap(pattern string, pointer interface{}, mapping ...map[s
return gconv.MapToMap(j.Get(pattern), pointer, mapping...)
}
// GetMapToMapDeep retrieves the value by specified <pattern> and converts it to specified map
// variable recursively.
// See gconv.MapToMapDeep.
func (j *Json) GetMapToMapDeep(pattern string, pointer interface{}, mapping ...map[string]string) error {
return gconv.MapToMapDeep(j.Get(pattern), pointer, mapping...)
}
// GetMapToMaps retrieves the value by specified <pattern> and converts it to specified map slice
// variable.
// See gconv.MapToMaps.

View File

@ -85,15 +85,6 @@ func (j *Json) ToMapToMap(pointer interface{}, mapping ...map[string]string) err
return gconv.MapToMap(*(j.p), pointer, mapping...)
}
// ToMapToMapDeep converts current Json object to specified map variable recursively.
// The parameter of <pointer> should be type of *map.
// Deprecated, use MapToMap instead.
func (j *Json) ToMapToMapDeep(pointer interface{}, mapping ...map[string]string) error {
j.mu.RLock()
defer j.mu.RUnlock()
return gconv.MapToMapDeep(*(j.p), pointer, mapping...)
}
// ToMapToMaps converts current Json object to specified map variable slice.
// The parameter of <pointer> should be type of []map/*map.
// Deprecated, use MapToMaps instead.

View File

@ -97,18 +97,13 @@ func DB(name ...string) gdb.DB {
// relational databases but also for NoSQL databases in the future. The name
// "Table" is not proper for that purpose any more.
// Deprecated, use Model instead.
func Table(tables ...string) *gdb.Model {
return DB().Model(tables...)
func Table(tableNameOrStruct ...interface{}) *gdb.Model {
return DB().Model(tableNameOrStruct...)
}
// Model creates and returns a model based on configuration of default database group.
func Model(tables ...string) *gdb.Model {
return DB().Model(tables...)
}
// With creates and returns an ORM model based on meta data of given object.
func With(object interface{}) *gdb.Model {
return DB().With(object)
func Model(tableNameOrStruct ...interface{}) *gdb.Model {
return DB().Model(tableNameOrStruct...)
}
// Redis returns an instance of redis client with specified configuration group name.

View File

@ -48,22 +48,26 @@ func Database(name ...string) gdb.DB {
configMap = Config().GetMap(configNodeKey)
}
if len(configMap) == 0 && !gdb.IsConfigured() {
if !Config().Available() {
configFilePath, err := Config().GetFilePath()
if configFilePath == "" {
exampleFileName := "config.example.toml"
if Config().Available(exampleFileName) {
panic(gerror.Newf(
if exampleConfigFilePath, _ := Config().GetFilePath(exampleFileName); exampleConfigFilePath != "" {
panic(gerror.Wrapf(
err,
`configuration file "%s" not found, but found "%s", did you miss renaming the configuration example file?`,
Config().GetFileName(),
exampleFileName,
))
} else {
panic(gerror.Newf(
panic(gerror.Wrapf(
err,
`configuration file "%s" not found, did you miss the configuration file or the file name setting?`,
Config().GetFileName(),
))
}
}
panic(gerror.Newf(
panic(gerror.Wrapf(
err,
`database initialization failed: "%s" node not found, is configuration file or configuration node missing?`,
configNodeNameDatabase,
))

View File

@ -47,7 +47,14 @@ func Redis(name ...string) *gredis.Redis {
panic(fmt.Sprintf(`configuration for redis not found for group "%s"`, group))
}
} else {
panic(fmt.Sprintf(`incomplete configuration for redis: "redis" node not found in config file "%s"`, config.FilePath()))
filepath, err := config.GetFilePath()
if err != nil {
panic(err)
}
panic(fmt.Sprintf(
`incomplete configuration for redis: "redis" node not found in config file "%s"`,
filepath,
))
}
return nil
})

8
go.mod
View File

@ -1,6 +1,6 @@
module github.com/gogf/gf
go 1.11
go 1.14
require (
github.com/BurntSushi/toml v0.3.1
@ -11,8 +11,10 @@ require (
github.com/gorilla/websocket v1.4.1
github.com/grokify/html-strip-tags-go v0.0.0-20190921062105-daaa06bf1aaf
github.com/mattn/go-runewidth v0.0.10 // indirect
github.com/olekukonko/tablewriter v0.0.1
go.opentelemetry.io/otel v0.16.0
github.com/olekukonko/tablewriter v0.0.5
go.opentelemetry.io/otel v0.19.0
go.opentelemetry.io/otel/oteltest v0.19.0
go.opentelemetry.io/otel/trace v0.19.0
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102
golang.org/x/text v0.3.4
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c

59
go.sum Normal file
View File

@ -0,0 +1,59 @@
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/clbanning/mxj v1.8.5-0.20200714211355-ff02cfb8ea28 h1:LdXxtjzvZYhhUaonAaAKArG3pyC67kGL3YY+6hGG8G4=
github.com/clbanning/mxj v1.8.5-0.20200714211355-ff02cfb8ea28/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0=
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grokify/html-strip-tags-go v0.0.0-20190921062105-daaa06bf1aaf h1:wIOAyJMMen0ELGiFzlmqxdcV1yGbkyHBAB6PolcNbLA=
github.com/grokify/html-strip-tags-go v0.0.0-20190921062105-daaa06bf1aaf/go.mod h1:2Su6romC5/1VXOQMaWL2yb618ARB8iVo6/DR99A6d78=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg=
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
go.opentelemetry.io/otel v0.19.0 h1:Lenfy7QHRXPZVsw/12CWpxX6d/JkrX8wrx2vO8G80Ng=
go.opentelemetry.io/otel v0.19.0/go.mod h1:j9bF567N9EfomkSidSfmMwIwIBuP37AMAIzVW85OxSg=
go.opentelemetry.io/otel/metric v0.19.0 h1:dtZ1Ju44gkJkYvo+3qGqVXmf88tc+a42edOywypengg=
go.opentelemetry.io/otel/metric v0.19.0/go.mod h1:8f9fglJPRnXuskQmKpnad31lcLJ2VmNNqIsx/uIwBSc=
go.opentelemetry.io/otel/oteltest v0.19.0 h1:YVfA0ByROYqTwOxqHVZYZExzEpfZor+MU1rU+ip2v9Q=
go.opentelemetry.io/otel/oteltest v0.19.0/go.mod h1:tI4yxwh8U21v7JD6R3BcA/2+RBoTKFexE/PJ/nSO7IA=
go.opentelemetry.io/otel/trace v0.19.0 h1:1ucYlenXIDA1OlHVLDZKX0ObXV5RLaq06DtUKz5e5zc=
go.opentelemetry.io/otel/trace v0.19.0/go.mod h1:4IXiNextNOpPnRlI4ryK69mn5iC84bjBWZQA5DXz/qg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102 h1:42cLlJJdEh+ySyeUUbEQ5bsTiq8voBeTuweGVkY6Puw=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -7,45 +7,40 @@
// Package gi18n implements internationalization and localization.
package gi18n
var (
// defaultManager is the default i18n instance for package functions.
defaultManager = Instance()
)
// SetPath sets the directory path storing i18n files.
func SetPath(path string) error {
return defaultManager.SetPath(path)
return Instance().SetPath(path)
}
// SetLanguage sets the language for translator.
func SetLanguage(language string) {
defaultManager.SetLanguage(language)
Instance().SetLanguage(language)
}
// SetDelimiters sets the delimiters for translator.
func SetDelimiters(left, right string) {
defaultManager.SetDelimiters(left, right)
Instance().SetDelimiters(left, right)
}
// T is alias of Translate for convenience.
func T(content string, language ...string) string {
return defaultManager.T(content, language...)
return Instance().T(content, language...)
}
// Tf is alias of TranslateFormat for convenience.
func Tf(format string, values ...interface{}) string {
return defaultManager.TranslateFormat(format, values...)
return Instance().TranslateFormat(format, values...)
}
// Tfl is alias of TranslateFormatLang for convenience.
func Tfl(language string, format string, values ...interface{}) string {
return defaultManager.TranslateFormatLang(language, format, values...)
return Instance().TranslateFormatLang(language, format, values...)
}
// TranslateFormat translates, formats and returns the <format> with configured language
// and given <values>.
func TranslateFormat(format string, values ...interface{}) string {
return defaultManager.TranslateFormat(format, values...)
return Instance().TranslateFormat(format, values...)
}
// TranslateFormatLang translates, formats and returns the <format> with configured language
@ -53,17 +48,17 @@ func TranslateFormat(format string, values ...interface{}) string {
// configured language. If <language> is given empty string, it uses the default configured
// language for the translation.
func TranslateFormatLang(language string, format string, values ...interface{}) string {
return defaultManager.TranslateFormatLang(format, language, values...)
return Instance().TranslateFormatLang(language, format, 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...)
return Instance().Translate(content, language...)
}
// GetValue retrieves and returns the configured content for given key and specified language.
// It returns an empty string if not found.
func GetContent(key string, language ...string) string {
return defaultManager.GetContent(key, language...)
return Instance().GetContent(key, language...)
}

View File

@ -9,6 +9,7 @@ package empty
import (
"reflect"
"time"
)
// apiString is used for type assert api for String().
@ -26,6 +27,11 @@ type apiMapStrAny interface {
MapStrAny() map[string]interface{}
}
type apiTime interface {
Date() (year int, month time.Month, day int)
IsZero() bool
}
// IsEmpty checks whether given `value` empty.
// It returns true if `value` is in: 0, nil, false, "", len(slice/map/chan) == 0,
// or else it returns false.
@ -80,6 +86,12 @@ func IsEmpty(value interface{}) bool {
return len(value) == 0
default:
// Common interfaces checks.
if f, ok := value.(apiTime); ok {
if f == nil {
return true
}
return f.IsZero()
}
if f, ok := value.(apiString); ok {
if f == nil {
return true

View File

@ -6,6 +6,8 @@
package structs
import "reflect"
// Tag returns the value associated with key in the tag string. If there is no
// such key in the tag, Tag returns the empty string.
func (f *Field) Tag(key string) string {
@ -34,6 +36,24 @@ func (f *Field) Type() Type {
}
}
// Kind returns the reflect.Kind for Value of Field `f`.
func (f *Field) Kind() reflect.Kind {
return f.Value.Kind()
}
// OriginalKind retrieves and returns the original reflect.Kind for Value of Field `f`.
func (f *Field) OriginalKind() reflect.Kind {
var (
kind = f.Value.Kind()
value = f.Value
)
for kind == reflect.Ptr {
value = value.Elem()
kind = value.Kind()
}
return kind
}
// FieldMap retrieves and returns struct field as map[name/tag]*Field from `pointer`.
//
// The parameter `pointer` should be type of struct/*struct.

View File

@ -13,16 +13,17 @@ import (
"github.com/gogf/gf/net/ghttp/internal/client"
"github.com/gogf/gf/net/ghttp/internal/httputil"
"github.com/gogf/gf/net/gtrace"
"github.com/gogf/gf/text/gstr"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/label"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/trace"
"io/ioutil"
"net/http"
)
const (
tracingMaxContentLogSize = 512 * 1024 // Max log size for request and response body.
tracingInstrumentName = "github.com/gogf/gf/net/ghttp.Server"
tracingEventHttpRequest = "http.request"
tracingEventHttpRequestHeaders = "http.request.headers"
@ -41,7 +42,7 @@ func MiddlewareClientTracing(c *Client, r *http.Request) (*ClientResponse, error
// MiddlewareServerTracing is a serer middleware that enables tracing feature using standards of OpenTelemetry.
func MiddlewareServerTracing(r *Request) {
tr := otel.GetTracerProvider().Tracer(tracingInstrumentName, trace.WithInstrumentationVersion(gf.VERSION))
ctx := otel.GetTextMapPropagator().Extract(r.Context(), r.Header)
ctx := otel.GetTextMapPropagator().Extract(r.Context(), propagation.HeaderCarrier(r.Header))
ctx, span := tr.Start(ctx, r.URL.String(), trace.WithSpanKind(trace.SpanKindServer))
defer span.End()
@ -51,21 +52,17 @@ func MiddlewareServerTracing(r *Request) {
r.SetCtx(ctx)
// Request content logging.
var reqBodyContent string
if r.ContentLength <= tracingMaxContentLogSize {
reqBodyContentBytes, _ := ioutil.ReadAll(r.Body)
r.Body = utils.NewReadCloser(reqBodyContentBytes, false)
reqBodyContent = string(reqBodyContentBytes)
} else {
reqBodyContent = fmt.Sprintf(
"[Request Body Too Large For Tracing, Max: %d bytes]",
tracingMaxContentLogSize,
)
}
reqBodyContentBytes, _ := ioutil.ReadAll(r.Body)
r.Body = utils.NewReadCloser(reqBodyContentBytes, false)
span.AddEvent(tracingEventHttpRequest, trace.WithAttributes(
label.Any(tracingEventHttpRequestHeaders, httputil.HeaderToMap(r.Header)),
label.Any(tracingEventHttpRequestBaggage, gtrace.GetBaggageMap(ctx)),
label.String(tracingEventHttpRequestBody, reqBodyContent),
attribute.Any(tracingEventHttpRequestHeaders, httputil.HeaderToMap(r.Header)),
attribute.Any(tracingEventHttpRequestBaggage, gtrace.GetBaggageMap(ctx)),
attribute.String(tracingEventHttpRequestBody, gstr.StrLimit(
string(reqBodyContentBytes),
gtrace.MaxContentLogSize(),
"...",
)),
))
// Continue executing.
@ -77,17 +74,16 @@ func MiddlewareServerTracing(r *Request) {
}
// Response content logging.
var resBodyContent string
if r.Response.BufferLength() <= tracingMaxContentLogSize {
resBodyContent = r.Response.BufferString()
} else {
resBodyContent = fmt.Sprintf(
"[Response Body Too Large For Tracing, Max: %d bytes]",
tracingMaxContentLogSize,
)
}
resBodyContent = r.Response.BufferString()
resBodyContent = gstr.StrLimit(
r.Response.BufferString(),
gtrace.MaxContentLogSize(),
"...",
)
span.AddEvent(tracingEventHttpResponse, trace.WithAttributes(
label.Any(tracingEventHttpResponseHeaders, httputil.HeaderToMap(r.Response.Header())),
label.String(tracingEventHttpResponseBody, resBodyContent),
attribute.Any(tracingEventHttpResponseHeaders, httputil.HeaderToMap(r.Response.Header())),
attribute.String(tracingEventHttpResponseBody, resBodyContent),
))
return
}

View File

@ -139,14 +139,14 @@ func (r *Request) GetVar(key string, def ...interface{}) *gvar.Var {
// GetRaw is alias of GetBody.
// See GetBody.
// Deprecated.
// Deprecated, use GetBody instead.
func (r *Request) GetRaw() []byte {
return r.GetBody()
}
// GetRawString is alias of GetBodyString.
// See GetBodyString.
// Deprecated.
// Deprecated, use GetBodyString instead.
func (r *Request) GetRawString() string {
return r.GetBodyString()
}

View File

@ -18,7 +18,7 @@ import (
// Note that if there're multiple parameters with the same name, the parameters are retrieved
// and overwrote in order of priority: form > body.
//
// Deprecated.
// Deprecated, use GetForm instead.
func (r *Request) GetPost(key string, def ...interface{}) interface{} {
r.parseForm()
if len(r.formMap) > 0 {
@ -38,82 +38,82 @@ func (r *Request) GetPost(key string, def ...interface{}) interface{} {
return nil
}
// Deprecated.
// Deprecated, use GetFormVar instead.
func (r *Request) GetPostVar(key string, def ...interface{}) *gvar.Var {
return gvar.New(r.GetPost(key, def...))
}
// Deprecated.
// Deprecated, use GetFormString instead.
func (r *Request) GetPostString(key string, def ...interface{}) string {
return r.GetPostVar(key, def...).String()
}
// Deprecated.
// Deprecated, use GetFormBool instead.
func (r *Request) GetPostBool(key string, def ...interface{}) bool {
return r.GetPostVar(key, def...).Bool()
}
// Deprecated.
// Deprecated, use GetFormInt instead.
func (r *Request) GetPostInt(key string, def ...interface{}) int {
return r.GetPostVar(key, def...).Int()
}
// Deprecated.
// Deprecated, use GetFormInt32 instead.
func (r *Request) GetPostInt32(key string, def ...interface{}) int32 {
return r.GetPostVar(key, def...).Int32()
}
// Deprecated.
// Deprecated, use GetFormInt64 instead.
func (r *Request) GetPostInt64(key string, def ...interface{}) int64 {
return r.GetPostVar(key, def...).Int64()
}
// Deprecated.
// Deprecated, use GetFormInts instead.
func (r *Request) GetPostInts(key string, def ...interface{}) []int {
return r.GetPostVar(key, def...).Ints()
}
// Deprecated.
// Deprecated, use GetFormUint instead.
func (r *Request) GetPostUint(key string, def ...interface{}) uint {
return r.GetPostVar(key, def...).Uint()
}
// Deprecated.
// Deprecated, use GetFormUint32 instead.
func (r *Request) GetPostUint32(key string, def ...interface{}) uint32 {
return r.GetPostVar(key, def...).Uint32()
}
// Deprecated.
// Deprecated, use GetFormUint64 instead.
func (r *Request) GetPostUint64(key string, def ...interface{}) uint64 {
return r.GetPostVar(key, def...).Uint64()
}
// Deprecated.
// Deprecated, use GetFormFloat32 instead.
func (r *Request) GetPostFloat32(key string, def ...interface{}) float32 {
return r.GetPostVar(key, def...).Float32()
}
// Deprecated.
// Deprecated, use GetFormFloat64 instead.
func (r *Request) GetPostFloat64(key string, def ...interface{}) float64 {
return r.GetPostVar(key, def...).Float64()
}
// Deprecated.
// Deprecated, use GetFormFloats instead.
func (r *Request) GetPostFloats(key string, def ...interface{}) []float64 {
return r.GetPostVar(key, def...).Floats()
}
// Deprecated.
// Deprecated, use GetFormArray instead.
func (r *Request) GetPostArray(key string, def ...interface{}) []string {
return r.GetPostVar(key, def...).Strings()
}
// Deprecated.
// Deprecated, use GetFormStrings instead.
func (r *Request) GetPostStrings(key string, def ...interface{}) []string {
return r.GetPostVar(key, def...).Strings()
}
// Deprecated.
// Deprecated, use GetFormInterfaces instead.
func (r *Request) GetPostInterfaces(key string, def ...interface{}) []interface{} {
return r.GetPostVar(key, def...).Interfaces()
}

View File

@ -193,7 +193,7 @@ func (s *Server) Start() error {
// If this is a child process, it then notifies its parent exit.
if gproc.IsChild() {
gtimer.SetTimeout(2*time.Second, func() {
gtimer.SetTimeout(time.Duration(s.config.GracefulTimeout)*time.Second, func() {
if err := gproc.Send(gproc.PPid(), []byte("exit"), adminGProcCommGroup); err != nil {
//glog.Error("server error in process communication:", err)
}
@ -268,7 +268,7 @@ func (s *Server) GetRouterArray() []RouterItem {
item.Middleware += gdebug.FuncName(v)
}
}
// If the domain does not exist in the dump map, it create the map.
// If the domain does not exist in the dump map, it creates the map.
// The value of the map is a custom sorted array.
if _, ok := m[item.Domain]; !ok {
// Sort in ASC order.

View File

@ -218,6 +218,9 @@ type ServerConfig struct {
// Graceful enables graceful reload feature for all servers of the process.
Graceful bool `json:"graceful"`
// GracefulTimeout set the maximum survival time (seconds) of the parent process.
GracefulTimeout uint8 `json:"gracefulTimeout"`
}
// Deprecated. Use NewConfig instead.
@ -265,6 +268,7 @@ func NewConfig() ServerConfig {
FormParsingMemory: 1024 * 1024, // 1MB
Rewrites: make(map[string]string),
Graceful: false,
GracefulTimeout: 2, // seconds
}
}

View File

@ -160,14 +160,14 @@ func (c *Cookie) Remove(key string) {
"",
c.request.Server.GetCookieDomain(),
c.request.Server.GetCookiePath(),
-86400,
-24*time.Hour,
)
}
// RemoveCookie deletes specified key and its value from cookie using given domain and path.
// It actually tells the http client that the cookie is expired, do not send it to server next time.
func (c *Cookie) RemoveCookie(key, domain, path string) {
c.SetCookie(key, "", domain, path, -86400)
c.SetCookie(key, "", domain, path, -24*time.Hour)
}
// Flush outputs the cookie items to client.

View File

@ -80,9 +80,9 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Close the request and response body
// to release the file descriptor in time.
request.Request.Body.Close()
_ = request.Request.Body.Close()
if request.Request.Response != nil {
request.Request.Response.Body.Close()
_ = request.Request.Response.Body.Close()
}
}()
@ -168,7 +168,8 @@ 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 it creates a new session id in this request
// and SessionCookieOutput is enabled.
if s.config.SessionCookieOutput &&
request.Session.IsDirty() &&
request.Session.Id() != request.GetSessionId() {

View File

@ -52,7 +52,5 @@ func Test_Client_Request_13_Dump(t *testing.T) {
t.Assert(gstr.Contains(dumpedText3, "test_for_request_body"), true)
dumpedText4 := r2.RawResponse()
t.Assert(gstr.Contains(dumpedText4, "test_for_request_body"), false)
})
}

View File

@ -18,6 +18,7 @@ import (
"golang.org/x/net/proxy"
"net"
"net/http"
"net/http/cookiejar"
"net/url"
"strings"
"time"
@ -36,7 +37,6 @@ type Client struct {
prefix string // Prefix for request.
authUser string // HTTP basic authentication: user.
authPass string // HTTP basic authentication: pass.
browserMode bool // Whether auto saving and sending cookie content.
retryCount int // Retry count when request fails.
retryInterval time.Duration // Retry interval when request fails.
middlewareHandler []HandlerFunc // Interceptor handlers
@ -84,7 +84,10 @@ func (c *Client) Clone() *Client {
// When browser mode is enabled, it automatically saves and sends cookie content
// from and to server.
func (c *Client) SetBrowserMode(enabled bool) *Client {
c.browserMode = enabled
if enabled {
jar, _ := cookiejar.New(nil)
c.Jar = jar
}
return c
}

View File

@ -115,18 +115,6 @@ func (c *Client) DoRequest(method, url string, data ...interface{}) (resp *Respo
} else {
resp, err = c.callRequest(req)
}
// Auto saving cookie content.
if c.browserMode && resp != nil && resp.Response != nil {
now := time.Now()
for _, v := range resp.Response.Cookies() {
if !v.Expires.IsZero() && v.Expires.UnixNano() < now.UnixNano() {
delete(c.cookies, v.Name)
} else {
c.cookies[v.Name] = v.Value
}
}
}
return resp, err
}
@ -136,53 +124,67 @@ func (c *Client) prepareRequest(method, url string, data ...interface{}) (req *h
if len(c.prefix) > 0 {
url = c.prefix + gstr.Trim(url)
}
param := ""
var params string
if len(data) > 0 {
switch c.header["Content-Type"] {
case "application/json":
switch data[0].(type) {
case string, []byte:
param = gconv.String(data[0])
params = gconv.String(data[0])
default:
if b, err := json.Marshal(data[0]); err != nil {
return nil, err
} else {
param = gconv.UnsafeBytesToStr(b)
params = string(b)
}
}
case "application/xml":
switch data[0].(type) {
case string, []byte:
param = gconv.String(data[0])
params = gconv.String(data[0])
default:
if b, err := gparser.VarToXml(data[0]); err != nil {
return nil, err
} else {
param = gconv.UnsafeBytesToStr(b)
params = string(b)
}
}
default:
param = httputil.BuildParams(data[0])
params = httputil.BuildParams(data[0])
}
}
if method == "GET" {
// It appends the parameters to the url if http method is GET.
if param != "" {
if gstr.Contains(url, "?") {
url = url + "&" + param
} else {
url = url + "?" + param
var bodyBuffer *bytes.Buffer
if params != "" {
switch c.header["Content-Type"] {
case
"application/json",
"application/xml":
bodyBuffer = bytes.NewBuffer([]byte(params))
default:
// It appends the parameters to the url
// if http method is GET and Content-Type is not specified.
if gstr.Contains(url, "?") {
url = url + "&" + params
} else {
url = url + "?" + params
}
bodyBuffer = bytes.NewBuffer(nil)
}
} else {
bodyBuffer = bytes.NewBuffer(nil)
}
if req, err = http.NewRequest(method, url, bytes.NewBuffer(nil)); err != nil {
if req, err = http.NewRequest(method, url, bodyBuffer); err != nil {
return nil, err
}
} else {
if strings.Contains(param, "@file:") {
if strings.Contains(params, "@file:") {
// File uploading request.
buffer := new(bytes.Buffer)
writer := multipart.NewWriter(buffer)
for _, item := range strings.Split(param, "&") {
var (
buffer = bytes.NewBuffer(nil)
writer = multipart.NewWriter(buffer)
)
for _, item := range strings.Split(params, "&") {
array := strings.Split(item, "=")
if len(array[1]) > 6 && strings.Compare(array[1][0:6], "@file:") == 0 {
path := array[1][6:]
@ -225,7 +227,7 @@ func (c *Client) prepareRequest(method, url string, data ...interface{}) (req *h
}
} else {
// Normal request.
paramBytes := []byte(param)
paramBytes := []byte(params)
if req, err = http.NewRequest(method, url, bytes.NewReader(paramBytes)); err != nil {
return nil, err
} else {
@ -236,7 +238,7 @@ func (c *Client) prepareRequest(method, url string, data ...interface{}) (req *h
if (paramBytes[0] == '[' || paramBytes[0] == '{') && json.Valid(paramBytes) {
// Auto detecting and setting the post content format: JSON.
req.Header.Set("Content-Type", "application/json")
} else if gregex.IsMatchString(`^[\w\[\]]+=.+`, param) {
} else if gregex.IsMatchString(`^[\w\[\]]+=.+`, params) {
// If the parameters passed like "name=value", it then uses form type.
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
}

View File

@ -12,9 +12,11 @@ import (
"github.com/gogf/gf/internal/utils"
"github.com/gogf/gf/net/ghttp/internal/httputil"
"github.com/gogf/gf/net/gtrace"
"github.com/gogf/gf/text/gstr"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/label"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/trace"
"io/ioutil"
"net/http"
@ -22,7 +24,6 @@ import (
)
const (
tracingMaxContentLogSize = 512 * 1024 // Max log size for request and response body.
tracingInstrumentName = "github.com/gogf/gf/net/ghttp.Client"
tracingAttrHttpAddressRemote = "http.address.remote"
tracingAttrHttpAddressLocal = "http.address.local"
@ -48,7 +49,7 @@ func MiddlewareTracing(c *Client, r *http.Request) (response *Response, err erro
span.SetAttributes(gtrace.CommonLabels()...)
// Inject tracing content into http header.
otel.GetTextMapPropagator().Inject(ctx, r.Header)
otel.GetTextMapPropagator().Inject(ctx, propagation.HeaderCarrier(r.Header))
// Continue client handler executing.
response, err = c.Next(
@ -64,21 +65,17 @@ func MiddlewareTracing(c *Client, r *http.Request) (response *Response, err erro
if response == nil || response.Response == nil {
return
}
var resBodyContent string
if response.ContentLength <= tracingMaxContentLogSize {
reqBodyContentBytes, _ := ioutil.ReadAll(response.Body)
resBodyContent = string(reqBodyContentBytes)
response.Body = utils.NewReadCloser(reqBodyContentBytes, false)
} else {
resBodyContent = fmt.Sprintf(
"[Response Body Too Large For Tracing, Max: %d bytes]",
tracingMaxContentLogSize,
)
}
reqBodyContentBytes, _ := ioutil.ReadAll(response.Body)
response.Body = utils.NewReadCloser(reqBodyContentBytes, false)
span.AddEvent(tracingEventHttpResponse, trace.WithAttributes(
label.Any(tracingEventHttpResponseHeaders, httputil.HeaderToMap(response.Header)),
label.String(tracingEventHttpResponseBody, resBodyContent),
attribute.Any(tracingEventHttpResponseHeaders, httputil.HeaderToMap(response.Header)),
attribute.String(tracingEventHttpResponseBody, gstr.StrLimit(
string(reqBodyContentBytes),
gtrace.MaxContentLogSize(),
"...",
)),
))
return
}

View File

@ -12,8 +12,9 @@ import (
"fmt"
"github.com/gogf/gf/internal/utils"
"github.com/gogf/gf/net/gtrace"
"github.com/gogf/gf/text/gstr"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/label"
"go.opentelemetry.io/otel/trace"
"io/ioutil"
"net/http"
@ -39,11 +40,11 @@ func newClientTrace(ctx context.Context, span trace.Span, request *http.Request)
request: request,
headers: make(map[string]interface{}),
}
if ct.request.ContentLength <= tracingMaxContentLogSize {
reqBodyContent, _ := ioutil.ReadAll(ct.request.Body)
ct.requestBody = reqBodyContent
ct.request.Body = utils.NewReadCloser(reqBodyContent, false)
}
reqBodyContent, _ := ioutil.ReadAll(ct.request.Body)
ct.requestBody = reqBodyContent
ct.request.Body = utils.NewReadCloser(reqBodyContent, false)
return &httptrace.ClientTrace{
GetConn: ct.getConn,
GotConn: ct.gotConn,
@ -70,8 +71,8 @@ func (ct *clientTracer) getConn(host string) {
func (ct *clientTracer) gotConn(info httptrace.GotConnInfo) {
ct.span.SetAttributes(
label.String(tracingAttrHttpAddressRemote, info.Conn.RemoteAddr().String()),
label.String(tracingAttrHttpAddressLocal, info.Conn.LocalAddr().String()),
attribute.String(tracingAttrHttpAddressRemote, info.Conn.RemoteAddr().String()),
attribute.String(tracingAttrHttpAddressLocal, info.Conn.LocalAddr().String()),
)
}
@ -83,7 +84,7 @@ func (ct *clientTracer) putIdleConn(err error) {
func (ct *clientTracer) dnsStart(info httptrace.DNSStartInfo) {
ct.span.SetAttributes(
label.String(tracingAttrHttpDnsStart, info.Host),
attribute.String(tracingAttrHttpDnsStart, info.Host),
)
}
@ -99,13 +100,13 @@ func (ct *clientTracer) dnsDone(info httptrace.DNSDoneInfo) {
ct.span.SetStatus(codes.Error, fmt.Sprintf(`%+v`, info.Err))
}
ct.span.SetAttributes(
label.String(tracingAttrHttpDnsDone, buffer.String()),
attribute.String(tracingAttrHttpDnsDone, buffer.String()),
)
}
func (ct *clientTracer) connectStart(network, addr string) {
ct.span.SetAttributes(
label.String(tracingAttrHttpConnectStart, network+"@"+addr),
attribute.String(tracingAttrHttpConnectStart, network+"@"+addr),
)
}
@ -114,7 +115,7 @@ func (ct *clientTracer) connectDone(network, addr string, err error) {
ct.span.SetStatus(codes.Error, fmt.Sprintf(`%+v`, err))
}
ct.span.SetAttributes(
label.String(tracingAttrHttpConnectDone, network+"@"+addr),
attribute.String(tracingAttrHttpConnectDone, network+"@"+addr),
)
}
@ -144,19 +145,15 @@ func (ct *clientTracer) wroteRequest(info httptrace.WroteRequestInfo) {
if info.Err != nil {
ct.span.SetStatus(codes.Error, fmt.Sprintf(`%+v`, info.Err))
}
var bodyContent string
if ct.request.ContentLength <= tracingMaxContentLogSize {
bodyContent = string(ct.requestBody)
} else {
bodyContent = fmt.Sprintf(
"[Request Body Too Large For Logging, Max: %d bytes]",
tracingMaxContentLogSize,
)
}
ct.span.AddEvent(tracingEventHttpRequest, trace.WithAttributes(
label.Any(tracingEventHttpRequestHeaders, ct.headers),
label.Any(tracingEventHttpRequestBaggage, gtrace.GetBaggageMap(ct.Context)),
label.String(tracingEventHttpRequestBody, bodyContent),
attribute.Any(tracingEventHttpRequestHeaders, ct.headers),
attribute.Any(tracingEventHttpRequestBaggage, gtrace.GetBaggageMap(ct.Context)),
attribute.String(tracingEventHttpRequestBody, gstr.StrLimit(
string(ct.requestBody),
gtrace.MaxContentLogSize(),
"...",
)),
))
}

View File

@ -9,11 +9,13 @@ package gtrace
import (
"context"
"fmt"
"github.com/gogf/gf/container/gmap"
"github.com/gogf/gf/container/gvar"
"github.com/gogf/gf/net/gipv4"
"github.com/gogf/gf/os/gcmd"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/label"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/trace"
"os"
@ -23,12 +25,14 @@ import (
const (
tracingCommonKeyIpIntranet = `ip.intranet`
tracingCommonKeyIpHostname = `hostname`
cmdEnvKey = "gf.gtrace" // Configuration key for command argument or environment.
)
var (
intranetIps, _ = gipv4.GetIntranetIpArray()
intranetIpStr = strings.Join(intranetIps, ",")
hostname, _ = os.Hostname()
intranetIps, _ = gipv4.GetIntranetIpArray()
intranetIpStr = strings.Join(intranetIps, ",")
hostname, _ = os.Hostname()
tracingMaxContentLogSize = 256 * 1024 // Max log size for request and response body, especially for HTTP/RPC request.
// defaultTextMapPropagator is the default propagator for context propagation between peers.
defaultTextMapPropagator = propagation.NewCompositeTextMapPropagator(
propagation.TraceContext{},
@ -37,15 +41,23 @@ var (
)
func init() {
if maxContentLogSize := gcmd.GetOptWithEnv(fmt.Sprintf("%s.maxcontentlogsize", cmdEnvKey)).Int(); maxContentLogSize > 0 {
tracingMaxContentLogSize = maxContentLogSize
}
CheckSetDefaultTextMapPropagator()
}
// MaxContentLogSize returns the max log size for request and response body, especially for HTTP/RPC request.
func MaxContentLogSize() int {
return tracingMaxContentLogSize
}
// CommonLabels returns common used attribute labels:
// ip.intranet, hostname.
func CommonLabels() []label.KeyValue {
return []label.KeyValue{
label.String(tracingCommonKeyIpHostname, hostname),
label.String(tracingCommonKeyIpIntranet, intranetIpStr),
func CommonLabels() []attribute.KeyValue {
return []attribute.KeyValue{
attribute.String(tracingCommonKeyIpHostname, hostname),
attribute.String(tracingCommonKeyIpIntranet, intranetIpStr),
}
}
@ -73,7 +85,7 @@ func GetTraceId(ctx context.Context) string {
if ctx == nil {
return ""
}
traceId := trace.SpanContextFromContext(ctx).TraceID
traceId := trace.SpanContextFromContext(ctx).TraceID()
if traceId.IsValid() {
return traceId.String()
}
@ -86,7 +98,7 @@ func GetSpanId(ctx context.Context) string {
if ctx == nil {
return ""
}
spanId := trace.SpanContextFromContext(ctx).SpanID
spanId := trace.SpanContextFromContext(ctx).SpanID()
if spanId.IsValid() {
return spanId.String()
}
@ -94,13 +106,13 @@ func GetSpanId(ctx context.Context) string {
}
// SetBaggageValue is a convenient function for adding one key-value pair to baggage.
// Note that it uses label.Any to set the key-value pair.
// Note that it uses attribute.Any to set the key-value pair.
func SetBaggageValue(ctx context.Context, key string, value interface{}) context.Context {
return NewBaggage(ctx).SetValue(key, value)
}
// SetBaggageMap is a convenient function for adding map key-value pairs to baggage.
// Note that it uses label.Any to set the key-value pair.
// Note that it uses attribute.Any to set the key-value pair.
func SetBaggageMap(ctx context.Context, data map[string]interface{}) context.Context {
return NewBaggage(ctx).SetMap(data)
}

View File

@ -10,8 +10,8 @@ import (
"context"
"github.com/gogf/gf/container/gmap"
"github.com/gogf/gf/container/gvar"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/baggage"
"go.opentelemetry.io/otel/label"
)
// Baggage holds the data through all tracing spans.
@ -35,18 +35,18 @@ func (b *Baggage) Ctx() context.Context {
}
// SetValue is a convenient function for adding one key-value pair to baggage.
// Note that it uses label.Any to set the key-value pair.
// Note that it uses attribute.Any to set the key-value pair.
func (b *Baggage) SetValue(key string, value interface{}) context.Context {
b.ctx = baggage.ContextWithValues(b.ctx, label.Any(key, value))
b.ctx = baggage.ContextWithValues(b.ctx, attribute.Any(key, value))
return b.ctx
}
// SetMap is a convenient function for adding map key-value pairs to baggage.
// Note that it uses label.Any to set the key-value pair.
// Note that it uses attribute.Any to set the key-value pair.
func (b *Baggage) SetMap(data map[string]interface{}) context.Context {
pairs := make([]label.KeyValue, 0)
pairs := make([]attribute.KeyValue, 0)
for k, v := range data {
pairs = append(pairs, label.Any(k, v))
pairs = append(pairs, attribute.Any(k, v))
}
b.ctx = baggage.ContextWithValues(b.ctx, pairs...)
return b.ctx
@ -70,6 +70,6 @@ func (b *Baggage) GetMap() *gmap.StrAnyMap {
// GetVar retrieves value and returns a *gvar.Var for specified key from baggage.
func (b *Baggage) GetVar(key string) *gvar.Var {
value := baggage.Value(b.ctx, label.Key(key))
value := baggage.Value(b.ctx, attribute.Key(key))
return gvar.New(value.AsInterface())
}

View File

@ -11,41 +11,49 @@ import (
"github.com/gogf/gf/util/gconv"
)
type Carrier struct {
data map[string]interface{}
}
// Carrier is the storage medium used by a TextMapPropagator.
type Carrier map[string]interface{}
func NewCarrier(data ...map[string]interface{}) *Carrier {
carrier := &Carrier{}
func NewCarrier(data ...map[string]interface{}) Carrier {
if len(data) > 0 && data[0] != nil {
carrier.data = data[0]
return data[0]
} else {
carrier.data = make(map[string]interface{})
return make(map[string]interface{})
}
return carrier
}
func (c *Carrier) Get(k string) string {
return gconv.String(c.data[k])
// Get returns the value associated with the passed key.
func (c Carrier) Get(k string) string {
return gconv.String(c[k])
}
func (c *Carrier) Set(k, v string) {
c.data[k] = v
// Set stores the key-value pair.
func (c Carrier) Set(k, v string) {
c[k] = v
}
func (c *Carrier) MustMarshal() []byte {
b, err := json.Marshal(c.data)
// Keys lists the keys stored in this carrier.
func (c Carrier) Keys() []string {
keys := make([]string, 0, len(c))
for k := range c {
keys = append(keys, k)
}
return keys
}
func (c Carrier) MustMarshal() []byte {
b, err := json.Marshal(c)
if err != nil {
panic(err)
}
return b
}
func (c *Carrier) String() string {
func (c Carrier) String() string {
return string(c.MustMarshal())
}
func (c *Carrier) UnmarshalJSON(b []byte) error {
func (c Carrier) UnmarshalJSON(b []byte) error {
carrier := NewCarrier(nil)
return json.Unmarshal(b, carrier.data)
return json.Unmarshal(b, carrier)
}

View File

@ -46,11 +46,11 @@ func mustSpanIDFromHex(s string) (t trace.SpanID) {
func TestNewCarrier(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
ctx := trace.ContextWithRemoteSpanContext(context.Background(), trace.SpanContext{
ctx := trace.ContextWithRemoteSpanContext(context.Background(), trace.NewSpanContext(trace.SpanContextConfig{
TraceID: traceID,
SpanID: spanID,
TraceFlags: trace.FlagsSampled,
})
}))
ctx, _ = oteltest.DefaultTracer().Start(ctx, "inject")
carrier1 := gtrace.NewCarrier()
otel.GetTextMapPropagator().Inject(ctx, carrier1)
@ -58,7 +58,7 @@ func TestNewCarrier(t *testing.T) {
ctx = otel.GetTextMapPropagator().Extract(ctx, carrier1)
gotSc := trace.RemoteSpanContextFromContext(ctx)
t.Assert(gotSc.TraceID.String(), traceID.String())
t.Assert(gotSc.SpanID.String(), "0000000000000002")
t.Assert(gotSc.TraceID().String(), traceID.String())
t.Assert(gotSc.SpanID().String(), "0000000000000002")
})
}

View File

@ -11,9 +11,11 @@ import (
"bytes"
"errors"
"fmt"
"github.com/gogf/gf/errors/gerror"
"github.com/gogf/gf/internal/intlog"
"github.com/gogf/gf/os/gcmd"
"github.com/gogf/gf/text/gstr"
"github.com/gogf/gf/util/gmode"
"github.com/gogf/gf/os/gres"
@ -45,7 +47,7 @@ var (
)
// New returns a new configuration management object.
// The parameter <file> specifies the default configuration file name for reading.
// The parameter `file` specifies the default configuration file name for reading.
func New(file ...string) *Config {
name := DefaultConfigFile
if len(file) > 0 {
@ -67,7 +69,7 @@ func New(file ...string) *Config {
_ = c.SetPath(customPath)
} else {
if errorPrint() {
glog.Errorf("Configuration directory path does not exist: %s", customPath)
glog.Errorf("[gcfg] Configuration directory path does not exist: %s", customPath)
}
}
} else {
@ -93,42 +95,8 @@ func New(file ...string) *Config {
return c
}
// filePath returns the absolute configuration file path for the given filename by <file>.
func (c *Config) filePath(file ...string) (path string) {
name := c.defaultName
if len(file) > 0 {
name = file[0]
}
path = c.FilePath(name)
if path == "" {
var (
buffer = bytes.NewBuffer(nil)
)
if c.searchPaths.Len() > 0 {
buffer.WriteString(fmt.Sprintf("[gcfg] cannot find config file \"%s\" in following paths:", name))
c.searchPaths.RLockFunc(func(array []string) {
index := 1
for _, v := range array {
v = gstr.TrimRight(v, `\/`)
buffer.WriteString(fmt.Sprintf("\n%d. %s", index, v))
index++
buffer.WriteString(fmt.Sprintf("\n%d. %s", index, v+gfile.Separator+"config"))
index++
}
})
} else {
buffer.WriteString(fmt.Sprintf("[gcfg] cannot find config file \"%s\" with no path set/add", name))
}
if errorPrint() {
glog.Error(buffer.String())
}
}
return path
}
// SetPath sets the configuration directory path for file search.
// The parameter <path> can be absolute or relative path,
// The parameter `path` can be absolute or relative path,
// but absolute path is strongly recommended.
func (c *Config) SetPath(path string) error {
var (
@ -246,14 +214,14 @@ func (c *Config) AddPath(path string) error {
} else {
buffer.WriteString(fmt.Sprintf(`[gcfg] AddPath failed: path "%s" does not exist`, path))
}
err := errors.New(buffer.String())
err := gerror.New(buffer.String())
if errorPrint() {
glog.Error(err)
}
return err
}
if !isDir {
err := fmt.Errorf(`[gcfg] AddPath failed: path "%s" should be directory type`, path)
err := gerror.Newf(`[gcfg] AddPath failed: path "%s" should be directory type`, path)
if errorPrint() {
glog.Error(err)
}
@ -268,11 +236,38 @@ func (c *Config) AddPath(path string) error {
return nil
}
// GetFilePath returns the absolute path of the specified configuration file.
// If <file> is not passed, it returns the configuration file path of the default name.
// If the specified configuration file does not exist,
// an empty string is returned.
func (c *Config) FilePath(file ...string) (path string) {
// SetFileName sets the default configuration file name.
func (c *Config) SetFileName(name string) *Config {
c.defaultName = name
return c
}
// GetFileName returns the default configuration file name.
func (c *Config) GetFileName() string {
return c.defaultName
}
// Available checks and returns whether configuration of given `file` is available.
func (c *Config) Available(file ...string) bool {
var name string
if len(file) > 0 && file[0] != "" {
name = file[0]
} else {
name = c.defaultName
}
if path, _ := c.GetFilePath(name); path != "" {
return true
}
if GetContent(name) != "" {
return true
}
return false
}
// GetFilePath returns the absolute configuration file path for the given filename by `file`.
// If `file` is not passed, it returns the configuration file path of the default name.
// It returns an empty `path` string and an error if the given `file` does not exist.
func (c *Config) GetFilePath(file ...string) (path string, err error) {
name := c.defaultName
if len(file) > 0 {
name = file[0]
@ -296,6 +291,7 @@ func (c *Config) FilePath(file ...string) (path string) {
}
})
}
c.autoCheckAndAddMainPkgPathToSearchPaths()
// Searching the file system.
c.searchPaths.RLockFunc(func(array []string) {
for _, prefix := range array {
@ -308,38 +304,45 @@ func (c *Config) FilePath(file ...string) (path string) {
}
}
})
// If it cannot find the path of `file`, it formats and returns a detailed error.
if path == "" {
var (
buffer = bytes.NewBuffer(nil)
)
if c.searchPaths.Len() > 0 {
buffer.WriteString(fmt.Sprintf(`[gcfg] cannot find config file "%s" in resource manager or the following paths:`, name))
c.searchPaths.RLockFunc(func(array []string) {
index := 1
for _, v := range array {
v = gstr.TrimRight(v, `\/`)
buffer.WriteString(fmt.Sprintf("\n%d. %s", index, v))
index++
buffer.WriteString(fmt.Sprintf("\n%d. %s", index, v+gfile.Separator+"config"))
index++
}
})
} else {
buffer.WriteString(fmt.Sprintf("[gcfg] cannot find config file \"%s\" with no path configured", name))
}
err = gerror.New(buffer.String())
}
return
}
// SetFileName sets the default configuration file name.
func (c *Config) SetFileName(name string) *Config {
c.defaultName = name
return c
// autoCheckAndAddMainPkgPathToSearchPaths automatically checks and adds directory path of package main
// to the searching path list if it's currently in development environment.
func (c *Config) autoCheckAndAddMainPkgPathToSearchPaths() {
if gmode.IsDevelop() {
mainPkgPath := gfile.MainPkgPath()
if mainPkgPath != "" {
if !c.searchPaths.Contains(mainPkgPath) {
c.searchPaths.Append(mainPkgPath)
}
}
}
}
// GetFileName returns the default configuration file name.
func (c *Config) GetFileName() string {
return c.defaultName
}
// Available checks and returns whether configuration of given <file> is available.
func (c *Config) Available(file ...string) bool {
var name string
if len(file) > 0 && file[0] != "" {
name = file[0]
} else {
name = c.defaultName
}
if c.FilePath(name) != "" {
return true
}
if GetContent(name) != "" {
return true
}
return false
}
// getJson returns a *gjson.Json object for the specified <file> content.
// getJson returns a *gjson.Json object for the specified `file` content.
// It would print error if file reading fails. It return nil if any error occurs.
func (c *Config) getJson(file ...string) *gjson.Json {
var name string
@ -350,14 +353,18 @@ func (c *Config) getJson(file ...string) *gjson.Json {
}
r := c.jsonMap.GetOrSetFuncLock(name, func() interface{} {
var (
content = ""
filePath = ""
err error
content string
filePath string
)
// The configured content can be any kind of data type different from its file type.
isFromConfigContent := true
if content = GetContent(name); content == "" {
isFromConfigContent = false
filePath = c.filePath(name)
filePath, err = c.GetFilePath(name)
if err != nil && errorPrint() {
glog.Error(err)
}
if filePath == "" {
return nil
}
@ -369,8 +376,7 @@ func (c *Config) getJson(file ...string) *gjson.Json {
}
// Note that the underlying configuration json object operations are concurrent safe.
var (
j *gjson.Json
err error
j *gjson.Json
)
dataType := gfile.ExtName(name)
if gjson.IsValidDataType(dataType) && !isFromConfigContent {
@ -394,9 +400,9 @@ func (c *Config) getJson(file ...string) *gjson.Json {
}
if errorPrint() {
if filePath != "" {
glog.Criticalf(`[gcfg] Load config file "%s" failed: %s`, filePath, err.Error())
glog.Criticalf(`[gcfg] load config file "%s" failed: %s`, filePath, err.Error())
} else {
glog.Criticalf(`[gcfg] Load configuration failed: %s`, err.Error())
glog.Criticalf(`[gcfg] load configuration failed: %s`, err.Error())
}
}
return nil

View File

@ -16,7 +16,7 @@ import (
"github.com/gogf/gf/os/gtime"
)
// Set sets value with specified <pattern>.
// Set sets value with specified `pattern`.
// It supports hierarchical data access by char separator, which is '.' in default.
// It is commonly used for updates certain configuration value in runtime.
func (c *Config) Set(pattern string, value interface{}) error {
@ -26,14 +26,14 @@ func (c *Config) Set(pattern string, value interface{}) error {
return nil
}
// Get retrieves and returns value by specified <pattern>.
// It returns all values of current Json object if <pattern> is given empty or string ".".
// It returns nil if no value found by <pattern>.
// Get retrieves and returns value by specified `pattern`.
// It returns all values of current Json object if `pattern` is given empty or string ".".
// It returns nil if no value found by `pattern`.
//
// We can also access slice item by its index number in <pattern> like:
// We can also access slice item by its index number in `pattern` like:
// "list.10", "array.0.name", "array.0.1.id".
//
// It returns a default value specified by <def> if value for <pattern> is not found.
// It returns a default value specified by `def` if value for `pattern` is not found.
func (c *Config) Get(pattern string, def ...interface{}) interface{} {
if j := c.getJson(); j != nil {
return j.Get(pattern, def...)
@ -41,7 +41,7 @@ func (c *Config) Get(pattern string, def ...interface{}) interface{} {
return nil
}
// GetVar returns a gvar.Var with value by given <pattern>.
// GetVar returns a gvar.Var with value by given `pattern`.
func (c *Config) GetVar(pattern string, def ...interface{}) *gvar.Var {
if j := c.getJson(); j != nil {
return j.GetVar(pattern, def...)
@ -49,7 +49,7 @@ func (c *Config) GetVar(pattern string, def ...interface{}) *gvar.Var {
return gvar.New(nil)
}
// Contains checks whether the value by specified <pattern> exist.
// Contains checks whether the value by specified `pattern` exist.
func (c *Config) Contains(pattern string) bool {
if j := c.getJson(); j != nil {
return j.Contains(pattern)
@ -57,7 +57,7 @@ func (c *Config) Contains(pattern string) bool {
return false
}
// GetMap retrieves and returns the value by specified <pattern> as map[string]interface{}.
// GetMap retrieves and returns the value by specified `pattern` as map[string]interface{}.
func (c *Config) GetMap(pattern string, def ...interface{}) map[string]interface{} {
if j := c.getJson(); j != nil {
return j.GetMap(pattern, def...)
@ -65,7 +65,7 @@ func (c *Config) GetMap(pattern string, def ...interface{}) map[string]interface
return nil
}
// GetMapStrStr retrieves and returns the value by specified <pattern> as map[string]string.
// GetMapStrStr retrieves and returns the value by specified `pattern` as map[string]string.
func (c *Config) GetMapStrStr(pattern string, def ...interface{}) map[string]string {
if j := c.getJson(); j != nil {
return j.GetMapStrStr(pattern, def...)
@ -73,7 +73,7 @@ func (c *Config) GetMapStrStr(pattern string, def ...interface{}) map[string]str
return nil
}
// GetArray retrieves the value by specified <pattern>,
// GetArray retrieves the value by specified `pattern`,
// and converts it to a slice of []interface{}.
func (c *Config) GetArray(pattern string, def ...interface{}) []interface{} {
if j := c.getJson(); j != nil {
@ -82,7 +82,7 @@ func (c *Config) GetArray(pattern string, def ...interface{}) []interface{} {
return nil
}
// GetBytes retrieves the value by specified <pattern> and converts it to []byte.
// GetBytes retrieves the value by specified `pattern` and converts it to []byte.
func (c *Config) GetBytes(pattern string, def ...interface{}) []byte {
if j := c.getJson(); j != nil {
return j.GetBytes(pattern, def...)
@ -90,7 +90,7 @@ func (c *Config) GetBytes(pattern string, def ...interface{}) []byte {
return nil
}
// GetString retrieves the value by specified <pattern> and converts it to string.
// GetString retrieves the value by specified `pattern` and converts it to string.
func (c *Config) GetString(pattern string, def ...interface{}) string {
if j := c.getJson(); j != nil {
return j.GetString(pattern, def...)
@ -98,7 +98,7 @@ func (c *Config) GetString(pattern string, def ...interface{}) string {
return ""
}
// GetStrings retrieves the value by specified <pattern> and converts it to []string.
// GetStrings retrieves the value by specified `pattern` and converts it to []string.
func (c *Config) GetStrings(pattern string, def ...interface{}) []string {
if j := c.getJson(); j != nil {
return j.GetStrings(pattern, def...)
@ -115,7 +115,7 @@ func (c *Config) GetInterfaces(pattern string, def ...interface{}) []interface{}
return nil
}
// GetBool retrieves the value by specified <pattern>,
// GetBool retrieves the value by specified `pattern`,
// converts and returns it as bool.
// It returns false when value is: "", 0, false, off, nil;
// or returns true instead.
@ -126,7 +126,7 @@ func (c *Config) GetBool(pattern string, def ...interface{}) bool {
return false
}
// GetFloat32 retrieves the value by specified <pattern> and converts it to float32.
// GetFloat32 retrieves the value by specified `pattern` and converts it to float32.
func (c *Config) GetFloat32(pattern string, def ...interface{}) float32 {
if j := c.getJson(); j != nil {
return j.GetFloat32(pattern, def...)
@ -134,7 +134,7 @@ func (c *Config) GetFloat32(pattern string, def ...interface{}) float32 {
return 0
}
// GetFloat64 retrieves the value by specified <pattern> and converts it to float64.
// GetFloat64 retrieves the value by specified `pattern` and converts it to float64.
func (c *Config) GetFloat64(pattern string, def ...interface{}) float64 {
if j := c.getJson(); j != nil {
return j.GetFloat64(pattern, def...)
@ -142,7 +142,7 @@ func (c *Config) GetFloat64(pattern string, def ...interface{}) float64 {
return 0
}
// GetFloats retrieves the value by specified <pattern> and converts it to []float64.
// GetFloats retrieves the value by specified `pattern` and converts it to []float64.
func (c *Config) GetFloats(pattern string, def ...interface{}) []float64 {
if j := c.getJson(); j != nil {
return j.GetFloats(pattern, def...)
@ -150,7 +150,7 @@ func (c *Config) GetFloats(pattern string, def ...interface{}) []float64 {
return nil
}
// GetInt retrieves the value by specified <pattern> and converts it to int.
// GetInt retrieves the value by specified `pattern` and converts it to int.
func (c *Config) GetInt(pattern string, def ...interface{}) int {
if j := c.getJson(); j != nil {
return j.GetInt(pattern, def...)
@ -158,7 +158,7 @@ func (c *Config) GetInt(pattern string, def ...interface{}) int {
return 0
}
// GetInt8 retrieves the value by specified <pattern> and converts it to int8.
// GetInt8 retrieves the value by specified `pattern` and converts it to int8.
func (c *Config) GetInt8(pattern string, def ...interface{}) int8 {
if j := c.getJson(); j != nil {
return j.GetInt8(pattern, def...)
@ -166,7 +166,7 @@ func (c *Config) GetInt8(pattern string, def ...interface{}) int8 {
return 0
}
// GetInt16 retrieves the value by specified <pattern> and converts it to int16.
// GetInt16 retrieves the value by specified `pattern` and converts it to int16.
func (c *Config) GetInt16(pattern string, def ...interface{}) int16 {
if j := c.getJson(); j != nil {
return j.GetInt16(pattern, def...)
@ -174,7 +174,7 @@ func (c *Config) GetInt16(pattern string, def ...interface{}) int16 {
return 0
}
// GetInt32 retrieves the value by specified <pattern> and converts it to int32.
// GetInt32 retrieves the value by specified `pattern` and converts it to int32.
func (c *Config) GetInt32(pattern string, def ...interface{}) int32 {
if j := c.getJson(); j != nil {
return j.GetInt32(pattern, def...)
@ -182,7 +182,7 @@ func (c *Config) GetInt32(pattern string, def ...interface{}) int32 {
return 0
}
// GetInt64 retrieves the value by specified <pattern> and converts it to int64.
// GetInt64 retrieves the value by specified `pattern` and converts it to int64.
func (c *Config) GetInt64(pattern string, def ...interface{}) int64 {
if j := c.getJson(); j != nil {
return j.GetInt64(pattern, def...)
@ -190,7 +190,7 @@ func (c *Config) GetInt64(pattern string, def ...interface{}) int64 {
return 0
}
// GetInts retrieves the value by specified <pattern> and converts it to []int.
// GetInts retrieves the value by specified `pattern` and converts it to []int.
func (c *Config) GetInts(pattern string, def ...interface{}) []int {
if j := c.getJson(); j != nil {
return j.GetInts(pattern, def...)
@ -198,7 +198,7 @@ func (c *Config) GetInts(pattern string, def ...interface{}) []int {
return nil
}
// GetUint retrieves the value by specified <pattern> and converts it to uint.
// GetUint retrieves the value by specified `pattern` and converts it to uint.
func (c *Config) GetUint(pattern string, def ...interface{}) uint {
if j := c.getJson(); j != nil {
return j.GetUint(pattern, def...)
@ -206,7 +206,7 @@ func (c *Config) GetUint(pattern string, def ...interface{}) uint {
return 0
}
// GetUint8 retrieves the value by specified <pattern> and converts it to uint8.
// GetUint8 retrieves the value by specified `pattern` and converts it to uint8.
func (c *Config) GetUint8(pattern string, def ...interface{}) uint8 {
if j := c.getJson(); j != nil {
return j.GetUint8(pattern, def...)
@ -214,7 +214,7 @@ func (c *Config) GetUint8(pattern string, def ...interface{}) uint8 {
return 0
}
// GetUint16 retrieves the value by specified <pattern> and converts it to uint16.
// GetUint16 retrieves the value by specified `pattern` and converts it to uint16.
func (c *Config) GetUint16(pattern string, def ...interface{}) uint16 {
if j := c.getJson(); j != nil {
return j.GetUint16(pattern, def...)
@ -222,7 +222,7 @@ func (c *Config) GetUint16(pattern string, def ...interface{}) uint16 {
return 0
}
// GetUint32 retrieves the value by specified <pattern> and converts it to uint32.
// GetUint32 retrieves the value by specified `pattern` and converts it to uint32.
func (c *Config) GetUint32(pattern string, def ...interface{}) uint32 {
if j := c.getJson(); j != nil {
return j.GetUint32(pattern, def...)
@ -230,7 +230,7 @@ func (c *Config) GetUint32(pattern string, def ...interface{}) uint32 {
return 0
}
// GetUint64 retrieves the value by specified <pattern> and converts it to uint64.
// GetUint64 retrieves the value by specified `pattern` and converts it to uint64.
func (c *Config) GetUint64(pattern string, def ...interface{}) uint64 {
if j := c.getJson(); j != nil {
return j.GetUint64(pattern, def...)
@ -238,7 +238,7 @@ func (c *Config) GetUint64(pattern string, def ...interface{}) uint64 {
return 0
}
// GetTime retrieves the value by specified <pattern> and converts it to time.Time.
// GetTime retrieves the value by specified `pattern` and converts it to time.Time.
func (c *Config) GetTime(pattern string, format ...string) time.Time {
if j := c.getJson(); j != nil {
return j.GetTime(pattern, format...)
@ -246,7 +246,7 @@ func (c *Config) GetTime(pattern string, format ...string) time.Time {
return time.Time{}
}
// GetDuration retrieves the value by specified <pattern> and converts it to time.Duration.
// GetDuration retrieves the value by specified `pattern` and converts it to time.Duration.
func (c *Config) GetDuration(pattern string, def ...interface{}) time.Duration {
if j := c.getJson(); j != nil {
return j.GetDuration(pattern, def...)
@ -254,7 +254,7 @@ func (c *Config) GetDuration(pattern string, def ...interface{}) time.Duration {
return 0
}
// GetGTime retrieves the value by specified <pattern> and converts it to *gtime.Time.
// GetGTime retrieves the value by specified `pattern` and converts it to *gtime.Time.
func (c *Config) GetGTime(pattern string, format ...string) *gtime.Time {
if j := c.getJson(); j != nil {
return j.GetGTime(pattern, format...)
@ -262,7 +262,7 @@ func (c *Config) GetGTime(pattern string, format ...string) *gtime.Time {
return nil
}
// GetJson gets the value by specified <pattern>,
// GetJson gets the value by specified `pattern`,
// and converts it to a un-concurrent-safe Json object.
func (c *Config) GetJson(pattern string, def ...interface{}) *gjson.Json {
if j := c.getJson(); j != nil {
@ -271,7 +271,7 @@ func (c *Config) GetJson(pattern string, def ...interface{}) *gjson.Json {
return nil
}
// GetJsons gets the value by specified <pattern>,
// GetJsons gets the value by specified `pattern`,
// and converts it to a slice of un-concurrent-safe Json object.
func (c *Config) GetJsons(pattern string, def ...interface{}) []*gjson.Json {
if j := c.getJson(); j != nil {
@ -280,7 +280,7 @@ func (c *Config) GetJsons(pattern string, def ...interface{}) []*gjson.Json {
return nil
}
// GetJsonMap gets the value by specified <pattern>,
// GetJsonMap gets the value by specified `pattern`,
// and converts it to a map of un-concurrent-safe Json object.
func (c *Config) GetJsonMap(pattern string, def ...interface{}) map[string]*gjson.Json {
if j := c.getJson(); j != nil {
@ -289,8 +289,8 @@ func (c *Config) GetJsonMap(pattern string, def ...interface{}) map[string]*gjso
return nil
}
// GetStruct retrieves the value by specified <pattern> and converts it to specified object
// <pointer>. The <pointer> should be the pointer to an object.
// GetStruct retrieves the value by specified `pattern` and converts it to specified object
// `pointer`. The `pointer` should be the pointer to an object.
func (c *Config) GetStruct(pattern string, pointer interface{}, mapping ...map[string]string) error {
if j := c.getJson(); j != nil {
return j.GetStruct(pattern, pointer, mapping...)
@ -324,7 +324,7 @@ func (c *Config) GetStructsDeep(pattern string, pointer interface{}, mapping ...
return errors.New("configuration not found")
}
// GetMapToMap retrieves the value by specified <pattern> and converts it to specified map variable.
// GetMapToMap retrieves the value by specified `pattern` and converts it to specified map variable.
// See gconv.MapToMap.
func (c *Config) GetMapToMap(pattern string, pointer interface{}, mapping ...map[string]string) error {
if j := c.getJson(); j != nil {
@ -333,17 +333,7 @@ func (c *Config) GetMapToMap(pattern string, pointer interface{}, mapping ...map
return errors.New("configuration not found")
}
// GetMapToMapDeep retrieves the value by specified <pattern> and converts it to specified map
// variable recursively.
// See gconv.MapToMapDeep.
func (c *Config) GetMapToMapDeep(pattern string, pointer interface{}, mapping ...map[string]string) error {
if j := c.getJson(); j != nil {
return j.GetMapToMapDeep(pattern, pointer, mapping...)
}
return errors.New("configuration not found")
}
// GetMapToMaps retrieves the value by specified <pattern> and converts it to specified map slice
// GetMapToMaps retrieves the value by specified `pattern` and converts it to specified map slice
// variable.
// See gconv.MapToMaps.
func (c *Config) GetMapToMaps(pattern string, pointer interface{}, mapping ...map[string]string) error {
@ -353,7 +343,7 @@ func (c *Config) GetMapToMaps(pattern string, pointer interface{}, mapping ...ma
return errors.New("configuration not found")
}
// GetMapToMapsDeep retrieves the value by specified <pattern> and converts it to specified map slice
// GetMapToMapsDeep retrieves the value by specified `pattern` and converts it to specified map slice
// variable recursively.
// See gconv.MapToMapsDeep.
func (c *Config) GetMapToMapsDeep(pattern string, pointer interface{}, mapping ...map[string]string) error {
@ -382,7 +372,7 @@ func (c *Config) ToArray() []interface{} {
}
// ToStruct converts current Json object to specified object.
// The <pointer> should be a pointer type of *struct.
// The `pointer` should be a pointer type of *struct.
func (c *Config) ToStruct(pointer interface{}, mapping ...map[string]string) error {
if j := c.getJson(); j != nil {
return j.ToStruct(pointer, mapping...)
@ -391,7 +381,7 @@ func (c *Config) ToStruct(pointer interface{}, mapping ...map[string]string) err
}
// ToStructDeep converts current Json object to specified object recursively.
// The <pointer> should be a pointer type of *struct.
// The `pointer` should be a pointer type of *struct.
func (c *Config) ToStructDeep(pointer interface{}, mapping ...map[string]string) error {
if j := c.getJson(); j != nil {
return j.ToStructDeep(pointer, mapping...)
@ -400,7 +390,7 @@ func (c *Config) ToStructDeep(pointer interface{}, mapping ...map[string]string)
}
// ToStructs converts current Json object to specified object slice.
// The <pointer> should be a pointer type of []struct/*struct.
// The `pointer` should be a pointer type of []struct/*struct.
func (c *Config) ToStructs(pointer interface{}, mapping ...map[string]string) error {
if j := c.getJson(); j != nil {
return j.ToStructs(pointer, mapping...)
@ -409,7 +399,7 @@ func (c *Config) ToStructs(pointer interface{}, mapping ...map[string]string) er
}
// ToStructsDeep converts current Json object to specified object slice recursively.
// The <pointer> should be a pointer type of []struct/*struct.
// The `pointer` should be a pointer type of []struct/*struct.
func (c *Config) ToStructsDeep(pointer interface{}, mapping ...map[string]string) error {
if j := c.getJson(); j != nil {
return j.ToStructsDeep(pointer, mapping...)
@ -418,7 +408,7 @@ func (c *Config) ToStructsDeep(pointer interface{}, mapping ...map[string]string
}
// ToMapToMap converts current Json object to specified map variable.
// The parameter of <pointer> should be type of *map.
// The parameter of `pointer` should be type of *map.
func (c *Config) ToMapToMap(pointer interface{}, mapping ...map[string]string) error {
if j := c.getJson(); j != nil {
return j.ToMapToMap(pointer, mapping...)
@ -426,17 +416,8 @@ func (c *Config) ToMapToMap(pointer interface{}, mapping ...map[string]string) e
return errors.New("configuration not found")
}
// ToMapToMapDeep converts current Json object to specified map variable recursively.
// The parameter of <pointer> should be type of *map.
func (c *Config) ToMapToMapDeep(pointer interface{}, mapping ...map[string]string) error {
if j := c.getJson(); j != nil {
return j.ToMapToMapDeep(pointer, mapping...)
}
return errors.New("configuration not found")
}
// ToMapToMaps converts current Json object to specified map variable slice.
// The parameter of <pointer> should be type of []map/*map.
// The parameter of `pointer` should be type of []map/*map.
func (c *Config) ToMapToMaps(pointer interface{}, mapping ...map[string]string) error {
if j := c.getJson(); j != nil {
return j.ToMapToMaps(pointer, mapping...)
@ -445,7 +426,7 @@ func (c *Config) ToMapToMaps(pointer interface{}, mapping ...map[string]string)
}
// ToMapToMapsDeep converts current Json object to specified map variable slice recursively.
// The parameter of <pointer> should be type of []map/*map.
// The parameter of `pointer` should be type of []map/*map.
func (c *Config) ToMapToMapsDeep(pointer interface{}, mapping ...map[string]string) error {
if j := c.getJson(); j != nil {
return j.ToMapToMapsDeep(pointer, mapping...)

View File

@ -16,14 +16,14 @@ var (
configs = gmap.NewStrStrMap(true)
)
// SetContent sets customized configuration content for specified <file>.
// The <file> is unnecessary param, default is DefaultConfigFile.
// SetContent sets customized configuration content for specified `file`.
// The `file` is unnecessary param, default is DefaultConfigFile.
func SetContent(content string, file ...string) {
name := DefaultConfigFile
if len(file) > 0 {
name = file[0]
}
// Clear file cache for instances which cached <name>.
// Clear file cache for instances which cached `name`.
instances.LockFunc(func(m map[string]interface{}) {
if configs.Contains(name) {
for _, v := range m {
@ -34,8 +34,8 @@ func SetContent(content string, file ...string) {
})
}
// GetContent returns customized configuration content for specified <file>.
// The <file> is unnecessary param, default is DefaultConfigFile.
// GetContent returns customized configuration content for specified `file`.
// The `file` is unnecessary param, default is DefaultConfigFile.
func GetContent(file ...string) string {
name := DefaultConfigFile
if len(file) > 0 {
@ -44,14 +44,14 @@ func GetContent(file ...string) string {
return configs.Get(name)
}
// RemoveContent removes the global configuration with specified <file>.
// If <name> is not passed, it removes configuration of the default group name.
// RemoveContent removes the global configuration with specified `file`.
// If `name` is not passed, it removes configuration of the default group name.
func RemoveContent(file ...string) {
name := DefaultConfigFile
if len(file) > 0 {
name = file[0]
}
// Clear file cache for instances which cached <name>.
// Clear file cache for instances which cached `name`.
instances.LockFunc(func(m map[string]interface{}) {
if configs.Contains(name) {
for _, v := range m {

View File

@ -22,7 +22,7 @@ var (
)
// Instance returns an instance of Config with default settings.
// The parameter <name> is the name for the instance. But very note that, if the file "name.toml"
// The parameter `name` is the name for the instance. But very note that, if the file "name.toml"
// exists in the configuration directory, it then sets it as the default configuration file. The
// toml file type is the default configuration file type.
func Instance(name ...string) *Config {

View File

@ -81,7 +81,8 @@ array = [1,2,3]
"disk": "127.0.0.1:6379,0",
"cache": "127.0.0.1:6379,1",
})
t.AssertEQ(c.FilePath(), gfile.Pwd()+gfile.Separator+path)
filepath, _ := c.GetFilePath()
t.AssertEQ(filepath, gfile.Pwd()+gfile.Separator+path)
})
}
@ -223,8 +224,8 @@ func Test_SetFileName(t *testing.T) {
"disk": "127.0.0.1:6379,0",
"cache": "127.0.0.1:6379,1",
})
t.AssertEQ(c.FilePath(), gfile.Pwd()+gfile.Separator+path)
filepath, _ := c.GetFilePath()
t.AssertEQ(filepath, gfile.Pwd()+gfile.Separator+path)
})
}
@ -281,9 +282,9 @@ func TestCfg_AddPath(t *testing.T) {
func TestCfg_FilePath(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
c := gcfg.New("config.yml")
path := c.FilePath("tmp")
path, _ := c.GetFilePath("tmp")
t.Assert(path, "")
path = c.FilePath("tmp")
path, _ = c.GetFilePath("tmp")
t.Assert(path, "")
})
}

View File

@ -77,7 +77,8 @@ v4 = "1.234"
"disk": "127.0.0.1:6379,0",
"cache": "127.0.0.1:6379,1",
})
t.AssertEQ(c.FilePath(), gfile.Pwd()+gfile.Separator+path)
filepath, _ := c.GetFilePath()
t.AssertEQ(filepath, gfile.Pwd()+gfile.Separator+path)
})
}

View File

@ -67,7 +67,7 @@ func GetArgAll() []string {
}
// GetWithEnv is alias of GetOptWithEnv.
// Deprecated.
// Deprecated, use GetOptWithEnv instead.
func GetWithEnv(key string, def ...interface{}) *gvar.Var {
return GetOptWithEnv(key, def...)
}

View File

@ -343,10 +343,14 @@ func Name(path string) string {
// Dir returns all but the last element of path, typically the path's directory.
// After dropping the final element, Dir calls Clean on the path and trailing
// slashes are removed.
// If the path is empty, Dir returns ".".
// If the path consists entirely of separators, Dir returns a single separator.
// If the `path` is empty, Dir returns ".".
// If the `path` is ".", Dir treats the path as current working directory.
// If the `path` consists entirely of separators, Dir returns a single separator.
// The returned path does not end in a separator unless it is the root directory.
func Dir(path string) string {
if path == "." {
return filepath.Dir(RealPath(path))
}
return filepath.Dir(path)
}

View File

@ -8,6 +8,7 @@ package gfile
import (
"github.com/gogf/gf/text/gstr"
"os"
"runtime"
"strings"
@ -44,11 +45,16 @@ func MainPkgPath() string {
if path != "" {
return path
}
var lastFile string
for i := 1; i < 10000; i++ {
if pc, file, _, ok := runtime.Caller(i); ok {
if goRootForFilter != "" && len(file) >= len(goRootForFilter) && file[0:len(goRootForFilter)] == goRootForFilter {
continue
}
if Ext(file) != ".go" {
continue
}
lastFile = file
// Check if it is called in package initialization function,
// in which it here cannot retrieve main package path,
// it so just returns that can make next check.
@ -58,10 +64,7 @@ func MainPkgPath() string {
continue
}
}
if Ext(file) != ".go" {
continue
}
if gregex.IsMatchString(`package\s+main`, GetContents(file)) {
if gregex.IsMatchString(`package\s+main\s+`, GetContents(file)) {
mainPkgPath.Set(Dir(file))
return Dir(file)
}
@ -69,5 +72,20 @@ func MainPkgPath() string {
break
}
}
// If it still cannot find the path of the package main,
// it recursively searches the directory and its parents directory of the last go file.
// It's usually necessary for uint testing cases of business project.
if lastFile != "" {
for path = Dir(lastFile); len(path) > 1 && Exists(path) && path[len(path)-1] != os.PathSeparator; {
files, _ := ScanDir(path, "*.go")
for _, v := range files {
if gregex.IsMatchString(`package\s+main\s+`, GetContents(v)) {
mainPkgPath.Set(path)
return path
}
}
path = Dir(path)
}
}
return ""
}

View File

@ -166,7 +166,7 @@ func (l *Logger) print(std io.Writer, lead string, values ...interface{}) {
if l.ctx != nil {
// Tracing values.
spanCtx := trace.SpanContextFromContext(l.ctx)
if traceId := spanCtx.TraceID; traceId.IsValid() {
if traceId := spanCtx.TraceID(); traceId.IsValid() {
buffer.WriteString(fmt.Sprintf("{TraceID:%s} ", traceId.String()))
}
// Context values.

View File

@ -15,9 +15,13 @@ import (
// Manager for sessions.
type Manager struct {
ttl time.Duration // TTL for sessions.
storage Storage // Storage interface for session storage.
sessionData *gcache.Cache // Session data cache for session TTL.
ttl time.Duration // TTL for sessions.
storage Storage // Storage interface for session storage.
// sessionData is the memory data cache for session TTL,
// which is available only if the Storage does not stores any session data in synchronizing.
// Please refer to the implements of StorageFile, StorageMemory and StorageRedis.
sessionData *gcache.Cache
}
// New creates and returns a new session manager.

View File

@ -7,7 +7,6 @@
package gsession
import (
"fmt"
"github.com/gogf/gf/container/gmap"
"github.com/gogf/gf/errors/gerror"
"github.com/gogf/gf/internal/intlog"
@ -48,10 +47,10 @@ func NewStorageFile(path ...string) *StorageFile {
if len(path) > 0 && path[0] != "" {
storagePath, _ = gfile.Search(path[0])
if storagePath == "" {
panic(fmt.Sprintf("'%s' does not exist", path[0]))
panic(gerror.Newf("'%s' does not exist", path[0]))
}
if !gfile.IsWritable(storagePath) {
panic(fmt.Sprintf("'%s' is not writable", path[0]))
panic(gerror.Newf("'%s' is not writable", path[0]))
}
}
if storagePath != "" {
@ -65,26 +64,28 @@ func NewStorageFile(path ...string) *StorageFile {
cryptoEnabled: DefaultStorageFileCryptoEnabled,
updatingIdSet: gset.NewStrSet(true),
}
// Batch updates the TTL for session ids timely.
gtimer.AddSingleton(DefaultStorageFileLoopInterval, func() {
//intlog.Print("StorageFile.timer start")
var (
id string
err error
)
for {
if id = s.updatingIdSet.Pop(); id == "" {
break
}
if err = s.doUpdateTTL(id); err != nil {
intlog.Error(err)
}
}
//intlog.Print("StorageFile.timer end")
})
gtimer.AddSingleton(DefaultStorageFileLoopInterval, s.updateSessionTimely)
return s
}
// updateSessionTimely batch updates the TTL for sessions timely.
func (s *StorageFile) updateSessionTimely() {
var (
id string
err error
)
// Batch updating sessions.
for {
if id = s.updatingIdSet.Pop(); id == "" {
break
}
if err = s.updateSessionTTl(id); err != nil {
intlog.Error(err)
}
}
}
// SetCryptoKey sets the crypto key for session storage.
// The crypto key is used when crypto feature is enabled.
func (s *StorageFile) SetCryptoKey(key []byte) {
@ -229,9 +230,9 @@ func (s *StorageFile) UpdateTTL(id string, ttl time.Duration) error {
return nil
}
// doUpdateTTL updates the TTL for session id.
func (s *StorageFile) doUpdateTTL(id string) error {
intlog.Printf("StorageFile.doUpdateTTL: %s", id)
// updateSessionTTL updates the TTL for specified session id.
func (s *StorageFile) updateSessionTTl(id string) error {
intlog.Printf("StorageFile.updateSession: %s", id)
path := s.sessionFilePath(id)
file, err := gfile.OpenWithFlag(path, os.O_WRONLY)
if err != nil {

28
os/gtime/gtime_sql.go Normal file
View File

@ -0,0 +1,28 @@
package gtime
import (
"database/sql/driver"
)
// Scanner is an interface used by Scan in package database/sql for Scanning value
// from database to local golang variable.
func (t *Time) Scan(value interface{}) error {
if t == nil {
return nil
}
newTime := New(value)
*t = *newTime
return nil
}
// Value is the interface providing the Value method for package database/sql/driver
// for retrieving value from golang variable to database.
func (t *Time) Value() (driver.Value, error) {
if t == nil {
return nil, nil
}
if t.IsZero() {
return nil, nil
}
return t.Time, nil
}

View File

@ -220,6 +220,15 @@ func (t *Time) String() string {
return t.Format("Y-m-d H:i:s")
}
// IsZero reports whether t represents the zero time instant,
// January 1, year 1, 00:00:00 UTC.
func (t *Time) IsZero() bool {
if t == nil {
return true
}
return t.Time.IsZero()
}
// Clone returns a new Time object which is a clone of current time object.
func (t *Time) Clone() *Time {
return New(t.Time)

View File

@ -0,0 +1,41 @@
package gtime_test
import (
"github.com/gogf/gf/os/gtime"
"github.com/gogf/gf/test/gtest"
"testing"
)
func TestTime_Scan(t1 *testing.T) {
gtest.C(t1, func(t *gtest.T) {
tt := gtime.Time{}
//test string
s := gtime.Now().String()
t.Assert(tt.Scan(s), nil)
t.Assert(tt.String(), s)
//test nano
n := gtime.TimestampNano()
t.Assert(tt.Scan(n), nil)
t.Assert(tt.TimestampNano(), n)
//test nil
none := (*gtime.Time)(nil)
t.Assert(none.Scan(nil), nil)
t.Assert(none, nil)
})
}
func TestTime_Value(t1 *testing.T) {
gtest.C(t1, func(t *gtest.T) {
tt := gtime.Now()
s, err := tt.Value()
t.Assert(err, nil)
t.Assert(s, tt.Time)
//test nil
none := (*gtime.Time)(nil)
s, err = none.Value()
t.Assert(err, nil)
t.Assert(s, nil)
})
}

View File

@ -341,7 +341,7 @@ func compareMap(value, expect interface{}) error {
return fmt.Errorf(`[ASSERT] EXPECT MAP LENGTH %d == %d`, rvValue.Len(), rvExpect.Len())
}
} else {
return fmt.Errorf(`[ASSERT] EXPECT VALUE TO BE A MAP`)
return fmt.Errorf(`[ASSERT] EXPECT VALUE TO BE A MAP, BUT GIVEN "%s"`, rvValue.Kind())
}
}
return nil

View File

@ -540,7 +540,7 @@ func SplitAndTrim(str, delimiter string, characterMask ...string) []string {
// SplitAndTrimSpace splits string <str> by a string <delimiter> to an array,
// and calls TrimSpace to every element of this array.
// Deprecated.
// Deprecated, use SplitAndTrim instead.
func SplitAndTrimSpace(str, delimiter string) []string {
array := make([]string, 0)
for _, v := range strings.Split(str, delimiter) {

View File

@ -45,8 +45,8 @@ var (
StructTagPriority = []string{"gconv", "param", "params", "c", "p", "json"}
)
// Convert converts the variable <i> to the type <t>, the type <t> is specified by string.
// The optional parameter <params> is used for additional necessary parameter for this conversion.
// Convert converts the variable `i` to the type `t`, the type `t` is specified by string.
// The optional parameter `params` is used for additional necessary parameter for this conversion.
// It supports common types conversion as its conversion based on type name string.
func Convert(any interface{}, t string, params ...interface{}) interface{} {
switch t {
@ -277,7 +277,7 @@ func Convert(any interface{}, t string, params ...interface{}) interface{} {
}
}
// Byte converts <i> to byte.
// Byte converts `i` to byte.
func Byte(any interface{}) byte {
if v, ok := any.(byte); ok {
return v
@ -285,7 +285,7 @@ func Byte(any interface{}) byte {
return Uint8(any)
}
// Bytes converts <i> to []byte.
// Bytes converts `i` to []byte.
func Bytes(any interface{}) []byte {
if any == nil {
return nil
@ -296,11 +296,14 @@ func Bytes(any interface{}) []byte {
case []byte:
return value
default:
if f, ok := value.(apiBytes); ok {
return f.Bytes()
}
return gbinary.Encode(any)
}
}
// Rune converts <i> to rune.
// Rune converts `i` to rune.
func Rune(any interface{}) rune {
if v, ok := any.(rune); ok {
return v
@ -308,7 +311,7 @@ func Rune(any interface{}) rune {
return rune(Int32(any))
}
// Runes converts <i> to []rune.
// Runes converts `i` to []rune.
func Runes(any interface{}) []rune {
if v, ok := any.([]rune); ok {
return v
@ -316,7 +319,7 @@ func Runes(any interface{}) []rune {
return []rune(String(any))
}
// String converts <i> to string.
// String converts `i` to string.
// It's most common used converting function.
func String(any interface{}) string {
if any == nil {
@ -419,8 +422,8 @@ func String(any interface{}) string {
}
}
// Bool converts <i> to bool.
// It returns false if <i> is: false, "", 0, "false", "off", "no", empty slice/map.
// Bool converts `i` to bool.
// It returns false if `i` is: false, "", 0, "false", "off", "no", empty slice/map.
func Bool(any interface{}) bool {
if any == nil {
return false
@ -439,6 +442,9 @@ func Bool(any interface{}) bool {
}
return true
default:
if f, ok := value.(apiBool); ok {
return f.Bool()
}
rv := reflect.ValueOf(any)
switch rv.Kind() {
case reflect.Ptr:
@ -461,7 +467,7 @@ func Bool(any interface{}) bool {
}
}
// Int converts <i> to int.
// Int converts `i` to int.
func Int(any interface{}) int {
if any == nil {
return 0
@ -472,7 +478,7 @@ func Int(any interface{}) int {
return int(Int64(any))
}
// Int8 converts <i> to int8.
// Int8 converts `i` to int8.
func Int8(any interface{}) int8 {
if any == nil {
return 0
@ -483,7 +489,7 @@ func Int8(any interface{}) int8 {
return int8(Int64(any))
}
// Int16 converts <i> to int16.
// Int16 converts `i` to int16.
func Int16(any interface{}) int16 {
if any == nil {
return 0
@ -494,7 +500,7 @@ func Int16(any interface{}) int16 {
return int16(Int64(any))
}
// Int32 converts <i> to int32.
// Int32 converts `i` to int32.
func Int32(any interface{}) int32 {
if any == nil {
return 0
@ -505,7 +511,7 @@ func Int32(any interface{}) int32 {
return int32(Int64(any))
}
// Int64 converts <i> to int64.
// Int64 converts `i` to int64.
func Int64(any interface{}) int64 {
if any == nil {
return 0
@ -543,6 +549,9 @@ func Int64(any interface{}) int64 {
case []byte:
return gbinary.DecodeToInt64(value)
default:
if f, ok := value.(apiInt64); ok {
return f.Int64()
}
s := String(value)
isMinus := false
if len(s) > 0 {
@ -583,7 +592,7 @@ func Int64(any interface{}) int64 {
}
}
// Uint converts <i> to uint.
// Uint converts `i` to uint.
func Uint(any interface{}) uint {
if any == nil {
return 0
@ -594,7 +603,7 @@ func Uint(any interface{}) uint {
return uint(Uint64(any))
}
// Uint8 converts <i> to uint8.
// Uint8 converts `i` to uint8.
func Uint8(any interface{}) uint8 {
if any == nil {
return 0
@ -605,7 +614,7 @@ func Uint8(any interface{}) uint8 {
return uint8(Uint64(any))
}
// Uint16 converts <i> to uint16.
// Uint16 converts `i` to uint16.
func Uint16(any interface{}) uint16 {
if any == nil {
return 0
@ -616,7 +625,7 @@ func Uint16(any interface{}) uint16 {
return uint16(Uint64(any))
}
// Uint32 converts <i> to uint32.
// Uint32 converts `i` to uint32.
func Uint32(any interface{}) uint32 {
if any == nil {
return 0
@ -627,7 +636,7 @@ func Uint32(any interface{}) uint32 {
return uint32(Uint64(any))
}
// Uint64 converts <i> to uint64.
// Uint64 converts `i` to uint64.
func Uint64(any interface{}) uint64 {
if any == nil {
return 0
@ -665,6 +674,9 @@ func Uint64(any interface{}) uint64 {
case []byte:
return gbinary.DecodeToUint64(value)
default:
if f, ok := value.(apiUint64); ok {
return f.Uint64()
}
s := String(value)
// Hexadecimal
if len(s) > 2 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X') {
@ -687,7 +699,7 @@ func Uint64(any interface{}) uint64 {
}
}
// Float32 converts <i> to float32.
// Float32 converts `i` to float32.
func Float32(any interface{}) float32 {
if any == nil {
return 0
@ -700,12 +712,15 @@ func Float32(any interface{}) float32 {
case []byte:
return gbinary.DecodeToFloat32(value)
default:
if f, ok := value.(apiFloat32); ok {
return f.Float32()
}
v, _ := strconv.ParseFloat(String(any), 64)
return float32(v)
}
}
// Float64 converts <i> to float64.
// Float64 converts `i` to float64.
func Float64(any interface{}) float64 {
if any == nil {
return 0
@ -718,6 +733,9 @@ func Float64(any interface{}) float64 {
case []byte:
return gbinary.DecodeToFloat64(value)
default:
if f, ok := value.(apiFloat64); ok {
return f.Float64()
}
v, _ := strconv.ParseFloat(String(any), 64)
return v
}

View File

@ -11,11 +11,41 @@ type apiString interface {
String() string
}
// apiBool is used for type assert api for Bool().
type apiBool interface {
Bool() bool
}
// apiInt64 is used for type assert api for Int64().
type apiInt64 interface {
Int64() int64
}
// apiUint64 is used for type assert api for Uint64().
type apiUint64 interface {
Uint64() uint64
}
// apiFloat32 is used for type assert api for Float32().
type apiFloat32 interface {
Float32() float32
}
// apiFloat64 is used for type assert api for Float64().
type apiFloat64 interface {
Float64() float64
}
// apiError is used for type assert api for Error().
type apiError interface {
Error() string
}
// apiBytes is used for type assert api for Bytes().
type apiBytes interface {
Bytes() []byte
}
// apiInterfaces is used for type assert api for Interfaces().
type apiInterfaces interface {
Interfaces() []interface{}

View File

@ -7,7 +7,6 @@
package gconv
import (
"github.com/gogf/gf/errors/gerror"
"github.com/gogf/gf/internal/json"
"reflect"
"strings"
@ -16,17 +15,17 @@ import (
"github.com/gogf/gf/internal/utils"
)
// Map converts any variable <value> to map[string]interface{}. If the parameter <value> is not a
// Map converts any variable `value` to map[string]interface{}. If the parameter `value` is not a
// map/struct/*struct type, then the conversion will fail and returns nil.
//
// If <value> is a struct/*struct object, the second parameter <tags> specifies the most priority
// If `value` is a struct/*struct object, the second parameter `tags` specifies the most priority
// tags that will be detected, otherwise it detects the tags in order of:
// gconv, json, field name.
func Map(value interface{}, tags ...string) map[string]interface{} {
return doMapConvert(value, false, tags...)
}
// MapDeep does Map function recursively, which means if the attribute of <value>
// MapDeep does Map function recursively, which means if the attribute of `value`
// is also a struct/*struct, calls Map function on this attribute converting it to
// a map[string]interface{} type variable.
// Also see Map.
@ -35,7 +34,7 @@ func MapDeep(value interface{}, tags ...string) map[string]interface{} {
}
// doMapConvert implements the map converting.
// It automatically checks and converts json string to map if <value> is string/[]byte.
// It automatically checks and converts json string to map if `value` is string/[]byte.
//
// TODO completely implement the recursive converting for all types, especially the map.
func doMapConvert(value interface{}, recursive bool, tags ...string) map[string]interface{} {
@ -154,7 +153,7 @@ func doMapConvert(value interface{}, recursive bool, tags ...string) map[string]
reflectKind = reflectValue.Kind()
}
switch reflectKind {
// If <value> is type of array, it converts the value of even number index as its key and
// If `value` is type of array, it converts the value of even number index as its key and
// the value of odd number index as its corresponding value, for example:
// []string{"k1","v1","k2","v2"} => map[string]interface{}{"k1":"v1", "k2":"v2"}
// []string{"k1","v1","k2"} => map[string]interface{}{"k1":"v1", "k2":nil}
@ -354,7 +353,7 @@ func doMapConvertForMapOrStructValue(isRoot bool, value interface{}, recursive b
return value
}
// MapStrStr converts <value> to map[string]string.
// MapStrStr converts `value` to map[string]string.
// Note that there might be data copy for this map type converting.
func MapStrStr(value interface{}, tags ...string) map[string]string {
if r, ok := value.(map[string]string); ok {
@ -371,7 +370,7 @@ func MapStrStr(value interface{}, tags ...string) map[string]string {
return nil
}
// MapStrStrDeep converts <value> to map[string]string recursively.
// MapStrStrDeep converts `value` to map[string]string recursively.
// Note that there might be data copy for this map type converting.
func MapStrStrDeep(value interface{}, tags ...string) map[string]string {
if r, ok := value.(map[string]string); ok {
@ -387,196 +386,3 @@ func MapStrStrDeep(value interface{}, tags ...string) map[string]string {
}
return nil
}
// MapToMap converts any map type variable <params> to another map type variable <pointer>
// using reflect.
// See doMapToMap.
func MapToMap(params interface{}, pointer interface{}, mapping ...map[string]string) error {
return doMapToMap(params, pointer, mapping...)
}
// MapToMapDeep converts any map type variable <params> to another map type variable <pointer>
// using reflect recursively.
// Deprecated, use MapToMap instead.
func MapToMapDeep(params interface{}, pointer interface{}, mapping ...map[string]string) error {
return doMapToMap(params, pointer, mapping...)
}
// doMapToMap converts any map type variable <params> to another map type variable <pointer>.
//
// The parameter <params> can be any type of map, like:
// map[string]string, map[string]struct, , map[string]*struct, etc.
//
// The parameter <pointer> should be type of *map, like:
// map[int]string, map[string]struct, , map[string]*struct, etc.
//
// The optional parameter <mapping> is used for struct attribute to map key mapping, which makes
// sense only if the items of original map <params> is type struct.
func doMapToMap(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) {
var (
paramsRv = reflect.ValueOf(params)
paramsKind = paramsRv.Kind()
)
if paramsKind == reflect.Ptr {
paramsRv = paramsRv.Elem()
paramsKind = paramsRv.Kind()
}
if paramsKind != reflect.Map {
return gerror.New("params should be type of map")
}
// Empty params map, no need continue.
if paramsRv.Len() == 0 {
return nil
}
var pointerRv reflect.Value
if v, ok := pointer.(reflect.Value); ok {
pointerRv = v
} else {
pointerRv = reflect.ValueOf(pointer)
}
pointerKind := pointerRv.Kind()
for pointerKind == reflect.Ptr {
pointerRv = pointerRv.Elem()
pointerKind = pointerRv.Kind()
}
if pointerKind != reflect.Map {
return gerror.New("pointer should be type of *map")
}
defer func() {
// Catch the panic, especially the reflect operation panics.
if exception := recover(); exception != nil {
if e, ok := exception.(errorStack); ok {
err = e
} else {
err = gerror.NewSkipf(1, "%v", exception)
}
}
}()
var (
paramsKeys = paramsRv.MapKeys()
pointerKeyType = pointerRv.Type().Key()
pointerValueType = pointerRv.Type().Elem()
pointerValueKind = pointerValueType.Kind()
dataMap = reflect.MakeMapWithSize(pointerRv.Type(), len(paramsKeys))
)
// Retrieve the true element type of target map.
if pointerValueKind == reflect.Ptr {
pointerValueKind = pointerValueType.Elem().Kind()
}
for _, key := range paramsKeys {
e := reflect.New(pointerValueType).Elem()
switch pointerValueKind {
case reflect.Map, reflect.Struct:
if err = Struct(paramsRv.MapIndex(key).Interface(), e, mapping...); err != nil {
return err
}
default:
e.Set(
reflect.ValueOf(
Convert(
paramsRv.MapIndex(key).Interface(),
pointerValueType.String(),
),
),
)
}
dataMap.SetMapIndex(
reflect.ValueOf(
Convert(
key.Interface(),
pointerKeyType.Name(),
),
),
e,
)
}
pointerRv.Set(dataMap)
return nil
}
// MapToMaps converts any map type variable <params> to another map type variable <pointer>.
// See doMapToMaps.
func MapToMaps(params interface{}, pointer interface{}, mapping ...map[string]string) error {
return doMapToMaps(params, pointer, mapping...)
}
// MapToMapsDeep converts any map type variable <params> to another map type variable
// <pointer> recursively.
// Deprecated, use MapToMaps instead.
func MapToMapsDeep(params interface{}, pointer interface{}, mapping ...map[string]string) error {
return doMapToMaps(params, pointer, mapping...)
}
// doMapToMaps converts any map type variable <params> to another map type variable <pointer>.
//
// The parameter <params> can be any type of map, of which the item type is slice map, like:
// map[int][]map, map[string][]map.
//
// The parameter <pointer> should be type of *map, of which the item type is slice map, like:
// map[string][]struct, map[string][]*struct.
//
// The optional parameter <mapping> is used for struct attribute to map key mapping, which makes
// sense only if the items of original map is type struct.
//
// TODO it's supposed supporting target type <pointer> like: map[int][]map, map[string][]map.
func doMapToMaps(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) {
var (
paramsRv = reflect.ValueOf(params)
paramsKind = paramsRv.Kind()
)
if paramsKind == reflect.Ptr {
paramsRv = paramsRv.Elem()
paramsKind = paramsRv.Kind()
}
if paramsKind != reflect.Map {
return gerror.New("params should be type of map")
}
// Empty params map, no need continue.
if paramsRv.Len() == 0 {
return nil
}
var (
pointerRv = reflect.ValueOf(pointer)
pointerKind = pointerRv.Kind()
)
for pointerKind == reflect.Ptr {
pointerRv = pointerRv.Elem()
pointerKind = pointerRv.Kind()
}
if pointerKind != reflect.Map {
return gerror.New("pointer should be type of *map/**map")
}
defer func() {
// Catch the panic, especially the reflect operation panics.
if exception := recover(); exception != nil {
if e, ok := exception.(errorStack); ok {
err = e
} else {
err = gerror.NewSkipf(1, "%v", exception)
}
}
}()
var (
paramsKeys = paramsRv.MapKeys()
pointerKeyType = pointerRv.Type().Key()
pointerValueType = pointerRv.Type().Elem()
dataMap = reflect.MakeMapWithSize(pointerRv.Type(), len(paramsKeys))
)
for _, key := range paramsKeys {
e := reflect.New(pointerValueType).Elem()
if err = Structs(paramsRv.MapIndex(key).Interface(), e.Addr(), mapping...); err != nil {
return err
}
dataMap.SetMapIndex(
reflect.ValueOf(
Convert(
key.Interface(),
pointerKeyType.Name(),
),
),
e,
)
}
pointerRv.Set(dataMap)
return nil
}

119
util/gconv/gconv_maps.go Normal file
View File

@ -0,0 +1,119 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gconv
import "github.com/gogf/gf/internal/json"
// SliceMap is alias of Maps.
func SliceMap(any interface{}) []map[string]interface{} {
return Maps(any)
}
// SliceMapDeep is alias of MapsDeep.
func SliceMapDeep(any interface{}) []map[string]interface{} {
return MapsDeep(any)
}
// SliceStruct is alias of Structs.
func SliceStruct(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) {
return Structs(params, pointer, mapping...)
}
// Maps converts `i` to []map[string]interface{}.
// Note that it automatically checks and converts json string to []map if `value` is string/[]byte.
func Maps(value interface{}, tags ...string) []map[string]interface{} {
if value == nil {
return nil
}
switch r := value.(type) {
case string:
list := make([]map[string]interface{}, 0)
if len(r) > 0 && r[0] == '[' && r[len(r)-1] == ']' {
if err := json.Unmarshal([]byte(r), &list); err != nil {
return nil
}
return list
} else {
return nil
}
case []byte:
list := make([]map[string]interface{}, 0)
if len(r) > 0 && r[0] == '[' && r[len(r)-1] == ']' {
if err := json.Unmarshal(r, &list); err != nil {
return nil
}
return list
} else {
return nil
}
case []map[string]interface{}:
return r
default:
array := Interfaces(value)
if len(array) == 0 {
return nil
}
list := make([]map[string]interface{}, len(array))
for k, v := range array {
list[k] = Map(v, tags...)
}
return list
}
}
// MapsDeep converts `i` to []map[string]interface{} recursively.
//
// TODO completely implement the recursive converting for all types.
func MapsDeep(value interface{}, tags ...string) []map[string]interface{} {
if value == nil {
return nil
}
switch r := value.(type) {
case string:
list := make([]map[string]interface{}, 0)
if len(r) > 0 && r[0] == '[' && r[len(r)-1] == ']' {
if err := json.Unmarshal([]byte(r), &list); err != nil {
return nil
}
return list
} else {
return nil
}
case []byte:
list := make([]map[string]interface{}, 0)
if len(r) > 0 && r[0] == '[' && r[len(r)-1] == ']' {
if err := json.Unmarshal(r, &list); err != nil {
return nil
}
return list
} else {
return nil
}
case []map[string]interface{}:
list := make([]map[string]interface{}, len(r))
for k, v := range r {
list[k] = MapDeep(v, tags...)
}
return list
default:
array := Interfaces(value)
if len(array) == 0 {
return nil
}
list := make([]map[string]interface{}, len(array))
for k, v := range array {
list[k] = MapDeep(v, tags...)
}
return list
}
}

View File

@ -0,0 +1,141 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gconv
import (
"github.com/gogf/gf/errors/gerror"
"github.com/gogf/gf/internal/json"
"reflect"
)
// MapToMap converts any map type variable `params` to another map type variable `pointer`
// using reflect.
// See doMapToMap.
func MapToMap(params interface{}, pointer interface{}, mapping ...map[string]string) error {
return doMapToMap(params, pointer, mapping...)
}
// doMapToMap converts any map type variable `params` to another map type variable `pointer`.
//
// The parameter `params` can be any type of map, like:
// map[string]string, map[string]struct, , map[string]*struct, etc.
//
// The parameter `pointer` should be type of *map, like:
// map[int]string, map[string]struct, , map[string]*struct, etc.
//
// The optional parameter `mapping` is used for struct attribute to map key mapping, which makes
// sense only if the items of original map `params` is type struct.
func doMapToMap(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) {
// If given `params` is JSON, it then uses json.Unmarshal doing the converting.
switch r := params.(type) {
case []byte:
if json.Valid(r) {
if rv, ok := pointer.(reflect.Value); ok {
if rv.Kind() == reflect.Ptr {
return json.Unmarshal(r, rv.Interface())
}
} else {
return json.Unmarshal(r, pointer)
}
}
case string:
if paramsBytes := []byte(r); json.Valid(paramsBytes) {
if rv, ok := pointer.(reflect.Value); ok {
if rv.Kind() == reflect.Ptr {
return json.Unmarshal(paramsBytes, rv.Interface())
}
} else {
return json.Unmarshal(paramsBytes, pointer)
}
}
}
var (
paramsRv reflect.Value
paramsKind reflect.Kind
)
if v, ok := params.(reflect.Value); ok {
paramsRv = v
} else {
paramsRv = reflect.ValueOf(params)
}
paramsKind = paramsRv.Kind()
if paramsKind == reflect.Ptr {
paramsRv = paramsRv.Elem()
paramsKind = paramsRv.Kind()
}
if paramsKind != reflect.Map {
return doMapToMap(Map(params), pointer, mapping...)
}
// Empty params map, no need continue.
if paramsRv.Len() == 0 {
return nil
}
var pointerRv reflect.Value
if v, ok := pointer.(reflect.Value); ok {
pointerRv = v
} else {
pointerRv = reflect.ValueOf(pointer)
}
pointerKind := pointerRv.Kind()
for pointerKind == reflect.Ptr {
pointerRv = pointerRv.Elem()
pointerKind = pointerRv.Kind()
}
if pointerKind != reflect.Map {
return gerror.Newf("pointer should be type of *map, but got:%s", pointerKind)
}
defer func() {
// Catch the panic, especially the reflect operation panics.
if exception := recover(); exception != nil {
if e, ok := exception.(errorStack); ok {
err = e
} else {
err = gerror.NewSkipf(1, "%v", exception)
}
}
}()
var (
paramsKeys = paramsRv.MapKeys()
pointerKeyType = pointerRv.Type().Key()
pointerValueType = pointerRv.Type().Elem()
pointerValueKind = pointerValueType.Kind()
dataMap = reflect.MakeMapWithSize(pointerRv.Type(), len(paramsKeys))
)
// Retrieve the true element type of target map.
if pointerValueKind == reflect.Ptr {
pointerValueKind = pointerValueType.Elem().Kind()
}
for _, key := range paramsKeys {
e := reflect.New(pointerValueType).Elem()
switch pointerValueKind {
case reflect.Map, reflect.Struct:
if err = Struct(paramsRv.MapIndex(key).Interface(), e, mapping...); err != nil {
return err
}
default:
e.Set(
reflect.ValueOf(
Convert(
paramsRv.MapIndex(key).Interface(),
pointerValueType.String(),
),
),
)
}
dataMap.SetMapIndex(
reflect.ValueOf(
Convert(
key.Interface(),
pointerKeyType.Name(),
),
),
e,
)
}
pointerRv.Set(dataMap)
return nil
}

View File

@ -0,0 +1,146 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gconv
import (
"github.com/gogf/gf/errors/gerror"
"github.com/gogf/gf/internal/json"
"reflect"
)
// MapToMaps converts any slice type variable `params` to another map slice type variable `pointer`.
// See doMapToMaps.
func MapToMaps(params interface{}, pointer interface{}, mapping ...map[string]string) error {
return doMapToMaps(params, pointer, mapping...)
}
// MapToMapsDeep converts any slice type variable `params` to another map slice type variable
// `pointer` recursively.
// Deprecated, use MapToMaps instead.
func MapToMapsDeep(params interface{}, pointer interface{}, mapping ...map[string]string) error {
return doMapToMaps(params, pointer, mapping...)
}
// doMapToMaps converts any map type variable `params` to another map slice variable `pointer`.
//
// The parameter `params` can be type of []map, []*map, []struct, []*struct.
//
// The parameter `pointer` should be type of []map, []*map.
//
// The optional parameter `mapping` is used for struct attribute to map key mapping, which makes
// sense only if the item of `params` is type struct.
func doMapToMaps(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) {
// If given `params` is JSON, it then uses json.Unmarshal doing the converting.
switch r := params.(type) {
case []byte:
if json.Valid(r) {
if rv, ok := pointer.(reflect.Value); ok {
if rv.Kind() == reflect.Ptr {
return json.Unmarshal(r, rv.Interface())
}
} else {
return json.Unmarshal(r, pointer)
}
}
case string:
if paramsBytes := []byte(r); json.Valid(paramsBytes) {
if rv, ok := pointer.(reflect.Value); ok {
if rv.Kind() == reflect.Ptr {
return json.Unmarshal(paramsBytes, rv.Interface())
}
} else {
return json.Unmarshal(paramsBytes, pointer)
}
}
}
// Params and its element type check.
var (
paramsRv reflect.Value
paramsKind reflect.Kind
)
if v, ok := params.(reflect.Value); ok {
paramsRv = v
} else {
paramsRv = reflect.ValueOf(params)
}
paramsKind = paramsRv.Kind()
if paramsKind == reflect.Ptr {
paramsRv = paramsRv.Elem()
paramsKind = paramsRv.Kind()
}
if paramsKind != reflect.Array && paramsKind != reflect.Slice {
return gerror.New("params should be type of slice, eg: []map/[]*map/[]struct/[]*struct")
}
var (
paramsElem = paramsRv.Type().Elem()
paramsElemKind = paramsElem.Kind()
)
if paramsElemKind == reflect.Ptr {
paramsElem = paramsElem.Elem()
paramsElemKind = paramsElem.Kind()
}
if paramsElemKind != reflect.Map && paramsElemKind != reflect.Struct && paramsElemKind != reflect.Interface {
return gerror.Newf("params element should be type of map/*map/struct/*struct, but got: %s", paramsElemKind)
}
// Empty slice, no need continue.
if paramsRv.Len() == 0 {
return nil
}
// Pointer and its element type check.
var (
pointerRv = reflect.ValueOf(pointer)
pointerKind = pointerRv.Kind()
)
for pointerKind == reflect.Ptr {
pointerRv = pointerRv.Elem()
pointerKind = pointerRv.Kind()
}
if pointerKind != reflect.Array && pointerKind != reflect.Slice {
return gerror.New("pointer should be type of *[]map/*[]*map")
}
var (
pointerElemType = pointerRv.Type().Elem()
pointerElemKind = pointerElemType.Kind()
)
if pointerElemKind == reflect.Ptr {
pointerElemKind = pointerElemType.Elem().Kind()
}
if pointerElemKind != reflect.Map {
return gerror.New("pointer element should be type of map/*map")
}
defer func() {
// Catch the panic, especially the reflect operation panics.
if exception := recover(); exception != nil {
if e, ok := exception.(errorStack); ok {
err = e
} else {
err = gerror.NewSkipf(1, "%v", exception)
}
}
}()
var (
pointerSlice = reflect.MakeSlice(pointerRv.Type(), paramsRv.Len(), paramsRv.Len())
)
for i := 0; i < paramsRv.Len(); i++ {
var item reflect.Value
if pointerElemType.Kind() == reflect.Ptr {
item = reflect.New(pointerElemType.Elem())
if err = MapToMap(paramsRv.Index(i).Interface(), item, mapping...); err != nil {
return err
}
pointerSlice.Index(i).Set(item)
} else {
item = reflect.New(pointerElemType)
if err = MapToMap(paramsRv.Index(i).Interface(), item, mapping...); err != nil {
return err
}
pointerSlice.Index(i).Set(item.Elem())
}
}
pointerRv.Set(pointerSlice)
return
}

View File

@ -11,18 +11,39 @@ import (
"reflect"
)
// Scan automatically calls Struct or Structs function according to the type of parameter
// <pointer> to implement the converting.
// It calls function Struct if <pointer> is type of *struct/**struct to do the converting.
// It calls function Structs if <pointer> is type of *[]struct/*[]*struct to do the converting.
// Scan automatically calls MapToMap, MapToMaps, Struct or Structs function according to
// the type of parameter `pointer` to implement the converting.
// It calls function MapToMap if `pointer` is type of *map to do the converting.
// It calls function MapToMaps if `pointer` is type of *[]map/*[]*map to do the converting.
// It calls function Struct if `pointer` is type of *struct/**struct to do the converting.
// It calls function Structs if `pointer` is type of *[]struct/*[]*struct to do the converting.
func Scan(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) {
t := reflect.TypeOf(pointer)
k := t.Kind()
if k != reflect.Ptr {
return gerror.Newf("params should be type of pointer, but got: %v", k)
var (
pointerType = reflect.TypeOf(pointer)
pointerKind = pointerType.Kind()
)
if pointerKind != reflect.Ptr {
return gerror.Newf("params should be type of pointer, but got: %v", pointerKind)
}
switch t.Elem().Kind() {
var (
pointerElem = pointerType.Elem()
pointerElemKind = pointerElem.Kind()
)
switch pointerElemKind {
case reflect.Map:
return MapToMap(params, pointer, mapping...)
case reflect.Array, reflect.Slice:
var (
sliceElem = pointerElem.Elem()
sliceElemKind = sliceElem.Kind()
)
for sliceElemKind == reflect.Ptr {
sliceElem = sliceElem.Elem()
sliceElemKind = sliceElem.Kind()
}
if sliceElemKind == reflect.Map {
return MapToMaps(params, pointer, mapping...)
}
return Structs(params, pointer, mapping...)
default:
return Struct(params, pointer, mapping...)
@ -30,9 +51,9 @@ func Scan(params interface{}, pointer interface{}, mapping ...map[string]string)
}
// ScanDeep automatically calls StructDeep or StructsDeep function according to the type of
// parameter <pointer> to implement the converting..
// It calls function StructDeep if <pointer> is type of *struct/**struct to do the converting.
// It calls function StructsDeep if <pointer> is type of *[]struct/*[]*struct to do the converting.
// parameter `pointer` to implement the converting..
// It calls function StructDeep if `pointer` is type of *struct/**struct to do the converting.
// It calls function StructsDeep if `pointer` is type of *[]struct/*[]*struct to do the converting.
// Deprecated, use Scan instead.
func ScanDeep(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) {
t := reflect.TypeOf(pointer)

View File

@ -5,123 +5,3 @@
// You can obtain one at https://github.com/gogf/gf.
package gconv
import (
"github.com/gogf/gf/internal/json"
)
// SliceMap is alias of Maps.
func SliceMap(any interface{}) []map[string]interface{} {
return Maps(any)
}
// SliceMapDeep is alias of MapsDeep.
func SliceMapDeep(any interface{}) []map[string]interface{} {
return MapsDeep(any)
}
// SliceStruct is alias of Structs.
func SliceStruct(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) {
return Structs(params, pointer, mapping...)
}
// SliceStructDeep is alias of StructsDeep.
// Deprecated, use SliceStruct instead.
func SliceStructDeep(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) {
return StructsDeep(params, pointer, mapping...)
}
// Maps converts <i> to []map[string]interface{}.
// Note that it automatically checks and converts json string to []map if <value> is string/[]byte.
func Maps(value interface{}, tags ...string) []map[string]interface{} {
if value == nil {
return nil
}
switch r := value.(type) {
case string:
list := make([]map[string]interface{}, 0)
if len(r) > 0 && r[0] == '[' && r[len(r)-1] == ']' {
if err := json.Unmarshal([]byte(r), &list); err != nil {
return nil
}
return list
} else {
return nil
}
case []byte:
list := make([]map[string]interface{}, 0)
if len(r) > 0 && r[0] == '[' && r[len(r)-1] == ']' {
if err := json.Unmarshal(r, &list); err != nil {
return nil
}
return list
} else {
return nil
}
case []map[string]interface{}:
return r
default:
array := Interfaces(value)
if len(array) == 0 {
return nil
}
list := make([]map[string]interface{}, len(array))
for k, v := range array {
list[k] = Map(v, tags...)
}
return list
}
}
// MapsDeep converts <i> to []map[string]interface{} recursively.
//
// TODO completely implement the recursive converting for all types.
func MapsDeep(value interface{}, tags ...string) []map[string]interface{} {
if value == nil {
return nil
}
switch r := value.(type) {
case string:
list := make([]map[string]interface{}, 0)
if len(r) > 0 && r[0] == '[' && r[len(r)-1] == ']' {
if err := json.Unmarshal([]byte(r), &list); err != nil {
return nil
}
return list
} else {
return nil
}
case []byte:
list := make([]map[string]interface{}, 0)
if len(r) > 0 && r[0] == '[' && r[len(r)-1] == ']' {
if err := json.Unmarshal(r, &list); err != nil {
return nil
}
return list
} else {
return nil
}
case []map[string]interface{}:
list := make([]map[string]interface{}, len(r))
for k, v := range r {
list[k] = MapDeep(v, tags...)
}
return list
default:
array := Interfaces(value)
if len(array) == 0 {
return nil
}
list := make([]map[string]interface{}, len(array))
for k, v := range array {
list[k] = MapDeep(v, tags...)
}
return list
}
}

View File

@ -15,7 +15,7 @@ func SliceAny(any interface{}) []interface{} {
return Interfaces(any)
}
// Interfaces converts <i> to []interface{}.
// Interfaces converts `i` to []interface{}.
func Interfaces(any interface{}) []interface{} {
if any == nil {
return nil

View File

@ -23,12 +23,12 @@ func SliceFloat64(any interface{}) []float64 {
return Floats(any)
}
// Floats converts <i> to []float64.
// Floats converts `i` to []float64.
func Floats(any interface{}) []float64 {
return Float64s(any)
}
// Float32s converts <i> to []float32.
// Float32s converts `i` to []float32.
func Float32s(any interface{}) []float32 {
if any == nil {
return nil
@ -148,7 +148,7 @@ func Float32s(any interface{}) []float32 {
return array
}
// Float64s converts <i> to []float64.
// Float64s converts `i` to []float64.
func Float64s(any interface{}) []float64 {
if any == nil {
return nil

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