\ No newline at end of file
diff --git a/.example/os/gview/layout/layout2/main.go b/.example/os/gview/layout/layout2/main.go
index f0abb36ec..f90ae6fc1 100644
--- a/.example/os/gview/layout/layout2/main.go
+++ b/.example/os/gview/layout/layout2/main.go
@@ -9,11 +9,13 @@ func main() {
s := g.Server()
s.BindHandler("/main1", func(r *ghttp.Request) {
r.Response.WriteTpl("layout.html", g.Map{
+ "name": "smith",
"mainTpl": "main/main1.html",
})
})
s.BindHandler("/main2", func(r *ghttp.Request) {
r.Response.WriteTpl("layout.html", g.Map{
+ "name": "john",
"mainTpl": "main/main2.html",
})
})
diff --git a/.example/util/gutil/try_catch.go b/.example/util/gutil/try_catch.go
index 6ae1afea4..0aa3ccf4b 100644
--- a/.example/util/gutil/try_catch.go
+++ b/.example/util/gutil/try_catch.go
@@ -11,7 +11,7 @@ func main() {
fmt.Println(1)
gutil.Throw("error")
fmt.Println(2)
- }, func(err interface{}) {
+ }, func(err error) {
fmt.Println(err)
})
}
diff --git a/.travis.yml b/.travis.yml
index 5ef9c9afa..ee2f52ac1 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,6 +5,7 @@ go:
- "1.12.x"
- "1.13.x"
- "1.14.x"
+ - "1.15.x"
branches:
only:
diff --git a/DONATOR.MD b/DONATOR.MD
deleted file mode 100644
index 0a96fcb47..000000000
--- a/DONATOR.MD
+++ /dev/null
@@ -1,99 +0,0 @@
-# Donators
-
-We currently accept donation by [Alipay](https://goframe.org/images/donate.png) / [Gitee](https://gitee.com/johng/gf),
-please note your github/gitee account in your payment bill.
-
-| Name | Channel | Amount | Comment
-|---|---|--- | ---
-|[hailaz](https://gitee.com/hailaz)|gitee|¥20.00 |
-|[ireadx](https://github.com/ireadx)|alipay|¥301.00 |
-|[mg91](https://gitee.com/mg91)|gitee|¥10.00 |
-|[pibigstar](https://github.com/pibigstar)|alipay|¥10.00 |
-|[tiangenglan](https://gitee.com/tiangenglan)|gitee|¥30.00 |
-|[wxkj](https://gitee.com/wxkj)|wechat|¥10.00 |
-|[zhuhuan12](https://gitee.com/zhuhuan12)|gitee|¥50.00 |
-|[zfan_codes](https://gitee.com/zfan_codes)|gitee|¥10.00 |
-|[arden](https://github.com/arden)|alipay|¥10.00 |
-|[macnie](https://www.macnie.com)|wechat|¥110.00 |
-|lah|wechat|¥100.00 |
-|x*z|wechat|¥20.00 |
-|潘兄|wechat|¥100.00 |
-|Fly的狐狸|wechat|¥100.00 |
-|全|alipay|¥100.00 |
-|东东|wechat|¥100.00 |
-|严宇轩|alipay|¥99.99 |
-|土豆相公|alipay|¥66.60 |
-|Hades|alipay|¥66.66 |
-|蔡蔡|wechat|¥666.00 | gf越来越强
-|上海金保证网络科技|bank|¥2000.00 |
-|[foxhack](https://github.com/foxhack)|wechat|¥20.00 |
-|*栈|wechat|¥5.00 |
-|*络|wechat|¥10.00|
-|M*e|wechat|¥20.00|
-|*G|wechat|¥10.00|
-|E*_|wechat|¥10.00|
-|A*y|wechat|¥1.00| 感谢大佬goframe
-|K*e|wechat|¥168.00| 感谢老大
-|*雨|wechat|¥100.00|
-|*洁|wechat|¥10.00|赞助你肥宅快乐水
-|R*s|wechat|¥18.88| 谢谢GF!辛苦了!
-|粟*e|wechat|¥50.00|
-|[李超](https://github.com/effortlee)|wechat|¥124.00|
-|soidea666|wechat+qq|¥800.00|
-|[王哈哈](https://gitee.com/develop1024)|wechat|¥6.66| 希望gf越来越好
-|夕景|alipay+qq|¥9.96+3.57|
-|struggler|alipay|¥18.80|
-|*铁|wechat|¥0.01|
-|C*e|wechat|¥66.66| GF越来越好,棒👍!
-|---P คิดถึง|wechat|¥6.66|
-|[王飞](https://gitee.com/wang_2018)|gitee|¥20.00| 感谢您的开源项目!
-|[Zeroing-ZY](https://gitee.com/yunjieg)|gitee|¥20.00| 感谢您的开源项目!
-|[katydid酱](https://gitee.com/katydid2005)|gitee|¥50.00| 感谢您的开源项目!框架给予了很大的帮助!谢谢大佬!
-|[李海峰](https://gitee.com/dlhf)|gitee|¥10.00| 希望GF越来越好,框架很牛逼!
-|陆昱天|alipay|¥100.00|
-|[Dockercore](https://github.com/dockercore)|wechat|¥200.00| 非常喜欢!简洁好用!文档超级全!
-|🚶|wechat|¥6.88| 喝杯冰阔落
-|a*l|wechat|¥10.00| gf
-|[wxkj](https://gitee.com/wxkj)|wechat|¥10.00|
-|[米司特包](https://github.com/misitebao)|wechat|¥9.99|
-|重庆宝尔威科技|wechat|¥6.66|
-|琦玉-QPT|wechat|¥6.66|
-|sailsea|wechat|¥11.00|
-|[seny0929](https://gitee.com/seny0929)|wechat|¥99.90|
-|*华|wechat|¥6.66| 感谢郭强的热心
-|[Playhi](https://github.com/Playhi)|alipay|¥10.00|
-|北京京纬互动科技|alipay|¥200.00|
-|[米司特包](https://github.com/misitebao)|wechat|¥99.99|
-|金毛|alipay|¥100.00|
-|1*1x|wechat|¥100.00|
-|[ywanbing](https://github.com/ywanbing)|wechat|¥66.66|
-|[侯哥](http://www.macnie.com)|wechat|¥10.00|
-|如果🍋|alipay|¥100.00| 错过的奶茶^_^
-|蔡蔡|wechat|¥666.00| gf真强大,让项目省心
-|jack|wechat|¥100.00|
-|sbilly|wechat|¥100.00| 祝好!
-|[米司特包](https://github.com/misitebao)|wechat|¥166.66| 大佬加油!
-|*秦|wechat|¥20.00| 给群主献上一杯咖啡...
-|[zhuhuan12](https://gitee.com/zhuhuan12)|wechat|¥50.00|
-|faddei|qq|¥9.99|
-|*天|wechat|¥10.00|
-|*.|wechat|¥20.00| 学生一个微薄之力,冲
-|李勇|wechat|¥50.00|
-|[米司特包](https://github.com/misitebao)|wechat|¥66.66|
-|千年|wechat|¥1.08|
-|[szzxing](https://github.com/szzxing)|wechat|¥50.00|
-|伟客互联|wechat|¥66.66|
-|[sbilly](https://github.com/sbilly)|wechat|¥99.00|
-|**阳|alipay|¥100.01|
-|**亮|alipay|¥10.00|
-|[mingzaily](https://github.com/mingzaily)|alipay|¥30.00|
-|[seny0929](https://gitee.com/seny0929)|alipay|¥9.90|
-|Fly的狐狸|alipay|¥100.00|
-|六七 ·|wechat|¥88.88| gf越来越好
-|Tom|wechat|¥500.00| 一点心意 希望GF越来越好
-|Chao|wechat|¥166.00|
-
-
-
-
-
diff --git a/README.MD b/README.MD
index 8fe86519a..cdc2e6ac1 100644
--- a/README.MD
+++ b/README.MD
@@ -9,8 +9,8 @@
English | [简体中文](README_ZH.MD)
-`GF(GoFrame)` is a modular, powerful, high-performance and production-ready application development framework
-of golang. Providing a series of core components and dozens of practical modules, such as:
+`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.
@@ -47,56 +47,9 @@ golang version >= 1.11
# Performance
-Here's the most popular Golang frameworks and libraries performance testing result in `WEB Server`. Performance testing cases source codes are hosted at: https://github.com/gogf/gf-performance
+The `Web` component performance of `GoFrame`, please refer to third-party project: https://github.com/the-benchmarker/web-frameworks
-## Environment
- OS : Ubuntu 18.04 amd64
- CPU : AMD A8-6600K x 4
- MEM : 32GB
- GO : v1.13.4
-
-## Testing Tool
-
-`ab`: Apache HTTP server benchmarking tool.
-
-Command:
-```
-ab -t 10 -c 100 http://127.0.0.1:3000/hello
-ab -t 10 -c 100 http://127.0.0.1:3000/query?id=10000
-ab -t 10 -c 100 http://127.0.0.1:3000/json
-```
-The concurrency starts from `100` to `10000`.
-
-> Run `5` times for each case of each project and pick up the best testing result.
-
-## 1. Hello World
-
-
-
Throughputs
-
Mean Latency
-
P99 Latency
-
-
-
-
-
-
-
-
-## 2. Json Response
-
-
-
Throughputs
-
Mean Latency
-
P99 Latency
-
-
-
-
-
-
-
# Documentation
@@ -115,9 +68,19 @@ The concurrency starts from `100` to `10000`.
`GF` is licensed under the [MIT License](LICENSE), 100% free and open-source, forever.
-# Known Users
+# Part Of Users
-Logos are not authorized to be shown due to trademark copyrights.
+- [Tencent](https://www.tencent.com/)
+- [ZTE](https://www.zte.com.cn/china/)
+- [Ant Financial Services](https://www.antfin.com/)
+- [MedLinker](https://www.medlinker.com/)
+- [KuCoin](https://www.kucoin.io/)
+- [LeYouJia](https://www.leyoujia.com/)
+- [IGG](https://igg.com)
+- [XiMaLaYa](https://www.ximalaya.com)
+- [ZYBang](https://www.zybang.com/)
+
+> We list part of the users here, if your company or products are using `GoFrame`, please let us know [here](https://github.com/gogf/gf/issues/168).
# Contributors
@@ -127,7 +90,7 @@ This project exists thanks to all the people who contribute. [[Contributors](htt
# Donators
-We currently accept donation by Alipay/WechatPay, please note your github/gitee account in your payment bill. If you like `GF`, why not [buy developer a cup of coffee](DONATOR.MD)?
+If you love `GF`, why not [buy developer a cup of coffee](https://itician.org/pages/viewpage.action?pageId=1115633)?
# Sponsors
We appreciate any kind of sponsorship for `GF` development. If you've got some interesting, please contact WeChat `389961817` / Email `john@goframe.org`.
@@ -135,8 +98,8 @@ We appreciate any kind of sponsorship for `GF` development. If you've got some i
# Thanks
-
-
+
+
diff --git a/README_ZH.MD b/README_ZH.MD
index efcb41889..77f472620 100644
--- a/README_ZH.MD
+++ b/README_ZH.MD
@@ -8,7 +8,7 @@
[English](README.MD) | 简体中文
-`GF(Go Frame)`是一款模块化、高性能、生产级的Go基础开发框架。
+`GF(Go Frame)`是一款模块化、高性能、企业级的Go基础开发框架。
实现了比较完善的基础设施建设以及开发工具链,提供了常用的基础开发模块,
如:缓存、日志、队列、数组、集合、容器、定时器、命令行、内存锁、对象池、
配置管理、资源管理、数据校验、数据编码、定时任务、数据库ORM、TCP/UDP组件、进程管理/通信等等。
@@ -65,58 +65,7 @@ golang版本 >= 1.11
# 性能
-以下是目前最流行的`WEB Server` Golang框架/类库性能测试结果。
-性能测试用例源代码仓库: https://github.com/gogf/gf-performance
-
-## 环境:
-
- OS : Ubuntu 18.04 amd64
- CPU : AMD A8-6600K x 4
- MEM : 32GB
- GO : v1.13.4
-
-## 工具
-
-`ab`: Apache HTTP server benchmarking tool.
-
-测试命令:
-```
-ab -t 10 -c 100 http://127.0.0.1:3000/hello
-ab -t 10 -c 100 http://127.0.0.1:3000/query?id=10000
-ab -t 10 -c 100 http://127.0.0.1:3000/json
-```
-并发客户端数量从 `100` 递增到 `10000`。
-
-> 每个项目的每个用例均运行`5`次,取最优的结果展示。
-
-## 1. Hello World
-
-
-
Throughputs
-
Mean Latency
-
P99 Latency
-
-
-
-
-
-
-
-
-## 2. Json Response
-
-
-
Throughputs
-
Mean Latency
-
P99 Latency
-
-
-
-
-
-
-
-
+`Web`组件的性能测试,请参考第三方性能测试评估:https://github.com/the-benchmarker/web-frameworks
# 文档
@@ -137,7 +86,17 @@ ab -t 10 -c 100 http://127.0.0.1:3000/json
# 用户
-由于商标版权缘故,未经厂商商务部授权允许无法用于宣传展示。
+- [腾讯科技](https://www.tencent.com/)
+- [中兴科技](https://www.zte.com.cn/china/)
+- [蚂蚁金服](https://www.antfin.com/)
+- [医联科技](https://www.medlinker.com/)
+- [库币科技](https://www.kucoin.io/)
+- [乐有家](https://www.leyoujia.com/)
+- [IGG](https://igg.com)
+- [喜马拉雅](https://www.ximalaya.com)
+- [作业帮](https://www.zybang.com/)
+
+> 在这里只列举了部分知名的用户,如果您的企业或者产品正在使用`GoFrame`,欢迎到 [这里](https://github.com/gogf/gf/issues/168) 留言。
# 贡献
@@ -147,7 +106,7 @@ ab -t 10 -c 100 http://127.0.0.1:3000/json
# 捐赠
-如果您喜欢`GF`,要不[给开发者来杯咖啡吧](DONATOR.MD)!
+如果您喜欢`GF`,要不给开发者 [来杯咖啡](https://itician.org/pages/viewpage.action?pageId=1115633) 吧!
请在捐赠时备注您的`github`/`gitee`账号名称。
# 赞助
@@ -155,5 +114,5 @@ ab -t 10 -c 100 http://127.0.0.1:3000/json
赞助支持`GF`框架的快速研发,如果您感兴趣,请联系 微信 `389961817` / 邮件 `john@goframe.org`。
# 感谢
-
-
+
+
diff --git a/RELEASE.1.MD b/RELEASE.1.MD
deleted file mode 100644
index 8ce74ae84..000000000
--- a/RELEASE.1.MD
+++ /dev/null
@@ -1,566 +0,0 @@
-# `v1.8.0` (2019-07-15)
-
-## 新功能改进
-1. 框架目前 `69` 个开发模块(不包括内部模块),原生代码 `65302` 行(不包含第三包依赖包),单元测试覆盖率达到`77%`;
-1. 新增`gerror`错误处理模块:https://goframe.org/errors/gerror/index
-1. 改进`gcharset`字符编码转换模块,支持更多的字符集:https://goframe.org/encoding/gcharset/index
-1. 新增`gmutex`模块,基于`channel`实现的高级互斥锁模块,支持更丰富的互斥锁特性:https://goframe.org/os/gmutex/index
-1. 改进`glog`日志模块:
- - 新增日志异步输出特性:https://goframe.org/os/glog/async
- - 新增`Flags`额外功能特性:https://goframe.org/os/glog/flags
- - 新增`Json`数据格式输出:https://goframe.org/os/glog/json
- - 新增自定义`Writer`接口特性:https://goframe.org/os/glog/writer
- - **修改`Backtrace`名称为`Stack`,并改进调用堆栈输出格式;**
- - 新增`Expose`方法暴露内部默认`Logger`对象;
-1. 改进`gdb`数据库ORM模块:
- - **改进错误处理,当数据库操作没有查询到数据时,`error`返回`sql.ErrNoRows`**:https://goframe.org/database/gdb/error
- - 改进`Update`/`Delete`方法支持`Order BY`及`LIMIT`特性;
- - 数据库链式操作及方法操作中,预处理变量参数支持`slice`参数:https://goframe.org/database/gdb/chaining/model
- - **修改`Priority`权重配置名称为`Weight`;**
- - 新增`Debug`配置,可配置开启/关闭调试特性:https://goframe.org/database/gdb/config
- - 新增`Offset`方法,该方法为可选链式操作方法,`pgsql`数据库可直接通过`Limit`方法第二个参数自动识别为`Offset`语法;
- - 改进数据库动态切换特性,支持不同数据库类型的当前操作数据库切换;
- - 改进简化配置文件结构:https://goframe.org/database/gdb/config
-1. 改进`gconv`数据转换模块:
- - 对结构体对象转换时支持更多的标签:`gconv/c/json`;
- - 支持`*struct/[]struct/[]*struct`自动初始化创建对象/数组:https://goframe.org/util/gconv/struct
- - 新增`Strusts/StrctsDeep`方法,用于结构体数组的递归转换;
- - 新增`StructDeep`方法,用于对结构体对象的递归转换;
- - 新增`MapDeep`方法,用于对结构体属性的递归转换;
-1. 改进`ghttp`模块:
- - 改进`ghttp`模块的分组路由功能,完善逻辑处理细节,程序更加稳健;
- - 改进`ghttp.Request.Get*ToStruct`方法,支持`params/param/p`标签,支持结构体递归转换,并且支持`**struct`参数的对象自动初始化;
- - 改进`ghttp.CORSDefault`的跨域设置参数,`AllowOrigin`参数调整为`*`;
-1. 改进`gvalid`数据校验模块:
- - 增加对校验标签`gvalid/valid/v`的支持;
- - 改进`CheckStruct`支持对结构体对象的递归校验:https://goframe.org/util/gvalid/checkstruct
-1. 改进`gtcp`TCP通信模块:
- - 改进通信包协议设计,更加轻量级高效:https://goframe.org/net/gtcp/conn/pkg
- - 改进`TCP Server`增加对`TLS`的支持:https://goframe.org/net/gtcp/tls
- - 增加`Server.Cloce`服务端关闭方法;
-1. 改进`gproc`模块的通信数据结构,并使用`gtcp`的轻量级包协议重构消息发送逻辑;
-1. 改进`gqueue`模块增加数据同步缓冲机制,解决大数据量下的内存占用及延迟问题;
-1. 改进`gmlock`模块,使用`gmutex`模块替换内部的互斥锁,增加更多的操作方法;
-1. 改进`gaes`加密模块,增加`CBC`模式的加密/解密方法:
-1. 改进`garray.Range/SubSlice`方法,改进设计,提高性能;
-1. 改进`gjson`/`gparser`模块实现`MarshalJSON`接口以实现自定义的`JSON`数据格式转换;
-1. **改进`crypto`分类下模块的方法返回值,增加`error`错误变量返回,以保证更严谨的接口设计风格;**
-1. **改进`gbase64`模块,输入输出类型发生改变,并增加多个相关方法;**
-1. 改进`gflock`修改方法名`UnLock`为`Unlock`,新增`IsRLocked`方法;
-1. 新增`gfile.CopyFile/CopyDir`方法,用于文件及目录的复制;
-1. 改进`gjson/gparser/gvar/gcfg`模块增加更多的类型转换方法;
-1. 改进`gcache`模块,过期时间参数支持`time.Duration`类型;
-1. 新增`internal/structs`包,强大且便捷的结构体解析,并改进框架中所有涉及到结构体反射处理的模块;
-1. 改进`gbinary`增加封装方法对`BigEndian`的支持;
-
-## Bug Fix
-1. 修复`garray.Search`返回值问题;
-1. 修复`garray.Contains`, `garray.New*ArrayFromCopy`方法逻辑问题;
-1. 修复`gjson.Remove`删除`slice`参数问题;
-1. 修复`gtree.AVLTree.Remove`方法返回值问题;
-1. 修复`gqueue.Size`不准确的大小问题;
-1. 修复`queue.Close`问题;
-1. 修复`gcache.GetOrSetLockFunc`当回调函数返回`nil`结果时的死锁问题;
-1. 修复`gfsnotify.Add`方法默认递归监控添加失效问题;
-1. 修复`gdb.Model.Scan`在某些参数类型下的失效问题;
-
-## 注意事项
-
-请注意以上粗体文字部分,如有使用,在您升级时可能会出现不兼容性。
-
-
-
-# `v1.7.0` (2019-06-10)
-## 新功能/改进
-1. 重构改进`glog`模块:
- - 去掉日志模块所有的锁机制,改为无锁设计,执行性能更加高效
- - 增加日志内容的异步输出特性:https://goframe.org/os/glog/async
- - 增加日志输出内容的`Json`格式支持:https://goframe.org/os/glog/json
- - 增加`Flags`额外特性支持,包括文件行号打印、自定义时间格式、异步输出等特性控制:https://goframe.org/os/glog/flags
- - 增加`Writer`接口支持,便于开发者进行自定义的日志功能扩展,或者与第三方服务/模块对接集成:https://goframe.org/os/glog/writer
- - 修改`SetStdPrint`方法名为`SetStdoutPrint`
- - 修改链式方法`StdPrint`方法名为`Stdout`
- - 标记淘汰`*fln`日志输出方法,`*f`方法支持自动的换行输出
- - 新增更多的链式方法支持:https://goframe.org/os/glog/chain
-1. 重构改进`gmap`模块:
- - 增加更多数据格式支持:`HashMap`/`ListMap`/`TreeMap`
- - 简化类型名称,如`gmap.StringInterfaceMap`简化为`gmap.StrAnyMap`
- - 改进`Map/Keys/Values`方法以提高性能
- - 修改`BatchSet`/`BatchRemove`方法名为`Sets`/`Removes`
- - 新增更多功能方法支持:https://goframe.org/container/gmap/index
-1. 改进`gtime`时间模块:
- - 增加并完善更多的类`PHP`时间格式支持
- - 新增更多功能方法,如`FormatTo`/`LayoutTo`等等
- - 详见开发文档:https://goframe.org/os/gtime/index
-1. 改进`gdb`数据库模块:
- - 增加对继承结构体的数据转换支持:https://goframe.org/database/gdb/senior
- - 新增`GetLastSql`方法,用以在调试模式下获取最近一条执行的SQL语句
- - 其他的细节处理改进
-1. 改进`gtcp`通信模块:
- - 完善处理细节,提高通信性能;
- - 增加`TLS`服务端/客户端通信支持:https://goframe.org/net/gtcp/tls
- - 增加简单协议支持,便于开发者封包/解包,并解决粘包/半包问题:https://goframe.org/net/gtcp/conn/pkg
- - TCP服务端增加`Close`方法
- - 更多细节查看开发文档:https://goframe.org/net/gtcp/index
-1. 改进`gconv`类型转换模块
- - 修改`gconv.TimeDuration`转换方法名称为`gconv.Duration`
- - 新增`gconv.StructDeep`及`gconv.MapDeep`方法,支持递归转换
- - 详见开发文档:https://goframe.org/util/gconv/struct
-1. 改进`ghttp`模块:
- - 日志输出增加`http/https`字段:https://goframe.org/net/ghttp/logs
- - 新增`ghttp.Server.SetKeepAlive`设置方法,用以开启/关闭`KeepAlive`特性
- - 增加`ghttp.Request.GetUrl`方法,用以获取当前完整的URL请求地址
- - `ghttp.Client`客户端支持开发者自定义`Transport`属性,`ghttp.Client.Post`方法支持`浏览器模式`:https://goframe.org/net/ghttp/client
-1. 新增`gtree`树形数据结构容器支持:https://goframe.org/container/gtree/index
-1. 改进`gudp`通信模块,具体请参考开发文档:https://goframe.org/net/gudp/index
-1. 改进`gcfg`配置管理模块,所有`Get*`方法增加默认值支持:https://goframe.org/os/gcfg/index
-1. `gredis`模块新增`DoVar`/`ReceiveVar`方法以便于开发者对执行结果进行灵活的数据格式转换:https://goframe.org/database/gredis/index
-1. `gcache`模块`BatchSet`/`BatchRemove`方法名修改为`Sets`/`Removes`
-1. 改进`gjson`/`gparser`模块,增加更多方法:https://goframe.org/encoding/gjson/index
-1. 改进`gfile.MainPkgPath`方法,以支持不同平台的开发环境;
-1. 改进`grpool`协程池模块,提高执行性能:https://goframe.org/os/grpool/index
-1. 改进`TryCatch`方法,当开发者不传递`Catch`参数时,默认抑制并忽略错误的处理
-1. 改进`gmlock`模块,增加`TryLockFunc`/`TryRLockFunc`方法,并且为`gmlock.Mutex`高级互斥锁对象增加`TryLockFunc`/`TryRLockFunc`方法
-1. 去除`gvar.VarRead`接口类型支持
-
-## Bug Fix
-1. 解决`gdb`模块与其他第三方`ORM`模块同时使用的冲突;
-1. 修复`gcron.AddOnce`方法的细节逻辑问题;
-1. 修复内部`empty`模块的`IsEmpty`方法对结构体属性的空校验错误;
-1. 修复`gview`模板引擎的并发安全问题;
-1. 修复`ghttp.Server`的SESSION初始化过期时间问题;
-
-# `v1.6.0` (2019-04-09)
-
-## 新功能/改进
-1. `gcron`定时任务模块增加运行日志记录功能:https://goframe.org/os/gcron/index
-1. `gredis`增加全局分组配置功能,并增加更多的配置选项`maxIdle/maxActive/idleTimeout/maxConnLifetime`:https://goframe.org/database/gredis/index
-1. `gcfg`模块增加更多的默认配置文件检索路径,并且增加全局分组配置特性,增加`Instance`单例方法:https://goframe.org/os/gcfg/index
-1. `gview`模块增加更多的默认配置文件检索路径,并且增加`Instance`单例方法:https://goframe.org/os/gview/index
-1. `ghttp`模块新功能及改进:
- - 新增`CORS`HTTP(S)跨域请求特性: https://goframe.org/net/ghttp/cors
- - 增加`TLSConfig`配置功能;
- - 去掉路由注册方法的`error`返回值,当产生注册错误时直接终端打印错误/输出到日志文件;
- - 增加在`HTTP Code 302`跳转时的`Set-Cookie`支持;
- - 增加对`SESSION ID`的安全性检查;
- - 增加对基于`HTTPS`的`WebSocket`支持(`WSS`):https://goframe.org/net/ghttp/websocket/index
- - `Request`对象增加`Error`方法,用于输出自定义错误信息到`WebServer`错误日志中;
- - 其他一些改进;
-1. `gdb`模块新功能及改进:
- - 新增`Instance`单例管理方法;
- - 新增`Structs/Scan`链式操作方法,`gdb.DB/TX`新增`GetStructs/GetScan`方法,用于结果集`struct`/`slice`映射转换:https://goframe.org/database/gdb/chaining
- - 新增`Safe`链式操作方法(默认非并发安全),用于链式安全控制:https://goframe.org/database/gdb/chaining
- - `Where`链式操作方法改进:
- - 方法支持任意的`string/map/slice/struct/*struct`类型;
- - 逻辑调整,当链式操作中存在多个`Where`方法调用时,自动转换为`And`条件;
- - 支持`slice`条件参数,常用在`SELECT IN`查询中,例如:`Where("uid IN(?)", g.Slice{1,2,3})`;
- - 支持在`map`类型条件参数的`key`中传递条件,例如:`Where(g.Map{"uid>?", uid})`;
-1. `gconv`及`gvalid`模块改进并去掉对私有`struct`方法属性的转换/校验;
-1. `gconv.Map`转换方法新增对`json tag`: `-`, `omitempty`的支持: https://goframe.org/util/gconv/map
-1. `gstr`模块新增 `ReplaceI/ReplaceIByArray/ReplaceIByMap`大小写非敏感替换方法;
-1. `gutil`模块增加`IsEmpty`方法,用于判断给定变量是否为空(整型0, 布尔false, slice/map长度为0, 其他为nil的情况,判断为空),并增加快捷方法`g.IsEmpty`;
-1. `gutil`模块增加`Export`方法,用于导出返回格式化打印的变量内容字符串,并增加快捷方法`g.Export`;
-1. `gspath`增加缓存及非缓存检索检索方法`Search`/`SearchWithCache`;
-1. `gjson`模块增加默认的`UseNumber`功能支持;
-1. `gmap`增加`SetIfNotExistFunc/SetIfNotExistFuncLock`方法;
-1. 迁移`greuseport`模块到新的仓库:https://github.com/gogf/greuseport
-1. 大量的单元测试完善;
-
-## Bug Fix
-1. 修复`gqueue`模块的资源竞争问题;
-1. 修复`gconv.GTime`转换失败问题;
-1. 修复`gconv.String`在转换`int`参数时字节溢出问题;
-1. 修复`ghttp.Request`的`HTTP Basic Auth`校验问题;
-1. 修复`gxml`针对于非`UTF-8`编码内容转换的并发安全问题;
-1. 修复`gtime`部分`Format`(`G`&`j`)格式失效问题;
-1. 修复`gudp.Conn`对象的`RemoteAddr`获取客户端连接地址方法问题;
-1. 修复`gmap/gcache`模块的`GetOrSetFuncLock`方法,增加对回调方法返回值的`nil`判断,只有非nil返回值才会被保存;
-
-
-
-# `v1.5.8` (2019-02-28)
-
-## 新特性
-1. 主库从`gitee`迁移到了`github`( https://github.com/gogf/gf ),`gitee`作为镜像站,用于国内的代码贡献及ISSUE提交,迁移说明详见:https://goframe.org/upgradeto150
-1. 对常用的`container`数组模块: `garray`做了大量改进/完善工作,新增大量常用方法,并完善单元测试用例及方法注释,详见API文档:https://godoc.org/github.com/gogf/gf/container/garray
-1. 对常用的`container`集合模块: `gset`做了大量改进/完善工作,新增大量常用方法,并完善单元测试用例及方法注释,详见API文档:https://godoc.org/github.com/gogf/gf/container/gset
-1. 对常用的`container`MAP模块: `gmap`做了大量改进/完善工作,新增大量常用方法,并完善单元测试用例及方法注释,详见API文档:https://godoc.org/github.com/gogf/gf/container/gmap
-1. 对常用的字符串模块: `gstr`做了大量改进/完善工作,新增大量常用方法,并完善单元测试用例及方法注释,详见API文档:https://godoc.org/github.com/gogf/gf/text/gstr
-1. 改进`gform`中对`struct`/`*struct`参数的支持,`*Insert/*Save/*Replace/*Update/Where/Data`方法的参数调整为`interface{}`类型,并支持任意类型的: `string/map/slice/struct/*struct`参数传递,具体请参考:https://goframe.org/database/orm/chaining
-1. 新增/完善若干模块的单元测试用例, 包括:`gvalid`/`gregex`/`garray`/`gset`/`gmap`/`gstr`/`gconv`/`ghttp`/`gdb`;
-1. 由于`gkafka`模块比较重,且不是框架核心模块,因此将该模块迁移到新的仓库中独立管理,并去掉相关依赖包:https://github.com/gogf/gkafka
-1. 新增`greuseport`模块,用以实现TCP的`REUSEPORT`特性:https://godoc.org/github.com/gogf/gf/net/greuseport
-
-## 新功能/改进
-1. 去掉模板引擎内置变量中自动初始化`session`对象带来的内存占用问题;
-1. `ghttp.Client`改进,增加若干方法,详见:https://goframe.org/net/ghttp/client
-1. `ghttp`分组路由增加`COMMON`方法,用以注册常用的`HTTP METHOD`(`GET/PUT/POST/DELETE`)路由;
-1. 更新框架依赖的`golang.org/x/sys`模块;
-1. 改进`gform`的批量操作(`Batch*`操作)返回结果对象,可以通过该结果对象获得批量操作准确的受影响记录行数;
-1. 将`gstr`/`gregex`模块从`util`分类迁移到了`text`分类目录下;
-1. 将`gtest`模块从`util`分类迁移到了`test`分类目录下;
-1. 完善`glog`方法注释;
-
-## Bug Fix
-1. 修复带点的邮件格式,用`gvalid.Check`的"`email`"规则不能匹配成功;
-1. 修复`gvalid.Check`在`regex`规则下的检查失败问题;
-1. 修复`gcron`模块定时规则中天和周不允许`?`符号的问题;
-1. 修复`ghttp.Server`在部分异常情况下仍然返回`200`状态码的问题;
-1. 修复`gfpool`模块中由于原子操作问题造成的高并发"内存泄露"问题;
-1. 修复分组路由注册对象/控制时,方法`Index`的路由仅能通过`/xxx/index`访问的问题;
-1. 修复模板引擎使用中,当不存在`config.toml`(即使没使用)配置文件时的报错问题;
-1. 其他一些修复;
-
-
-
-# `v1.4.6` (2019-01-24)
-
-## 新特性
-1. 新增并发安全的高性能任务定时器模块`gtimer`, 类似于Java的`Timer`,但是比较于Java的`Timer`更加强大,内部实现采用灵活高效的`分层时间轮`设计,被设计为可管理维护百万级别以上数量的定时任务。`gtimer`为`GF`框架的核心模块之一,单元测试覆盖率达到`93.6%`:[https://goframe.org/os/gtimer/index](https://goframe.org/os/gtimer/index)
-1. 采用任务定时器`gtimer`重构`gcron`定时任务模块,去掉第三方`github.com/robfig/cron`包的使用。`gcron`增加单例模式的定时任务:[https://goframe.org/os/gcron/index#](https://goframe.org/os/gcron/index#);
-1. `gconv`类型转换模块支持对`struct`结构体中的**指针属性**转换:[https://goframe.org/util/gconv/struct](https://goframe.org/util/gconv/struct);
-1. `gform`增加对数据库类型的自动识别特性,这一特性在需要将查询结果`json`编码返回时非常有用: [https://goframe.org/database/orm/index](https://goframe.org/database/orm/index)
-1. `Travis CI`增加对`386`架构的自动化测试支持(目前已支持`386`和`amd64`);
-
-## 新功能
-1. `ghttp`模块新增`Exit`、`ExitAll`、`ExitHook`方法,用于HTTP请求处理流程控制: [https://goframe.org/net/ghttp/service/object](https://goframe.org/net/ghttp/service/object);
-1. `grand`模块增加`Meet/MeetProb`方法,用于给定概率的随机满足判断,增加别名方法`N/Str/Digits/Letters`;
-1. `gvalid`数据/表单校验模块增加`16X`及`19X`手机号的校验支持;
-
-## 功能改进
-1. `gform`设置默认的数据库连接池`CONN_MAX_LIFE`参数值为`30`秒;
-1. 改进`glist`模块,提高约`20%`左右性能,并增加若干链表操作方法;
-1. 改进`gqueue`模块,提高约`50`左右性能,并增加模块对`select`语法的支持(使用`Queue.C`): [https://goframe.org/container/gqueue/index](https://goframe.org/container/gqueue/index);
-1. 改进`gmlock`内存锁模块,并完善单元测试用例:[https://goframe.org/os/gmlock/index](https://goframe.org/os/gmlock/index);
-1. 改进并发安全容器所有的模块,调整并发安全控制非必需参数`safe...bool`为`unsafe...bool`;
-1. 改进`gpool`对象复用模块,支持并发安全;
-1. 更新`gkafka`模块的第三方依赖包;
-1. 完善`ghttp`模块的单元测试用例;
-
-
-## Bug Fix
-1. 修复`gmd5`模块操作文件时的文件指针未关闭问题;
-1. 修复`gcache`缓存项过期删除失效问题;
-1. 其他修复;
-
-# `v1.3.8` (2018-12-26)
-
-## 新特性
-1. 对`gform`完成重构,以提高扩展性,并修复部分细节问题、完善单元测试用例([https://goframe.org/database/orm/index](https://goframe.org/database/orm/index));
-1. `WebServer`路由注册新增分组路由特性([https://goframe.org/net/ghttp/group](https://goframe.org/net/ghttp/group));
-1. `WebServer`新增`Rewrite`路由重写特性([https://goframe.org/net/ghttp/static](https://goframe.org/net/ghttp/static));
-1. 增加框架运行时对开发环境的自动识别;
-1. 增加了`Travis CI`自动化构建/测试;
-
-## 新功能
-1. 改进`WebServer`静态文件服务功能,增加`SetStaticPath`/`AddStaticPath`方法([https://goframe.org/net/ghttp/static](https://goframe.org/net/ghttp/static));
-1. `gform`新增`Filter`链式操作方法,用于过滤参数中的非表字段键值对([https://goframe.org/database/orm/linkop](https://goframe.org/database/orm/linkop));
-1. `gcache`新增`Data`方法,用以获取所有的缓存数据项;
-1. `gredis`增加`GetConn`方法获取原生redis连接对象;
-
-## 功能改进
-1. 改进`gform`的`Where`方法,支持`slice`类型的参数,并更方便地支持`in`操作查询([https://goframe.org/database/orm/linkop](https://goframe.org/database/orm/linkop));
-1. 改进`gproc`进程间通信数据结构,将`pid`字段从`16bit`扩展为`24bit`;
-1. 改进`gconv`/`gmap`/`garray`,增加若干操作方法;
-1. 改进`gview`模板引擎中的`date`内置函数,当给定的时间戳为空时打印当前的系统时间;
-1. 改进`gview`模板引擎中,当打印的变量不存在时,显示为空(标准库默认显示为``);
-1. 改进`WebServer`,去掉`HANGUP`的信号监听,避免程序通过`nohup`运行时产生异常退出问题;
-1. 改进`gcache`性能,并完善基准测试;
-
-## Bug Fix
-1. 修复`gcache`在非LRU特性开启时的缓存关闭资源竞争问题,并修复`doSetWithLockCheck`内部方法的返回值问题;
-1. 修复`grand.intn`内部方法在`x86`架构下的随机数位溢出问题;
-1. 修复`gbinary`中`Int`方法针对`[]byte`参数长度自动匹配造成的字节长度溢出问题;
-1. 修复`gjson`由于官方标准库`json`不支持`map[interface{}]*`类型造成的Go变量编码问题;
-1. 修复`garray`中部分方法的数据竞争问题,修复二分查找排序问题;
-1. 修复`ghttp.Request.GetVar`方法获取参数问题;
-1. 修复`gform`的数据库连接池不起作用的问题;
-
-
-
-
-
-# `v1.2.11` (2018-11-26)
-## 新特性
-1. `ORM`新增对`SQLServer`及`Oracle`的支持([https://goframe.org/database/orm/database](https://goframe.org/database/orm/database));
-1. 完成`gvalid`模块校验结果的顺序特性([https://goframe.org/util/gvalid/checkmap](https://goframe.org/util/gvalid/checkmap));
-1. 改进`ghttp.Request.Exit`,使得调用该方法时立即退出业务执行,开发者无需调用`Exit`方法时再使用`return`返回([https://goframe.org/net/ghttp/service/object](https://goframe.org/net/ghttp/service/object));
-1. 模板引擎新增若干内置函数:`text/html/htmldecode/url/urldecode/date/compare/substr/strlimit/hidestr/highlight/toupper/tolower/nl2br` ([https://goframe.org/os/gview/funcs](https://goframe.org/os/gview/funcs));
-1. 模板引擎新增内置变量`Config` ([https://goframe.org/os/gview/vars](https://goframe.org/os/gview/vars));
-1. 改进`gconv.Struct`转换默认规则,支持不区分大小写的键名与属性名称匹配;
-1. `gform`配置文件支持`linkinfo`自定义数据库连接字段([https://goframe.org/database/orm/config](https://goframe.org/database/orm/config));
-1. `gfsnotify`模块增加对特定回调的取消注册功能([https://goframe.org/os/gfsnotify/index](https://goframe.org/os/gfsnotify/index));
-
-
-
-## 新功能
-1. 改进`ghttp.Request`,增加`SetParam/GetParam`请求流程自定义变量设置/获取方法,用于在请求流程中的回调函数共享变量([https://goframe.org/net/ghttp/request](https://goframe.org/net/ghttp/request));
-1. 改进`ghttp.Response`,增加`ServeFileDownload`方法,用于WebServer引导客户端下载文件([https://goframe.org/net/ghttp/response](https://goframe.org/net/ghttp/response));
-1. `gvar`模块新增`gvar.VarRead`只读接口,用于控制对外只暴露数据读取功能;
-1. 增加`g.Throw`抛异常方法,`g.TryCatch`异常捕获方法封装;
-1. 改进`gcron`模块,增加自定义的Cron管理对象,增加`New/Start/Stop`方法;
-
-
-## 功能改进
-1. WebServer添加`RouterCacheExpire`配置参数,用于设置路由检索缓存过期时间;
-1. WebServer允许同一`HOOK`事件被多次绑定注册,先注册的回调函数优先级更高([https://goframe.org/net/ghttp/service/hook](https://goframe.org/net/ghttp/service/hook));
-1. 当前工作目录为系统临时目录时,`gcfg`/`gview`/`ghttp`模块默认不添加工作目录到搜索路径;
-1. 改进`WebSocket`默认支持跨域请求([https://goframe.org/net/ghttp/websocket](https://goframe.org/net/ghttp/websocket));
-1. 改进`gtime.Format`支持中文;
-1. 改进`gfsnotify`,支持编辑器对文件非执行标准编辑时(RENAME+CHMOD)的热更新问题;
-1. 改进`gtype.Set`方法,增加Set原子操作返回旧的变量值;
-1. `gfile.ScanDir`增加支持`pattern`多个文件模式匹配,使用'`,`'符号分隔多个匹配模式;
-1. `gcfg`模块增加获取配置变量为`*gvar.Var`;
-1. `gstr`模块增加对中文截取方法;
-1. 改进`gtime.StrToTime`对常用时间格式匹配模式,新增`gtime.ParseTimeFromContent`方法;
-1. 修改配置管理、模板引擎、调试模式的环境变量名称为大写下划线标准格式;
-1. 改进`grand`模块随机数生成设计,底层使用`crypto/rand`+缓冲区实现高速的随机数生成([https://goframe.org/util/grand/index](https://goframe.org/util/grand/index));
-
-## 问题修复
-1. 修复`gspath`模块在`windows`下搜索失效问题;
-1. 修复`gspath`模块Search时带有indexFiles的检索问题;
-1. bug fix INZS1([https://github.com/gogf/gf/issues/INZS1](https://github.com/gogf/gf/issues/INZS1));
-1. 修复`gproc.ShellRun`在windows下的执行问题;
-
-
-
-
-
-
-# `v1.0.898 stable` (2018-10-24)
-
-## 新特性
-1. `gf-orm`增加`sqlite`数据库类型支持([http://gf.johng.cn/database/orm/database](http://gf.johng.cn/database/orm/database));
-1. 增加`gkafka`模块,对kafka的客户端程序封装,支持分组消费及指定起始位置等特性,并提供简便易用的API接口([http://gf.johng.cn/database/gkafka/index](http://gf.johng.cn/database/gkafka/index));
-1. 增加go语言最新版本的`go modules`特性支持;
-1. 增加`gcron`定时任务模块([http://gf.johng.cn/os/gcron/index](http://gf.johng.cn/os/gcron/index));
-1. `Web Server`增加路由注册项获取/打印特性,所有的路由注册/回调注册一览无余;
-1. 模板引擎增加全局变量管理,并增加多个常用的内置函数及内置变量([http://gf.johng.cn/os/gview/funcs](http://gf.johng.cn/os/gview/funcs));
-1. `gredis`改进为单例操作方式(基于基层连接池特性),每次操作`redis`服务器时开发者无需显示调用`Close`方法执行关闭([http://gf.johng.cn/database/gredis/index](http://gf.johng.cn/database/gredis/index));
-1. `gf-orm`增加数据库操作自动`Close`特性(基于底层链接池特性),开发者无需再`defer db.Close()`,并增加`g.DB`数据库对象单例别名([http://gf.johng.cn/database/orm/linkop](http://gf.johng.cn/database/orm/linkop));
-1. 增加`gvar`通用动态变量模块([http://gf.johng.cn/container/gvar/index](http://gf.johng.cn/container/gvar/index));
-1. 数据结构容器增加`并发安全特性开启/关闭功能`,当关闭后和普通的数据结构无异,且在非并发安全模式下性能会得到提高;
-1. 新增`gmlock`内存锁模块([http://gf.johng.cn/os/gmlock/index](http://gf.johng.cn/os/gmlock/index));
-1. 增加`gaes`算法模块([http://gf.johng.cn/crypto/gaes/index](http://gf.johng.cn/crypto/gaes/index));
-1. `gproc`模块增加执行`shell`命令方法([http://gf.johng.cn/os/gproc/index](http://gf.johng.cn/os/gproc/index));
-1. 新增`gfcache`模块,用于带自动缓存更新的文件内容操作(文档待完善);
-
-## 新功能
-1. `glog`增加链式操作方法,增加日志级别管理控制、分类管理、调试管理功能;
-1. `g.View`增加分组名称设置,支持通过`g.*`对象管理器获取多个命名的单例模板引擎对象;
-1. `glog`增加对文件名称格式的自定义设置,支持`gtime日期格式`;
-1. `gconv`增加`Ints/Uints/Floats/Interfaces`转换方法;
-1. `gjson`增加`Append`方法;
-1. `gparser`增加`NewUnsafe/Append`方法;
-1. `gcache`增加`GetOrSet/GetOrSetFunc/GetOrSetFuncLock`方法;
-1. `gset`增加`LockFunc/RLockFunc`方法;
-1. `ghttp.Response`方法完善,增加`ParseTpl/ParseTplContent/TplContent`方法,`Template`修改为`Tpl`方法;
-1. `ghttp.Request`增加获取用户真实IP判断;
-1. `Session`增加`Contains`方法;
-1. 完善`ghtml`模块,增加多个方法;
-1. `gcache`新增`Contains/SetIfNotExist`方法;
-1. `gvalid`增加`Error`对象,用以管理校验错误信息;
-1. `gvalid`模块增加`struct tag`的校验规则、自定义错误提示信息绑定的支持特性([http://gf.johng.cn/util/gvalid/index](http://gf.johng.cn/util/gvalid/index));
-1. `ghttp`增加输入参数与`struct`的`绑定机制`,并增加对应`params`标签支持([http://gf.johng.cn/net/ghttp/service/handler](http://gf.johng.cn/net/ghttp/service/handler));
-1. `ghttp.Request`增加服务端`BasicAuth`功能(文档待完善);
-1. `gvalid`增加字段校验别名,用于自定义返回结果字段,并更新WebServer中相关使用的模块;
-1. `gf-orm`链式操作增加`ForPage`方法,调整`Chunks`方法;
-1. `ghttp`对象路由注册增加`Init&Shut`自动回调方法,增加重复路由注册检测功能;
-1. `gfsnotify`增加默认递归`Add/Remove`特性;
-1. `ghttp.Response`增加`ServiceFile`方法;
-1. 其他一些新功能;
-
-## 功能改进
-1. 改进`ghttp.Server`配置管理;
-1. 改进`gcache`底层对象继承关系,改进部分设计细节,提高性能;
-1. 改进`gfpool`文件指针池,修复部分错误,提升性能,并增加基准测试代码;
-1. 改进`gmap`系列并发安全map数据结构,增加多个易用性的方法;
-1. 改进`gconv.Struct`对象转换功能([http://gf.johng.cn/util/gconv/index](http://gf.johng.cn/util/gconv/index));
-1. 改进`grand`随机数生成规则,提供了极高的随机数生成性能,并保证每一次调用随机方法时生成的都是不同的随机数值([http://gf.johng.cn/util/grand/index](http://gf.johng.cn/util/grand/index));
-1. 改进`gfile`文件内容操作方法,增加若干常用的文件内容读取方法;
-1. 改进`gtime`模块,并增加时区转换方法;
-1. 改进`COOKIE`,去掉锁机制;
-1. 改进`SESSION`获取方法,新增多个类型获取方法;
-1. 改进`g.DB/g.Config`单例缓存键名;
-1. 改进`gtcp/gudp`超时错误判断机制;
-1. 改进`gtype`底层统一修改为原子操作;
-1. 改进`gvalid`对`struct`的`string`属性的默认值非必需校验;
-1. 改进`gvalid`在关联规则下的非必需校验;
-1. 改进`gf-orm`在调试模式下日志自动输出功能;
-1. `ghttp.Server/gspath`模块静态文件检索改进;
-1. 优化`ghttp.ServerConfig`配置,增加`struct/method``名称到uri`的转换规则,通过`SetNameToUri`方法进行灵活配置([http://gf.johng.cn/net/ghttp/service/object](http://gf.johng.cn/net/ghttp/service/object));
-1. 改进`*any/:name`路由匹配规则,支持不带名字的`*/:`路由规则;
-1. 修改默认配置文件名称 `config.yml` -> `config.toml`([http://gf.johng.cn/os/gcfg/index](http://gf.johng.cn/os/gcfg/index));
-1. 调整服务注册的`BindControllerMethod`及`BindObjectMethod`逻辑为绑定路由到指定的方法执行;
-1. 改进`garray`二分查找方法,增加安全操作处理;
-1. 改进`gdb.Result/Recorde` `ToXml`方法,增加可选的`rootTag`参数;
-1. 其他一些改进;
-
-## 问题修复
-1. 修复`ghttp.Server`在`windows`下的重启失效问题;
-1. 修复`ghttp.Server`服务注册与回调注册路由重复判断问题;
-1. 修复`garray`排序数组`Add`变参时的死锁问题;
-1. 修复`gfsnotify`默认递归监控整个`gspath.Add`添加的目录的问题;
-1. 修复`ghttp.BindParams`对`@file`文件上传标识符的转义问题;
-1. 修复`ghttp.Server`日志路径丢失问题;
-1. 修复`多WebServer`下的状态检测问题;
-1. 修复`gvalid`模块`min/max`校验问题;
-1. 修复控制器和执行对象服务注册时绑定'/'路由的问题;
-1. 修复`gvalid.CheckStruct`自定义错误提示失效问题;
-1. `ghttp.Server`修复`hook`与`serve`方法的路由影响,并新增跳转方法;
-1. 其他一些修复;
-
-## 其他改动
-1. 去掉`gfile.IsExecutable`方法;
-1. 目录调整,将`加密/解密`相关的包从`encoding`目录迁移到`crypto`目录下;
-1. 增加`gfsnotify/gfcache`调试信息;
-1. `gf-orm`允许写入的键值为`nil`时往数据库中写入`null`;
-1. 统一使用`gview.Params`类型作为模板变量类型;
-1. `gconv.MapToStruct`方法名称修改为`gconv.Struct`;
-1. `ghttp.Server`完善重启及停止的终端提示信息;
-1. 完善`gring`模块,增加`约瑟夫问题`代码作为`gring`示例程序;
-1. 其他一些改动;
-
-
-
-# `v0.99.682 beta` (2018-08-07)
-## 新特性
- 1、新增gdes包,用于DES加密/加密算法处理;
- 2、新增gkafka包,kafka的golang客户端;
- 3、新增gpool对象复用池,比较于标准库的sync.Pool更加灵活强大,可自定义对象的缓存时间、创建方法、销毁方法(http://gf.johng.cn/686654);
- 4、完成网络通信gtcp/gudp包的重构,并进行了大量的改进工作,新增了详尽的开发文档及示例代码(http://gf.johng.cn/494382);
- 5、增加gring并发安全环,标准库container/ring包的并发安全版本,并做了易用性的封装(http://gf.johng.cn/686655);
- 6、gtime包新增了自定义日期格式话的支持,格式化语法类似PHP的date语法(http://gf.johng.cn/494387);
- 7、gdb增加调试模式特性,使用SetDebug方法实现,在调试模式下可以获取详细的SQL执行记录,增加了详细的开发文档及示例代码(http://gf.johng.cn/702801);
- 8、gdb增加查询缓存特性,使用Cache方法实现,增加了详细的开发文档及示例代码(http://gf.johng.cn/702801);
- 9、ghttp.Server路由功能增加字段匹配规则特性,支持如:/order/list/{page}.html 动态路由规则特性(http://gf.johng.cn/702766);
- 10、gpage分页包增加分页URL规则生成模板特性,内部可使用{.page}变量指定页码位置(http://gf.johng.cn/716438);
- 11、增加gmap.Map对象,这是gmap.InterfaceInterfaceMap的别名;
-
-## 新功能
- 1、gdb增加MaxIdleConnCount/MaxOpenConnCount/MaxConnLifetime三项配置,并增加SetMaxConnLifetime方法;
- 2、ghttp.Client增加HTTP账号密码设置功能(SetBasicAuth);
- 3、glog新增对系统换行符号的自适配调整(\n|\r\n);
- 4、增加glog控制台调试模式打印开关(SetDebug);
- 5、gcfg增加SetFileName方法设置默认读取的配置文件名称;
- 6、gcfg/gjson/gparser包新增Int8/16/32/64,Uint8/16/32/64方法;
- 7、增加gzip方法的封装(Zip/Unzip);
- 8、gview增加模板变量分隔符设置方法SetDelimiters;
- 9、ghttp.Response增加Writef、Writefln方法;
-
-## 功能改进
- 1、改进gfilepool文件指针池设计;改进gfile文本内容写入,增加指针池使用
- 2、gdb包增加调试模式特性,并支持在调试模式下获得已执行的SQL列表结果
- 3、改进gproc进程间通信机制,增加进程消息分组特性,并限定队列大小
- 4、gdb结果方法处理增加ToXml/ToJson方法
- 5、gregx包名修改为gregex
- 6、改进gtime.StrToTime方法,新增对常见标准时间日期的自动转换,以及对时区的自动识别支持,并调整gconv,gvalid对该包的引用
- 7、增加对字符集转换的封装,gxml包中使用新增的字符集转换包来做处理
- 8、ghttp.Server.EnableAdmin页面Restart接口支持GET参数newExeFilePath支持
- 9、ghttp.Server平滑重启机制增加可自定义重启可执行文件路径,特别是针对windows系统特别有用(因为windows下不支持可执行文件覆盖更新)
- 10、改进ghttp.Server静态文件检索设计,增加开发环境时的main包源码目录查找机制;改进gcfg/gview的main包源码目录查找机制
- 11、优化gcache设计,LRU特性非默认开启;优化gtype/gcache基准测试脚本;新增gregx基准测试脚本,改进设计,提升性能
- 12、gfile包增加GoRootOfBuild方法,用于获取编译时的GOROOT数值;并改进glog包中backtrace的GOROOT路径过滤处理;
- 13、改进grpool代码质量,并改进对池化goroutine数量的限制设计
- 14、改进gdb.Map/List及g.Map/List的类型定义,改用别名特性以便支持原生类型输入(map/slice),并修复gdb.Model.Update方法参数处理问题
- 15、调整ghttp包示例代码目录结构,增加ghttp.Client自定义Header方法,ghttp.Cookie增加Map方法用于获得客户端提交的所有cookie值,构造成map返回
- 16、删除gcharset中的getcharset方法
- 17、去掉gmap中常用的基本数据类型转换获取方法
- 18、改进gconv.String方法,当无法使用基本类型进行字符串转换时,使用json.Marshal进行转换
- 19、gvalid.CheckObject方法名称修改为gvalid.CheckStruct
-
-
-## 问题修复
- 1、修正gstr.IsNumeric错误
- 2、修复当xml中encoding字符集为非UTF-8字符集时报错的问题
- 3、修正gconv包float32->float64精度问题
- 4、修复gpage包分页计数问题
- 5、修复gdb批量数据Save错误
- 6、去掉gpool中math.MAXINT64常量的使用,以修复int64到int类型的转换错误,兼容32位系统
- 7、修正ghttp包没有使用Server仍然初始化相关异步goroutine的问题
-
-
-
-
-# `v0.98.503 beta` (2018-05-21)
-## 新特性
- 1、平滑重启特性( http://gf.johng.cn/625833 );
- 2、gflock文件锁模块( http://gf.johng.cn/626062 );
- 3、gproc进程管理及通信模块( http://gf.johng.cn/626063 );
- 4、gpage分页管理模块,强大的动态分页及静态分页功能,并为开发者自定义分页样式提供了极高的灵活度( http://gf.johng.cn/597431 );
- 5、ghttp.Server增加多端口监听特性,并支持HTTP/HTTPS( http://gf.johng.cn/494366 , http://gf.johng.cn/598802 );
- 6、增加gspath目录检索包管理工具,支持对多目录下的文件检索特性;
- 7、ghttp包控制器及执行对象注册增加更灵活的动态路由特性,路由规则增加{method}变量支持;
-
-## 新功能
- 1、gutil包增加MapToStruct方法,支持将map数据类型映射为struct对象;
- 2、gconv
- 1)、gconv包增加按照类型名称字符串进行类型转换;
- 2)、gconv包新增Time/TimeDuration类型转换方法;
- 3、ghttp
- 1)、增加Web Server目录安全访问控制机制;
- 2)、ghttp.Server增加自定义状态码回调函数注册处理;
- 4、gdb
- 1)、gdb包增加gdb.GetStruct/gdb.Model.Struct方法,获取查询结果记录自动转换为指定对象;
- 2)、gdb增加Value/Record/Result类型,增加对Value类型的系列类型转换方法;
- 3)、gdb包增加db.GetCount,tx.GetCount,model.Count数量查询方法;
-
-## 功能改进
- 1、改进gredis客户端功能封装;
- 2、改进grand包随机数生成性能;
- 3、grand/gdb/gredis包增加benchmark性能测试脚本;
- 4、改进gjson/gparser包的ToStruct方法实现;
- 5、gdb :改进gdb.New获取ORM操作对象性能;
- 6、gcfg :改进配置文件检索功能;
- 7、gview:模板引擎增加多目录检索功能;
- 8、gfile:增加源码main包目录获取方法MainPkgPath;
- 9、ghttp
- 1)、ghttp.Request增加请求进入和完成时间记录,并增加到默认日志内容中;
- 2)、ghttp.Server事件回调之间支持通过ghttp.Request.Param自定义参数进行流程传参;
- 10、gdb
- 1)、改进gdb.Result与gdb.List, gdb.Record与gdb.Map之间的类型转换,便于业务层数据编码处理(如json/xml);
- 2)、改进gdb.Tx.GetValue返回值类型;
- 3)、gdb.Model.Data参数支持更加灵活的map参数;
-
-## 问题修复
- 1、ghttp
- 1)、修复ghttp包路由缓存问题;
- 2)、修复服务注册时的控制器及执行对象方法丢失问题;
- 2、gconv
- 1)、修正gconv.Float64方法位大小设置问题;
- 2)、修复gconv.Int64(float64(xxx))问题;
- 2、gdb
- 1)、修复gdb.GetAll针对返回数据列表的for..range...的返回结果slice相同指针问题;
- 2)、修复gdb.Delete方法错误;
- 3)、修复gdb.Model.And/Or方法;
- 4)、修复gdb.Model.Where方法参数处理问题;
- 3、garray:修复garray包Remove方法锁机制问题;
- 4、gtype :修复gtype.Float32/gtype.Float64对象类型的方法逻辑错误;
- 5、gfsnotify:修复在windows下文件参数中不同文件分隔符引起的热更新机制失效问题;
- 6、修复gvalid包验证问题:如果值为nil,并且不需要require*验证时,其他验证失效。并增加单元测试项,测试通过。
-
-
-
-
-
-# `v0.97.399 beta` (2018-04-23)
- 1、 增加gfsnotify文件监控模块;
- 2、 配置管理模块增加配置文件自动检测更新机制;
- 3、 模板引擎增加对模板文件的自动检测更新机制;
- 4、 改进gconv包基本类型转换功能,提高转换性能;
- 5、 增加gpage分页管理包,支持动态分页、静态分页以及自定义分页样式特性;
- 6、 ghttp.Request增加Exit方法,用以标记服务退出,当在服务执行前调用后,服务将不再执行;
- 7、 ghttp.Response去掉WriteString方法,统一使用Write方法返回数据流,是使用灵活的参数形式;
- 8、 模板引擎增加模板变量暴露接口LockFunc/RLockFunc,以便支持开发者灵活处理模板变量;
- 9、 ghttp.Server增加access & error log功能,并支持开发者自定义日志处理回调函数注册;
- 10、增加gredis包,支持对redis的客户端操作封装,并将gredis.Redis对象加入到gins单例管理器中进行统一配置管理维护;
- 11、gins单例管理器增加对单例对象配置文件的自动检测更新机制,当配置文件在外部发生变更时,自动刷新单例管理器中的单例对象;
- 12、gdb数据库ORM包增加And/Or条件链式方法,并改进Where/Data方法参数灵活性;
- 13、对于新增加的模块,同时也增加了对应的开发文档,并梳理完善了现有的其他模块开发文档;
- 14、修复ISSUE:
- #IISWI github.com/gogf/gf/issues/IISWI,
- #IISMY github.com/gogf/gf/issues/IISMY,
- 反馈并跟踪完成第三方依赖mxj包的ISSUE修复(github.com/clbanning/mxj/issues/48);
-
-
-
diff --git a/RELEASE.2.MD b/RELEASE.2.MD
deleted file mode 100644
index 3dc331d0e..000000000
--- a/RELEASE.2.MD
+++ /dev/null
@@ -1,824 +0,0 @@
-# `v1.13.1` (2020-06-10)
-
-# GoFrame
-
-`GF(Go Frame)`是一款模块化、高性能、生产级的Go基础开发框架。实现了比较完善的基础设施建设以及开发工具链,提供了常用的基础开发模块,如:缓存、日志、队列、数组、集合、容器、定时器、命令行、内存锁、对象池、配置管理、资源管理、数据校验、数据编码、定时任务、数据库ORM、TCP/UDP组件、进程管理/通信等等。并提供了Web服务开发的系列核心组件,如:Router、Cookie、Session、Middleware、服务注册、模板引擎等等,支持热重启、热更新、域名绑定、TLS/HTTPS、Rewrite等特性。
-
-## 特点
-
-* 模块化、松耦合设计;
-* 模块丰富、开箱即用;
-* 简便易用、易于维护;
-* 高代码质量、高单元测试覆盖率;
-* 社区活跃,大牛谦逊低调脾气好;
-* 详尽的开发文档及示例;
-* 完善的本地中文化支持;
-* 设计为团队及企业使用;
-
-## 发展
-
-`GoFrame`开始得比较早,`2011`年始于北京一个智能物联网平台项目,那时还没有这么多物联网的现行标准,`Go`的标准库以及生态也未如此丰富。`2017`年的时候`GF`才开始发布测试版,`2018`年`1024`程序员节日的时候才发布`v1.0`正式版,为`Go`生态发展添砖加瓦。开源以来快速迭代、发展成长,广受开发者和企业的青睐,也有许多的开发者加入了贡献行列。`GF`原本是为开发团队设计的,因此她的开发效率和可维护性做得非常好,有着很高的代码质量以及丰富的单元测试和示例,并且`GF`是目前中文化文档做的最好的`Golang`开发框架。
-
-# Change Log
-
-1. 应多数开发者的要求,框架要求的最低`Golang`运行版本降级为了`v1.11`。
-1. 新增`GoFrame`视频教程地址:
- - bilibili:https://www.bilibili.com/video/av94410029
- - 西瓜视频: https://www.ixigua.com/pseries/6809291194665796100/
-1. 将不常用的`guuid`模块迁移到 github.com/gogf/guuid 作为社区模块维护,保持`gf`主仓库的轻量级。
-1. 新增`guid`模块,用于高效轻量级的唯一字符串生成:https://goframe.org/util/guid/index
-
-
-## `tool chain`
-
-1. 工具链更新:https://goframe.org/toolchain/cli
-1. 新增`gf env`命令,更优雅地查看当前`Golang`环境变量信息。
-1. 新增`gf mod path`命令,用于将当前`go modules`包拷贝到`GOPATH`中,以便使用原始的`GOPATH`方式开发项目。
-1. 对现有`cli`命令进行了一些改进,提高使用体验;预编译二进制版本在部分平台下提供了`upx`压缩,使得下载的文件更小。
-
-## `container`
-1. `garray`
- - https://goframe.org/container/garray/index
- - 简化数组使用方式,支持类似于`var garray.Array`的变量定义使用方式;
- - 增加`Walk`方法,用于自定义的数组元素处理方法;
- - 增加`ContainsI`方法,用于大小写忽略匹配的数组元素项存在性查找;
- - 完善单元测试,代码覆盖率`94%`;
- - 代码改进,提高性能;
- - 修复一些问题;
-1. `gchan`
- - 由于该封装包实际意义不是很大,因此从主框架中删除;
-1. `glist`
- - https://goframe.org/container/glist/index
- - 简化链表使用方式,支持类似于`var glist.List`的变量定义使用方式;
- - 完善单元测试,代码覆盖率`99%`;
-1. `gmap`
- - https://goframe.org/container/gmap/index
- - 简化`Map`使用方式,支持类似于`var gmap.Map`的变量定义使用方式;
- - 完善单元测试,代码覆盖率`81%`;
- - 代码改进,提高性能;
-1. `gset`
- - https://goframe.org/container/gset/index
- - 简化集合使用方式,支持类似于`var gset.Set`的变量定义使用方式;
- - 增加`Walk`方法,用于自定义的集合元素处理方法;
- - 完善单元测试,代码覆盖率`90%`;
- - 代码改进,提高性能;
-1. `gtree`
- - https://goframe.org/container/gtree/index
- - 简化树型使用方式,支持类似于`var gtree.BTree`的变量定义使用方式;
- - 完善单元测试,代码覆盖率`90%`;
-1. `gvar`
- - https://goframe.org/container/gvar/index
- - 完善单元测试,代码覆盖率`69%`;
- - 代码组织结构调整,提高维护性;
- - 代码改进,提高性能;
-
-## `database`
-
-1. `gdb`
- - 增加`Transaction(f func(tx *TX) error) (err error)`接口方法,用于通过闭包实现事务封装处理:https://goframe.org/database/gdb/transaction
- - 去掉不常用的`From`接口方法,改进`Table`及`Model`方法的参数为不定参数,并支持通过不定参数传递表别名:https://goframe.org/database/gdb/chaining/select
- - 增加`DryRun`特性,支持空跑时只执行查询不执行写入/更新/删除操作:https://goframe.org/database/gdb/senior
- - 增加`create_at`, `update_at`写入时间、更新时间字段自动填充特性:https://goframe.org/database/gdb/chaining/auto-time
- - 增加`delete_at`软删除特性:https://goframe.org/database/gdb/chaining/auto-time
- - 增加`Having`链式操作方法,用于`having`条件查询:https://goframe.org/database/gdb/chaining/select
- - `Result`结果对象增加`Chunk`方法,用于自定义的数据分批处理:https://goframe.org/database/gdb/result
- - 改进`Schema`数据库运行时切换特性;
- - 改进对`pgsql`, `mssql`, `sqlite`, `oracle`数据库字段类型的支持;
- - 进一步完善单元测试;
- - 代码组织结构调整,提高维护性;
- - 代码改进,提高性能;
-1. `gredis`
- - 增加`MaxActive`连接池参数默认配置为`100`,限制默认的连接数量;
- - 改进`Conn`连接对象的`Do`方法,支持对`map/slice/struct`类型进行自动的`json.Marshal`处理,注意获取数据时使用`DoVar`方法获取:https://goframe.org/database/gredis/usage
- - 完善单元测试,代码覆盖率`72%`;
-
-## `net`
-1. `ghttp`
- - 增加`Prefix`及`Retry`客户端链式操作方法;
- - 增加客户端原始请求打印特性:https://goframe.org/net/ghttp/client/demo/dump
- - 增加`ClientMaxBodySize`的服务端配置,用于限制客户端提交的`Body`大小,默认为`8MB`;在涉及到上传的Server中需要增加该配置的大小,在配置文件中指定对应的大小即可,如`ClientMaxBodySize="100MB"`:https://goframe.org/net/ghttp/config
- - 改进`SessionId`生成的随机性,提高`Session`安全性:https://goframe.org/os/gsession/index
- - 改进`ghttp.Server`实现了标准库的`http.Handler`接口,便于与其他第三方的服务如`Prometheus`进行代码集成;
- - 其他大量的代码细节改进工作,提高性能及持久维护性;
- - 完善单元测试,代码覆盖率`61%`;
-
-1. `gipv4`
- - 增加`GetIpArray`方法,用于获取当前主机的所有IPv4地址;
- - 增加`GetMacArray`及`GetMac`方法,用于获取当前主机的`MAC`地址信息;
- - 修改`IntranetIP`方法名称为`GetIntranetIp`,修改`IntranetIPArray`方法名称为`GetIntranetIpArray`;
-
-## `encoding`
-1. `gjson`
- - 新增`GetMaps`获取`JSON`内部节点变量方法;
- - 改进`NewWithTag`方法对`map/struct`的处理;
- - 完善单元测试,代码覆盖率`77%`;
-1. `gyaml`
- - 升级依赖的第三方`yaml`解析包,解决了`map[interface{}]interface{}`转换问题;
-
-## `error`
-1. `gerror`
- - 新增`NewfSkip`方法,用于创建`skip`指定堆栈的错误对象;
- - 放开框架所有的堆栈链路打印,展示错误时真实的链路调用详情;
-
-## `os`
-1. `gcache`
- - 增加`GetVar`方法,用于获得可以便捷转换为其他数据类型的"泛型"变量;
- - 标记`Removes`方法废弃,改进`Remove`方法参数为不定参数,统一使用`Remove`方法删除单个/多个键值对;
- - 完善单元测试,代码覆盖率`96%`;
-
-1. `genv`
- - 增加`GetVar`方法,用于获得可以便捷转换为其他数据类型的"泛型"变量;
-
-1. `gfile`
- - 改进`CopyDir/CopyFile`复制目录/文件方法;
- - 新增`ScanDirFunc`方法,用于支持自定义处理回调的目录检索;
- - 完善单元测试,代码覆盖率`64%`;
-
-1. `glog`
- - 增加支持`Context`上下文变量的日志打印特性:https://goframe.org/os/glog/context
-
-1. `gres`
- - 改进打包特性,增强生成二进制文件及Go文件的压缩比,比旧版本增加`20%`压缩率,使得编译生成的二进制文件体积更小;
- - 代码结构改进,提高执行效率及可持久维护性;
-
-1. `gsession`
- - 改进`SessionId`默认生成方法,采用`guid.S`方法生成;
- - 增加`SetId`及`SetIdFunc`方法,用于自定义`SessionId`及自定义的`SessionId`生成方法;
-
-## `frame`
-1. `g`
- - 新增`g.Table`方法,用于快速创建数据库模型操作对象;
-
-## `i18n`
-1. `gi18n`
- - 新增`GetContent`方法,用于获取指定`i18n`关键字为转译内容;
- - 改进代码细节,提高性能和持久可维护性;
- - 完善单元测试,代码覆盖率`74%`;
-
-## `test`
-1. `gtest`
- - 增加`AssertNQ`断言方法,用于强类型的不相等判断;
-
-## `text`
-1. `gstr`
- - 增加`SubStrRune`方法,用于支持`unicode`的字符串截取;
- - 增加`StrLimitRune`方法,用于支持`unicode`的字符串截断隐藏;
- - 增加`LenRune`方法,用于替换`RuneLen`方法,统一方法命名风格;
- - 增加`PosRune/PosIRune/PosRRune/PosRIRune`方法,用于支持`unicode`的字符串左右位置查找;
- - 增加`CompareVersionGo`方法,用于`Golang`风格的版本号大小比较;
- - 完善单元测试,代码覆盖率`75%`;
-
-## `util`
-1. `gconv`
- - 改进`Convert`转换方法,支持常见`map`类型的转换;
- - 改进类型转换过程中异常错误的捕获,通过`error`返回;
- - 其他一些细节改进;
- - 完善单元测试,代码覆盖率`63%`;
-
-1. `grand`
- - 增加`B`方法,用于获得随机的二进制数据;
- - 改进代码底层实现,部分接口性能提高`50%`;
- - 完善单元测试,代码覆盖率`74%`;
-
-1. `guid`
- - 新增`guid`模块,用于高效轻量级的唯一字符串生成:https://goframe.org/util/guid/index
-
-1. `gutil`
- - 增加`MapContains`方法,用于判断map中是否包含指定键名;
- - 增加`MapDelete`方法,用于删除map中指定的键名,可以为多个键名;
- - 增加`MapMerge`方法,用于合并两个map;
- - 增加`MapMergeCopy`方法,用于拷贝多个map;
- - 增加`MapContainsPossibleKey`方法,用于查找指定键名,忽略大小写及字符`'-'/'_'/'.'/' '`;
-
-1. `gvalid`
- - 所有默认的错误提示改为了英文;
- - 错误提示的配置改为了通过`i18n`来配置实现,以便支持国际化:https://goframe.org/util/gvalid/message
- - 身份证号规则名称从`id-number`改为了`resident-id `;
- - 银行卡号规则名称从`luhn`改为了`bank-card`;
- - 完善单元测试,代码覆盖率`96%`;
-
-## Bug Fix
-1. 修复`gcompress`的多文件`zip`压缩问题;
-1. 修复`ghttp.Client`获取返回的过期`Cookie`的问题;
-1. 修复`gres.File`对于`http.File`接口的实现细节;
-1. 修复`garray.Pop*`方法的边界问题;
-1. 修复`gres`中`Readdir`方法参数为`0`时报错的问题;
-1. 其他一些修复:https://github.com/gogf/gf/issues?q=is%3Aissue+label%3Abug
-
-
-
-
-# `v1.12.1` (2020-03-31)
-
-大家好啊!久等啦!
-
-由于自从上次版本的发布以来,越来越多小伙伴加入了`GF`的大家庭,并提供了许多不错的建议和反馈,这次版本对其中大部分反馈进行了处理,包括大部分的改进建议和部分新特性,因此这次的版本发布时隔了两个多月。`GF`非常注重代码质量以及可持续维护性,这次版本也进一步对框架大部分模块的示例、注释和单元测试用例进行了完善,目前单元测试用例数量约为`1991`例,代码覆盖率为`71%`,覆盖了所有模块的绝大部分主要功能。
-
-`GF`框架提供了比较常用、高质量的基础开发模块,轻量级、模块化、高性能,推荐大家阅读框架源码了解更多细节。本次发布有个别的不兼容升级,往往批量替换即可,以下`change log`比较完善,建议升级前仔细阅读。
-
-本次发布即意味下一版本开发计划的开启,欢迎更多小伙伴参与开源贡献:https://github.com/gogf/gf/projects/8
-
-感谢大家支持!Enjoy your `GF`!
-
-
-# GoFrame
-
-`GF(Go Frame)`是一款模块化、高性能、生产级的Go基础开发框架。实现了比较完善的基础设施建设以及开发工具链,提供了常用的基础开发模块,如:缓存、日志、队列、数组、集合、容器、定时器、命令行、内存锁、对象池、配置管理、资源管理、数据校验、数据编码、定时任务、数据库ORM、TCP/UDP组件、进程管理/通信等等。并提供了Web服务开发的系列核心组件,如:Router、Cookie、Session、Middleware、服务注册、模板引擎等等,支持热重启、热更新、域名绑定、TLS/HTTPS、Rewrite等特性。
-
-## 特点
-* 模块化、松耦合设计;
-* 模块丰富,开箱即用;
-* 简便易用,易于维护;
-* 社区活跃,大牛谦逊低调脾气好;
-* 高代码质量、高单元测试覆盖率;
-* 详尽的开发文档及示例;
-* 完善的本地中文化支持;
-* 更适合企业及团队使用;
-
-## 地址
-- 官网:https://goframe.org
-- 主库:https://github.com/gogf/gf
-- 码云:https://gitee.com/johng/gf
-
-# Change Log
-
-从`GF v1.12`版本开始,框架要求的最低`Golang`运行版本为`v1.13`,由于`Golang`新版本都是向后兼容的,因此推荐大家更新使用`Golang`新版本:https://golang.google.cn/dl/
-
-> 本次版本也新增了`Swagger`的工具及插件支持,另行独立发布。
-
-## `tool chain`
-1. `gen model`命令新增对`pgsql/mssql/sqlite/oracle`的模型生成支持。
-1. `gen model`命令生成模型新增公开包变量`Columns`用于获得表的字段名称。
-
-## `net`
-1. `ghttp`
- - 注意:从该版本开始,`Server`默认关闭了平滑重启特性。开发者可以通过相应的配置选项打开。
- - 改进`Client.Get`方法,增加可选的请求参数。
- - 新增`Client`链式操作方法:`Header`, `HeaderRaw`, `Cookie`, `ContentType`, `ContentJson`, `ContentXml`, `Timeout`, `BasicAuth`, `Ctx`:https://goframe.org/net/ghttp/client/chain
- - 新增`Request.GetCtx/GetCtxVar/SetCtxVar/Context`上下文变量管理方法,用于请求内部的上下文变量特性:
- - 自定义变量:https://goframe.org/net/ghttp/request/custom
- - 上下文变量:https://goframe.org/net/ghttp/request/context
- - 新增`Request.GetUploadFile/GetUploadFiles`方法,以及`UploadFile`类型,极大简化文件上传处理逻辑:https://goframe.org/net/ghttp/client/demo/upload
- - 新增`Request.GetPage`方法,用于便捷地获得分页对象:
- - 基本介绍:https://goframe.org/util/gpage/index
- - 动态分页:https://goframe.org/util/gpage/dynamic
- - 静态分页:https://goframe.org/util/gpage/static
- - Ajax分页:https://goframe.org/util/gpage/ajax
- - URL模板:https://goframe.org/util/gpage/template
- - 自定义分页:https://goframe.org/util/gpage/custom
- - 改进`Response.Redirect*`方法,增加自定义的跳转HTTP状态码参数。
- - 改进`CORS`特性,完善跨域功能处理,并完全遵守`W3C`关于`OPTIONS`请求方法的规范约定:https://goframe.org/net/ghttp/cors
- - 模板视图对象增加`Request`内置变量,用于模板获得请求参数。由于`GF`框架的模板引擎采用两级缓存设计,减少`IO`开销的同时提升了执行效率,即使增加了大量的内置变量以及内置方法,经过大规模的性能测试,性能比其他`WebServer`库相同逻辑下高出`50% - 200%`的效率。
- - 新增`Server`实验性的`Plugin`特性。
- - 改进`Server`底层路由存储、检索逻辑,优化代码,提升效率。
- - 改进分组路由注册的源码位置记录功能,当路由注册冲突时能够精准定位及提示重复路由源码位置。
- - 改进`Server`日志处理。
- - 完善单元测试。
-
-
-## `database`
-1. `gdb`
- - 代码重构改进,增加`Driver`驱动接口设计,方便开发者自定义驱动实现。新增`Register`包方法,用于开发者注册自定义的数据库类型驱动:https://goframe.org/database/gdb/driver
- - 新增`GetArray`接口及实现,用于获取指定字段列的数据,构造成数组返回:https://goframe.org/database/gdb/chaining/select
- - 新增`InsertIgnore`接口及实现,用于写入时忽略写入冲突,仅对`mysql`数据库类型有效:https://goframe.org/database/gdb/chaining/insert-save
- - 新增`Schema`接口及实现,用于动态切换并获取指定名称的数据库对象:https://goframe.org/database/gdb/chaining/schema
- - 新增`FieldsStr/FieldsExStr`模型方法,用于获取表字段,并构造成字符串返回:hhttps://goframe.org/database/gdb/chaining/fields-retrieve
- - 新增`LockUpdate/LockShared`模型链式操作方法,用于实现悲观锁操作:https://goframe.org/database/gdb/chaining/lock
- - 改进`Where/Data`方法对更新参数输入方式的支持,提高灵活性:
- - https://goframe.org/database/gdb/chaining/select
- - https://goframe.org/database/gdb/chaining/update-delete
- - 查询结果对象`Result`新增`Array`方法,用于获得指定字段的数值,构造成数组返回:https://goframe.org/database/gdb/result
- - 改进`OmitEmpty`方法对`Data`输入参数的过滤,当给定的`Data`参数为空时间对象时,将会被过滤。
- - 增加默认的数据库连接池参数:`MaxIdleConns=10`。
- - 其他一些改进。
- - 完善单元测试。
-
-1. `gredis`
- - 增加/修改默认的数据库连接池参数:`MaxIdle=10`, `IdleTimeout=10s`, `MaxConnLifetime=30s`, `Wait=true`。
- - 完善单元测试。
-
-## `container`
-1. 所有容器对象新增`UnmarshalValue(interface{}) error`接口方法实现,用于`gconv`转换时根据任意类型变量创建/设置对象内容,`GF`框架的所有容器对象均实现了该接口。
-
-1. `garray`
- - 新增`RemoveValue`方法,用于根据数值检索并删除元素项。
- - 新增`FilterNil`方法,用于遍历并删除数组中的`nil`元素项。
- - 新增`FilterEmpty`方法,用于遍历并删除数组中的空值元素项,空值包括:`0, nil, false, "", len(slice/map/chan) == 0`。
- - 改进`Set/Fill/InsertBefore/InsertAfter`方法严谨性,将原有的链式操作返回值对象修改为`error`返回值。
- - 改进`Get/Remove/PopRand/PopLeft/PopRight/Rand`方法严谨性,增加`found`的`bool`返回值,标识当前方法是否有数据返回,当空数组或者操作越界时`found`返回值为`false`。
- - 改变`Rands`方法原有逻辑,保证按照给定大小返回随机数组。
- - 完善单元测试。
-
-1. `gpool`
- - 调整缓存池过期时间参数类型,旧版本为`int`类型表示毫秒,新版本为`time.Duration`类型使得时间控制更灵活。
- - 内部代码优化,性能改进。
- - 完善单元测试。
- - 完善注释。
-
-## `os`
-1. `glog`
- - 增加`Rotation`日志滚动切分特性,新增按照文件大小或过期时间进行日志切分,并支持切分文件数量限制、对日志文件进行自动压缩、可自定义压缩级别(`1-9`)、支持对切分文件过期时间清理:https://goframe.org/os/glog/rotate
- - 新增`LevelPrefixes`特性,支持对日志级别的前缀名称进行自定义:https://goframe.org/os/glog/level
- - 新增`SetLevelStr`方法,并增加按照字符串进行日志级别配置的特性:
- - https://goframe.org/os/glog/level
- - https://goframe.org/os/glog/config
- - 新增`SetDefaultLogger`包方法,用于设置全局默认的`Logger`对象。
-
-1. `gres`
- 1. 改进资源内容编码设计,采用新的压缩算法,减少资源文件大小,例如原本`15MB`的网站静态资源文件(`css/js/html`等),资源文件打包后约为`4MB`左右:https://goframe.org/os/gres/index
- 1. 注意:该改进与旧版本无法兼容,需要重新打包原有的资源文件。
- 1. 完善单元测试。
-
-1. `gcfg`
- - 去掉配置对象属性的原子并发安全控制,改为普通变量类型。由于配置管理是最常用模块之一,因此确保高效的设计及方法实现。
- - 单例对象做较大调整:为方便多文件场景下的配置文件调用,简便使用并提高开发效率,因此当给定的单例名称对应的`toml`配置文件在配置目录中存在时,将自动设置该单例对象的默认配置文件为该文件。例如:`g.Cfg("redis")`获取到的单例对象将会默认去检索并设置默认的配置文件为`redis.toml`,当该文件不存在时,则使用默认的配置文件(`config.toml`):https://goframe.org/net/ghttp/config
- - 完善单元测试。
-
-1. `gview`
- - 新增`concat`内置字符串拼接方法:https://goframe.org/os/gview/function/buildin
- - 完善单元测试。
-
-1. `gfile`
- - 改进`SelfPath`获取当前执行文件路径方法,提高执行效率。
- - 改进`Join`文件路径连接方法,防止多余的路径连接符号。
- - 改建`GetContents`文件内容获取方法执行效率,降低内存占用。
- - 新增`StrToSize`方法,用于将大小字符串转换为字节数字,大小字符串例如`10m`, `5KB`, `1.25Gib`等。
- - 新增`ReadLines/ReadByteLines`方法,用于按行读取指定文件内容,并给定读取回调函数。
- - 完善单元测试。
-
-1. `gtime`
- - 改进`gtime.Time`对象实现,统一字符串打印时间格式为`Y-m-d H:i:s`,如:`2020-01-01 12:00:00`。
-
-1. `gcmd`
- - 命令行解析方法增加`strict`参数,用于设置当前解析是否严格解析,严格解析下如果给定了非法的选项,将会停止解析并返回错误。默认情况下为非严格解析。
-
-1. `genv`
- - 改进`Remove`删除环境变量键值对方法,增加对多个键值对环境变量的删除。
-
-1. `gfpool`
- - 改进代码实现,提高效率。
- - 完善单元测试。
-
-1. `gfsnotify`
- - 改进包初始化方法,当系统原因引起默认`Watcher`对象创建失败时,直接`panic`。
-
-1. `gproc`
- - 改进`SearchBinaryPath`方法。
- - 改进`Process.Kill`方法在`windows`及`*niux`平台下不同表现的处理。
-
-## `encoding`
-1. `gjson`
- - 代码改进、完善注释、新增大量代码示例。
- - 文档更新:
- - 基本介绍:https://goframe.org/encoding/gjson/index
- - 对象创建:https://goframe.org/encoding/gjson/object
- - 层级访问:https://goframe.org/encoding/gjson/pattern
- - Struct转换:https://goframe.org/encoding/gjson/conversion-struct
- - 动态创建修改:https://goframe.org/encoding/gjson/dataset
- - 数据格式转换:https://goframe.org/encoding/gjson/conversion-format
-
-## `frame`
-1. `g`
- - 新增`IsNil`方法,用于判断当前给定的变量是否为`nil`,该方法有可能会使用到反射来实现判断。
- - 新增`IsEmpty`方法,用于判断当前给定的变量是否为`空`,该方法优先使用断言判断但有可能会使用到反射来实现判断。空值包括:`0, nil, false, "", len(slice/map/chan) == 0`。
- - 标记废弃`SetServerGraceful`方法,转而统一交给`Server`的配置来管理。
-1. `gins`
- - 改进代码结构,方便维护。
- - 改进、完善单元测试。
-1. `gmvc`
- - 新增`M`类型,为`*gdb.Model`的别名简称,用于工具链自动生成模型中的`M`属性。
-
-## `text`
-1. `gstr`
- - 新增`HasPrefix/HasSuffix`方法。
- - 新增`OctStr`方法,用于将八进制字符串转换为其对应的`unicode`字符串,例如转换为中文。常用于`gRPC`底层通信编码中。
- - 完善单元测试。
-
-## `debug`
-1. `gdebug`
- - 改进代码结构,方便维护。
- - 新增`TestDataPath`方法,用于当前包单元测试中获得当前包中`testdata`目录的绝对路径。
-
-## `util`
-1. `gconv`
- - 改进`String`字符串转换方法,增加对`time.Time/*time.Time/gtime.Time`类型的内置支持。
- - 改进`Map/Struct`转换方法,增加对一些特殊场景的细节处理。经过大规模的使用,大量的反馈改进,不断完善了细节。
- - 改进`Struct`转换方法,增加对`UnmarshalValue(interface{}) error`接口的支持。
- - 完善单元测试。
-
-1. `grand`
- - 注意:不兼容调整,原有的`Str`方法更名为`S`方法,用以获取指定长度的随机字符串、数字,并增加`symbol`参数,指定是否可以随机返回特殊可见字符。
- - 新增`Str`方法,用于从指定的字符串字符中随机获取指定长度的字符串。该方法同时支持`unicode`字符串,例如:中文:https://goframe.org/util/grand/index
- - 新增`Symbols`方法,用于随机返回指定场孤独的特殊可见字符:https://goframe.org/util/grand/index
- - 完善单元测试。
-
-1. `gvalid`
- - 长度校验规则增加对`unicode`字符串的支持,例如:中文。
-
-# Bug Fix
-1. 修复`Server`的视图对象配置失效问题。
-1. 修复`Server`中间件在中间件`panic`情况下,忽略`Middleware.Next`方法控制,导致鉴权中间件失效的问题。
-1. 修复`gudp.Server`在请求包大小超过`64bytes`时的断包问题,并调整默认缓冲区大小为`1024byte`,开发者可自定义缓冲区大小。
-1. 修复`gfile.MTimeMillisecond`方法返回错误的文件修改毫秒时间戳。
-1. 修复`gconv.Int64`对负数转换的支持。
-1. 其他一些修复。
-1. 详见:https://github.com/gogf/gf/issues?q=label%3Abug
-
-
-
-# `v1.11.2` (2020-01-14)
-
-`GF(Go Frame)` https://goframe.org 是一款模块化、高性能、生产级的Go基础开发框架。实现了比较完善的基础设施建设,包括常用的核心开发组件, 如:缓存、日志、文件、时间、队列、数组、集合、字符串、定时器、命令行、文件锁、内存锁、对象池、连接池、资源管理、数据校验、数据编码、文件监控、 定时任务、数据库ORM、TCP/UDP组件、进程管理/通信、并发安全容器等等。 并提供了Web服务开发的系列核心组件,如:Router、Cookie、Session、Middleware、服务注册、配置管理、模板引擎等等, 支持热重启、热更新、多域名、多端口、多服务、HTTPS、Rewrite等特性。
-
-`GF`有着丰富的基础模块、完善的工具链、详尽的开发文档。开源近两年以来,`GF`得到越来越多小伙伴的肯定和支持,从寂寂无名到现在被广泛应用于微服务、物联网、区块链、电商系统、银行系统等企业级的生产项目中,经历了百万级、千万级项目的考验,2019年度被码云`gitee`评选为`GVP`最有价值开源项目。`GF`正在快速地成长中,目前保持着1-2个月迭代版本的发布规律,社区活跃,欢迎加入`GF`大家庭。
-
-最后,祝大家2020新年快乐,鼠年大吉!
-
-
-
-## 新特性
-
-1. 新年新气象,官网文档大量更新:https://goframe.org/index
-1. `GF`工具链更新:https://goframe.org/toolchain/cli
- - 新增`gf run`热编译运行命令;
- - 新增`gf docker` Docker镜像编译命令;
- - 新增`gf gen model` 强大的模型自动生成命令;
- - `gf build`命令增加对配置文件配置支持;
- - 大量命令行工具改进工作;
- - 新增自动代理设置特性;
-1. 数据库`ORM`新特性:
- - 增加`prefix`数据表前缀支持:https://goframe.org/database/gdb/config
- - 新增`Schema`数据库对象并改进数据库切换特性:https://goframe.org/database/gdb/chaining/schema
- - 新增`WherePri`方法,用于自动识别主键的条件方法:https://goframe.org/database/gdb/chaining/select
- - 文档及示例大量更新,覆盖95%以上的功能特性;
-
-
-
-## 功能改进
-
-### `container`
-1. `garray`
- - 新增`New*ArrayRange`方法,用于初始化创建指定数值范围的数组。
- - 新增`Iterator*`方法,用于数组项元素回调遍历。
- - 完善单元测试。
-1. `gvar`
- - 改进`MapStrStr`、`MapStrStrDeep`方法实现。
-
-### `net`
-1. `ghttp`
- - 改进HTTP客户端,增加对提交参数的自动`Content-Type`识别功能。
- - `Request`对象增加`Parse`方法,用于快捷的对象转换即参数校验。
- - `Request.GetPost*`方法全部标记为`deprecated`,统一客户端参数提交方式为`QueryString`, `Form`, `Body`。
- - 去掉`Response`模板解析时的`Get`/`Post`内置变量,新增`Query`, `Form`, `Request`内置变量:https://goframe.org/net/ghttp/response/template
- - 改进`Response.WriteJson*`及`Response.WriteXml*`方法,增加对`string`, `[]byte`类型参数的支持。
- - `Server`新增`GetRouterArray`方法,用于向应用层暴露并获取`Server`的路由列表。
- - `Server`新增`Use`方法,该方法为`BindMiddlewareDefault`的别名,用以全局中间件的注册。
- - `Server`新增`RouteOverWrite`配置项,用于控制是否在注册路由冲突时自动覆盖,默认关闭并提示。
- - `Server`新增`Graceful`配置项,用于在单服务场景下控制平滑重启特性的开启/关闭,默认开启。
- - 完善单元测试。
-1. `gtcp`
- - 改进简单协议下的数据包发送接收功能。
- - 将连接池默认的缓存过期时间`30`秒修改为`10`秒。
- - 完善单元测试。
-
-### `database`
-1. `gdb`
- - 新增`As`数据表别名方法。
- - 改进数据表、字段的安全字符自动识别添加功能。
- - 新增`DB`数据库对象切换方法。
- - 新增`TX`链式操作事务支持方法。
- - 完善单元测试。
-### `os`
-1. `gcfg`
- - 新增`GetMapStrStr`方法。
-1. `gcmd`
- - 增加参数解析的`strict`严格参数,默认严格解析,不存在指定参数/选项名称时则报错返回。
-1. `genv`
- - 改进`Remove`方法支持多个环境变量的删除。
-1. `gfile`
- - 改进`TempDir`临时目录获取方法,在`*nix`系统下默认为`/tmp`目录。
- - 新增`ReadLines`, `ReadByteLines`方法,用以按行回调读取文件内容。
- - 新增`Copy*`方法,用以文件/目录的拷贝,支持递归。
- - 新增`Replace*`方法,用以目录下的文件内容替换,支持递归。
- - 改进`Scan*`方法,用以检索并返回指定目录下的所有文件/目录,支持文件模式指定,支持递归。
- - 完善单元测试。
-1. `gproc`
- - 改进命令行运行方法。
- - 改进`Shell`命令文件检索逻辑。
- - 改进实验性的进程间通信设计。
-1. `gtime`
- - 将包方法以及`Time`对象的时间戳方法`Second`, `Millisecond`, `Microsecond`, `Nanosecond`标记为废除,
- 并新增`Timestamp`, `TimestampMilli`, `TimestampMicro`, `TimestampNano`替换。
- - 需要注意的是以上修改可能和老版本存在兼容性问题。
-
-1. `gview`
- - 解析功能、缓存设计改进。
- - 新增`encode`, `decode`HTML编码/解码模板函数。
- - 新增`concat`字符串拼接模板函数。
- - 新增`dump`模板函数,功能类似于`g.Dump`方法。
- - 新增`AutoEncode`配置项,用于自动转码输出的`HTML`内容,常用于防止`XSS`,默认关闭。需要注意的是该特性并不会影响`include`内置函数: https://goframe.org/os/gview/xss
- - 单元测试完善。
-
-### `crypto`
-1. `gmd5`
- - 增加`MustEncrypt`, `MustEncryptBytes`, `MustEncryptString`, `MustEncryptFile`方法。
-1. `gsha1`
- - 增加`MustEncryptFile`方法
-
-### `encoding`
-1. `gbase64`
- - 新增`MustEncodeFile`, `MustEncodeFileToString`, `MustDecode`, `MustDecodeToString`方法。
-1. `gjson`/`gparser`
- - 新增`GetMapStrStr`方法。
- - 新增`Must*`方法,用于指定数据格式的转换失败时产生`panic`错误,而不会返回`error`参数。
-
-### `util`
-1. `gconv`
- - 改进`Convert`方法增加对`[]int32`, `[]int64`, `[]uint`, `[]uint32`, `[]uint64`, `[]float32`, `[]float64`数据类型的转换支持。
- - 改进`String`字符串转换方法对指针参数的支持。
- - 改进`Map*` Map转换方法的代码结构及性能。
- - 新增`Floats`, `Float32s`, `Float64s`对`[]float32`, `[]float64`类型转换方法。
- - 新增`Ints`, `Int32s`, `Int64s`对`[]int`, `[]int32`, `[]int64`类型转换方法。
- - 新增`Uints`, `Uint32s`, `Uint64s`对`[]uint`, `[]uint32`, `[]uint64`类型转换方法。
- - 完善单元测试。
-
-
-### `frame`
-1. `gins`
- - 所有的单例对象在获取失败时产生`panic`错误。
-
-## Bug Fix
-1. 增加对常见错误路由格式例如`/user//index`的兼容支持。
-1. 修复`gtcp`/`gudp`在数据接收时的间隔时间单位问题。
-1. 修复`gfile`/`gspath`/`gfsnotify`包对文件的存在性判断不严谨问题。
-1. 修复`gproc.Kill`方法在`windows`系统下的运行阻塞问题。
-1. 修复`gstr.TrimLeftStr`/`gstr.TrimRightStr`在被替换字符串长度小于替换字符串长度时的数组溢出问题。
-
-
-
-# `v1.10.0` (2019-12-05)
-
-各位`gfer`久等了,较上一次发布时间过去已有两个多月了,这段时间`GF`也在不断地迭代改进,细节比较多,拟了个大概,以下是`release log`。
-
-另外,`GoFrame`也参加了2019最受欢迎中国开源软件评选投票,明天就结束了,欢迎为`GF`投票啊:https://www.oschina.net/project/top_cn_2019 网页可以投一票,微信也可以投一票。
-
-## 新特性
-
-1. `Web Server`新特性:
- - 改进中间件及分组路由实现:https://goframe.org/net/ghttp/router/middleware
- - 增加文件配置管理特性:https://goframe.org/net/ghttp/config
- - 改进参数获取:https://goframe.org/net/ghttp/request
- - 改进文件上传:https://goframe.org/net/ghttp/client/demo/upload
-1. `Session`增加内置的多种`Storage`实现:
- - 基本介绍:https://goframe.org/os/gsession/index
- - 文件存储:https://goframe.org/os/gsession/file
- - 内存存储:https://goframe.org/os/gsession/memory
- - `Redis`存储:https://goframe.org/os/gsession/redis
-1. 增加日志组件单例对象,并优化配置管理:
- - https://goframe.org/frame/g/index
- - https://goframe.org/os/glog/config
-1. 常用的`container`容器增加`JSON`数据格式的`Marshal`/`UnMarshal`接口实现:
- - https://goframe.org/container/gmap/index
- - https://goframe.org/container/garray/index
- - https://goframe.org/container/gset/index
- - https://goframe.org/container/gvar/index
- - https://goframe.org/container/gtype/index
- - https://goframe.org/container/glist/index
- - https://goframe.org/container/gvar/index
-1. 新增`guuid`模块,用于通用的`UUID`生成:https://goframe.org/util/guuid/index
-
-## 功能改进
-
-### `net`
-1. `ghttp`
- - 改进请求流程处理性能;
- - `Server`增加对`Logger`日志对象的配置;
- - `Server`开放了`GetRouterMap`方法,用于获得当前服务的路由列表信息,使得开发者可以更方便地实现自定义权限管理;
- - `Server`配置管理优化;
- - `Client`客户端对象进行了大量的改进工作;
- - `Client`客户端对象增加多文件上传功能;
- - `Request`对象增加`GetError`方法,用于获取当前处理错误;
- - `Request`对象增加独立的视图对象及视图变量绑定功能,使得每个请求可以独立视图管理,也可以通过中间件切换请求对象的视图对象。默认情况下该功能关闭,视图解析时使用的是`Server`对象的视图对象;
- - 改建`Response`对象的`CORS`功能;
- - 增加`Response.WriteTplDefault`方法,用于解析并返回默认的模板内容;
- - 增加更多的单元测试用例;
- - 其他改进;
-1. `gipv4`/`gipv6`
- - 一些改进工作;
-1. `gtcp`/`gudp`
- - 一些改进工作;
-
-### `database`
-1. `gdb`
- - 大量细节改进工作;
- - 去掉查询数据为空时的`sql.ErrNoRows`错误返回,保留`Struct`/`Structs`/`Scan`方法在操作数据为空的该错误返回;
- - 调试模式开启时,输出的SQL语句改进为完整的带参数的SQL,仅作参考;
- - `Where`方法增加对`gmap`数据类型支持,包括顺序性的`ListMap`/`TreeMap`等等;
- - 查询缓存方法`Cache`的缓存时间参数类型修改为`time.Duration`;
- - 修改`Record`/`Result`的数据类型转换方法名称,原有的转换方法标记为`deprecated`;
- - `Record`/`Result`查询结果类型增加`IsEmpty`方法,用于判断结果集是否为空;
- - `Record`类型增加`GMap`方法,用于将查询记录转换为`gmap`类型;
- - 增加`Option`/`OptionOmitEmpty`方法,用于输入参数过滤,包括`Data`参数及`Where`参数:https://goframe.org/database/gdb/empty
- - 增加字段排除方法`FieldsEx`:https://goframe.org/database/gdb/senior
- - 增加日志功能特性:https://goframe.org/database/gdb/senior
- - 改进数据库配置管理:https://goframe.org/database/gdb/config
- - 增加大量单元测试;
-1. `gredis`
- - 返回数据类型转换改进:https://github.com/gogf/gf/issues/415
- - 完善单元测试;
- - 其他改进;
-
-### `os`
-1. `gcache`
- - 需要注意了:缓存的有效时间参数从`interface{}`类型调整为了`time.Duration`类型,因此不再兼容之前的`int`参数类型,以保证更好的性能;
-1. `gfcache`
- - 由于`gcache`组件的缓存时间参数类型的变更,因此该组件的时间参数也变更为了`time.Duration`类型;
-1. `gcfg`
- - 增加`Available`方法,用以判断配置是否有效;
-1. `gfile`
- - 增加`Chdir`方法,用于工作目录切换;
-1. `gtime`
- - 增加`JSON`数据格式的`Marshal`/`UnMarshal`接口实现;
-
-### `container`
-1. `gmap`
- - 增加`MapStrAny`方法,用于常见`map`类型的转换;
- - 增加`MapCopy`方法,用于底层`map`数据复制;
- - 增加`FilterEmpty`方法,用于`map`空值过滤;
- - 增加`Pop`/`Pops`方法,用于随机返回`map`中的数据项(并删除);
- - 增加`Replace`方法,用于给定的`map`数据覆盖底层`map`数据项;
- - 完善单元测试;
- - 其他改进;
-1. `garray`
- - 增加`Interfaces`转换方法,返回`[]interface{}`类型;
- - 对排序数组增加`SetComparator`方法用户自定义修改比较器;
- - 完善单元测试;
- - 其他改进;
-1. `glist`
- - 增加`NewFrom`方法,基于给定的`[]interface{}`变量创建链表;
- - 增加`Join`方法,用于将链表项使用给定字符串连接为字符串返回;
- - 完善单元测试;
- - 其他改进;
-1. `gset`
- - 增加`AddIfNotExistFunc`/`AddIfNotExistFuncLock`方法;
- - 完善单元测试;
- - 其他改进;
-1. `gtree`
- - 增加`Replace`方法,用于更新现有树的数据项;
- - 其他改进;
-1. `gtype`
- - 一些细节改进工作,不一一列出;
- - 完善基准测试、单元测试;
-1. `gvar`
- - 增加`Ints`/`Uints`类型转换方法;
- - 其他改进;
-
-### `crypto`
-1. `gmd5`
- - 小细节改进;
-1. `gsha1`
- - 小细节改进;
-
-### `text`
-1. `gstr`
- - 改进`SplitAndTrim`方法,将`SplitAndTrimSpace`标记为`deprecated`;
- - 增加`TrimStr`方法;
- - 完善单元测试;
- - 其他改进;
-
-### `debug`
-
-1. `gdebug`
- - 增加`CallerFileLineShort`/`FuncPath`/`FuncName`方法;
- - 其他改进;
-
-### `encoding`
-
-1. `gbase64`
- - 增加`EncodeToString`/`EncodeFile`/`EncodeFileToString`/`DecodeToString`方法;
- - 完善单元测试;
-1. `gjson`
- - 完善单元测试;
-
-### `frame`
-
-1. `g`/`gins`
- - https://goframe.org/frame/g/index
- - 增加`CreateVar`方法;
- - 完善单元测试;
- - 其他改进;
-
-### `util`
-
-1. `gconv`
- - 改进优化部分类型转换方法性能;
- - 增加`Uints`/`SliceUint`类型转换方法;
- - 增加`UnsafeStrToBytes`/`UnsafeBytesToStr`高性能的类型转换方法;
- - 增加对`MapStrAny`接口方法的支持,用于常见`map`类型的转换;
- - 其他改进;
-1. `gvalid`
- - 改进对中国身份证号的识别校验功能;
- - 增加`luhn`银行卡号的校验功能;
-1. `grand`
- - 一些性能改进工作;
-
-
-## Bug Fix
-1. 解决`WebSocket`关闭时的`hijacked`报错问题:https://github.com/gogf/gf/issues/381
-1. 解决静态文件服务时大文件的内存占用问题;
-1. 修复前置`Nginx`后默认情况下的`Cookie`域名设置问题;
-1. 修复`gconv.Struct`在属性为`[]struct`并且输入属性参数为空时的转换失败问题:https://github.com/gogf/gf/issues/405
-1. 其他一些修复;
-
-
-
-# `v1.9.3` (2019-09-24)
-
-该版本实际为`v2.0`的大版本发布,为避免`go module`机制严格要求`v2`版本以上需要修改`import`并加上`v2`后缀,因此使用了`v1.9`版本进行发布。
-
-## 新特性
-
-1. 新增`gf`命令行开发辅助工具:https://goframe.org/toolchain/cli
-1. 新增`gres`资源管理器模块:https://goframe.org/os/gres/index
-1. 重构`Session`功能,新增`gsession`模块,`WebServer`默认使用文件存储`Session`:https://goframe.org/net/ghttp/session
-1. `WebServer`新增中间件特性,并保留原有的HOOK设计,两者都可实现请求拦截、预处理等等特性:https://goframe.org/net/ghttp/router/middleware
-1. 新增`gi18n`国际化管理模块:https://goframe.org/i18n/gi18n/index
-1. 新增`gini`模块:https://goframe.org/encoding/gini/index
-1. `WebServer`新增更便捷的层级路由注册方式:https://goframe.org/net/ghttp/group/level
-1. `gcmd`命令行参数解析模块重构,增加`Parser`解析对象:https://goframe.org/os/gcmd/index
-1. 新增`gdebug`模块,用于堆栈信息获取/打印:https://goframe.org/debug/gdebug/index
-
-
-## 重大调整
-1. 去掉`1.x`版本中已经被标记为`deprecated`的方法;
-1. 调整`container`分类的容器模块,将默认并发安全参数调整为默认非并发安全;
-1. 目录调整:
- - 去掉`third`目录,统一使用`go module`管理包依赖;
- - 将原有`g`目录中的模块移出到框架主目录,原有的`g`模块移动到`frame/g`目录;
- - 将原有`geg`示例代码目录名称修改为`.example`;
-
-
-
-## 功能改进
-
-1. `ghttp`
- - 改进`Request`参数解析方式:https://goframe.org/net/ghttp/request
- - 改进跨域请求功能,新增`Origin`设置及校验功能:https://goframe.org/net/ghttp/cors
- - `Cookie`及`Session`的`TTL`配置数据类型修改为`time.Duration`;
- - 新增允许同时通过`Header/Cookie`传递`SessionId`;
- - 新增`ConfigFromMap/SetConfigWithMap`方法,支持通过`map`参数设置WebServer;
- - 改进默认的`CORS`配置,增加对常见`Header`参数的默认支持;
- - 新增`IsExitError`方法,用于开发者自定义处理`recover`错误处理时,过滤框架本身自定义的非异常错误;
- - 新增`SetSessionStorage`配置方法,用于开发者自定义`Session`存储;
- - `ghttp.Request`新增更多的参数获取方法;
-1. `gdb`
- - 增加对SQL中部分字段的自动转义(`Quote`)功能;
- - 增加对方法操作以及链式操作中的`slice`参数的支持;
- - 增加`SetLogger`方法用于开发者自定义数据库的日志打印;
- - 增加`Master/Slave`方法,开发者可自主选择数据库操作执行的主从节点;
- - 增加对`mssql/pgsql/oracle`的单元测试;
- - 在`debug`模式支持完整带参数整合的SQL语句调试打印;
- - 增加了更多的功能方法;
-1. `glog`
- - 新增`Default`方法用于获取默认的`Logger`对象;
- - 新增`StackWithFilter`方法用于自定义堆栈打印过滤;
- - 增加了更多的功能方法;
-1. `gfile`
- - 部分方法名称调整:`Get/PutBinContents`修改为`Get/PutBytes`;
- - 增加`ScanDirFile`方法,用于仅检索文件目录,支持递归检索;
- - 增加了更多的功能方法;
-1. `gview`
- - 新增`SetI18n`方法用于设置视图对象自定义的`gi18n`国际化对象;
- - 新增对`gres`资源管理器的内置支持;
-1. `gcompress`
- - 增加`zip`算法的文件/目录的压缩/解压方法;
- - 文件/目录压缩参数支持多路径;
-1. `gconv`
- - 改进对`[]byte`数据类型参数的支持;
- - 新增`Unsafe`转换方法,开发者可在特定场景下使用,提高转换效率;
- - 新增`MapDeep/StructDeep/StructsDeep`方法,支持递归`struct`转换;
-1. `gjson/gparser`
- - 改进类型自动识别功能;
- - 新增`LoadJson/LoadXml/LoadToml/LoadYaml/LoadIni`方法用于自定义的数据类型内容加载;
- - 增加了更多的功能方法;
-1. `gerror`
- - 改进错误堆栈获取逻辑;
- - 增加了更多的功能方法;
-1. `gmap/garray/gset/glist/gvar`
- - 改进并发安全基准测试脚本;
- - 修改`garray.StringArray`为`garray.StrArray`;
- - 增加了更多的功能方法;
-1. `gdes`
- - 规范化修改方法名称;
-1. `gstr`
- - 增加`Camel/Snake`相关命名转换方法;
- - 增加了更多的功能方法;
-1. `genv`
- - 增加了更多的功能方法;
-
-
-## Bug Fix
-1. 修复`gvalid`校验`struct`时的`tag`自定义错误失效的问题;
-1. 修复`gcfg`配置管理模块在特定情况下的内容类型自动识别失败问题;
-1. 修复`gqueue`在用户主动关闭队列时的并发安全问题;
-1. 修复`session`在开发者设置的`TTL`过大时的整型变量溢出问题;
\ No newline at end of file
diff --git a/TODO.Deprecated.MD b/TODO.Deprecated.MD
deleted file mode 100644
index 44b4f00f5..000000000
--- a/TODO.Deprecated.MD
+++ /dev/null
@@ -1,135 +0,0 @@
-> This markdown is deprecated and will be removed in future. All TODO features are staged in the issue: https://github.com/gogf/gf/issues
-
-# ON THE WAY
-1. 增加图形验证码支持,至少支持数字和英文字母;
-1. Cookie&Session数据池化处理;
-1. ghttp.Client增加proxy特性;
-1. gtime增加对时区转换的封装,并简化失去转换时对类似+80500时区的支持;
-1. orm增加sqlite对Save方法的支持(去掉触发器语句);
-1. ghttp.Server增加Ip访问控制功能(DenyIps&AllowIps);
-1. ghttp增加返回数据压缩机制;
-1. ghttp.Server增加proxy功能特性,本地proxy和远程proxy,本地即将路由规则映射;远程即反向代理;
-1. gjson对大json数据的解析效率问题;
-1. ghttp增加route name特性,并同时支持backend和template(提供内置函数)引用,可以通过RedirectRoute方法给定route name和路由参数跳转到指定的路由地址上;
-1. gvalid校验支持当第一个规则失败后便不再校验后续的规则,最好做成链式操作;
-1. ghttp.Request增加对输入参数的自动HtmlEncode机制;
-1. 常量命名风格根据golint进行修改;
-1. 开放rwmutex包,并将gjson的互斥锁使用自定义的mutex替换;
-1. 文档完善:
- - gconv struct tag、
- - 控制器及执行对象注册的Init&Shut方法、
- - ghttp.Response&ServeFile、gfcache、gproc shell执行、
- - ghttp Server&Client basic auth、
- - glog分类&日志等级&链式操作、gdb debug自动输出调试信息、gmlock内存锁、
-1. 服务注册域名增加对泛域名的支持;
-1. 项目参考:
- - https://github.com/namreg/godown
- - https://github.com/Masterminds/sprig
-1. gform参考 https://gohouse.github.io/gorose/dist/index.html 进行改进
-1. gtcp提供简便的包发送/接收方法(SendPkg/RecvPkg)以解决常见的TCP通信粘包问题,并完善文档(参考:https://www.cnblogs.com/kex1n/p/6502002.html);
-1. 路由增加不区分大小写得匹配方式;
-1. 改进WebServer获取POST参数处理逻辑,当提交非form数据时,例如json数据,针对某些方法可以直接解析;
-1. WebServer增加可选择的路由覆盖配置,默认情况下不覆盖;
-1. 增加jumplist的数据结构容器;
-1. DelayQueue/PriorityQueue;
-1. 权限管理模块;
-1. 从ghttp中剥离SESSION功能构成单独的模块gsession;
-1. 改进gproc进程间通信处理逻辑,提高稳定性,以应对进程间大批量的数据发送/接收;
-1. 添加Save/Replace/BatchSave/BatchReplace方法对sqlite数据库的支持;
-1. 添加sqlite数据库的单元测试用例;
-1. gredis增加cluster支持;
-1. gset.Add/Remove/Contains方法增加批量操作支持;
-1. gmlock增加手动清理机制:当内存锁不再使用时,由调用端决定是否清理内存锁;
-1. gtimer增加DelayAdd*方法返回Entry对象,以便DelayAdd*的定时任务也能进行状态控制;gcron同理需要改进;
-1. grpool增加支持阻塞添加任务接口;
-1. gdb.Model在链式安全的对象创建中增加sync.Pool的使用;
-1. 增加g.Table快捷方法以方便操作数据表,但是得考虑后续模型操作设计,特别是脚手架的模型管理;
-
-
-
-
-
-
-# DONE
-1. Cookie设置中文失效问题;
-1. gvalid增加支持对[]rune的长度校验(一个中文占3个字节);
-1. grpool性能压测结果变慢的问题;
-1. ghttp的热重启的本地进程端口监听,在不使用该特性时默认关闭掉;
-1. gtcp增加对TLS加密通信的支持;
-1. 改进gdb对pgsql/mssql/oracle的支持,使用方法覆盖的方式改进操作,而不是完全依靠正则替换的方式;
-1. gdb的Cache缓存功能增加可自定义缓存接口,以便支持外部缓存功能,缓存接口可以通过io.ReadWriter接口实现;
-1. 改进ghttp分组路由中对hook的支持方式,以便格式与BindHookHandler统一;
-1. 使用gconv将slice映射到struct属性上,例如redis hscan的结果集;
-1. gconv完善针对不同类型的判断,例如:尽量减少sprintf("%v", xxx)来执行string类型的转换;
-2. ghttp.Server请求执行中增加服务退出的方法,不再执行后续操作;
-3. ghttp.Response对象完善并改进数据返回方法(Write/WriteString);
-4. ghttp.Server请求执行中增加服务退出的方法,不再执行后续操作;
-5. 增加fsnotify包支持;
-6. 改进gcfg和gview的文件自动更新机制;
-7. 将模板变量进行暴露,以便应用端可以进行灵活控制;
-8. 跟踪第三方mxj包的issue问题:https://github.com/clbanning/mxj/issues/48;
-9. gdb Where方法参数的改进,研究是否可以将string参数类型修改为interface{};
-10. gpage分页控制功能;
-11. https支持;
-12. ghttp.Server日志中增加请求时间和返回时间,以便计算执行时间差;
-13. 由于去掉了gdb的单例模式,并且将gins的部分对象封装迁移到了g包中,需要同时梳理文档,完善修改;
-14. 在代码中增加https与http同时开启使用的示例代码,这块大家问得比较多;
-15. ghttp.Server多个事件之间通过ghttp.Request.Param自定义参数传参;
-16. 研究是否增加配置文件目录检索功能,特别是如何友好改进开发环境的配置文件默认目录问题;
-17. 增加ghttp.Server不同状态码的自定义处理方法;
-18. ghttp.Server平滑重启方案;
-19. 完善gconv类型转换功能,增加time.Time/time.Duration类型转换,并增加benchmark测试脚本
-20. 当二进制参数为nil时,gjson.LoadContent并将gjson.Json对象ToMap时会报错;
-21. 改进控制器及执行对象注册,更友好地支持动态路由注册,例如:注册规则为 /channel/:name,现有的控制器及执行对象注册很难友好支持这种动态形式;
-22. 当前gpage分页包的输出标签不支持li,大多数CSS框架都是li+a标签模式,需要提供可更加灵活的定制化功能实现;
-23. 平滑重启机制改进,以便于开发阶段调试;
-24. 对grpool进行优化改进,包括属性原子操作封装采用gtype实现,修正设计BUG:https://github.com/gogf/gf/issues/6;
-25. gredis增加redis密码支持;
-26. 改进ghttp.Server平滑重启机制,当新进程接管服务后,再使用进程间通信方式通知父进程销毁;
-27. gproc进程间通信增加分组特性,不同的进程间可以通过进程ID以及分组名称发送/获取进程消息;
-28. ORM增加获取被执行的sql语句的方法;
-29. gdb增加查询缓存特性;
-30. gpage分页增加对自定义后缀的支持,如:2.html, 2.php等等;
-31. gvalid包增加struct tag的校验规则、自定义错误提示信息绑定的支持特性;
-32. 增加文件缓存包,可根据fsnotify机制进行缓存更新;
-33. *any/:name路由匹配路由改进支持不带名字的*/:路由规则;
-34. ghttp静态文件服务改进(特别是403返回状态的修改);
-35. map转struct增加对tag的支持;
-36. gcache检查在i386下的int64->int转换问题;
-37. ghttp获取参数支持直接转struct功能;
-38. gfsnotify增加对于目录的监控;
-39. 检查windows下的平滑重启失效问题;
-40. ghttp.Server的Cookie及Session锁机制优化(去掉map锁机制);
-41. 解决glog串日志情况;
-42. glog增加对日志文件名称的生成规则设定,支持时间格式规则;
-43. ghttp日志增加客户端IP信息;
-44. 完善gform配置管理说明,g.DB/Database和gdb.New的区别;
-1. 完善配置管理章节,说明默认的配置文件更改方式;
-1. 服务注册时判断方法定义满足规范时才执行绑定,否则提示WARN信息;
-1. `gfsnotify`增加添加监听文件时的监听ID返回,以便调用端删除监听时只删除自己添加的监听,而不影响其他对该同一文件的监听回调;
-1. `gfsnotify`针对添加目录监听时无法使用多个`Watcher`,考虑改进,并考虑动态扩容全局`Watcher`方案;
-1. 由于系统对inotify实例数量(`fs.inotify.max_user_instances`)以及队列大小(`fs.inotify.max_user_watches`)有限制,需要改进`gfsnotify`;
-1. WebServer事件回调允许对同一个路由规则绑定多个事件回调;
-1. gcfg/gview/ghttp等模块加上对临时文件目录的自动添加监听判断(基本是开发环境下,特别是windows环境),去掉临时文件的监听,避免临时文件过大引起的运行缓慢占用内存问题;
-1. 改进gfpool在文件指针变化时的更新;
-1. ghttp hook回调使用方式在注册路由比较多的时候,优先级可能使得开发者混乱,考虑方式便于管理;
-1. gform对于MySQL字段类型为datetime类型的时区问题分析;
-1. 改进证书打开失败时的WebServer错误提示,前置HOOK校验后关闭后续的HOOK逻辑执行;
-1. 目前WebServer的HOOK是按照优先级执行的,需要增加覆盖特性;
-1. 更新跨域请求CORS相关功能文档;
-1. ghttp.Response增加输出内容后自动退出当前请求机制,不需要用户手动return,参考beego如何实现;
-1. gcfg包目前允许添加重复的目录路径,需要在SetPath/AddPath时判断重复性,不能添加重复的路径;
-1. gdb执行数据写入时,如果参数为struct/[]struct,自动映射与表字段对应关系,不再使用gconv标签标识;
-1. gdb的Data方法支持struct参数传入;
-1. gfcache依旧使用gcache作为缓存控制对象,不要使用gmap;
-1. 增加对ghttp路由注册的{.struct}/{.method}单元测试;
-1. gconv针对struct的转换增加json tag支持,gconv.Map默认也支持json tag, 完善开发文档;
-1. 增加SO_REUSEPORT的支持;
-1. gkafka这个包比较重,未来从框架中剥离出来;
-1. str_ireplace: http://php.net/manual/en/function.str-ireplace.php
-1. strpos/stripos/strrpos/strripos: http://php.net/manual/en/function.stripos.php
-1. gfile对于文件的读写强行使用了gfpool,在某些场景下不合适,需要考虑剥离开,并为开发者提供单独的指针池文件操作特性;
-1. ghttp.Client自动Close机制;
-1. ghttp路由功能增加分组路由特性;
-1. 增加可选择性的orm tag特性,用以数据表记录与struct对象转换的键名属性映射;
-1. gview中的template标签失效问题;
\ No newline at end of file
diff --git a/container/garray/garray_normal_any.go b/container/garray/garray_normal_any.go
index 2ec882819..4c41cc2d4 100644
--- a/container/garray/garray_normal_any.go
+++ b/container/garray/garray_normal_any.go
@@ -453,7 +453,7 @@ func (a *Array) Clone() (newArray *Array) {
array := make([]interface{}, len(a.array))
copy(array, a.array)
a.mu.RUnlock()
- return NewArrayFrom(array, !a.mu.IsSafe())
+ return NewArrayFrom(array, a.mu.IsSafe())
}
// Clear deletes all items of current array.
diff --git a/container/garray/garray_normal_int.go b/container/garray/garray_normal_int.go
index ef13c8851..69d4c157a 100644
--- a/container/garray/garray_normal_int.go
+++ b/container/garray/garray_normal_int.go
@@ -469,7 +469,7 @@ func (a *IntArray) Clone() (newArray *IntArray) {
array := make([]int, len(a.array))
copy(array, a.array)
a.mu.RUnlock()
- return NewIntArrayFrom(array, !a.mu.IsSafe())
+ return NewIntArrayFrom(array, a.mu.IsSafe())
}
// Clear deletes all items of current array.
diff --git a/container/garray/garray_normal_str.go b/container/garray/garray_normal_str.go
index 4d3f27b8b..889a19a09 100644
--- a/container/garray/garray_normal_str.go
+++ b/container/garray/garray_normal_str.go
@@ -457,7 +457,7 @@ func (a *StrArray) Clone() (newArray *StrArray) {
array := make([]string, len(a.array))
copy(array, a.array)
a.mu.RUnlock()
- return NewStrArrayFrom(array, !a.mu.IsSafe())
+ return NewStrArrayFrom(array, a.mu.IsSafe())
}
// Clear deletes all items of current array.
diff --git a/container/garray/garray_sorted_any.go b/container/garray/garray_sorted_any.go
index d056464d7..3122be023 100644
--- a/container/garray/garray_sorted_any.go
+++ b/container/garray/garray_sorted_any.go
@@ -446,7 +446,7 @@ func (a *SortedArray) binSearch(value interface{}, lock bool) (index int, result
mid := 0
cmp := -2
for min <= max {
- mid = (min + max) / 2
+ mid = min + int((max-min)/2)
cmp = a.getComparator()(value, a.array[mid])
switch {
case cmp < 0:
@@ -499,7 +499,7 @@ func (a *SortedArray) Clone() (newArray *SortedArray) {
array := make([]interface{}, len(a.array))
copy(array, a.array)
a.mu.RUnlock()
- return NewSortedArrayFrom(array, a.comparator, !a.mu.IsSafe())
+ return NewSortedArrayFrom(array, a.comparator, a.mu.IsSafe())
}
// Clear deletes all items of current array.
diff --git a/container/garray/garray_sorted_int.go b/container/garray/garray_sorted_int.go
index 28d3e97f9..fa44ed534 100644
--- a/container/garray/garray_sorted_int.go
+++ b/container/garray/garray_sorted_int.go
@@ -443,7 +443,7 @@ func (a *SortedIntArray) binSearch(value int, lock bool) (index int, result int)
mid := 0
cmp := -2
for min <= max {
- mid = (min + max) / 2
+ mid = min + int((max-min)/2)
cmp = a.getComparator()(value, a.array[mid])
switch {
case cmp < 0:
@@ -496,7 +496,7 @@ func (a *SortedIntArray) Clone() (newArray *SortedIntArray) {
array := make([]int, len(a.array))
copy(array, a.array)
a.mu.RUnlock()
- return NewSortedIntArrayFrom(array, !a.mu.IsSafe())
+ return NewSortedIntArrayFrom(array, a.mu.IsSafe())
}
// Clear deletes all items of current array.
diff --git a/container/garray/garray_sorted_str.go b/container/garray/garray_sorted_str.go
index 649732347..762017d72 100644
--- a/container/garray/garray_sorted_str.go
+++ b/container/garray/garray_sorted_str.go
@@ -445,7 +445,7 @@ func (a *SortedStrArray) binSearch(value string, lock bool) (index int, result i
mid := 0
cmp := -2
for min <= max {
- mid = (min + max) / 2
+ mid = min + int((max-min)/2)
cmp = a.getComparator()(value, a.array[mid])
switch {
case cmp < 0:
@@ -498,7 +498,7 @@ func (a *SortedStrArray) Clone() (newArray *SortedStrArray) {
array := make([]string, len(a.array))
copy(array, a.array)
a.mu.RUnlock()
- return NewSortedStrArrayFrom(array, !a.mu.IsSafe())
+ return NewSortedStrArrayFrom(array, a.mu.IsSafe())
}
// Clear deletes all items of current array.
diff --git a/container/gmap/gmap_hash_any_any_map.go b/container/gmap/gmap_hash_any_any_map.go
index c2006af43..28859ed45 100644
--- a/container/gmap/gmap_hash_any_any_map.go
+++ b/container/gmap/gmap_hash_any_any_map.go
@@ -457,6 +457,12 @@ func (m *AnyAnyMap) Merge(other *AnyAnyMap) {
}
}
+// String returns the map as a string.
+func (m *AnyAnyMap) String() string {
+ b, _ := m.MarshalJSON()
+ return gconv.UnsafeBytesToStr(b)
+}
+
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
func (m *AnyAnyMap) MarshalJSON() ([]byte, error) {
return json.Marshal(gconv.Map(m.Map()))
diff --git a/container/gmap/gmap_hash_int_any_map.go b/container/gmap/gmap_hash_int_any_map.go
index 5cbbafe63..52df0cebc 100644
--- a/container/gmap/gmap_hash_int_any_map.go
+++ b/container/gmap/gmap_hash_int_any_map.go
@@ -56,7 +56,7 @@ func (m *IntAnyMap) Iterator(f func(k int, v interface{}) bool) {
// Clone returns a new hash map with copy of current map data.
func (m *IntAnyMap) Clone() *IntAnyMap {
- return NewIntAnyMapFrom(m.MapCopy(), !m.mu.IsSafe())
+ return NewIntAnyMapFrom(m.MapCopy(), m.mu.IsSafe())
}
// Map returns the underlying data map.
@@ -455,6 +455,12 @@ func (m *IntAnyMap) Merge(other *IntAnyMap) {
}
}
+// String returns the map as a string.
+func (m *IntAnyMap) String() string {
+ b, _ := m.MarshalJSON()
+ return gconv.UnsafeBytesToStr(b)
+}
+
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
func (m *IntAnyMap) MarshalJSON() ([]byte, error) {
m.mu.RLock()
diff --git a/container/gmap/gmap_hash_int_int_map.go b/container/gmap/gmap_hash_int_int_map.go
index d37e8d453..4d546661e 100644
--- a/container/gmap/gmap_hash_int_int_map.go
+++ b/container/gmap/gmap_hash_int_int_map.go
@@ -54,7 +54,7 @@ func (m *IntIntMap) Iterator(f func(k int, v int) bool) {
// Clone returns a new hash map with copy of current map data.
func (m *IntIntMap) Clone() *IntIntMap {
- return NewIntIntMapFrom(m.MapCopy(), !m.mu.IsSafe())
+ return NewIntIntMapFrom(m.MapCopy(), m.mu.IsSafe())
}
// Map returns the underlying data map.
@@ -426,6 +426,12 @@ func (m *IntIntMap) Merge(other *IntIntMap) {
}
}
+// String returns the map as a string.
+func (m *IntIntMap) String() string {
+ b, _ := m.MarshalJSON()
+ return gconv.UnsafeBytesToStr(b)
+}
+
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
func (m *IntIntMap) MarshalJSON() ([]byte, error) {
m.mu.RLock()
diff --git a/container/gmap/gmap_hash_int_str_map.go b/container/gmap/gmap_hash_int_str_map.go
index 9d131054e..8d1f1edfd 100644
--- a/container/gmap/gmap_hash_int_str_map.go
+++ b/container/gmap/gmap_hash_int_str_map.go
@@ -54,7 +54,7 @@ func (m *IntStrMap) Iterator(f func(k int, v string) bool) {
// Clone returns a new hash map with copy of current map data.
func (m *IntStrMap) Clone() *IntStrMap {
- return NewIntStrMapFrom(m.MapCopy(), !m.mu.IsSafe())
+ return NewIntStrMapFrom(m.MapCopy(), m.mu.IsSafe())
}
// Map returns the underlying data map.
@@ -426,6 +426,12 @@ func (m *IntStrMap) Merge(other *IntStrMap) {
}
}
+// String returns the map as a string.
+func (m *IntStrMap) String() string {
+ b, _ := m.MarshalJSON()
+ return gconv.UnsafeBytesToStr(b)
+}
+
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
func (m *IntStrMap) MarshalJSON() ([]byte, error) {
m.mu.RLock()
diff --git a/container/gmap/gmap_hash_str_any_map.go b/container/gmap/gmap_hash_str_any_map.go
index f5f502eff..423923e2c 100644
--- a/container/gmap/gmap_hash_str_any_map.go
+++ b/container/gmap/gmap_hash_str_any_map.go
@@ -56,7 +56,7 @@ func (m *StrAnyMap) Iterator(f func(k string, v interface{}) bool) {
// Clone returns a new hash map with copy of current map data.
func (m *StrAnyMap) Clone() *StrAnyMap {
- return NewStrAnyMapFrom(m.MapCopy(), !m.mu.IsSafe())
+ return NewStrAnyMapFrom(m.MapCopy(), m.mu.IsSafe())
}
// Map returns the underlying data map.
@@ -451,6 +451,12 @@ func (m *StrAnyMap) Merge(other *StrAnyMap) {
}
}
+// String returns the map as a string.
+func (m *StrAnyMap) String() string {
+ b, _ := m.MarshalJSON()
+ return gconv.UnsafeBytesToStr(b)
+}
+
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
func (m *StrAnyMap) MarshalJSON() ([]byte, error) {
m.mu.RLock()
diff --git a/container/gmap/gmap_hash_str_int_map.go b/container/gmap/gmap_hash_str_int_map.go
index 1716b055f..58f98f017 100644
--- a/container/gmap/gmap_hash_str_int_map.go
+++ b/container/gmap/gmap_hash_str_int_map.go
@@ -54,7 +54,7 @@ func (m *StrIntMap) Iterator(f func(k string, v int) bool) {
// Clone returns a new hash map with copy of current map data.
func (m *StrIntMap) Clone() *StrIntMap {
- return NewStrIntMapFrom(m.MapCopy(), !m.mu.IsSafe())
+ return NewStrIntMapFrom(m.MapCopy(), m.mu.IsSafe())
}
// Map returns the underlying data map.
@@ -429,6 +429,12 @@ func (m *StrIntMap) Merge(other *StrIntMap) {
}
}
+// String returns the map as a string.
+func (m *StrIntMap) String() string {
+ b, _ := m.MarshalJSON()
+ return gconv.UnsafeBytesToStr(b)
+}
+
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
func (m *StrIntMap) MarshalJSON() ([]byte, error) {
m.mu.RLock()
diff --git a/container/gmap/gmap_hash_str_str_map.go b/container/gmap/gmap_hash_str_str_map.go
index c6922eae6..5565b28a7 100644
--- a/container/gmap/gmap_hash_str_str_map.go
+++ b/container/gmap/gmap_hash_str_str_map.go
@@ -55,7 +55,7 @@ func (m *StrStrMap) Iterator(f func(k string, v string) bool) {
// Clone returns a new hash map with copy of current map data.
func (m *StrStrMap) Clone() *StrStrMap {
- return NewStrStrMapFrom(m.MapCopy(), !m.mu.IsSafe())
+ return NewStrStrMapFrom(m.MapCopy(), m.mu.IsSafe())
}
// Map returns the underlying data map.
@@ -429,6 +429,12 @@ func (m *StrStrMap) Merge(other *StrStrMap) {
}
}
+// String returns the map as a string.
+func (m *StrStrMap) String() string {
+ b, _ := m.MarshalJSON()
+ return gconv.UnsafeBytesToStr(b)
+}
+
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
func (m *StrStrMap) MarshalJSON() ([]byte, error) {
m.mu.RLock()
diff --git a/container/gmap/gmap_list_map.go b/container/gmap/gmap_list_map.go
index 407e5decb..823cd7a54 100644
--- a/container/gmap/gmap_list_map.go
+++ b/container/gmap/gmap_list_map.go
@@ -510,6 +510,12 @@ func (m *ListMap) Merge(other *ListMap) {
})
}
+// String returns the map as a string.
+func (m *ListMap) String() string {
+ b, _ := m.MarshalJSON()
+ return gconv.UnsafeBytesToStr(b)
+}
+
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
func (m *ListMap) MarshalJSON() ([]byte, error) {
return json.Marshal(gconv.Map(m.Map()))
diff --git a/container/gpool/gpool.go b/container/gpool/gpool.go
index 3248331e3..e7a59905d 100644
--- a/container/gpool/gpool.go
+++ b/container/gpool/gpool.go
@@ -1,4 +1,4 @@
-// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
+// Copyright GoFrame gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
@@ -19,18 +19,10 @@ import (
// Pool is an Object-Reusable Pool.
type Pool struct {
- // Available/idle items list.
- list *glist.List
-
- // Whether the pool is closed.
- closed *gtype.Bool
-
- // Time To Live for pool items.
- TTL time.Duration
-
- // Callback function to create pool item.
- NewFunc func() (interface{}, error)
-
+ list *glist.List // Available/idle items list.
+ closed *gtype.Bool // Whether the pool is closed.
+ TTL time.Duration // Time To Live for pool items.
+ NewFunc func() (interface{}, error) // Callback function to create pool item.
// ExpireFunc is the for expired items destruction.
// This function needs to be defined when the pool items
// need to perform additional destruction operations.
@@ -40,8 +32,8 @@ type Pool struct {
// Pool item.
type poolItem struct {
- expire int64 // Expire timestamp in milliseconds.
- value interface{} // Item value.
+ value interface{} // Item value.
+ expireAt int64 // Expire timestamp in milliseconds.
}
// Creation function for object.
@@ -80,11 +72,11 @@ func (p *Pool) Put(value interface{}) error {
value: value,
}
if p.TTL == 0 {
- item.expire = 0
+ item.expireAt = 0
} else {
// As for Golang version < 1.13, there's no method Milliseconds for time.Duration.
// So we need calculate the milliseconds using its nanoseconds value.
- item.expire = gtime.TimestampMilli() + p.TTL.Nanoseconds()/1000000
+ item.expireAt = gtime.TimestampMilli() + p.TTL.Nanoseconds()/1000000
}
p.list.PushBack(item)
return nil
@@ -112,8 +104,11 @@ func (p *Pool) Get() (interface{}, error) {
for !p.closed.Val() {
if r := p.list.PopFront(); r != nil {
f := r.(*poolItem)
- if f.expire == 0 || f.expire > gtime.TimestampMilli() {
+ if f.expireAt == 0 || f.expireAt > gtime.TimestampMilli() {
return f.value, nil
+ } else if p.ExpireFunc != nil {
+ // TODO: move expire function calling asynchronously from `Get` operation.
+ p.ExpireFunc(f.value)
}
} else {
break
@@ -169,9 +164,9 @@ func (p *Pool) checkExpireItems() {
}
if r := p.list.PopFront(); r != nil {
item := r.(*poolItem)
- latestExpire = item.expire
+ latestExpire = item.expireAt
// TODO improve the auto-expiration mechanism of the pool.
- if item.expire > timestampMilli {
+ if item.expireAt > timestampMilli {
p.list.PushFront(item)
break
}
diff --git a/container/gtree/gtree_avltree.go b/container/gtree/gtree_avltree.go
index 028e19850..eb8e72d05 100644
--- a/container/gtree/gtree_avltree.go
+++ b/container/gtree/gtree_avltree.go
@@ -56,7 +56,7 @@ func NewAVLTreeFrom(comparator func(v1, v2 interface{}) int, data map[interface{
// Clone returns a new tree with a copy of current tree.
func (tree *AVLTree) Clone() *AVLTree {
- newTree := NewAVLTree(tree.comparator, !tree.mu.IsSafe())
+ newTree := NewAVLTree(tree.comparator, tree.mu.IsSafe())
newTree.Sets(tree.Map())
return newTree
}
@@ -441,9 +441,9 @@ func (tree *AVLTree) MapStrAny() map[string]interface{} {
func (tree *AVLTree) Flip(comparator ...func(v1, v2 interface{}) int) {
t := (*AVLTree)(nil)
if len(comparator) > 0 {
- t = NewAVLTree(comparator[0], !tree.mu.IsSafe())
+ t = NewAVLTree(comparator[0], tree.mu.IsSafe())
} else {
- t = NewAVLTree(tree.comparator, !tree.mu.IsSafe())
+ t = NewAVLTree(tree.comparator, tree.mu.IsSafe())
}
tree.IteratorAsc(func(key, value interface{}) bool {
t.put(value, key, nil, &t.root)
diff --git a/container/gtree/gtree_btree.go b/container/gtree/gtree_btree.go
index 926898add..a8c1bbb3b 100644
--- a/container/gtree/gtree_btree.go
+++ b/container/gtree/gtree_btree.go
@@ -68,7 +68,7 @@ func NewBTreeFrom(m int, comparator func(v1, v2 interface{}) int, data map[inter
// Clone returns a new tree with a copy of current tree.
func (tree *BTree) Clone() *BTree {
- newTree := NewBTree(tree.m, tree.comparator, !tree.mu.IsSafe())
+ newTree := NewBTree(tree.m, tree.comparator, tree.mu.IsSafe())
newTree.Sets(tree.Map())
return newTree
}
@@ -620,7 +620,7 @@ func (tree *BTree) middle() int {
func (tree *BTree) search(node *BTreeNode, key interface{}) (index int, found bool) {
low, mid, high := 0, 0, len(node.Entries)-1
for low <= high {
- mid = (high + low) / 2
+ mid = low + int((high-low)/2)
compare := tree.getComparator()(key, node.Entries[mid].Key)
switch {
case compare > 0:
diff --git a/container/gtree/gtree_redblacktree.go b/container/gtree/gtree_redblacktree.go
index c464889b4..f73c9e568 100644
--- a/container/gtree/gtree_redblacktree.go
+++ b/container/gtree/gtree_redblacktree.go
@@ -83,7 +83,7 @@ func (tree *RedBlackTree) SetComparator(comparator func(a, b interface{}) int) {
// Clone returns a new tree with a copy of current tree.
func (tree *RedBlackTree) Clone() *RedBlackTree {
- newTree := NewRedBlackTree(tree.comparator, !tree.mu.IsSafe())
+ newTree := NewRedBlackTree(tree.comparator, tree.mu.IsSafe())
newTree.Sets(tree.Map())
return newTree
}
@@ -656,9 +656,9 @@ func (tree *RedBlackTree) Search(key interface{}) (value interface{}, found bool
func (tree *RedBlackTree) Flip(comparator ...func(v1, v2 interface{}) int) {
t := (*RedBlackTree)(nil)
if len(comparator) > 0 {
- t = NewRedBlackTree(comparator[0], !tree.mu.IsSafe())
+ t = NewRedBlackTree(comparator[0], tree.mu.IsSafe())
} else {
- t = NewRedBlackTree(tree.comparator, !tree.mu.IsSafe())
+ t = NewRedBlackTree(tree.comparator, tree.mu.IsSafe())
}
tree.IteratorAsc(func(key, value interface{}) bool {
t.doSet(value, key)
diff --git a/container/gvar/gvar.go b/container/gvar/gvar.go
index e9bc40dc0..3ee988702 100644
--- a/container/gvar/gvar.go
+++ b/container/gvar/gvar.go
@@ -1,4 +1,4 @@
-// Copyright 2018-2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
+// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
@@ -11,8 +11,6 @@ import (
"github.com/gogf/gf/internal/json"
"time"
- "github.com/gogf/gf/internal/empty"
-
"github.com/gogf/gf/container/gtype"
"github.com/gogf/gf/os/gtime"
"github.com/gogf/gf/util/gconv"
@@ -88,16 +86,6 @@ func (v *Var) Interface() interface{} {
return v.Val()
}
-// IsNil checks whether is nil.
-func (v *Var) IsNil() bool {
- return v.Val() == nil
-}
-
-// IsEmpty checks whether is empty.
-func (v *Var) IsEmpty() bool {
- return empty.IsEmpty(v.Val())
-}
-
// Bytes converts and returns as []byte.
func (v *Var) Bytes() []byte {
return gconv.Bytes(v.Val())
diff --git a/container/gvar/gvar_is.go b/container/gvar/gvar_is.go
new file mode 100644
index 000000000..396b4e40e
--- /dev/null
+++ b/container/gvar/gvar_is.go
@@ -0,0 +1,98 @@
+// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package gvar
+
+import (
+ "github.com/gogf/gf/internal/empty"
+ "reflect"
+)
+
+// IsNil checks whether is nil.
+func (v *Var) IsNil() bool {
+ return v.Val() == nil
+}
+
+// IsEmpty checks whether is empty.
+func (v *Var) IsEmpty() bool {
+ return empty.IsEmpty(v.Val())
+}
+
+// IsInt checks whether is type of int.
+func (v *Var) IsInt() bool {
+ switch v.Val().(type) {
+ case int, *int, int8, *int8, int16, *int16, int32, *int32, int64, *int64:
+ return true
+ }
+ return false
+}
+
+// IsUint checks whether is type of uint.
+func (v *Var) IsUint() bool {
+ switch v.Val().(type) {
+ case uint, *uint, uint8, *uint8, uint16, *uint16, uint32, *uint32, uint64, *uint64:
+ return true
+ }
+ return false
+}
+
+// IsFloat checks whether is type of float.
+func (v *Var) IsFloat() bool {
+ switch v.Val().(type) {
+ case float32, *float32, float64, *float64:
+ return true
+ }
+ return false
+}
+
+// IsSlice checks whether is type of slice.
+func (v *Var) IsSlice() bool {
+ var (
+ reflectValue = reflect.ValueOf(v.Val())
+ reflectKind = reflectValue.Kind()
+ )
+ for reflectKind == reflect.Ptr {
+ reflectValue = reflectValue.Elem()
+ }
+ switch reflectKind {
+ case reflect.Slice, reflect.Array:
+ return true
+ }
+ return false
+}
+
+// IsMap checks whether is type of map.
+func (v *Var) IsMap() bool {
+ var (
+ reflectValue = reflect.ValueOf(v.Val())
+ reflectKind = reflectValue.Kind()
+ )
+ for reflectKind == reflect.Ptr {
+ reflectValue = reflectValue.Elem()
+ }
+ switch reflectKind {
+ case reflect.Map:
+ return true
+ }
+ return false
+}
+
+// IsStruct checks whether is type of struct.
+func (v *Var) IsStruct() bool {
+ var (
+ reflectValue = reflect.ValueOf(v.Val())
+ reflectKind = reflectValue.Kind()
+ )
+ for reflectKind == reflect.Ptr {
+ reflectValue = reflectValue.Elem()
+ reflectKind = reflectValue.Kind()
+ }
+ switch reflectKind {
+ case reflect.Struct:
+ return true
+ }
+ return false
+}
diff --git a/container/gvar/gvar_struct.go b/container/gvar/gvar_struct.go
index 6ae985279..d215e4df1 100644
--- a/container/gvar/gvar_struct.go
+++ b/container/gvar/gvar_struct.go
@@ -20,6 +20,7 @@ func (v *Var) Struct(pointer interface{}, mapping ...map[string]string) error {
// Struct maps value of to recursively.
// The parameter should be a pointer to a struct instance.
// The parameter is used to specify the key-to-attribute mapping rules.
+// Deprecated, use Struct instead.
func (v *Var) StructDeep(pointer interface{}, mapping ...map[string]string) error {
return gconv.StructDeep(v.Val(), pointer, mapping...)
}
@@ -30,6 +31,7 @@ func (v *Var) Structs(pointer interface{}, mapping ...map[string]string) error {
}
// StructsDeep converts and returns as given struct slice recursively.
+// Deprecated, use Struct instead.
func (v *Var) StructsDeep(pointer interface{}, mapping ...map[string]string) error {
return gconv.StructsDeep(v.Val(), pointer, mapping...)
}
diff --git a/container/gvar/gvar_z_unit_is_test.go b/container/gvar/gvar_z_unit_is_test.go
new file mode 100644
index 000000000..c1656e044
--- /dev/null
+++ b/container/gvar/gvar_z_unit_is_test.go
@@ -0,0 +1,170 @@
+// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package gvar_test
+
+import (
+ "github.com/gogf/gf/frame/g"
+ "github.com/gogf/gf/test/gtest"
+ "testing"
+)
+
+func TestVar_IsNil(t *testing.T) {
+ gtest.C(t, func(t *gtest.T) {
+ t.Assert(g.NewVar(0).IsNil(), false)
+ t.Assert(g.NewVar(nil).IsNil(), true)
+ t.Assert(g.NewVar(g.Map{}).IsNil(), false)
+ t.Assert(g.NewVar(g.Slice{}).IsNil(), false)
+ })
+ gtest.C(t, func(t *gtest.T) {
+ t.Assert(g.NewVar(1).IsNil(), false)
+ t.Assert(g.NewVar(0.1).IsNil(), false)
+ t.Assert(g.NewVar(g.Map{"k": "v"}).IsNil(), false)
+ t.Assert(g.NewVar(g.Slice{0}).IsNil(), false)
+ })
+}
+
+func TestVar_IsEmpty(t *testing.T) {
+ gtest.C(t, func(t *gtest.T) {
+ t.Assert(g.NewVar(0).IsEmpty(), true)
+ t.Assert(g.NewVar(nil).IsEmpty(), true)
+ t.Assert(g.NewVar(g.Map{}).IsEmpty(), true)
+ t.Assert(g.NewVar(g.Slice{}).IsEmpty(), true)
+ })
+ gtest.C(t, func(t *gtest.T) {
+ t.Assert(g.NewVar(1).IsEmpty(), false)
+ t.Assert(g.NewVar(0.1).IsEmpty(), false)
+ t.Assert(g.NewVar(g.Map{"k": "v"}).IsEmpty(), false)
+ t.Assert(g.NewVar(g.Slice{0}).IsEmpty(), false)
+ })
+}
+
+func TestVar_IsInt(t *testing.T) {
+ gtest.C(t, func(t *gtest.T) {
+ t.Assert(g.NewVar(0).IsInt(), true)
+ t.Assert(g.NewVar(nil).IsInt(), false)
+ t.Assert(g.NewVar(g.Map{}).IsInt(), false)
+ t.Assert(g.NewVar(g.Slice{}).IsInt(), false)
+ })
+ gtest.C(t, func(t *gtest.T) {
+ t.Assert(g.NewVar(1).IsInt(), true)
+ t.Assert(g.NewVar(-1).IsInt(), true)
+ t.Assert(g.NewVar(0.1).IsInt(), false)
+ t.Assert(g.NewVar(g.Map{"k": "v"}).IsInt(), false)
+ t.Assert(g.NewVar(g.Slice{0}).IsInt(), false)
+ })
+ gtest.C(t, func(t *gtest.T) {
+ t.Assert(g.NewVar(int8(1)).IsInt(), true)
+ t.Assert(g.NewVar(uint8(1)).IsInt(), false)
+ })
+}
+
+func TestVar_IsUint(t *testing.T) {
+ gtest.C(t, func(t *gtest.T) {
+ t.Assert(g.NewVar(0).IsUint(), false)
+ t.Assert(g.NewVar(nil).IsUint(), false)
+ t.Assert(g.NewVar(g.Map{}).IsUint(), false)
+ t.Assert(g.NewVar(g.Slice{}).IsUint(), false)
+ })
+ gtest.C(t, func(t *gtest.T) {
+ t.Assert(g.NewVar(1).IsUint(), false)
+ t.Assert(g.NewVar(-1).IsUint(), false)
+ t.Assert(g.NewVar(0.1).IsUint(), false)
+ t.Assert(g.NewVar(g.Map{"k": "v"}).IsUint(), false)
+ t.Assert(g.NewVar(g.Slice{0}).IsUint(), false)
+ })
+ gtest.C(t, func(t *gtest.T) {
+ t.Assert(g.NewVar(int8(1)).IsUint(), false)
+ t.Assert(g.NewVar(uint8(1)).IsUint(), true)
+ })
+}
+
+func TestVar_IsFloat(t *testing.T) {
+ gtest.C(t, func(t *gtest.T) {
+ t.Assert(g.NewVar(0).IsFloat(), false)
+ t.Assert(g.NewVar(nil).IsFloat(), false)
+ t.Assert(g.NewVar(g.Map{}).IsFloat(), false)
+ t.Assert(g.NewVar(g.Slice{}).IsFloat(), false)
+ })
+ gtest.C(t, func(t *gtest.T) {
+ t.Assert(g.NewVar(1).IsFloat(), false)
+ t.Assert(g.NewVar(-1).IsFloat(), false)
+ t.Assert(g.NewVar(0.1).IsFloat(), true)
+ t.Assert(g.NewVar(float64(1)).IsFloat(), true)
+ t.Assert(g.NewVar(g.Map{"k": "v"}).IsFloat(), false)
+ t.Assert(g.NewVar(g.Slice{0}).IsFloat(), false)
+ })
+ gtest.C(t, func(t *gtest.T) {
+ t.Assert(g.NewVar(int8(1)).IsFloat(), false)
+ t.Assert(g.NewVar(uint8(1)).IsFloat(), false)
+ })
+}
+
+func TestVar_IsSlice(t *testing.T) {
+ gtest.C(t, func(t *gtest.T) {
+ t.Assert(g.NewVar(0).IsSlice(), false)
+ t.Assert(g.NewVar(nil).IsSlice(), false)
+ t.Assert(g.NewVar(g.Map{}).IsSlice(), false)
+ t.Assert(g.NewVar(g.Slice{}).IsSlice(), true)
+ })
+ gtest.C(t, func(t *gtest.T) {
+ t.Assert(g.NewVar(1).IsSlice(), false)
+ t.Assert(g.NewVar(-1).IsSlice(), false)
+ t.Assert(g.NewVar(0.1).IsSlice(), false)
+ t.Assert(g.NewVar(float64(1)).IsSlice(), false)
+ t.Assert(g.NewVar(g.Map{"k": "v"}).IsSlice(), false)
+ t.Assert(g.NewVar(g.Slice{0}).IsSlice(), true)
+ })
+ gtest.C(t, func(t *gtest.T) {
+ t.Assert(g.NewVar(int8(1)).IsSlice(), false)
+ t.Assert(g.NewVar(uint8(1)).IsSlice(), false)
+ })
+}
+
+func TestVar_IsMap(t *testing.T) {
+ gtest.C(t, func(t *gtest.T) {
+ t.Assert(g.NewVar(0).IsMap(), false)
+ t.Assert(g.NewVar(nil).IsMap(), false)
+ t.Assert(g.NewVar(g.Map{}).IsMap(), true)
+ t.Assert(g.NewVar(g.Slice{}).IsMap(), false)
+ })
+ gtest.C(t, func(t *gtest.T) {
+ t.Assert(g.NewVar(1).IsMap(), false)
+ t.Assert(g.NewVar(-1).IsMap(), false)
+ t.Assert(g.NewVar(0.1).IsMap(), false)
+ t.Assert(g.NewVar(float64(1)).IsMap(), false)
+ t.Assert(g.NewVar(g.Map{"k": "v"}).IsMap(), true)
+ t.Assert(g.NewVar(g.Slice{0}).IsMap(), false)
+ })
+ gtest.C(t, func(t *gtest.T) {
+ t.Assert(g.NewVar(int8(1)).IsMap(), false)
+ t.Assert(g.NewVar(uint8(1)).IsMap(), false)
+ })
+}
+
+func TestVar_IsStruct(t *testing.T) {
+ gtest.C(t, func(t *gtest.T) {
+ t.Assert(g.NewVar(0).IsStruct(), false)
+ t.Assert(g.NewVar(nil).IsStruct(), false)
+ t.Assert(g.NewVar(g.Map{}).IsStruct(), false)
+ t.Assert(g.NewVar(g.Slice{}).IsStruct(), false)
+ })
+ gtest.C(t, func(t *gtest.T) {
+ t.Assert(g.NewVar(1).IsStruct(), false)
+ t.Assert(g.NewVar(-1).IsStruct(), false)
+ t.Assert(g.NewVar(0.1).IsStruct(), false)
+ t.Assert(g.NewVar(float64(1)).IsStruct(), false)
+ t.Assert(g.NewVar(g.Map{"k": "v"}).IsStruct(), false)
+ t.Assert(g.NewVar(g.Slice{0}).IsStruct(), false)
+ })
+ gtest.C(t, func(t *gtest.T) {
+ a := &struct {
+ }{}
+ t.Assert(g.NewVar(a).IsStruct(), true)
+ t.Assert(g.NewVar(*a).IsStruct(), true)
+ t.Assert(g.NewVar(&a).IsStruct(), true)
+ })
+}
diff --git a/database/gdb/gdb.go b/database/gdb/gdb.go
index 1814c19be..41846facd 100644
--- a/database/gdb/gdb.go
+++ b/database/gdb/gdb.go
@@ -1,4 +1,4 @@
-// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
+// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
@@ -8,12 +8,15 @@
package gdb
import (
+ "context"
"database/sql"
- "errors"
"fmt"
+ "github.com/gogf/gf/errors/gerror"
+ "github.com/gogf/gf/os/gcmd"
+ "time"
+
"github.com/gogf/gf/container/gvar"
"github.com/gogf/gf/internal/intlog"
- "time"
"github.com/gogf/gf/os/glog"
@@ -40,6 +43,12 @@ type DB interface {
// Note that it is not recommended using the this function manually.
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.
+ Ctx(ctx context.Context) DB
+
// ===========================================================================
// Query APIs.
// ===========================================================================
@@ -126,6 +135,7 @@ type DB interface {
GetDryRun() bool
SetLogger(logger *glog.Logger)
GetLogger() *glog.Logger
+ GetConfig() *ConfigNode
SetMaxIdleConnCount(n int)
SetMaxOpenConnCount(n int)
SetMaxConnLifetime(d time.Duration)
@@ -134,6 +144,7 @@ type DB interface {
// Utility methods.
// ===========================================================================
+ GetCtx() context.Context
GetChars() (charLeft string, charRight string)
GetMaster(schema ...string) (*sql.DB, error)
GetSlave(schema ...string) (*sql.DB, error)
@@ -142,6 +153,7 @@ type DB interface {
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)
// HandleSqlBeforeCommit is a hook function, which deals with the sql string before
// it's committed to underlying driver. The parameter specifies the current
@@ -150,27 +162,24 @@ type DB interface {
HandleSqlBeforeCommit(link Link, sql string, args []interface{}) (string, []interface{})
// ===========================================================================
- // Internal methods.
+ // Internal methods, for internal usage purpose, you do not need consider it.
// ===========================================================================
- filterFields(schema, table string, data map[string]interface{}) map[string]interface{}
- convertValue(fieldValue []byte, fieldType string) interface{}
- rowsToResult(rows *sql.Rows) (Result, error)
+ 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)
}
// Core is the base struct for database management.
type Core struct {
- DB DB // DB interface object.
- group string // Configuration group name.
- debug *gtype.Bool // Enable debug mode for the database.
- cache *gcache.Cache // Cache manager.
- schema *gtype.String // Custom schema for this object.
- dryrun *gtype.Bool // Dry run.
- prefix string // Table prefix.
- logger *glog.Logger // Logger.
- maxIdleConnCount int // Max idle connection count.
- maxOpenConnCount int // Max open connection count.
- maxConnLifetime time.Duration // Max TTL for a connection.
+ DB DB // DB interface object.
+ group string // Configuration group name.
+ debug *gtype.Bool // Enable debug mode for the database, which can be changed in runtime.
+ cache *gcache.Cache // Cache manager, SQL result cache only.
+ schema *gtype.String // Custom schema for this object.
+ logger *glog.Logger // Logger.
+ config *ConfigNode // Current config node.
+ ctx context.Context // Context for chaining operation only.
}
// Driver is the interface for integrating sql drivers into package gdb.
@@ -187,6 +196,7 @@ type Sql struct {
Error error // Execution result.
Start int64 // Start execution timestamp in milliseconds.
End int64 // End execution timestamp in milliseconds.
+ Group string // Group is the group name of the configuration that the sql is executed from.
}
// TableField is the struct for table field.
@@ -208,32 +218,30 @@ type Link interface {
Prepare(sql string) (*sql.Stmt, error)
}
+// Counter is the type for update count.
+type Counter struct {
+ Field string
+ Value float64
+}
+
type (
- // Value is the field value type.
- Value = *gvar.Var
-
- // Record is the row record of the table.
- Record map[string]Value
-
- // Result is the row record array.
- Result []Record
-
- // Map is alias of map[string]interface{},
- // which is the most common usage map type.
- Map = map[string]interface{}
-
- // List is type of map array.
- List = []Map
+ Raw string // Raw is a raw sql that will not be treated as argument but as a direct sql part.
+ Value = *gvar.Var // Value is the field value type.
+ Record map[string]Value // Record is the row record of the table.
+ Result []Record // Result is the row record array.
+ Map = map[string]interface{} // Map is alias of map[string]interface{}, which is the most common usage map type.
+ List = []Map // List is type of map array.
)
const (
- gINSERT_OPTION_DEFAULT = 0
- gINSERT_OPTION_REPLACE = 1
- gINSERT_OPTION_SAVE = 2
- gINSERT_OPTION_IGNORE = 3
- gDEFAULT_BATCH_NUM = 10 // Per count for batch insert/replace/save
- gDEFAULT_CONN_MAX_IDLE_COUNT = 10 // Max idle connection count in pool.
- gDEFAULT_CONN_MAX_LIFE_TIME = 30 // Max life time for per connection in pool in seconds.
+ insertOptionDefault = 0
+ insertOptionReplace = 1
+ insertOptionSave = 2
+ insertOptionIgnore = 3
+ defaultBatchNumber = 10 // Per count for batch insert/replace/save.
+ defaultMaxIdleConnCount = 10 // Max idle connection count in pool.
+ defaultMaxOpenConnCount = 100 // Max open connection count in pool.
+ defaultMaxConnLifeTime = 30 * time.Second // Max life time for per connection in pool in seconds.
)
var (
@@ -258,9 +266,21 @@ var (
// regularFieldNameRegPattern is the regular expression pattern for a string
// which is a regular field name of table.
- regularFieldNameRegPattern = `^[\w\.\-]+$`
+ regularFieldNameRegPattern = `^[\w\.\-\_]+$`
+
+ // internalCache is the memory cache for internal usage.
+ internalCache = gcache.New()
+
+ // allDryRun sets dry-run feature for all database connections.
+ // It is commonly used for command options for convenience.
+ allDryRun = false
)
+func init() {
+ // allDryRun is initialized from environment or command options.
+ allDryRun = gcmd.GetWithEnv("gf.gdb.dryrun", false).Bool()
+}
+
// Register registers custom database driver to gdb.
func Register(name string, driver Driver) error {
driverMap[name] = driver
@@ -269,30 +289,27 @@ func Register(name string, driver Driver) error {
// New creates and returns an ORM object with global configurations.
// The parameter specifies the configuration group name,
-// which is DEFAULT_GROUP_NAME in default.
-func New(name ...string) (db DB, err error) {
- group := configs.group
- if len(name) > 0 && name[0] != "" {
- group = name[0]
+// which is DefaultGroupName in default.
+func New(group ...string) (db DB, err error) {
+ groupName := configs.group
+ if len(group) > 0 && group[0] != "" {
+ groupName = group[0]
}
configs.RLock()
defer configs.RUnlock()
if len(configs.config) < 1 {
- return nil, errors.New("empty database configuration")
+ return nil, gerror.New("empty database configuration")
}
- if _, ok := configs.config[group]; ok {
- if node, err := getConfigNodeByGroup(group, true); err == nil {
+ if _, ok := configs.config[groupName]; ok {
+ if node, err := getConfigNodeByGroup(groupName, true); err == nil {
c := &Core{
- group: group,
- debug: gtype.NewBool(),
- cache: gcache.New(),
- schema: gtype.NewString(),
- dryrun: gtype.NewBool(),
- logger: glog.New(),
- prefix: node.Prefix,
- maxIdleConnCount: gDEFAULT_CONN_MAX_IDLE_COUNT,
- maxConnLifetime: gDEFAULT_CONN_MAX_LIFE_TIME, // Default max connection life time if user does not configure.
+ group: groupName,
+ debug: gtype.NewBool(),
+ cache: gcache.New(),
+ schema: gtype.NewString(),
+ logger: glog.New(),
+ config: node,
}
if v, ok := driverMap[node.Type]; ok {
c.DB, err = v.New(c, node)
@@ -301,19 +318,19 @@ func New(name ...string) (db DB, err error) {
}
return c.DB, nil
} else {
- return nil, errors.New(fmt.Sprintf(`unsupported database type "%s"`, node.Type))
+ return nil, gerror.New(fmt.Sprintf(`unsupported database type "%s"`, node.Type))
}
} else {
return nil, err
}
} else {
- return nil, errors.New(fmt.Sprintf(`database configuration node "%s" is not found`, group))
+ return nil, gerror.New(fmt.Sprintf(`database configuration node "%s" is not found`, groupName))
}
}
// Instance returns an instance for DB operations.
// The parameter specifies the configuration group name,
-// which is DEFAULT_GROUP_NAME in default.
+// which is DefaultGroupName in default.
func Instance(name ...string) (db DB, err error) {
group := configs.group
if len(name) > 0 && name[0] != "" {
@@ -347,7 +364,7 @@ func getConfigNodeByGroup(group string, master bool) (*ConfigNode, error) {
}
}
if len(masterList) < 1 {
- return nil, errors.New("at least one master node configuration's need to make sense")
+ return nil, gerror.New("at least one master node configuration's need to make sense")
}
if len(slaveList) < 1 {
slaveList = masterList
@@ -358,7 +375,7 @@ func getConfigNodeByGroup(group string, master bool) (*ConfigNode, error) {
return getConfigNodeByWeight(slaveList), nil
}
} else {
- return nil, errors.New(fmt.Sprintf("empty database configuration for item name '%s'", group))
+ return nil, gerror.New(fmt.Sprintf("empty database configuration for item name '%s'", group))
}
}
@@ -425,30 +442,34 @@ func (c *Core) getSqlDb(master bool, schema ...string) (sqlDb *sql.DB, err error
node = &n
}
// Cache the underlying connection pool object by node.
- v := c.cache.GetOrSetFuncLock(node.String(), func() interface{} {
+ v, _ := internalCache.GetOrSetFuncLock(node.String(), func() (interface{}, error) {
sqlDb, err = c.DB.Open(node)
if err != nil {
intlog.Printf("DB open failed: %v, %+v", err, node)
- return nil
+ return nil, err
}
- if c.maxIdleConnCount > 0 {
- sqlDb.SetMaxIdleConns(c.maxIdleConnCount)
- } else if node.MaxIdleConnCount > 0 {
- sqlDb.SetMaxIdleConns(node.MaxIdleConnCount)
+ if c.config.MaxIdleConnCount > 0 {
+ sqlDb.SetMaxIdleConns(c.config.MaxIdleConnCount)
+ } else {
+ sqlDb.SetMaxIdleConns(defaultMaxIdleConnCount)
}
-
- if c.maxOpenConnCount > 0 {
- sqlDb.SetMaxOpenConns(c.maxOpenConnCount)
- } else if node.MaxOpenConnCount > 0 {
- sqlDb.SetMaxOpenConns(node.MaxOpenConnCount)
+ if c.config.MaxOpenConnCount > 0 {
+ sqlDb.SetMaxOpenConns(c.config.MaxOpenConnCount)
+ } else {
+ sqlDb.SetMaxOpenConns(defaultMaxOpenConnCount)
}
-
- if c.maxConnLifetime > 0 {
- sqlDb.SetConnMaxLifetime(c.maxConnLifetime * time.Second)
- } else if node.MaxConnLifetime > 0 {
- sqlDb.SetConnMaxLifetime(node.MaxConnLifetime * time.Second)
+ 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)
+ } else {
+ sqlDb.SetConnMaxLifetime(c.config.MaxConnLifetime * time.Second)
+ }
+ } else {
+ sqlDb.SetConnMaxLifetime(defaultMaxConnLifeTime)
}
- return sqlDb
+ return sqlDb, nil
}, 0)
if v != nil && sqlDb == nil {
sqlDb = v.(*sql.DB)
diff --git a/database/gdb/gdb_core.go b/database/gdb/gdb_core.go
index 372a6f073..14cab14f1 100644
--- a/database/gdb/gdb_core.go
+++ b/database/gdb/gdb_core.go
@@ -1,4 +1,4 @@
-// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
+// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
@@ -8,19 +8,51 @@
package gdb
import (
+ "context"
"database/sql"
- "errors"
"fmt"
- "github.com/gogf/gf/internal/utils"
+ "github.com/gogf/gf/errors/gerror"
+ "github.com/gogf/gf/text/gstr"
"reflect"
"strings"
+ "github.com/gogf/gf/internal/utils"
+
"github.com/gogf/gf/container/gvar"
"github.com/gogf/gf/os/gtime"
"github.com/gogf/gf/text/gregex"
"github.com/gogf/gf/util/gconv"
)
+// 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.
+func (c *Core) Ctx(ctx context.Context) DB {
+ if ctx == nil {
+ return c.DB
+ }
+ var (
+ err error
+ newCore = &Core{}
+ configNode = c.DB.GetConfig()
+ )
+ *newCore = *c
+ newCore.ctx = ctx
+ newCore.DB, err = driverMap[configNode.Type].New(newCore, configNode)
+ // Seldom error, just log it.
+ if err != nil {
+ c.DB.GetLogger().Ctx(ctx).Error(err)
+ }
+ return newCore.DB
+}
+
+// GetCtx returns the context for current DB.
+// Note that it might be nil.
+func (c *Core) GetCtx() context.Context {
+ return c.ctx
+}
+
// Master creates and returns a connection from master node if master-slave configured.
// It returns the default connection if master-slave not configured.
func (c *Core) Master() (*sql.DB, error) {
@@ -59,6 +91,7 @@ func (c *Core) DoQuery(link Link, sql string, args ...interface{}) (rows *sql.Ro
Error: err,
Start: mTime1,
End: mTime2,
+ Group: c.DB.GetGroup(),
}
c.writeSqlToLogger(s)
} else {
@@ -102,6 +135,7 @@ func (c *Core) DoExec(link Link, sql string, args ...interface{}) (result sql.Re
Error: err,
Start: mTime1,
End: mTime2,
+ Group: c.DB.GetGroup(),
}
c.writeSqlToLogger(s)
} else {
@@ -160,7 +194,7 @@ func (c *Core) DoGetAll(link Link, sql string, args ...interface{}) (result Resu
return nil, err
}
defer rows.Close()
- return c.DB.rowsToResult(rows)
+ return c.DB.convertRowsToResult(rows)
}
// GetOne queries and returns one record from database.
@@ -307,6 +341,11 @@ func (c *Core) Transaction(f func(tx *TX) error) (err error) {
return err
}
defer func() {
+ if err == nil {
+ if e := recover(); e != nil {
+ err = fmt.Errorf("%v", e)
+ }
+ }
if err != nil {
if e := tx.Rollback(); e != nil {
err = e
@@ -331,7 +370,10 @@ func (c *Core) Transaction(f func(tx *TX) error) (err error) {
//
// The parameter specifies the batch operation count when given data is slice.
func (c *Core) Insert(table string, data interface{}, batch ...int) (sql.Result, error) {
- return c.DB.DoInsert(nil, table, data, gINSERT_OPTION_DEFAULT, batch...)
+ if len(batch) > 0 {
+ return c.Model(table).Data(data).Batch(batch[0]).Insert()
+ }
+ return c.Model(table).Data(data).Insert()
}
// InsertIgnore does "INSERT IGNORE INTO ..." statement for the table.
@@ -344,7 +386,10 @@ func (c *Core) Insert(table string, data interface{}, batch ...int) (sql.Result,
//
// The parameter specifies the batch operation count when given data is slice.
func (c *Core) InsertIgnore(table string, data interface{}, batch ...int) (sql.Result, error) {
- return c.DB.DoInsert(nil, table, data, gINSERT_OPTION_IGNORE, batch...)
+ if len(batch) > 0 {
+ return c.Model(table).Data(data).Batch(batch[0]).InsertIgnore()
+ }
+ return c.Model(table).Data(data).InsertIgnore()
}
// Replace does "REPLACE INTO ..." statement for the table.
@@ -360,7 +405,10 @@ func (c *Core) InsertIgnore(table string, data interface{}, batch ...int) (sql.R
// If given data is type of slice, it then does batch replacing, and the optional parameter
// specifies the batch operation count.
func (c *Core) Replace(table string, data interface{}, batch ...int) (sql.Result, error) {
- return c.DB.DoInsert(nil, table, data, gINSERT_OPTION_REPLACE, batch...)
+ if len(batch) > 0 {
+ return c.Model(table).Data(data).Batch(batch[0]).Replace()
+ }
+ return c.Model(table).Data(data).Replace()
}
// Save does "INSERT INTO ... ON DUPLICATE KEY UPDATE..." statement for the table.
@@ -375,11 +423,14 @@ func (c *Core) Replace(table string, data interface{}, batch ...int) (sql.Result
// If given data is type of slice, it then does batch saving, and the optional parameter
// specifies the batch operation count.
func (c *Core) Save(table string, data interface{}, batch ...int) (sql.Result, error) {
- return c.DB.DoInsert(nil, table, data, gINSERT_OPTION_SAVE, batch...)
+ if len(batch) > 0 {
+ return c.Model(table).Data(data).Batch(batch[0]).Save()
+ }
+ return c.Model(table).Data(data).Save()
}
// doInsert inserts or updates data for given table.
-//
+// This function is usually used for custom interface definition, you do not need call it manually.
// The parameter can be type of map/gmap/struct/*struct/[]map/[]struct, etc.
// Eg:
// Data(g.Map{"uid": 10000, "name":"john"})
@@ -407,13 +458,19 @@ func (c *Core) DoInsert(link Link, table string, data interface{}, option int, b
switch reflectKind {
case reflect.Slice, reflect.Array:
return c.DB.DoBatchInsert(link, table, data, option, batch...)
- case reflect.Map, reflect.Struct:
- dataMap = DataToMapDeep(data)
+ case reflect.Struct:
+ if _, ok := data.(apiInterfaces); ok {
+ return c.DB.DoBatchInsert(link, table, data, option, batch...)
+ } else {
+ dataMap = ConvertDataForTableRecord(data)
+ }
+ case reflect.Map:
+ dataMap = ConvertDataForTableRecord(data)
default:
- return result, errors.New(fmt.Sprint("unsupported data type:", reflectKind))
+ return result, gerror.New(fmt.Sprint("unsupported data type:", reflectKind))
}
if len(dataMap) == 0 {
- return nil, errors.New("data cannot be empty")
+ return nil, gerror.New("data cannot be empty")
}
var (
charL, charR = c.DB.GetChars()
@@ -422,14 +479,18 @@ func (c *Core) DoInsert(link Link, table string, data interface{}, option int, b
)
for k, v := range dataMap {
fields = append(fields, charL+k+charR)
- values = append(values, "?")
- params = append(params, v)
+ if s, ok := v.(Raw); ok {
+ values = append(values, gconv.String(s))
+ } else {
+ values = append(values, "?")
+ params = append(params, v)
+ }
}
- if option == gINSERT_OPTION_SAVE {
+ if option == insertOptionSave {
for k, _ := range dataMap {
// If it's SAVE operation,
// do not automatically update the creating time.
- if utils.EqualFoldWithoutChars(k, gSOFT_FIELD_NAME_CREATE) {
+ if c.isSoftCreatedFiledName(k) {
continue
}
if len(updateStr) > 0 {
@@ -462,45 +523,58 @@ func (c *Core) DoInsert(link Link, table string, data interface{}, option int, b
// BatchInsert batch inserts data.
// The parameter must be type of slice of map or struct.
func (c *Core) BatchInsert(table string, list interface{}, batch ...int) (sql.Result, error) {
- return c.DB.DoBatchInsert(nil, table, list, gINSERT_OPTION_DEFAULT, batch...)
+ if len(batch) > 0 {
+ return c.Model(table).Data(list).Batch(batch[0]).Insert()
+ }
+ return c.Model(table).Data(list).Insert()
}
-// BatchInsert batch inserts data with ignore option.
+// BatchInsertIgnore batch inserts data with ignore option.
// The parameter must be type of slice of map or struct.
func (c *Core) BatchInsertIgnore(table string, list interface{}, batch ...int) (sql.Result, error) {
- return c.DB.DoBatchInsert(nil, table, list, gINSERT_OPTION_IGNORE, batch...)
+ if len(batch) > 0 {
+ return c.Model(table).Data(list).Batch(batch[0]).InsertIgnore()
+ }
+ return c.Model(table).Data(list).InsertIgnore()
}
// BatchReplace batch replaces data.
// The parameter must be type of slice of map or struct.
func (c *Core) BatchReplace(table string, list interface{}, batch ...int) (sql.Result, error) {
- return c.DB.DoBatchInsert(nil, table, list, gINSERT_OPTION_REPLACE, batch...)
+ if len(batch) > 0 {
+ return c.Model(table).Data(list).Batch(batch[0]).Replace()
+ }
+ return c.Model(table).Data(list).Replace()
}
// BatchSave batch replaces data.
// The parameter must be type of slice of map or struct.
func (c *Core) BatchSave(table string, list interface{}, batch ...int) (sql.Result, error) {
- return c.DB.DoBatchInsert(nil, table, list, gINSERT_OPTION_SAVE, batch...)
+ if len(batch) > 0 {
+ return c.Model(table).Data(list).Batch(batch[0]).Save()
+ }
+ return c.Model(table).Data(list).Save()
}
// DoBatchInsert batch inserts/replaces/saves data.
+// This function is usually used for custom interface definition, you do not need call it manually.
func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option int, batch ...int) (result sql.Result, err error) {
table = c.DB.QuotePrefixTableName(table)
var (
- keys []string
- values []string
- params []interface{}
- listMap List
+ keys []string // Field names.
+ values []string // Value holder string array, like: (?,?,?)
+ params []interface{} // Values that will be committed to underlying database driver.
+ listMap List // The data list that passed from caller.
)
- switch v := list.(type) {
+ switch value := list.(type) {
case Result:
- listMap = v.List()
+ listMap = value.List()
case Record:
- listMap = List{v.Map()}
+ listMap = List{value.Map()}
case List:
- listMap = v
+ listMap = value
case Map:
- listMap = List{v}
+ listMap = List{value}
default:
var (
rv = reflect.ValueOf(list)
@@ -515,16 +589,29 @@ func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option i
case reflect.Slice, reflect.Array:
listMap = make(List, rv.Len())
for i := 0; i < rv.Len(); i++ {
- listMap[i] = DataToMapDeep(rv.Index(i).Interface())
+ listMap[i] = ConvertDataForTableRecord(rv.Index(i).Interface())
+ }
+ case reflect.Map:
+ listMap = List{ConvertDataForTableRecord(value)}
+ case reflect.Struct:
+ if v, ok := value.(apiInterfaces); ok {
+ var (
+ array = v.Interfaces()
+ list = make(List, len(array))
+ )
+ for i := 0; i < len(array); i++ {
+ list[i] = ConvertDataForTableRecord(array[i])
+ }
+ listMap = list
+ } else {
+ listMap = List{ConvertDataForTableRecord(value)}
}
- case reflect.Map, reflect.Struct:
- listMap = List{DataToMapDeep(v)}
default:
- return result, errors.New(fmt.Sprint("unsupported list type:", kind))
+ return result, gerror.New(fmt.Sprint("unsupported list type:", kind))
}
}
if len(listMap) < 1 {
- return result, errors.New("data list cannot be empty")
+ return result, gerror.New("data list cannot be empty")
}
if link == nil {
if link, err = c.DB.Master(); err != nil {
@@ -532,25 +619,22 @@ func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option i
}
}
// Handle the field names and place holders.
- holders := []string(nil)
for k, _ := range listMap[0] {
keys = append(keys, k)
- holders = append(holders, "?")
}
// Prepare the batch result pointer.
var (
- charL, charR = c.DB.GetChars()
- batchResult = new(SqlResult)
- keysStr = charL + strings.Join(keys, charR+","+charL) + charR
- valueHolderStr = "(" + strings.Join(holders, ",") + ")"
- operation = GetInsertOperationByOption(option)
- updateStr = ""
+ charL, charR = c.DB.GetChars()
+ batchResult = new(SqlResult)
+ keysStr = charL + strings.Join(keys, charR+","+charL) + charR
+ operation = GetInsertOperationByOption(option)
+ updateStr = ""
)
- if option == gINSERT_OPTION_SAVE {
+ if option == insertOptionSave {
for _, k := range keys {
// If it's SAVE operation,
// do not automatically update the creating time.
- if utils.EqualFoldWithoutChars(k, gSOFT_FIELD_NAME_CREATE) {
+ if c.isSoftCreatedFiledName(k) {
continue
}
if len(updateStr) > 0 {
@@ -564,27 +648,34 @@ func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option i
}
updateStr = fmt.Sprintf("ON DUPLICATE KEY UPDATE %s", updateStr)
}
- batchNum := gDEFAULT_BATCH_NUM
+ batchNum := defaultBatchNumber
if len(batch) > 0 && batch[0] > 0 {
batchNum = batch[0]
}
- listMapLen := len(listMap)
+ var (
+ listMapLen = len(listMap)
+ valueHolder = make([]string, 0)
+ )
for i := 0; i < listMapLen; i++ {
+ values = values[:0]
// Note that the map type is unordered,
// so it should use slice+key to retrieve the value.
for _, k := range keys {
- params = append(params, listMap[i][k])
+ if s, ok := listMap[i][k].(Raw); ok {
+ values = append(values, gconv.String(s))
+ } else {
+ values = append(values, "?")
+ params = append(params, listMap[i][k])
+ }
}
- values = append(values, valueHolderStr)
+ valueHolder = append(valueHolder, "("+gstr.Join(values, ",")+")")
if len(values) == batchNum || (i == listMapLen-1 && len(values) > 0) {
r, err := c.DB.DoExec(
link,
fmt.Sprintf(
"%s INTO %s(%s) VALUES%s %s",
- operation,
- table,
- keysStr,
- strings.Join(values, ","),
+ operation, table, keysStr,
+ gstr.Join(valueHolder, ","),
updateStr,
),
params...,
@@ -599,7 +690,7 @@ func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option i
batchResult.affected += n
}
params = params[:0]
- values = values[:0]
+ valueHolder = valueHolder[:0]
}
}
return batchResult, nil
@@ -620,15 +711,11 @@ func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option i
// "age IN(?,?)", 18, 50
// User{ Id : 1, UserName : "john"}
func (c *Core) Update(table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error) {
- newWhere, newArgs := formatWhere(c.DB, condition, args, false)
- if newWhere != "" {
- newWhere = " WHERE " + newWhere
- }
- return c.DB.DoUpdate(nil, table, data, newWhere, newArgs...)
+ return c.Model(table).Data(data).Where(condition, args...).Update()
}
// doUpdate does "UPDATE ... " statement for the table.
-// Also see Update.
+// This function is usually used for custom interface definition, you do not need call it manually.
func (c *Core) DoUpdate(link Link, table string, data interface{}, condition string, args ...interface{}) (result sql.Result, err error) {
table = c.DB.QuotePrefixTableName(table)
var (
@@ -647,18 +734,38 @@ func (c *Core) DoUpdate(link Link, table string, data interface{}, condition str
case reflect.Map, reflect.Struct:
var (
fields []string
- dataMap = DataToMapDeep(data)
+ dataMap = ConvertDataForTableRecord(data)
)
for k, v := range dataMap {
- fields = append(fields, c.DB.QuoteWord(k)+"=?")
- params = append(params, v)
+ switch value := v.(type) {
+ case *Counter:
+ if value.Value != 0 {
+ 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)
+ fields = append(fields, fmt.Sprintf("%s=%s+?", column, column))
+ params = append(params, value.Value)
+ }
+ default:
+ if s, ok := v.(Raw); ok {
+ fields = append(fields, c.DB.QuoteWord(k)+"="+gconv.String(s))
+ } else {
+ fields = append(fields, c.DB.QuoteWord(k)+"=?")
+ params = append(params, v)
+ }
+
+ }
}
updates = strings.Join(fields, ",")
default:
updates = gconv.String(data)
}
if len(updates) == 0 {
- return nil, errors.New("data cannot be empty")
+ return nil, gerror.New("data cannot be empty")
}
if len(params) > 0 {
args = append(params, args...)
@@ -688,15 +795,11 @@ func (c *Core) DoUpdate(link Link, table string, data interface{}, condition str
// "age IN(?,?)", 18, 50
// User{ Id : 1, UserName : "john"}
func (c *Core) Delete(table string, condition interface{}, args ...interface{}) (result sql.Result, err error) {
- newWhere, newArgs := formatWhere(c.DB, condition, args, false)
- if newWhere != "" {
- newWhere = " WHERE " + newWhere
- }
- return c.DB.DoDelete(nil, table, newWhere, newArgs...)
+ return c.Model(table).Where(condition, args...).Delete()
}
// DoDelete does "DELETE FROM ... " statement for the table.
-// Also see Delete.
+// This function is usually used for custom interface definition, you do not need call it manually.
func (c *Core) DoDelete(link Link, table string, condition string, args ...interface{}) (result sql.Result, err error) {
if link == nil {
if link, err = c.DB.Master(); err != nil {
@@ -707,8 +810,8 @@ func (c *Core) DoDelete(link Link, table string, condition string, args ...inter
return c.DB.DoExec(link, fmt.Sprintf("DELETE FROM %s%s", table, condition), args...)
}
-// rowsToResult converts underlying data record type sql.Rows to Result type.
-func (c *Core) rowsToResult(rows *sql.Rows) (Result, error) {
+// convertRowsToResult converts underlying data record type sql.Rows to Result type.
+func (c *Core) convertRowsToResult(rows *sql.Rows) (Result, error) {
if !rows.Next() {
return nil, nil
}
@@ -724,7 +827,7 @@ func (c *Core) rowsToResult(rows *sql.Rows) (Result, error) {
columnNames[k] = v.Name()
}
var (
- values = make([]sql.RawBytes, len(columnNames))
+ values = make([]interface{}, len(columnNames))
records = make(Result, 0)
scanArgs = make([]interface{}, len(values))
)
@@ -735,19 +838,12 @@ func (c *Core) rowsToResult(rows *sql.Rows) (Result, error) {
if err := rows.Scan(scanArgs...); err != nil {
return records, err
}
- // Creates a new row object.
row := make(Record)
- // Note that the internal looping variable is type of []byte,
- // which points to the same memory address. So it should do a copy.
for i, value := range values {
if value == nil {
row[columnNames[i]] = gvar.New(nil)
} else {
- // As sql.RawBytes is type of slice,
- // it should do a copy of it.
- v := make([]byte, len(value))
- copy(v, value)
- row[columnNames[i]] = gvar.New(c.DB.convertValue(v, columnTypes[i]))
+ row[columnNames[i]] = gvar.New(c.DB.convertFieldValueToLocalValue(value, columnTypes[i]))
}
}
records = append(records, row)
@@ -768,13 +864,46 @@ func (c *Core) MarshalJSON() ([]byte, error) {
}
// writeSqlToLogger outputs the sql object to logger.
-// It is enabled when configuration "debug" is true.
+// It is enabled only if configuration "debug" is true.
func (c *Core) writeSqlToLogger(v *Sql) {
- s := fmt.Sprintf("[%3d ms] %s", v.End-v.Start, v.Format)
+ s := fmt.Sprintf("[%3d ms] [%s] %s", v.End-v.Start, v.Group, v.Format)
if v.Error != nil {
s += "\nError: " + v.Error.Error()
- c.logger.Error(s)
+ c.logger.Ctx(c.DB.GetCtx()).Error(s)
} else {
- c.logger.Debug(s)
+ c.logger.Ctx(c.DB.GetCtx()).Debug(s)
}
}
+
+// HasTable determine whether the table name exists in the database.
+func (c *Core) HasTable(name string) (bool, error) {
+ tableList, err := c.DB.Tables()
+ if err != nil {
+ return false, err
+ }
+ for _, table := range tableList {
+ if table == name {
+ return true, nil
+ }
+ }
+ return false, nil
+}
+
+// isSoftCreatedFiledName checks and returns whether given filed name is an automatic-filled created time.
+func (c *Core) isSoftCreatedFiledName(fieldName string) bool {
+ if fieldName == "" {
+ return false
+ }
+ if config := c.DB.GetConfig(); config.CreatedAt != "" {
+ if utils.EqualFoldWithoutChars(fieldName, config.CreatedAt) {
+ return true
+ }
+ return gstr.InArray(append([]string{config.CreatedAt}, createdFiledNames...), fieldName)
+ }
+ for _, v := range createdFiledNames {
+ if utils.EqualFoldWithoutChars(fieldName, v) {
+ return true
+ }
+ }
+ return false
+}
diff --git a/database/gdb/gdb_core_config.go b/database/gdb/gdb_core_config.go
index 9bee4bde3..85a9ee888 100644
--- a/database/gdb/gdb_core_config.go
+++ b/database/gdb/gdb_core_config.go
@@ -1,4 +1,4 @@
-// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
+// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
@@ -16,7 +16,8 @@ import (
)
const (
- DEFAULT_GROUP_NAME = "default" // Default group name.
+ DEFAULT_GROUP_NAME = "default" // Deprecated, use DefaultGroupName instead.
+ DefaultGroupName = "default" // Default group name.
)
// Config is the configuration management object.
@@ -27,22 +28,26 @@ type ConfigGroup []ConfigNode
// ConfigNode is configuration for one node.
type ConfigNode struct {
- Host string // Host of server, ip or domain like: 127.0.0.1, localhost
- Port string // Port, it's commonly 3306.
- User string // Authentication username.
- Pass string // Authentication password.
- Name string // Default used database name.
- Type string // Database type: mysql, sqlite, mssql, pgsql, oracle.
- Role string // (Optional, "master" in default) Node role, used for master-slave mode: master, slave.
- Debug bool // (Optional) Debug mode enables debug information logging and output.
- Prefix string // (Optional) Table prefix.
- DryRun bool // (Optional) Dry run, which does SELECT but no INSERT/UPDATE/DELETE statements.
- Weight int // (Optional) Weight for load balance calculating, it's useless if there's just one node.
- Charset string // (Optional, "utf8mb4" in default) Custom charset when operating on database.
- 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.
+ Host string // Host of server, ip or domain like: 127.0.0.1, localhost
+ Port string // Port, it's commonly 3306.
+ User string // Authentication username.
+ Pass string // Authentication password.
+ Name string // Default used database name.
+ Type string // Database type: mysql, sqlite, mssql, pgsql, oracle.
+ Role string // (Optional, "master" in default) Node role, used for master-slave mode: master, slave.
+ Debug bool // (Optional) Debug mode enables debug information logging and output.
+ Prefix string // (Optional) Table prefix.
+ DryRun bool // (Optional) Dry run, which does SELECT but no INSERT/UPDATE/DELETE statements.
+ Weight int // (Optional) Weight for load balance calculating, it's useless if there's just one node.
+ Charset string // (Optional, "utf8mb4" in default) Custom charset when operating on database.
+ 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.
+ CreatedAt string // (Optional) The filed name of table for automatic-filled created datetime.
+ UpdatedAt string // (Optional) The filed name of table for automatic-filled updated datetime.
+ DeletedAt string // (Optional) The filed name of table for automatic-filled updated datetime.
+ TimeMaintainDisabled bool // (Optional) Disable the automatic time maintaining feature.
}
// configs is internal used configuration object.
@@ -54,7 +59,7 @@ var configs struct {
func init() {
configs.config = make(Config)
- configs.group = DEFAULT_GROUP_NAME
+ configs.group = DefaultGroupName
}
// SetConfig sets the global configuration for package.
@@ -84,12 +89,12 @@ func AddConfigNode(group string, node ConfigNode) {
// AddDefaultConfigNode adds one node configuration to configuration of default group.
func AddDefaultConfigNode(node ConfigNode) {
- AddConfigNode(DEFAULT_GROUP_NAME, node)
+ AddConfigNode(DefaultGroupName, node)
}
// AddDefaultConfigGroup adds multiple node configurations to configuration of default group.
func AddDefaultConfigGroup(nodes ConfigGroup) {
- SetConfigGroup(DEFAULT_GROUP_NAME, nodes)
+ SetConfigGroup(DefaultGroupName, nodes)
}
// GetConfig retrieves and returns the configuration of given group.
@@ -115,6 +120,14 @@ func GetDefaultGroup() string {
return configs.group
}
+// IsConfigured checks and returns whether the database configured.
+// It returns true if any configuration exists.
+func IsConfigured() bool {
+ configs.RLock()
+ defer configs.RUnlock()
+ return len(configs.config) > 0
+}
+
// SetLogger sets the logger for orm.
func (c *Core) SetLogger(logger *glog.Logger) {
c.logger = logger
@@ -127,18 +140,18 @@ func (c *Core) GetLogger() *glog.Logger {
// SetMaxIdleConnCount sets the max idle connection count for underlying connection pool.
func (c *Core) SetMaxIdleConnCount(n int) {
- c.maxIdleConnCount = n
+ c.config.MaxIdleConnCount = n
}
// SetMaxOpenConnCount sets the max open connection count for underlying connection pool.
func (c *Core) SetMaxOpenConnCount(n int) {
- c.maxOpenConnCount = n
+ c.config.MaxOpenConnCount = n
}
// SetMaxConnLifetime sets the connection TTL for underlying connection pool.
// If parameter <= 0, it means the connection never expires.
func (c *Core) SetMaxConnLifetime(d time.Duration) {
- c.maxConnLifetime = d
+ c.config.MaxConnLifetime = d
}
// String returns the node as string.
@@ -154,6 +167,11 @@ func (node *ConfigNode) String() string {
)
}
+// GetConfig returns the current used node configuration.
+func (c *Core) GetConfig() *ConfigNode {
+ return c.config
+}
+
// SetDebug enables/disables the debug mode.
func (c *Core) SetDebug(debug bool) {
c.debug.Set(debug)
@@ -169,24 +187,27 @@ func (c *Core) GetCache() *gcache.Cache {
return c.cache
}
-// GetPrefix returns the table prefix string configured.
-func (c *Core) GetPrefix() string {
- return c.prefix
-}
-
// GetGroup returns the group string configured.
func (c *Core) GetGroup() string {
return c.group
}
// SetDryRun enables/disables the DryRun feature.
-func (c *Core) SetDryRun(dryrun bool) {
- c.dryrun.Set(dryrun)
+// Deprecated, use GetConfig instead.
+func (c *Core) SetDryRun(enabled bool) {
+ c.config.DryRun = enabled
}
// GetDryRun returns the DryRun value.
+// Deprecated, use GetConfig instead.
func (c *Core) GetDryRun() bool {
- return c.dryrun.Val()
+ return c.config.DryRun
+}
+
+// GetPrefix returns the table prefix string configured.
+// Deprecated, use GetConfig instead.
+func (c *Core) GetPrefix() string {
+ return c.config.Prefix
}
// SetSchema changes the schema for this database connection object.
diff --git a/database/gdb/gdb_core_structure.go b/database/gdb/gdb_core_structure.go
new file mode 100644
index 000000000..27fedb2a1
--- /dev/null
+++ b/database/gdb/gdb_core_structure.go
@@ -0,0 +1,197 @@
+// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+package gdb
+
+import (
+ "github.com/gogf/gf/errors/gerror"
+ "github.com/gogf/gf/util/gutil"
+ "strings"
+ "time"
+
+ "github.com/gogf/gf/text/gstr"
+
+ "github.com/gogf/gf/os/gtime"
+
+ "github.com/gogf/gf/encoding/gbinary"
+
+ "github.com/gogf/gf/text/gregex"
+ "github.com/gogf/gf/util/gconv"
+)
+
+// convertFieldValueToLocalValue automatically checks and converts field value from database type
+// to golang variable type.
+func (c *Core) convertFieldValueToLocalValue(fieldValue interface{}, fieldType string) interface{} {
+ // If there's no type retrieved, it returns the directly
+ // to use its original data type, as is type of interface{}.
+ if fieldType == "" {
+ return fieldValue
+ }
+ t, _ := gregex.ReplaceString(`\(.+\)`, "", fieldType)
+ t = strings.ToLower(t)
+ switch t {
+ case
+ "binary",
+ "varbinary",
+ "blob",
+ "tinyblob",
+ "mediumblob",
+ "longblob":
+ return gconv.Bytes(fieldValue)
+
+ case
+ "int",
+ "tinyint",
+ "small_int",
+ "smallint",
+ "medium_int",
+ "mediumint",
+ "serial":
+ if gstr.ContainsI(fieldType, "unsigned") {
+ gconv.Uint(gconv.String(fieldValue))
+ }
+ return gconv.Int(gconv.String(fieldValue))
+
+ case
+ "big_int",
+ "bigint",
+ "bigserial":
+ if gstr.ContainsI(fieldType, "unsigned") {
+ gconv.Uint64(gconv.String(fieldValue))
+ }
+ return gconv.Int64(gconv.String(fieldValue))
+
+ case "real":
+ return gconv.Float32(gconv.String(fieldValue))
+
+ case
+ "float",
+ "double",
+ "decimal",
+ "money",
+ "numeric",
+ "smallmoney":
+ return gconv.Float64(gconv.String(fieldValue))
+
+ case "bit":
+ s := gconv.String(fieldValue)
+ // mssql is true|false string.
+ if strings.EqualFold(s, "true") {
+ return 1
+ }
+ if strings.EqualFold(s, "false") {
+ return 0
+ }
+ return gbinary.BeDecodeToInt64(gconv.Bytes(fieldValue))
+
+ case "bool":
+ return gconv.Bool(fieldValue)
+
+ case "date":
+ if t, ok := fieldValue.(time.Time); ok {
+ return gtime.NewFromTime(t).Format("Y-m-d")
+ }
+ t, _ := gtime.StrToTime(gconv.String(fieldValue))
+ return t.Format("Y-m-d")
+
+ case
+ "datetime",
+ "timestamp":
+ if t, ok := fieldValue.(time.Time); ok {
+ return gtime.NewFromTime(t)
+ }
+ t, _ := gtime.StrToTime(gconv.String(fieldValue))
+ return t.String()
+
+ default:
+ // Auto detect field type, using key match.
+ switch {
+ case strings.Contains(t, "text") || strings.Contains(t, "char") || strings.Contains(t, "character"):
+ return gconv.String(fieldValue)
+
+ case strings.Contains(t, "float") || strings.Contains(t, "double") || strings.Contains(t, "numeric"):
+ return gconv.Float64(gconv.String(fieldValue))
+
+ case strings.Contains(t, "bool"):
+ return gconv.Bool(gconv.String(fieldValue))
+
+ case strings.Contains(t, "binary") || strings.Contains(t, "blob"):
+ return fieldValue
+
+ case strings.Contains(t, "int"):
+ return gconv.Int(gconv.String(fieldValue))
+
+ case strings.Contains(t, "time"):
+ s := gconv.String(fieldValue)
+ t, err := gtime.StrToTime(s)
+ if err != nil {
+ return s
+ }
+ return t.String()
+
+ case strings.Contains(t, "date"):
+ s := gconv.String(fieldValue)
+ t, err := gtime.StrToTime(s)
+ if err != nil {
+ return s
+ }
+ return t.Format("Y-m-d")
+
+ default:
+ return gconv.String(fieldValue)
+ }
+ }
+}
+
+// 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 {
+ fieldsKeyMap := make(map[string]interface{}, len(fieldsMap))
+ for k, _ := range fieldsMap {
+ fieldsKeyMap[k] = nil
+ }
+ // Automatic data key to table field name mapping.
+ var foundKey string
+ for dataKey, dataValue := range data {
+ if _, ok := fieldsKeyMap[dataKey]; !ok {
+ foundKey, _ = gutil.MapPossibleItemByKey(fieldsKeyMap, dataKey)
+ if foundKey != "" {
+ data[foundKey] = dataValue
+ delete(data, dataKey)
+ } else if !filter {
+ if schema != "" {
+ return nil, gerror.Newf(`no column of name "%s" found for table "%s" in schema "%s"`, dataKey, table, schema)
+ }
+ return nil, gerror.Newf(`no column of name "%s" found for table "%s"`, dataKey, table)
+ }
+ }
+ }
+ // Data filtering.
+ if filter {
+ for dataKey, _ := range data {
+ if _, ok := fieldsMap[dataKey]; !ok {
+ delete(data, dataKey)
+ }
+ }
+ }
+ }
+ return data, nil
+}
+
+//// filterFields removes all key-value pairs which are not the field of given table.
+//func (c *Core) filterFields(schema, table string, data map[string]interface{}) map[string]interface{} {
+// // It must use data copy here to avoid its changing the origin data map.
+// newDataMap := make(map[string]interface{}, len(data))
+// if fields, err := c.DB.TableFields(table, schema); err == nil {
+// for k, v := range data {
+// if _, ok := fields[k]; ok {
+// newDataMap[k] = v
+// }
+// }
+// }
+// return newDataMap
+//}
diff --git a/database/gdb/gdb_core_utility.go b/database/gdb/gdb_core_utility.go
index 5c7e13005..c86086d99 100644
--- a/database/gdb/gdb_core_utility.go
+++ b/database/gdb/gdb_core_utility.go
@@ -1,4 +1,4 @@
-// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
+// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
diff --git a/database/gdb/gdb_driver_mssql.go b/database/gdb/gdb_driver_mssql.go
index 520582156..b3718e333 100644
--- a/database/gdb/gdb_driver_mssql.go
+++ b/database/gdb/gdb_driver_mssql.go
@@ -1,4 +1,4 @@
-// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
+// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
@@ -13,8 +13,8 @@ package gdb
import (
"database/sql"
- "errors"
"fmt"
+ "github.com/gogf/gf/errors/gerror"
"strconv"
"strings"
@@ -64,10 +64,10 @@ func (d *DriverMssql) GetChars() (charLeft string, charRight string) {
// HandleSqlBeforeCommit deals with the sql string before commits it to underlying sql driver.
func (d *DriverMssql) HandleSqlBeforeCommit(link Link, sql string, args []interface{}) (string, []interface{}) {
var index int
- // Convert place holder char '?' to string "@vx".
+ // Convert place holder char '?' to string "@px".
str, _ := gregex.ReplaceStringFunc("\\?", sql, func(s string) string {
index++
- return fmt.Sprintf("@v%d", index)
+ return fmt.Sprintf("@p%d", index)
})
str, _ = gregex.ReplaceString("\"", "", str)
return d.parseSql(str), args
@@ -192,53 +192,64 @@ func (d *DriverMssql) TableFields(table string, schema ...string) (fields map[st
charL, charR := d.GetChars()
table = gstr.Trim(table, charL+charR)
if gstr.Contains(table, " ") {
- return nil, errors.New("function TableFields supports only single table operations")
+ return nil, gerror.New("function TableFields supports only single table operations")
}
checkSchema := d.DB.GetSchema()
if len(schema) > 0 && schema[0] != "" {
checkSchema = schema[0]
}
- v := d.DB.GetCache().GetOrSetFunc(
- fmt.Sprintf(`mssql_table_fields_%s_%s`, table, checkSchema), func() interface{} {
- var result Result
- var link *sql.DB
+ v, _ := internalCache.GetOrSetFunc(
+ fmt.Sprintf(`mssql_table_fields_%s_%s@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
+ return nil, err
}
- result, err = d.DB.DoGetAll(link, fmt.Sprintf(`
- SELECT a.name Field,
+ structureSql := fmt.Sprintf(`
+SELECT
+ a.name Field,
CASE b.name
WHEN 'datetime' THEN 'datetime'
- WHEN 'numeric' THEN b.name + '(' + convert(varchar(20),a.xprec) + ',' + convert(varchar(20),a.xscale) + ')'
- WHEN 'char' THEN b.name + '(' + convert(varchar(20),a.length)+ ')'
- WHEN 'varchar' THEN b.name + '(' + convert(varchar(20),a.length)+ ')'
- ELSE b.name + '(' + convert(varchar(20),a.length)+ ')' END as TYPE,
- case when a.isnullable=1 then 'YES'else 'NO' end as [Null],
- case when exists(SELECT 1 FROM sysobjects where xtype='PK' and name in (
- SELECT name FROM sysindexes WHERE indid in(
- SELECT indid FROM sysindexkeys WHERE id = a.id AND colid=a.colid
- ))) then 'PRI' else '' end AS [Key],
- case when COLUMNPROPERTY(a.id,a.name,'IsIdentity')=1 then 'auto_increment'else '' end Extra,
- isnull(e.text,'') as [Default],
+ WHEN 'numeric' THEN b.name + '(' + convert(varchar(20), a.xprec) + ',' + convert(varchar(20), a.xscale) + ')'
+ WHEN 'char' THEN b.name + '(' + convert(varchar(20), a.length)+ ')'
+ WHEN 'varchar' THEN b.name + '(' + convert(varchar(20), a.length)+ ')'
+ ELSE b.name + '(' + convert(varchar(20),a.length)+ ')' END AS Type,
+ CASE WHEN a.isnullable=1 THEN 'YES' ELSE 'NO' end AS [Null],
+ CASE WHEN exists (
+ SELECT 1 FROM sysobjects WHERE xtype='PK' AND name IN (
+ SELECT name FROM sysindexes WHERE indid IN (
+ SELECT indid FROM sysindexkeys WHERE id = a.id AND colid=a.colid
+ )
+ )
+ ) THEN 'PRI' ELSE '' END AS [Key],
+ CASE WHEN COLUMNPROPERTY(a.id,a.name,'IsIdentity')=1 THEN 'auto_increment' ELSE '' END Extra,
+ isnull(e.text,'') AS [Default],
isnull(g.[value],'') AS [Comment]
- FROM syscolumns a
- left join systypes b on a.xtype=b.xusertype
- inner join sysobjects d on a.id=d.id and d.xtype='U' and d.name<>'dtproperties'
- left join syscomments e on a.cdefault=e.id
- left join sys.extended_properties g on a.id=g.major_id and a.colid=g.minor_id
- left join sys.extended_properties f on d.id=f.major_id and f.minor_id =0
- where d.name='%s'
- order by a.id,a.colorder`, strings.ToUpper(table)))
+FROM syscolumns a
+LEFT JOIN systypes b ON a.xtype=b.xtype AND a.xusertype=b.xusertype
+INNER JOIN sysobjects d ON a.id=d.id AND d.xtype='U' AND d.name<>'dtproperties'
+LEFT JOIN syscomments e ON a.cdefault=e.id
+LEFT JOIN sys.extended_properties g ON a.id=g.major_id AND a.colid=g.minor_id
+LEFT JOIN sys.extended_properties f ON d.id=f.major_id AND f.minor_id =0
+WHERE d.name='%s'
+ORDER BY a.id,a.colorder`,
+ strings.ToUpper(table),
+ )
+ structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql))
+ result, err = d.DB.DoGetAll(link, structureSql)
if err != nil {
- return nil
+ return nil, err
}
fields = make(map[string]*TableField)
for i, m := range result {
- fields[strings.ToLower(m["FIELD"].String())] = &TableField{
+ fields[strings.ToLower(m["Field"].String())] = &TableField{
Index: i,
- Name: strings.ToLower(m["FIELD"].String()),
- Type: strings.ToLower(m["TYPE"].String()),
+ Name: strings.ToLower(m["Field"].String()),
+ Type: strings.ToLower(m["Type"].String()),
Null: m["Null"].Bool(),
Key: m["Key"].String(),
Default: m["Default"].Val(),
@@ -246,7 +257,7 @@ func (d *DriverMssql) TableFields(table string, schema ...string) (fields map[st
Comment: m["Comment"].String(),
}
}
- return fields
+ return fields, nil
}, 0)
if err == nil {
fields = v.(map[string]*TableField)
diff --git a/database/gdb/gdb_driver_mysql.go b/database/gdb/gdb_driver_mysql.go
index 2aecc9b3b..509152a58 100644
--- a/database/gdb/gdb_driver_mysql.go
+++ b/database/gdb/gdb_driver_mysql.go
@@ -1,4 +1,4 @@
-// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
+// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
@@ -8,8 +8,8 @@ package gdb
import (
"database/sql"
- "errors"
"fmt"
+ "github.com/gogf/gf/errors/gerror"
"github.com/gogf/gf/internal/intlog"
"github.com/gogf/gf/text/gregex"
"github.com/gogf/gf/text/gstr"
@@ -31,6 +31,7 @@ func (d *DriverMysql) New(core *Core, node *ConfigNode) (DB, error) {
}
// Open creates and returns a underlying sql.DB object for mysql.
+// Note that it converts time.Time argument to local timezone in default.
func (d *DriverMysql) Open(config *ConfigNode) (*sql.DB, error) {
var source string
if config.LinkInfo != "" {
@@ -41,7 +42,7 @@ func (d *DriverMysql) Open(config *ConfigNode) (*sql.DB, error) {
}
} else {
source = fmt.Sprintf(
- "%s:%s@tcp(%s:%s)/%s?charset=%s&multiStatements=true&parseTime=true&loc=Local",
+ "%s:%s@tcp(%s:%s)/%s?charset=%s&multiStatements=true&parseTime=true",
config.User, config.Pass, config.Host, config.Port, config.Name, config.Charset,
)
}
@@ -96,27 +97,29 @@ func (d *DriverMysql) TableFields(table string, schema ...string) (fields map[st
charL, charR := d.GetChars()
table = gstr.Trim(table, charL+charR)
if gstr.Contains(table, " ") {
- return nil, errors.New("function TableFields supports only single table operations")
+ return nil, gerror.New("function TableFields supports only single table operations")
}
checkSchema := d.schema.Val()
if len(schema) > 0 && schema[0] != "" {
checkSchema = schema[0]
}
- v := d.cache.GetOrSetFunc(
- fmt.Sprintf(`mysql_table_fields_%s_%s`, table, checkSchema),
- func() interface{} {
- var result Result
- var link *sql.DB
+ 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
+ )
link, err = d.DB.GetSlave(checkSchema)
if err != nil {
- return nil
+ return nil, err
}
result, err = d.DB.DoGetAll(
link,
fmt.Sprintf(`SHOW FULL COLUMNS FROM %s`, d.DB.QuoteWord(table)),
)
if err != nil {
- return nil
+ return nil, err
}
fields = make(map[string]*TableField)
for i, m := range result {
@@ -131,7 +134,7 @@ func (d *DriverMysql) TableFields(table string, schema ...string) (fields map[st
Comment: m["Comment"].String(),
}
}
- return fields
+ return fields, nil
}, 0)
if err == nil {
fields = v.(map[string]*TableField)
diff --git a/database/gdb/gdb_driver_oracle.go b/database/gdb/gdb_driver_oracle.go
index 59b06bae0..1e0ee3c98 100644
--- a/database/gdb/gdb_driver_oracle.go
+++ b/database/gdb/gdb_driver_oracle.go
@@ -1,4 +1,4 @@
-// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
+// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
@@ -13,10 +13,11 @@ package gdb
import (
"database/sql"
- "errors"
"fmt"
+ "github.com/gogf/gf/errors/gerror"
"github.com/gogf/gf/internal/intlog"
"github.com/gogf/gf/text/gstr"
+ "github.com/gogf/gf/util/gconv"
"reflect"
"strconv"
"strings"
@@ -72,50 +73,60 @@ func (d *DriverOracle) HandleSqlBeforeCommit(link Link, sql string, args []inter
return fmt.Sprintf(":v%d", index)
})
str, _ = gregex.ReplaceString("\"", "", str)
+ // Change time string argument wrapping with TO_DATE function.
+ for i, v := range args {
+ if reflect.TypeOf(v).Kind() == reflect.String {
+ valueStr := gconv.String(v)
+ if gregex.IsMatchString(`^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$`, valueStr) {
+ args[i] = fmt.Sprintf(`TO_DATE('%s','yyyy-MM-dd HH:MI:SS')`, valueStr)
+ }
+ }
+ }
return d.parseSql(str), args
}
// parseSql does some replacement of the sql before commits it to underlying driver,
// for support of oracle server.
func (d *DriverOracle) parseSql(sql string) string {
- patten := `^\s*(?i)(SELECT)|(LIMIT\s*(\d+)\s*,\s*(\d+))`
- if gregex.IsMatchString(patten, sql) == false {
+ var (
+ patten = `^\s*(?i)(SELECT)|(LIMIT\s*(\d+)\s*,{0,1}\s*(\d*))`
+ allMatch, _ = gregex.MatchAllString(patten, sql)
+ )
+ if len(allMatch) == 0 {
return sql
}
-
- res, err := gregex.MatchAllString(patten, sql)
- if err != nil {
- return ""
- }
-
var (
index = 0
- keyword = strings.ToUpper(strings.TrimSpace(res[index][0]))
+ keyword = strings.ToUpper(strings.TrimSpace(allMatch[index][0]))
)
index++
switch keyword {
case "SELECT":
- if len(res) < 2 || (strings.HasPrefix(res[index][0], "LIMIT") == false &&
- strings.HasPrefix(res[index][0], "limit") == false) {
+ if len(allMatch) < 2 || strings.HasPrefix(allMatch[index][0], "LIMIT") == false {
break
}
if gregex.IsMatchString("((?i)SELECT)(.+)((?i)LIMIT)", sql) == false {
break
}
queryExpr, _ := gregex.MatchString("((?i)SELECT)(.+)((?i)LIMIT)", sql)
- if len(queryExpr) != 4 || strings.EqualFold(queryExpr[1], "SELECT") == false ||
+ if len(queryExpr) != 4 ||
+ strings.EqualFold(queryExpr[1], "SELECT") == false ||
strings.EqualFold(queryExpr[3], "LIMIT") == false {
break
}
first, limit := 0, 0
- for i := 1; i < len(res[index]); i++ {
- if len(strings.TrimSpace(res[index][i])) == 0 {
+ for i := 1; i < len(allMatch[index]); i++ {
+ if len(strings.TrimSpace(allMatch[index][i])) == 0 {
continue
}
- if strings.HasPrefix(res[index][i], "LIMIT") || strings.HasPrefix(res[index][i], "limit") {
- first, _ = strconv.Atoi(res[index][i+1])
- limit, _ = strconv.Atoi(res[index][i+2])
+ if strings.HasPrefix(allMatch[index][i], "LIMIT") {
+ if allMatch[index][i+2] != "" {
+ first, _ = strconv.Atoi(allMatch[index][i+1])
+ limit, _ = strconv.Atoi(allMatch[index][i+2])
+ } else {
+ limit, _ = strconv.Atoi(allMatch[index][i+1])
+ }
break
}
}
@@ -151,24 +162,30 @@ func (d *DriverOracle) TableFields(table string, schema ...string) (fields map[s
charL, charR := d.GetChars()
table = gstr.Trim(table, charL+charR)
if gstr.Contains(table, " ") {
- return nil, errors.New("function TableFields supports only single table operations")
+ return nil, gerror.New("function TableFields supports only single table operations")
}
checkSchema := d.DB.GetSchema()
if len(schema) > 0 && schema[0] != "" {
checkSchema = schema[0]
}
- v := d.DB.GetCache().GetOrSetFunc(
- fmt.Sprintf(`oracle_table_fields_%s_%s`, table, checkSchema),
- func() interface{} {
+ v, _ := internalCache.GetOrSetFunc(
+ fmt.Sprintf(`oracle_table_fields_%s_%s@group:%s`, table, checkSchema, d.GetGroup()),
+ func() (interface{}, error) {
result := (Result)(nil)
- result, err = d.DB.GetAll(fmt.Sprintf(`
- SELECT COLUMN_NAME AS FIELD, CASE DATA_TYPE
- WHEN 'NUMBER' THEN DATA_TYPE||'('||DATA_PRECISION||','||DATA_SCALE||')'
- WHEN 'FLOAT' THEN DATA_TYPE||'('||DATA_PRECISION||','||DATA_SCALE||')'
- ELSE DATA_TYPE||'('||DATA_LENGTH||')' END AS TYPE
- FROM USER_TAB_COLUMNS WHERE TABLE_NAME = '%s' ORDER BY COLUMN_ID`, strings.ToUpper(table)))
+ structureSql := fmt.Sprintf(`
+SELECT
+ COLUMN_NAME AS FIELD,
+ CASE DATA_TYPE
+ WHEN 'NUMBER' THEN DATA_TYPE||'('||DATA_PRECISION||','||DATA_SCALE||')'
+ WHEN 'FLOAT' THEN DATA_TYPE||'('||DATA_PRECISION||','||DATA_SCALE||')'
+ ELSE DATA_TYPE||'('||DATA_LENGTH||')' END AS TYPE
+FROM USER_TAB_COLUMNS WHERE TABLE_NAME = '%s' ORDER BY COLUMN_ID`,
+ strings.ToUpper(table),
+ )
+ structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql))
+ result, err = d.DB.GetAll(structureSql)
if err != nil {
- return nil
+ return nil, err
}
fields = make(map[string]*TableField)
for i, m := range result {
@@ -178,7 +195,7 @@ func (d *DriverOracle) TableFields(table string, schema ...string) (fields map[s
Type: strings.ToLower(m["TYPE"].String()),
}
}
- return fields
+ return fields, nil
}, 0)
if err == nil {
fields = v.(map[string]*TableField)
@@ -188,24 +205,26 @@ func (d *DriverOracle) TableFields(table string, schema ...string) (fields map[s
func (d *DriverOracle) getTableUniqueIndex(table string) (fields map[string]map[string]string, err error) {
table = strings.ToUpper(table)
- v := d.DB.GetCache().GetOrSetFunc("table_unique_index_"+table, func() interface{} {
- res := (Result)(nil)
- res, err = d.DB.GetAll(fmt.Sprintf(`
+ v, _ := internalCache.GetOrSetFunc(
+ "table_unique_index_"+table,
+ func() (interface{}, error) {
+ res := (Result)(nil)
+ res, err = d.DB.GetAll(fmt.Sprintf(`
SELECT INDEX_NAME,COLUMN_NAME,CHAR_LENGTH FROM USER_IND_COLUMNS
WHERE TABLE_NAME = '%s'
AND INDEX_NAME IN(SELECT INDEX_NAME FROM USER_INDEXES WHERE TABLE_NAME='%s' AND UNIQUENESS='UNIQUE')
ORDER BY INDEX_NAME,COLUMN_POSITION`, table, table))
- if err != nil {
- return nil
- }
- fields := make(map[string]map[string]string)
- for _, v := range res {
- mm := make(map[string]string)
- mm[v["COLUMN_NAME"].String()] = v["CHAR_LENGTH"].String()
- fields[v["INDEX_NAME"].String()] = mm
- }
- return fields
- }, 0)
+ if err != nil {
+ return nil, err
+ }
+ fields := make(map[string]map[string]string)
+ for _, v := range res {
+ mm := make(map[string]string)
+ mm[v["COLUMN_NAME"].String()] = v["CHAR_LENGTH"].String()
+ fields[v["INDEX_NAME"].String()] = mm
+ }
+ return fields, nil
+ }, 0)
if err == nil {
fields = v.(map[string]map[string]string)
}
@@ -231,15 +250,15 @@ func (d *DriverOracle) DoInsert(link Link, table string, data interface{}, optio
case reflect.Map:
fallthrough
case reflect.Struct:
- dataMap = DataToMapDeep(data)
+ dataMap = ConvertDataForTableRecord(data)
default:
- return result, errors.New(fmt.Sprint("unsupported data type:", kind))
+ return result, gerror.New(fmt.Sprint("unsupported data type:", kind))
}
indexs := make([]string, 0)
indexMap := make(map[string]string)
indexExists := false
- if option != gINSERT_OPTION_DEFAULT {
+ if option != insertOptionDefault {
index, err := d.getTableUniqueIndex(table)
if err != nil {
return nil, err
@@ -267,7 +286,7 @@ func (d *DriverOracle) DoInsert(link Link, table string, data interface{}, optio
k = strings.ToUpper(k)
// 操作类型为REPLACE/SAVE时且存在唯一索引才使用merge,否则使用insert
- if (option == gINSERT_OPTION_REPLACE || option == gINSERT_OPTION_SAVE) && indexExists {
+ if (option == insertOptionReplace || option == insertOptionSave) && indexExists {
fields = append(fields, tableAlias1+"."+charL+k+charR)
values = append(values, tableAlias2+"."+charL+k+charR)
params = append(params, v)
@@ -293,18 +312,18 @@ func (d *DriverOracle) DoInsert(link Link, table string, data interface{}, optio
}
}
- if indexExists && option != gINSERT_OPTION_DEFAULT {
+ if indexExists && option != insertOptionDefault {
switch option {
- case gINSERT_OPTION_REPLACE:
+ case insertOptionReplace:
fallthrough
- case gINSERT_OPTION_SAVE:
+ case insertOptionSave:
tmp := fmt.Sprintf(
"MERGE INTO %s %s USING(SELECT %s FROM DUAL) %s ON(%s) WHEN MATCHED THEN UPDATE SET %s WHEN NOT MATCHED THEN INSERT (%s) VALUES(%s)",
table, tableAlias1, strings.Join(subSqlStr, ","), tableAlias2,
strings.Join(onStr, "AND"), strings.Join(updateStr, ","), strings.Join(fields, ","), strings.Join(values, ","),
)
return d.DB.DoExec(link, tmp, params...)
- case gINSERT_OPTION_IGNORE:
+ case insertOptionIgnore:
return d.DB.DoExec(link,
fmt.Sprintf(
"INSERT /*+ IGNORE_ROW_ON_DUPKEY_INDEX(%s(%s)) */ INTO %s(%s) VALUES(%s)",
@@ -351,19 +370,19 @@ func (d *DriverOracle) DoBatchInsert(link Link, table string, list interface{},
case reflect.Array:
listMap = make(List, rv.Len())
for i := 0; i < rv.Len(); i++ {
- listMap[i] = DataToMapDeep(rv.Index(i).Interface())
+ listMap[i] = ConvertDataForTableRecord(rv.Index(i).Interface())
}
case reflect.Map:
fallthrough
case reflect.Struct:
- listMap = List{Map(DataToMapDeep(list))}
+ listMap = List{Map(ConvertDataForTableRecord(list))}
default:
- return result, errors.New(fmt.Sprint("unsupported list type:", kind))
+ return result, gerror.New(fmt.Sprint("unsupported list type:", kind))
}
}
// 判断长度
if len(listMap) < 1 {
- return result, errors.New("empty data list")
+ return result, gerror.New("empty data list")
}
if link == nil {
if link, err = d.DB.Master(); err != nil {
@@ -382,7 +401,7 @@ func (d *DriverOracle) DoBatchInsert(link Link, table string, list interface{},
valueHolderStr := strings.Join(holders, ",")
// 当操作类型非insert时调用单笔的insert功能
- if option != gINSERT_OPTION_DEFAULT {
+ if option != insertOptionDefault {
for _, v := range listMap {
r, err := d.DB.DoInsert(link, table, v, option, 1)
if err != nil {
@@ -400,7 +419,7 @@ func (d *DriverOracle) DoBatchInsert(link Link, table string, list interface{},
}
// 构造批量写入数据格式(注意map的遍历是无序的)
- batchNum := gDEFAULT_BATCH_NUM
+ batchNum := defaultBatchNumber
if len(batch) > 0 {
batchNum = batch[0]
}
diff --git a/database/gdb/gdb_driver_pgsql.go b/database/gdb/gdb_driver_pgsql.go
index 134203248..828439e4b 100644
--- a/database/gdb/gdb_driver_pgsql.go
+++ b/database/gdb/gdb_driver_pgsql.go
@@ -1,4 +1,4 @@
-// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
+// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
@@ -13,8 +13,8 @@ package gdb
import (
"database/sql"
- "errors"
"fmt"
+ "github.com/gogf/gf/errors/gerror"
"github.com/gogf/gf/internal/intlog"
"github.com/gogf/gf/text/gstr"
"strings"
@@ -62,10 +62,10 @@ func (d *DriverPgsql) GetChars() (charLeft string, charRight string) {
// HandleSqlBeforeCommit deals with the sql string before commits it to underlying sql driver.
func (d *DriverPgsql) HandleSqlBeforeCommit(link Link, sql string, args []interface{}) (string, []interface{}) {
var index int
- // Convert place holder char '?' to string "$vx".
+ // Convert place holder char '?' to string "$x".
sql, _ = gregex.ReplaceStringFunc("\\?", sql, func(s string) string {
index++
- return fmt.Sprintf("$v%d", index)
+ return fmt.Sprintf("$%d", index)
})
sql, _ = gregex.ReplaceString(` LIMIT (\d+),\s*(\d+)`, ` LIMIT $2 OFFSET $1`, sql)
return sql, args
@@ -100,28 +100,35 @@ func (d *DriverPgsql) TableFields(table string, schema ...string) (fields map[st
charL, charR := d.GetChars()
table = gstr.Trim(table, charL+charR)
if gstr.Contains(table, " ") {
- return nil, errors.New("function TableFields supports only single table operations")
+ return nil, gerror.New("function TableFields supports only single table operations")
}
table, _ = gregex.ReplaceString("\"", "", table)
checkSchema := d.DB.GetSchema()
if len(schema) > 0 && schema[0] != "" {
checkSchema = schema[0]
}
- v := d.DB.GetCache().GetOrSetFunc(
- fmt.Sprintf(`pgsql_table_fields_%s_%s`, table, checkSchema), func() interface{} {
- var result Result
- var link *sql.DB
+ v, _ := internalCache.GetOrSetFunc(
+ fmt.Sprintf(`pgsql_table_fields_%s_%s@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
+ return nil, err
}
- result, err = d.DB.DoGetAll(link, fmt.Sprintf(`
- SELECT a.attname AS field, t.typname AS type FROM pg_class c, pg_attribute a
- LEFT OUTER JOIN pg_description b ON a.attrelid=b.objoid AND a.attnum = b.objsubid,pg_type t
- WHERE c.relname = '%s' and a.attnum > 0 and a.attrelid = c.oid and a.atttypid = t.oid
- ORDER BY a.attnum`, strings.ToLower(table)))
+ structureSql := fmt.Sprintf(`
+SELECT a.attname AS field, t.typname AS type FROM pg_class c, pg_attribute a
+LEFT OUTER JOIN pg_description b ON a.attrelid=b.objoid AND a.attnum = b.objsubid,pg_type t
+WHERE c.relname = '%s' and a.attnum > 0 and a.attrelid = c.oid and a.atttypid = t.oid
+ORDER BY a.attnum`,
+ strings.ToLower(table),
+ )
+ structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql))
+ result, err = d.DB.DoGetAll(link, structureSql)
if err != nil {
- return nil
+ return nil, err
}
fields = make(map[string]*TableField)
@@ -132,7 +139,7 @@ func (d *DriverPgsql) TableFields(table string, schema ...string) (fields map[st
Type: m["type"].String(),
}
}
- return fields
+ return fields, nil
}, 0)
if err == nil {
fields = v.(map[string]*TableField)
diff --git a/database/gdb/gdb_driver_sqlite.go b/database/gdb/gdb_driver_sqlite.go
index 3f9eea3b8..b70808b32 100644
--- a/database/gdb/gdb_driver_sqlite.go
+++ b/database/gdb/gdb_driver_sqlite.go
@@ -1,4 +1,4 @@
-// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
+// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
@@ -12,8 +12,8 @@ package gdb
import (
"database/sql"
- "errors"
"fmt"
+ "github.com/gogf/gf/errors/gerror"
"github.com/gogf/gf/internal/intlog"
"github.com/gogf/gf/os/gfile"
"github.com/gogf/gf/text/gstr"
@@ -36,15 +36,14 @@ func (d *DriverSqlite) New(core *Core, node *ConfigNode) (DB, error) {
// Open creates and returns a underlying sql.DB object for sqlite.
func (d *DriverSqlite) Open(config *ConfigNode) (*sql.DB, error) {
var source string
- var err error
if config.LinkInfo != "" {
source = config.LinkInfo
} else {
source = config.Name
}
- source, err = gfile.Search(source)
- if err != nil {
- return nil, err
+ // It searches the source file to locate its absolute path..
+ if absolutePath, _ := gfile.Search(source); absolutePath != "" {
+ source = absolutePath
}
intlog.Printf("Open: %s", source)
if db, err := sql.Open("sqlite3", source); err == nil {
@@ -92,23 +91,26 @@ func (d *DriverSqlite) TableFields(table string, schema ...string) (fields map[s
charL, charR := d.GetChars()
table = gstr.Trim(table, charL+charR)
if gstr.Contains(table, " ") {
- return nil, errors.New("function TableFields supports only single table operations")
+ return nil, gerror.New("function TableFields supports only single table operations")
}
checkSchema := d.DB.GetSchema()
if len(schema) > 0 && schema[0] != "" {
checkSchema = schema[0]
}
- v := d.DB.GetCache().GetOrSetFunc(
- fmt.Sprintf(`sqlite_table_fields_%s_%s`, table, checkSchema), func() interface{} {
- var result Result
- var link *sql.DB
+ 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
+ )
link, err = d.DB.GetSlave(checkSchema)
if err != nil {
- return nil
+ return nil, err
}
result, err = d.DB.DoGetAll(link, fmt.Sprintf(`PRAGMA TABLE_INFO(%s)`, table))
if err != nil {
- return nil
+ return nil, err
}
fields = make(map[string]*TableField)
for i, m := range result {
@@ -118,7 +120,7 @@ func (d *DriverSqlite) TableFields(table string, schema ...string) (fields map[s
Type: strings.ToLower(m["type"].String()),
}
}
- return fields
+ return fields, nil
}, 0)
if err == nil {
fields = v.(map[string]*TableField)
diff --git a/database/gdb/gdb_func.go b/database/gdb/gdb_func.go
index 1b1000c95..78c9ac47b 100644
--- a/database/gdb/gdb_func.go
+++ b/database/gdb/gdb_func.go
@@ -1,4 +1,4 @@
-// Copyright 2017-2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
+// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
@@ -8,9 +8,10 @@ package gdb
import (
"bytes"
- "errors"
"fmt"
+ "github.com/gogf/gf/errors/gerror"
"github.com/gogf/gf/internal/empty"
+ "github.com/gogf/gf/internal/json"
"github.com/gogf/gf/internal/utils"
"github.com/gogf/gf/os/gtime"
"github.com/gogf/gf/util/gutil"
@@ -55,21 +56,41 @@ const (
var (
// quoteWordReg is the regular expression object for a word check.
quoteWordReg = regexp.MustCompile(`^[a-zA-Z0-9\-_]+$`)
+
+ // Priority tags for struct converting for orm field mapping.
+ structTagPriority = append([]string{ORM_TAG_FOR_STRUCT}, gconv.StructTagPriority...)
)
-// ListItemValues is alias for gutil.ListItemValues.
+// ListItemValues retrieves and returns the elements of all item struct/map with key .
+// Note that the parameter should be type of slice which contains elements of map or struct,
+// or else it returns an empty slice.
+//
+// The parameter supports types like:
+// []map[string]interface{}
+// []map[string]sub-map
+// []struct
+// []struct:sub-struct
+// Note that the sub-map/sub-struct makes sense only if the optional parameter is given.
// See gutil.ListItemValues.
func ListItemValues(list interface{}, key interface{}, subKey ...interface{}) (values []interface{}) {
return gutil.ListItemValues(list, key, subKey...)
}
+// ListItemValuesUnique retrieves and returns the unique elements of all struct/map with key .
+// Note that the parameter should be type of slice which contains elements of map or struct,
+// or else it returns an empty slice.
+// See gutil.ListItemValuesUnique.
+func ListItemValuesUnique(list interface{}, key string, subKey ...interface{}) []interface{} {
+ return gutil.ListItemValuesUnique(list, key, subKey...)
+}
+
// GetInsertOperationByOption returns proper insert option with given parameter