mirror of
https://gitee.com/johng/gf
synced 2026-06-19 23:02:56 +08:00
Compare commits
103 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 69fa5bf464 | |||
| e7dc58ac6c | |||
| 2033299632 | |||
| 639d34d5d9 | |||
| 605181da32 | |||
| 183f631190 | |||
| 64c99b9871 | |||
| d29b0a27ff | |||
| aaa726e6dc | |||
| 05cc0c4644 | |||
| e1c0a92e60 | |||
| c770e4779a | |||
| c135122ca1 | |||
| 1d87df2afe | |||
| 8efc0ca0ea | |||
| 001d524ff7 | |||
| 5c5dce9dc3 | |||
| 293256c2ca | |||
| 4e027c1de3 | |||
| 6712a33164 | |||
| 98531143a6 | |||
| 71127ba308 | |||
| 0f6f571ccb | |||
| 9b6936a4fb | |||
| 8b78609412 | |||
| 4a31082f8c | |||
| 3643a69d8d | |||
| adbaa4aa89 | |||
| 0e025eda1b | |||
| 7c44bf8e94 | |||
| e6718f1113 | |||
| f3f6adb03a | |||
| 977827e453 | |||
| bcc9153991 | |||
| c04be14cd9 | |||
| de8f29751d | |||
| 12d58e4d08 | |||
| 3ae44185f4 | |||
| 1290f42f75 | |||
| 17b91dcad7 | |||
| 72da1642ee | |||
| 76d93b3a61 | |||
| 7be0ee9566 | |||
| 326e1f8da5 | |||
| e4e44ddd19 | |||
| 46bdde9265 | |||
| 09eba58927 | |||
| 0e884c78f5 | |||
| 2f44721086 | |||
| efc0501548 | |||
| bb07c60838 | |||
| 330ea05ca3 | |||
| 1f534b48a3 | |||
| aceae5eee3 | |||
| d4d66fd529 | |||
| b7686d6d37 | |||
| b97e397354 | |||
| ab8fbf171e | |||
| 2c0cfa24b0 | |||
| f28a76d470 | |||
| f3525c84a3 | |||
| abe9b54112 | |||
| b2aa59d893 | |||
| 9e9865afa7 | |||
| ecc439d4e8 | |||
| 452f0fc99e | |||
| 54f47845f6 | |||
| cb238cd6d9 | |||
| 897a9f4584 | |||
| 55f9b121de | |||
| 386f38af5e | |||
| 65cea430c2 | |||
| 4d38b508a3 | |||
| 5c774fd391 | |||
| f6d760e90f | |||
| c3ffa40bad | |||
| 5ce5d0e593 | |||
| b83f1efde8 | |||
| ca5f14c366 | |||
| 95a8b51fb4 | |||
| 2b64979730 | |||
| 508cb7db88 | |||
| e8d6d96883 | |||
| 9378a6e7bf | |||
| 1d609fc5c7 | |||
| 49d1e3dbaf | |||
| b0e9334852 | |||
| 95bd369959 | |||
| a433890097 | |||
| c9c3be517c | |||
| 89e122cd31 | |||
| 27c2a03ea8 | |||
| b56eb3a948 | |||
| fb1b0bfd88 | |||
| dd10167ec2 | |||
| 3138ad10ec | |||
| 080ca82605 | |||
| 0290de2360 | |||
| 9b330adc1d | |||
| f3ff1ae08b | |||
| 4f699af051 | |||
| 7ebc7259cb | |||
| 9d22edbdb8 |
@ -2,7 +2,6 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/frame/g"
|
||||
)
|
||||
|
||||
@ -11,11 +10,19 @@ func main() {
|
||||
// 开启调试模式,以便于记录所有执行的SQL
|
||||
db.SetDebug(true)
|
||||
|
||||
r, e := db.Table("test").Order("id asc").All()
|
||||
r, e := db.GetAll("SELECT * from `user` where id in(?)", g.Slice{})
|
||||
if e != nil {
|
||||
fmt.Println(e)
|
||||
}
|
||||
if r != nil {
|
||||
fmt.Println(r.List())
|
||||
fmt.Println(r)
|
||||
}
|
||||
return
|
||||
//r, e := db.Table("user").Where("id in(?)", g.Slice{}).All()
|
||||
//if e != nil {
|
||||
// fmt.Println(e)
|
||||
//}
|
||||
//if r != nil {
|
||||
// fmt.Println(r.List())
|
||||
//}
|
||||
}
|
||||
|
||||
@ -8,5 +8,5 @@ func main() {
|
||||
db := g.DB()
|
||||
db.SetDebug(true)
|
||||
|
||||
db.Table("user").Fields("DISTINCT id,nickname").Filter().All()
|
||||
db.Table("user").Data("num=num+1").Where("id", 8).Update()
|
||||
}
|
||||
|
||||
@ -7,11 +7,6 @@ import (
|
||||
func main() {
|
||||
s := g.Server()
|
||||
s.EnableAdmin()
|
||||
//s.BindHookHandler("/admin/*any", ghttp.HOOK_BEFORE_SERVE, func(r *ghttp.Request) {
|
||||
// if !r.BasicAuth("admin", "123", "") {
|
||||
// r.Exit()
|
||||
// }
|
||||
//})
|
||||
s.SetPort(8199)
|
||||
s.Run()
|
||||
}
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@ -6,7 +6,7 @@
|
||||
.idea/
|
||||
.settings/
|
||||
.vscode/
|
||||
vender/
|
||||
vendor/
|
||||
composer.lock
|
||||
gitpush.sh
|
||||
pkg/
|
||||
@ -15,4 +15,4 @@ cbuild
|
||||
**/.DS_Store
|
||||
.vscode/
|
||||
go.sum
|
||||
.example/other/
|
||||
.example/other/
|
||||
|
||||
15
DONATOR.MD
15
DONATOR.MD
@ -1,8 +1,7 @@
|
||||
# Donators
|
||||
|
||||
We currently accept donation by Alipay/WechatPay, please note your github/gitee account in your payment bill.
|
||||
|
||||
> If you cannot view the donation image, please click [here](https://goframe.org/images/donate.png).
|
||||
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
|
||||
|---|---|--- | ---
|
||||
@ -46,7 +45,7 @@ We currently accept donation by Alipay/WechatPay, please note your github/gitee
|
||||
|struggler|alipay|¥18.80|
|
||||
|*铁|wechat|¥0.01|
|
||||
|C*e|wechat|¥66.66| GF越来越好,棒👍!
|
||||
|(佚名)|wechat|¥6.66| (名字打不出来,也没备注,捐赠时间2020-02-21 14:24:34)
|
||||
|---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| 感谢您的开源项目!框架给予了很大的帮助!谢谢大佬!
|
||||
@ -84,13 +83,17 @@ We currently accept donation by Alipay/WechatPay, please note your github/gitee
|
||||
|千年|wechat|¥1.08|
|
||||
|[szzxing](https://github.com/szzxing)|wechat|¥50.00|
|
||||
|伟客互联|wechat|¥66.66|
|
||||
|sbilly|wechat|¥99.00|
|
||||
|[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|
|
||||
|
||||
|
||||
<img src="https://goframe.org/images/donate.png"/>
|
||||
|
||||
<img src="https://goframe.org/images/donate.jpeg"/>
|
||||
|
||||
|
||||
10
README.MD
10
README.MD
@ -31,6 +31,11 @@ require github.com/gogf/gf latest
|
||||
golang version >= 1.11
|
||||
```
|
||||
|
||||
# Architecture
|
||||
<div align=center>
|
||||
<img src="https://goframe.org/images/arch.png?v=12"/>
|
||||
</div>
|
||||
|
||||
# Packages
|
||||
1. **Primary Package**
|
||||
|
||||
@ -40,11 +45,6 @@ golang version >= 1.11
|
||||
|
||||
The community packages are contributed and maintained by community members, which are hosted in `gogf` organization. Some of the community packages are separated from the `gf` repository, which are not of common usage or are with heavy dependencies.
|
||||
|
||||
# Architecture
|
||||
<div align=center>
|
||||
<img src="https://goframe.org/images/arch.png?v=11"/>
|
||||
</div>
|
||||
|
||||
# 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
|
||||
|
||||
12
README_ZH.MD
12
README_ZH.MD
@ -45,6 +45,13 @@ require github.com/gogf/gf latest
|
||||
golang版本 >= 1.11
|
||||
```
|
||||
|
||||
|
||||
|
||||
# 架构
|
||||
<div align=center>
|
||||
<img src="https://goframe.org/images/arch.png?v=12"/>
|
||||
</div>
|
||||
|
||||
# 模块
|
||||
|
||||
1. **核心模块**
|
||||
@ -56,11 +63,6 @@ golang版本 >= 1.11
|
||||
社区模块主要由社区贡献并维护,大部分也是由`gf`主仓库的贡献者提供及维护,存放于`gogf`空间下,与`gf`主仓库处于同一级别。有的社区模块是从`gf`主仓库中剥离出来单独维护的模块,这些模块并不是特别常用,或者对外部依赖较重。
|
||||
|
||||
|
||||
# 架构
|
||||
<div align=center>
|
||||
<img src="https://goframe.org/images/arch.png?v=11"/>
|
||||
</div>
|
||||
|
||||
# 性能
|
||||
|
||||
以下是目前最流行的`WEB Server` Golang框架/类库性能测试结果。
|
||||
|
||||
210
RELEASE.2.MD
210
RELEASE.2.MD
@ -1,3 +1,213 @@
|
||||
# `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)
|
||||
|
||||
大家好啊!久等啦!
|
||||
|
||||
@ -8,10 +8,10 @@ package garray
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/internal/empty"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"math"
|
||||
"sort"
|
||||
@ -494,9 +494,11 @@ func (a *Array) Search(value interface{}) int {
|
||||
func (a *Array) Unique() *Array {
|
||||
a.mu.Lock()
|
||||
for i := 0; i < len(a.array)-1; i++ {
|
||||
for j := i + 1; j < len(a.array); j++ {
|
||||
for j := i + 1; j < len(a.array); {
|
||||
if a.array[i] == a.array[j] {
|
||||
a.array = append(a.array[:j], a.array[j+1:]...)
|
||||
} else {
|
||||
j++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,9 +8,9 @@ package garray
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"math"
|
||||
"sort"
|
||||
|
||||
@ -510,9 +510,11 @@ func (a *IntArray) Search(value int) int {
|
||||
func (a *IntArray) Unique() *IntArray {
|
||||
a.mu.Lock()
|
||||
for i := 0; i < len(a.array)-1; i++ {
|
||||
for j := i + 1; j < len(a.array); j++ {
|
||||
for j := i + 1; j < len(a.array); {
|
||||
if a.array[i] == a.array[j] {
|
||||
a.array = append(a.array[:j], a.array[j+1:]...)
|
||||
} else {
|
||||
j++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,9 +8,9 @@ package garray
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"math"
|
||||
"sort"
|
||||
@ -514,9 +514,11 @@ func (a *StrArray) Search(value string) int {
|
||||
func (a *StrArray) Unique() *StrArray {
|
||||
a.mu.Lock()
|
||||
for i := 0; i < len(a.array)-1; i++ {
|
||||
for j := i + 1; j < len(a.array); j++ {
|
||||
for j := i + 1; j < len(a.array); {
|
||||
if a.array[i] == a.array[j] {
|
||||
a.array = append(a.array[:j], a.array[j+1:]...)
|
||||
} else {
|
||||
j++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,9 +8,9 @@ package garray
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/internal/empty"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"github.com/gogf/gf/util/gutil"
|
||||
"math"
|
||||
|
||||
@ -8,8 +8,8 @@ package garray
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"math"
|
||||
"sort"
|
||||
|
||||
|
||||
@ -8,7 +8,7 @@ package garray
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"math"
|
||||
"sort"
|
||||
|
||||
@ -13,7 +13,7 @@ import (
|
||||
"github.com/gogf/gf/container/garray"
|
||||
)
|
||||
|
||||
func Example_basic() {
|
||||
func ExampleNew() {
|
||||
// A normal array.
|
||||
a := garray.New()
|
||||
|
||||
@ -72,18 +72,18 @@ func Example_basic() {
|
||||
// []
|
||||
}
|
||||
|
||||
func Example_iterate() {
|
||||
array := garray.NewStrArrayFrom(g.SliceStr{"a", "b", "c"})
|
||||
func ExampleArray_Iterator() {
|
||||
array := garray.NewArrayFrom(g.Slice{"a", "b", "c"})
|
||||
// Iterator is alias of IteratorAsc, which iterates the array readonly in ascending order
|
||||
// with given callback function <f>.
|
||||
// If <f> returns true, then it continues iterating; or false to stop.
|
||||
array.Iterator(func(k int, v string) bool {
|
||||
array.Iterator(func(k int, v interface{}) bool {
|
||||
fmt.Println(k, v)
|
||||
return true
|
||||
})
|
||||
// IteratorDesc iterates the array readonly in descending order with given callback function <f>.
|
||||
// If <f> returns true, then it continues iterating; or false to stop.
|
||||
array.IteratorDesc(func(k int, v string) bool {
|
||||
array.IteratorDesc(func(k int, v interface{}) bool {
|
||||
fmt.Println(k, v)
|
||||
return true
|
||||
})
|
||||
@ -97,7 +97,7 @@ func Example_iterate() {
|
||||
// 0 a
|
||||
}
|
||||
|
||||
func Example_reverse() {
|
||||
func ExampleArray_Reverse() {
|
||||
array := garray.NewFrom(g.Slice{1, 2, 3, 4, 5, 6, 7, 8, 9})
|
||||
|
||||
// Reverse makes array with elements in reverse order.
|
||||
@ -107,14 +107,14 @@ func Example_reverse() {
|
||||
// [9 8 7 6 5 4 3 2 1]
|
||||
}
|
||||
|
||||
func Example_shuffle() {
|
||||
func ExampleArray_Shuffle() {
|
||||
array := garray.NewFrom(g.Slice{1, 2, 3, 4, 5, 6, 7, 8, 9})
|
||||
|
||||
// Shuffle randomly shuffles the array.
|
||||
fmt.Println(array.Shuffle().Slice())
|
||||
}
|
||||
|
||||
func Example_rand() {
|
||||
func ExampleArray_Rands() {
|
||||
array := garray.NewFrom(g.Slice{1, 2, 3, 4, 5, 6, 7, 8, 9})
|
||||
|
||||
// Randomly retrieve and return 2 items from the array.
|
||||
@ -126,7 +126,19 @@ func Example_rand() {
|
||||
fmt.Println(array.PopRand())
|
||||
}
|
||||
|
||||
func Example_join() {
|
||||
func ExampleArray_PopRand() {
|
||||
array := garray.NewFrom(g.Slice{1, 2, 3, 4, 5, 6, 7, 8, 9})
|
||||
|
||||
// Randomly retrieve and return 2 items from the array.
|
||||
// It does not delete the items from array.
|
||||
fmt.Println(array.Rands(2))
|
||||
|
||||
// Randomly pick and return one item from the array.
|
||||
// It deletes the picked up item from array.
|
||||
fmt.Println(array.PopRand())
|
||||
}
|
||||
|
||||
func ExampleArray_Join() {
|
||||
array := garray.NewFrom(g.Slice{"a", "b", "c", "d"})
|
||||
fmt.Println(array.Join(","))
|
||||
|
||||
@ -134,7 +146,7 @@ func Example_join() {
|
||||
// a,b,c,d
|
||||
}
|
||||
|
||||
func Example_chunk() {
|
||||
func ExampleArray_Chunk() {
|
||||
array := garray.NewFrom(g.Slice{1, 2, 3, 4, 5, 6, 7, 8, 9})
|
||||
|
||||
// Chunk splits an array into multiple arrays,
|
||||
@ -146,7 +158,7 @@ func Example_chunk() {
|
||||
// [[1 2] [3 4] [5 6] [7 8] [9]]
|
||||
}
|
||||
|
||||
func Example_popItem() {
|
||||
func ExampleArray_PopLeft() {
|
||||
array := garray.NewFrom([]interface{}{1, 2, 3, 4, 5, 6, 7, 8, 9})
|
||||
|
||||
// Any Pop* functions pick, delete and return the item from array.
|
||||
@ -163,22 +175,58 @@ func Example_popItem() {
|
||||
// [7 8]
|
||||
}
|
||||
|
||||
func Example_walk() {
|
||||
var array garray.StrArray
|
||||
tables := g.SliceStr{"user", "user_detail"}
|
||||
prefix := "gf_"
|
||||
array.Append(tables...)
|
||||
// Add prefix for given table names.
|
||||
array.Walk(func(value string) string {
|
||||
return prefix + value
|
||||
})
|
||||
fmt.Println(array.Slice())
|
||||
func ExampleArray_PopLefts() {
|
||||
array := garray.NewFrom([]interface{}{1, 2, 3, 4, 5, 6, 7, 8, 9})
|
||||
|
||||
// Any Pop* functions pick, delete and return the item from array.
|
||||
|
||||
fmt.Println(array.PopLeft())
|
||||
fmt.Println(array.PopLefts(2))
|
||||
fmt.Println(array.PopRight())
|
||||
fmt.Println(array.PopRights(2))
|
||||
|
||||
// Output:
|
||||
// [gf_user gf_user_detail]
|
||||
// 1 true
|
||||
// [2 3]
|
||||
// 9 true
|
||||
// [7 8]
|
||||
}
|
||||
|
||||
func Example_contains() {
|
||||
func ExampleArray_PopRight() {
|
||||
array := garray.NewFrom([]interface{}{1, 2, 3, 4, 5, 6, 7, 8, 9})
|
||||
|
||||
// Any Pop* functions pick, delete and return the item from array.
|
||||
|
||||
fmt.Println(array.PopLeft())
|
||||
fmt.Println(array.PopLefts(2))
|
||||
fmt.Println(array.PopRight())
|
||||
fmt.Println(array.PopRights(2))
|
||||
|
||||
// Output:
|
||||
// 1 true
|
||||
// [2 3]
|
||||
// 9 true
|
||||
// [7 8]
|
||||
}
|
||||
|
||||
func ExampleArray_PopRights() {
|
||||
array := garray.NewFrom([]interface{}{1, 2, 3, 4, 5, 6, 7, 8, 9})
|
||||
|
||||
// Any Pop* functions pick, delete and return the item from array.
|
||||
|
||||
fmt.Println(array.PopLeft())
|
||||
fmt.Println(array.PopLefts(2))
|
||||
fmt.Println(array.PopRight())
|
||||
fmt.Println(array.PopRights(2))
|
||||
|
||||
// Output:
|
||||
// 1 true
|
||||
// [2 3]
|
||||
// 9 true
|
||||
// [7 8]
|
||||
}
|
||||
|
||||
func ExampleArray_Contains() {
|
||||
var array garray.StrArray
|
||||
array.Append("a")
|
||||
fmt.Println(array.Contains("a"))
|
||||
@ -191,7 +239,7 @@ func Example_contains() {
|
||||
// true
|
||||
}
|
||||
|
||||
func Example_mergeArray() {
|
||||
func ExampleArray_Merge() {
|
||||
array1 := garray.NewFrom(g.Slice{1, 2})
|
||||
array2 := garray.NewFrom(g.Slice{3, 4})
|
||||
slice1 := g.Slice{5, 6}
|
||||
@ -210,7 +258,18 @@ func Example_mergeArray() {
|
||||
// [1 2 1 2 3 4 5 6 7 8 9 0]
|
||||
}
|
||||
|
||||
func Example_filter() {
|
||||
func ExampleArray_FilterEmpty() {
|
||||
array1 := garray.NewFrom(g.Slice{0, 1, 2, nil, "", g.Slice{}, "john"})
|
||||
array2 := garray.NewFrom(g.Slice{0, 1, 2, nil, "", g.Slice{}, "john"})
|
||||
fmt.Printf("%#v\n", array1.FilterNil().Slice())
|
||||
fmt.Printf("%#v\n", array2.FilterEmpty().Slice())
|
||||
|
||||
// Output:
|
||||
// []interface {}{0, 1, 2, "", []interface {}{}, "john"}
|
||||
// []interface {}{1, 2, "john"}
|
||||
}
|
||||
|
||||
func ExampleArray_FilterNil() {
|
||||
array1 := garray.NewFrom(g.Slice{0, 1, 2, nil, "", g.Slice{}, "john"})
|
||||
array2 := garray.NewFrom(g.Slice{0, 1, 2, nil, "", g.Slice{}, "john"})
|
||||
fmt.Printf("%#v\n", array1.FilterNil().Slice())
|
||||
28
container/garray/garray_z_example_str_test.go
Normal file
28
container/garray/garray_z_example_str_test.go
Normal file
@ -0,0 +1,28 @@
|
||||
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package garray_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/container/garray"
|
||||
"github.com/gogf/gf/frame/g"
|
||||
)
|
||||
|
||||
func ExampleStrArray_Walk() {
|
||||
var array garray.StrArray
|
||||
tables := g.SliceStr{"user", "user_detail"}
|
||||
prefix := "gf_"
|
||||
array.Append(tables...)
|
||||
// Add prefix for given table names.
|
||||
array.Walk(func(value string) string {
|
||||
return prefix + value
|
||||
})
|
||||
fmt.Println(array.Slice())
|
||||
|
||||
// Output:
|
||||
// [gf_user gf_user_detail]
|
||||
}
|
||||
@ -9,8 +9,8 @@
|
||||
package garray_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -95,9 +95,9 @@ func TestArray_Sort(t *testing.T) {
|
||||
|
||||
func TestArray_Unique(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
expect := []interface{}{1, 1, 2, 3}
|
||||
expect := []interface{}{1, 2, 3, 4, 5, 3, 2, 2, 3, 5, 5}
|
||||
array := garray.NewArrayFrom(expect)
|
||||
t.Assert(array.Unique().Slice(), []interface{}{1, 2, 3})
|
||||
t.Assert(array.Unique().Slice(), []interface{}{1, 2, 3, 4, 5})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -9,8 +9,8 @@
|
||||
package garray_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -87,9 +87,9 @@ func TestIntArray_Sort(t *testing.T) {
|
||||
|
||||
func TestIntArray_Unique(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
expect := []int{1, 1, 2, 3}
|
||||
expect := []int{1, 2, 3, 4, 5, 3, 2, 2, 3, 5, 5}
|
||||
array := garray.NewIntArrayFrom(expect)
|
||||
t.Assert(array.Unique().Slice(), []int{1, 2, 3})
|
||||
t.Assert(array.Unique().Slice(), []int{1, 2, 3, 4, 5})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -9,8 +9,8 @@
|
||||
package garray_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@ -91,7 +91,7 @@ func TestStrArray_Sort(t *testing.T) {
|
||||
|
||||
func TestStrArray_Unique(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
expect := []string{"1", "1", "2", "3"}
|
||||
expect := []string{"1", "1", "2", "2", "3", "3", "2", "2"}
|
||||
array := garray.NewStrArrayFrom(expect)
|
||||
t.Assert(array.Unique().Slice(), []string{"1", "2", "3"})
|
||||
})
|
||||
|
||||
@ -9,8 +9,8 @@
|
||||
package garray_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"github.com/gogf/gf/util/gutil"
|
||||
"strings"
|
||||
"testing"
|
||||
@ -526,15 +526,21 @@ func TestSortedArray_CountValues(t *testing.T) {
|
||||
|
||||
func TestSortedArray_SetUnique(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
a1 := []interface{}{"a", "d", "c", "c"}
|
||||
|
||||
func1 := func(v1, v2 interface{}) int {
|
||||
return strings.Compare(gconv.String(v1), gconv.String(v2))
|
||||
}
|
||||
array1 := garray.NewSortedArrayFrom(a1, func1)
|
||||
a1 := []interface{}{1, 2, 3, 4, 5, 3, 2, 2, 3, 5, 5}
|
||||
array1 := garray.NewSortedArrayFrom(a1, gutil.ComparatorInt)
|
||||
array1.SetUnique(true)
|
||||
t.Assert(array1.Len(), 3)
|
||||
t.Assert(array1, []interface{}{"a", "c", "d"})
|
||||
t.Assert(array1.Len(), 5)
|
||||
t.Assert(array1, []interface{}{1, 2, 3, 4, 5})
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedArray_Unique(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
a1 := []interface{}{1, 2, 3, 4, 5, 3, 2, 2, 3, 5, 5}
|
||||
array1 := garray.NewSortedArrayFrom(a1, gutil.ComparatorInt)
|
||||
array1.Unique()
|
||||
t.Assert(array1.Len(), 5)
|
||||
t.Assert(array1, []interface{}{1, 2, 3, 4, 5})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -9,8 +9,8 @@
|
||||
package garray_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -436,7 +436,7 @@ func TestSortedIntArray_CountValues(t *testing.T) {
|
||||
|
||||
func TestSortedIntArray_SetUnique(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
a1 := []int{1, 2, 3, 4, 5, 3}
|
||||
a1 := []int{1, 2, 3, 4, 5, 3, 2, 2, 3, 5, 5}
|
||||
array1 := garray.NewSortedIntArrayFrom(a1)
|
||||
array1.SetUnique(true)
|
||||
t.Assert(array1.Len(), 5)
|
||||
@ -444,6 +444,16 @@ func TestSortedIntArray_SetUnique(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedIntArray_Unique(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
a1 := []int{1, 2, 3, 4, 5, 3, 2, 2, 3, 5, 5}
|
||||
array1 := garray.NewSortedIntArrayFrom(a1)
|
||||
array1.Unique()
|
||||
t.Assert(array1.Len(), 5)
|
||||
t.Assert(array1, []int{1, 2, 3, 4, 5})
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedIntArray_LockFunc(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
s1 := []int{1, 2, 3, 4}
|
||||
|
||||
@ -9,8 +9,8 @@
|
||||
package garray_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -454,11 +454,21 @@ func TestSortedStrArray_Chunk(t *testing.T) {
|
||||
|
||||
func TestSortedStrArray_SetUnique(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
a1 := []string{"e", "a", "d", "a", "c"}
|
||||
a1 := []string{"1", "1", "2", "2", "3", "3", "2", "2"}
|
||||
array1 := garray.NewSortedStrArrayFrom(a1)
|
||||
array2 := array1.SetUnique(true)
|
||||
t.Assert(array2.Len(), 4)
|
||||
t.Assert(array2, []string{"a", "c", "d", "e"})
|
||||
t.Assert(array2.Len(), 3)
|
||||
t.Assert(array2, []string{"1", "2", "3"})
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortedStrArray_Unique(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
a1 := []string{"1", "1", "2", "2", "3", "3", "2", "2"}
|
||||
array1 := garray.NewSortedStrArrayFrom(a1)
|
||||
array1.Unique()
|
||||
t.Assert(array1.Len(), 3)
|
||||
t.Assert(array1, []string{"1", "2", "3"})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -11,7 +11,7 @@ package glist
|
||||
import (
|
||||
"bytes"
|
||||
"container/list"
|
||||
"encoding/json"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
|
||||
"github.com/gogf/gf/internal/rwmutex"
|
||||
|
||||
@ -15,7 +15,7 @@ import (
|
||||
"github.com/gogf/gf/container/glist"
|
||||
)
|
||||
|
||||
func Example_basic() {
|
||||
func ExampleNew() {
|
||||
n := 10
|
||||
l := glist.New()
|
||||
for i := 0; i < n; i++ {
|
||||
@ -32,14 +32,14 @@ func Example_basic() {
|
||||
fmt.Println(l.Len())
|
||||
|
||||
// Output:
|
||||
//10
|
||||
//[0 1 2 3 4 5 6 7 8 9]
|
||||
//[9 8 7 6 5 4 3 2 1 0]
|
||||
//0123456789
|
||||
//0
|
||||
// 10
|
||||
// [0 1 2 3 4 5 6 7 8 9]
|
||||
// [9 8 7 6 5 4 3 2 1 0]
|
||||
// 0123456789
|
||||
// 0
|
||||
}
|
||||
|
||||
func Example_iterate() {
|
||||
func ExampleList_RLockFunc() {
|
||||
// concurrent-safe list.
|
||||
l := glist.NewFrom(garray.NewArrayRange(1, 10, 1).Slice(), true)
|
||||
// iterate reading from head.
|
||||
@ -63,21 +63,39 @@ func Example_iterate() {
|
||||
})
|
||||
|
||||
fmt.Println()
|
||||
// Output:
|
||||
// 12345678910
|
||||
// 10987654321
|
||||
}
|
||||
|
||||
func ExampleList_IteratorAsc() {
|
||||
// concurrent-safe list.
|
||||
l := glist.NewFrom(garray.NewArrayRange(1, 10, 1).Slice(), true)
|
||||
// iterate reading from head using IteratorAsc.
|
||||
l.IteratorAsc(func(e *glist.Element) bool {
|
||||
fmt.Print(e.Value)
|
||||
return true
|
||||
})
|
||||
fmt.Println()
|
||||
|
||||
// Output:
|
||||
// 12345678910
|
||||
}
|
||||
|
||||
func ExampleList_IteratorDesc() {
|
||||
// concurrent-safe list.
|
||||
l := glist.NewFrom(garray.NewArrayRange(1, 10, 1).Slice(), true)
|
||||
// iterate reading from tail using IteratorDesc.
|
||||
l.IteratorDesc(func(e *glist.Element) bool {
|
||||
fmt.Print(e.Value)
|
||||
return true
|
||||
})
|
||||
// Output:
|
||||
// 10987654321
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
|
||||
func ExampleList_LockFunc() {
|
||||
// concurrent-safe list.
|
||||
l := glist.NewFrom(garray.NewArrayRange(1, 10, 1).Slice(), true)
|
||||
// iterate writing from head.
|
||||
l.LockFunc(func(list *list.List) {
|
||||
length := list.Len()
|
||||
@ -92,30 +110,46 @@ func Example_iterate() {
|
||||
})
|
||||
fmt.Println(l)
|
||||
|
||||
//output:
|
||||
//12345678910
|
||||
//10987654321
|
||||
//12345678910
|
||||
//10987654321
|
||||
//[1,2,3,4,5,M,7,8,9,10]
|
||||
// Output:
|
||||
// [1,2,3,4,5,M,7,8,9,10]
|
||||
}
|
||||
|
||||
func Example_popItem() {
|
||||
func ExampleList_PopBack() {
|
||||
l := glist.NewFrom(g.Slice{1, 2, 3, 4, 5, 6, 7, 8, 9})
|
||||
|
||||
fmt.Println(l.PopBack())
|
||||
fmt.Println(l.PopBacks(2))
|
||||
fmt.Println(l.PopFront())
|
||||
fmt.Println(l.PopFronts(2))
|
||||
|
||||
// Output:
|
||||
// 9
|
||||
// [8 7]
|
||||
// 1
|
||||
// [2 3]
|
||||
}
|
||||
func ExampleList_PopBacks() {
|
||||
l := glist.NewFrom(g.Slice{1, 2, 3, 4, 5, 6, 7, 8, 9})
|
||||
|
||||
fmt.Println(l.PopBacks(2))
|
||||
|
||||
// Output:
|
||||
// [9 8]
|
||||
}
|
||||
|
||||
func Example_join() {
|
||||
func ExampleList_PopFront() {
|
||||
l := glist.NewFrom(g.Slice{1, 2, 3, 4, 5, 6, 7, 8, 9})
|
||||
|
||||
fmt.Println(l.PopFront())
|
||||
|
||||
// Output:
|
||||
// 1
|
||||
}
|
||||
|
||||
func ExampleList_PopFronts() {
|
||||
l := glist.NewFrom(g.Slice{1, 2, 3, 4, 5, 6, 7, 8, 9})
|
||||
|
||||
fmt.Println(l.PopFronts(2))
|
||||
|
||||
// Output:
|
||||
// [1 2]
|
||||
}
|
||||
|
||||
func ExampleList_Join() {
|
||||
var l glist.List
|
||||
l.PushBacks(g.Slice{"a", "b", "c", "d"})
|
||||
|
||||
|
||||
@ -8,7 +8,7 @@ package glist
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"encoding/json"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
package gmap
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
|
||||
"github.com/gogf/gf/internal/empty"
|
||||
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
package gmap
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
|
||||
"github.com/gogf/gf/internal/empty"
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
package gmap
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
|
||||
"github.com/gogf/gf/internal/empty"
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
package gmap
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
|
||||
"github.com/gogf/gf/internal/empty"
|
||||
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
package gmap
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
|
||||
"github.com/gogf/gf/internal/empty"
|
||||
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
package gmap
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
|
||||
"github.com/gogf/gf/internal/empty"
|
||||
"github.com/gogf/gf/internal/rwmutex"
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
package gmap
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
|
||||
"github.com/gogf/gf/internal/empty"
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
package gmap
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
|
||||
"github.com/gogf/gf/internal/empty"
|
||||
|
||||
|
||||
207
container/gmap/gmap_z_example_any_any_test.go
Normal file
207
container/gmap/gmap_z_example_any_any_test.go
Normal file
@ -0,0 +1,207 @@
|
||||
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with gm file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gmap_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/frame/g"
|
||||
|
||||
"github.com/gogf/gf/container/gmap"
|
||||
)
|
||||
|
||||
func ExampleNew() {
|
||||
m := gmap.New()
|
||||
|
||||
// Add data.
|
||||
m.Set("key1", "val1")
|
||||
|
||||
// Print size.
|
||||
fmt.Println(m.Size())
|
||||
|
||||
addMap := make(map[interface{}]interface{})
|
||||
addMap["key2"] = "val2"
|
||||
addMap["key3"] = "val3"
|
||||
addMap[1] = 1
|
||||
|
||||
fmt.Println(m.Values())
|
||||
|
||||
// Batch add data.
|
||||
m.Sets(addMap)
|
||||
|
||||
// Gets the value of the corresponding key.
|
||||
fmt.Println(m.Get("key3"))
|
||||
|
||||
// Get the value by key, or set it with given key-value if not exist.
|
||||
fmt.Println(m.GetOrSet("key4", "val4"))
|
||||
|
||||
// Set key-value if the key does not exist, then return true; or else return false.
|
||||
fmt.Println(m.SetIfNotExist("key3", "val3"))
|
||||
|
||||
// Remove key
|
||||
m.Remove("key2")
|
||||
fmt.Println(m.Keys())
|
||||
|
||||
// Batch remove keys.
|
||||
m.Removes([]interface{}{"key1", 1})
|
||||
fmt.Println(m.Keys())
|
||||
|
||||
// Contains checks whether a key exists.
|
||||
fmt.Println(m.Contains("key3"))
|
||||
|
||||
// Flip exchanges key-value of the map, it will change key-value to value-key.
|
||||
m.Flip()
|
||||
fmt.Println(m.Map())
|
||||
|
||||
// Clear deletes all data of the map.
|
||||
m.Clear()
|
||||
|
||||
fmt.Println(m.Size())
|
||||
|
||||
// May Output:
|
||||
// 1
|
||||
// [val1]
|
||||
// val3
|
||||
// val4
|
||||
// false
|
||||
// [key4 key1 key3 1]
|
||||
// [key4 key3]
|
||||
// true
|
||||
// map[val3:key3 val4:key4]
|
||||
// 0
|
||||
}
|
||||
|
||||
func ExampleAnyAnyMap_Keys() {
|
||||
var m gmap.Map
|
||||
m.Sets(g.MapAnyAny{
|
||||
"k1": "v1",
|
||||
"k2": "v2",
|
||||
"k3": "v3",
|
||||
"k4": "v4",
|
||||
})
|
||||
fmt.Println(m.Keys())
|
||||
fmt.Println(m.Values())
|
||||
|
||||
// May Output:
|
||||
// [k1 k2 k3 k4]
|
||||
// [v2 v3 v4 v1]
|
||||
}
|
||||
|
||||
func ExampleAnyAnyMap_Values() {
|
||||
var m gmap.Map
|
||||
m.Sets(g.MapAnyAny{
|
||||
"k1": "v1",
|
||||
"k2": "v2",
|
||||
"k3": "v3",
|
||||
"k4": "v4",
|
||||
})
|
||||
fmt.Println(m.Keys())
|
||||
fmt.Println(m.Values())
|
||||
|
||||
// May Output:
|
||||
// [k1 k2 k3 k4]
|
||||
// [v2 v3 v4 v1]
|
||||
}
|
||||
|
||||
func ExampleAnyAnyMap_Flip() {
|
||||
var m gmap.Map
|
||||
m.Sets(g.MapAnyAny{
|
||||
"k1": "v1",
|
||||
"k2": "v2",
|
||||
})
|
||||
m.Flip()
|
||||
fmt.Println(m.Map())
|
||||
|
||||
// May Output:
|
||||
// map[v1:k1 v2:k2]
|
||||
}
|
||||
|
||||
func ExampleAnyAnyMap_Pop() {
|
||||
var m gmap.Map
|
||||
m.Sets(g.MapAnyAny{
|
||||
"k1": "v1",
|
||||
"k2": "v2",
|
||||
"k3": "v3",
|
||||
"k4": "v4",
|
||||
})
|
||||
fmt.Println(m.Pop())
|
||||
fmt.Println(m.Pops(2))
|
||||
fmt.Println(m.Size())
|
||||
|
||||
// May Output:
|
||||
// k1 v1
|
||||
// map[k2:v2 k4:v4]
|
||||
// 1
|
||||
}
|
||||
|
||||
func ExampleAnyAnyMap_Pops() {
|
||||
var m gmap.Map
|
||||
m.Sets(g.MapAnyAny{
|
||||
"k1": "v1",
|
||||
"k2": "v2",
|
||||
"k3": "v3",
|
||||
"k4": "v4",
|
||||
})
|
||||
fmt.Println(m.Pop())
|
||||
fmt.Println(m.Pops(2))
|
||||
fmt.Println(m.Size())
|
||||
|
||||
// May Output:
|
||||
// k1 v1
|
||||
// map[k2:v2 k4:v4]
|
||||
// 1
|
||||
}
|
||||
|
||||
func ExampleAnyAnyMap_FilterEmpty() {
|
||||
m := gmap.NewFrom(g.MapAnyAny{
|
||||
"k1": "",
|
||||
"k2": nil,
|
||||
"k3": 0,
|
||||
"k4": 1,
|
||||
})
|
||||
m.FilterEmpty()
|
||||
fmt.Println(m.Map())
|
||||
|
||||
// May Output:
|
||||
// map[k4:1]
|
||||
}
|
||||
|
||||
func ExampleAnyAnyMap_FilterNil() {
|
||||
m := gmap.NewFrom(g.MapAnyAny{
|
||||
"k1": "",
|
||||
"k2": nil,
|
||||
"k3": 0,
|
||||
"k4": 1,
|
||||
})
|
||||
m.FilterNil()
|
||||
fmt.Println(m.Map())
|
||||
|
||||
// May Output:
|
||||
// map[k1: k3:0 k4:1]
|
||||
}
|
||||
|
||||
func ExampleAnyAnyMap_SetIfNotExist() {
|
||||
var m gmap.Map
|
||||
fmt.Println(m.SetIfNotExist("k1", "v1"))
|
||||
fmt.Println(m.SetIfNotExist("k1", "v1"))
|
||||
fmt.Println(m.Map())
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// false
|
||||
// map[k1:v1]
|
||||
}
|
||||
|
||||
func ExampleAnyAnyMap_Merge() {
|
||||
var m1, m2 gmap.Map
|
||||
m1.Set("key1", "val1")
|
||||
m2.Set("key2", "val2")
|
||||
m1.Merge(&m2)
|
||||
fmt.Println(m1.Map())
|
||||
|
||||
// May Output:
|
||||
// map[key1:val1 key2:val2]
|
||||
}
|
||||
@ -1,162 +0,0 @@
|
||||
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with gm file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gmap_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/frame/g"
|
||||
|
||||
"github.com/gogf/gf/container/gmap"
|
||||
)
|
||||
|
||||
func Example_normalBasic() {
|
||||
m := gmap.New()
|
||||
|
||||
//Add data
|
||||
m.Set("key1", "val1")
|
||||
|
||||
//Print size
|
||||
fmt.Println(m.Size())
|
||||
//output 1
|
||||
|
||||
add_map := make(map[interface{}]interface{})
|
||||
add_map["key2"] = "val2"
|
||||
add_map["key3"] = "val3"
|
||||
add_map[1] = 1
|
||||
|
||||
fmt.Println(m.Values())
|
||||
|
||||
//Batch add data
|
||||
m.Sets(add_map)
|
||||
|
||||
//Gets the value of the corresponding key
|
||||
key3_val := m.Get("key3")
|
||||
fmt.Println(key3_val)
|
||||
|
||||
//Get the value by key, or set it with given key-value if not exist.
|
||||
get_or_set_val := m.GetOrSet("key4", "val4")
|
||||
fmt.Println(get_or_set_val)
|
||||
|
||||
// Set key-value if the key does not exist, then return true; or else return false.
|
||||
is_set := m.SetIfNotExist("key3", "val3")
|
||||
fmt.Println(is_set)
|
||||
|
||||
//Remove key
|
||||
m.Remove("key2")
|
||||
fmt.Println(m.Keys())
|
||||
|
||||
//Batch remove keys
|
||||
remove_keys := []interface{}{"key1", 1}
|
||||
m.Removes(remove_keys)
|
||||
fmt.Println(m.Keys())
|
||||
|
||||
//Contains checks whether a key exists.
|
||||
is_contain := m.Contains("key3")
|
||||
fmt.Println(is_contain)
|
||||
|
||||
//Flip exchanges key-value of the map, it will change key-value to value-key.
|
||||
m.Flip()
|
||||
fmt.Println(m.Map())
|
||||
|
||||
// Clear deletes all data of the map,
|
||||
m.Clear()
|
||||
|
||||
fmt.Println(m.Size())
|
||||
}
|
||||
|
||||
func Example_keysValues() {
|
||||
var m gmap.Map
|
||||
m.Sets(g.MapAnyAny{
|
||||
"k1": "v1",
|
||||
"k2": "v2",
|
||||
"k3": "v3",
|
||||
"k4": "v4",
|
||||
})
|
||||
fmt.Println(m.Keys())
|
||||
fmt.Println(m.Values())
|
||||
|
||||
// May Output:
|
||||
// [k1 k2 k3 k4]
|
||||
// [v2 v3 v4 v1]
|
||||
}
|
||||
|
||||
func Example_flip() {
|
||||
var m gmap.Map
|
||||
m.Sets(g.MapAnyAny{
|
||||
"k1": "v1",
|
||||
"k2": "v2",
|
||||
})
|
||||
m.Flip()
|
||||
fmt.Println(m.Map())
|
||||
|
||||
// May Output:
|
||||
// map[v1:k1 v2:k2]
|
||||
}
|
||||
|
||||
func Example_pop() {
|
||||
var m gmap.Map
|
||||
m.Sets(g.MapAnyAny{
|
||||
"k1": "v1",
|
||||
"k2": "v2",
|
||||
"k3": "v3",
|
||||
"k4": "v4",
|
||||
})
|
||||
fmt.Println(m.Pop())
|
||||
fmt.Println(m.Pops(2))
|
||||
fmt.Println(m.Size())
|
||||
|
||||
// May Output:
|
||||
// k1 v1
|
||||
// map[k2:v2 k4:v4]
|
||||
// 1
|
||||
}
|
||||
|
||||
func Example_filter() {
|
||||
m1 := gmap.NewFrom(g.MapAnyAny{
|
||||
"k1": "",
|
||||
"k2": nil,
|
||||
"k3": 0,
|
||||
"k4": 1,
|
||||
})
|
||||
m2 := gmap.NewFrom(g.MapAnyAny{
|
||||
"k1": "",
|
||||
"k2": nil,
|
||||
"k3": 0,
|
||||
"k4": 1,
|
||||
})
|
||||
m1.FilterEmpty()
|
||||
m2.FilterNil()
|
||||
fmt.Println(m1.Map())
|
||||
fmt.Println(m2.Map())
|
||||
|
||||
// Output:
|
||||
// map[k4:1]
|
||||
// map[k1: k3:0 k4:1]
|
||||
}
|
||||
|
||||
func Example_setIfNotExist() {
|
||||
var m gmap.Map
|
||||
fmt.Println(m.SetIfNotExist("k1", "v1"))
|
||||
fmt.Println(m.SetIfNotExist("k1", "v1"))
|
||||
fmt.Println(m.Map())
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// false
|
||||
// map[k1:v1]
|
||||
}
|
||||
|
||||
func Example_normalMerge() {
|
||||
var m1, m2 gmap.Map
|
||||
m1.Set("key1", "val1")
|
||||
m2.Set("key2", "val2")
|
||||
m1.Merge(&m2)
|
||||
fmt.Println(m1.Map())
|
||||
|
||||
// May Output:
|
||||
// map[key1:val1 key2:val2]
|
||||
}
|
||||
@ -7,10 +7,10 @@
|
||||
package gmap_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gogf/gf/container/garray"
|
||||
"github.com/gogf/gf/container/gmap"
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"testing"
|
||||
|
||||
@ -7,9 +7,9 @@
|
||||
package gmap_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gogf/gf/container/garray"
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"testing"
|
||||
|
||||
|
||||
@ -7,9 +7,9 @@
|
||||
package gmap_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gogf/gf/container/garray"
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"testing"
|
||||
|
||||
|
||||
@ -7,9 +7,9 @@
|
||||
package gmap_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gogf/gf/container/garray"
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"testing"
|
||||
|
||||
|
||||
@ -7,8 +7,8 @@
|
||||
package gmap_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gogf/gf/container/garray"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"testing"
|
||||
|
||||
|
||||
@ -7,9 +7,9 @@
|
||||
package gmap_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gogf/gf/container/garray"
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"testing"
|
||||
|
||||
|
||||
@ -7,9 +7,9 @@
|
||||
package gmap_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gogf/gf/container/garray"
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"testing"
|
||||
|
||||
|
||||
@ -7,9 +7,9 @@
|
||||
package gmap_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gogf/gf/container/garray"
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"testing"
|
||||
|
||||
|
||||
@ -7,8 +7,8 @@
|
||||
package gmap_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"testing"
|
||||
|
||||
|
||||
@ -170,6 +170,7 @@ func (p *Pool) checkExpireItems() {
|
||||
if r := p.list.PopFront(); r != nil {
|
||||
item := r.(*poolItem)
|
||||
latestExpire = item.expire
|
||||
// TODO improve the auto-expiration mechanism of the pool.
|
||||
if item.expire > timestampMilli {
|
||||
p.list.PushFront(item)
|
||||
break
|
||||
|
||||
@ -9,7 +9,7 @@ package gset
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"github.com/gogf/gf/internal/rwmutex"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
|
||||
@ -9,7 +9,7 @@ package gset
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"github.com/gogf/gf/internal/rwmutex"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
)
|
||||
|
||||
@ -9,7 +9,7 @@ package gset
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"github.com/gogf/gf/internal/rwmutex"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
|
||||
@ -12,7 +12,7 @@ import (
|
||||
"github.com/gogf/gf/frame/g"
|
||||
)
|
||||
|
||||
func Example_intersectDiffUnionComplement() {
|
||||
func ExampleSet_Intersect() {
|
||||
s1 := gset.NewFrom(g.Slice{1, 2, 3})
|
||||
s2 := gset.NewFrom(g.Slice{4, 5, 6})
|
||||
s3 := gset.NewFrom(g.Slice{1, 2, 3, 4, 5, 6, 7})
|
||||
@ -29,7 +29,58 @@ func Example_intersectDiffUnionComplement() {
|
||||
// [4 5 6 7]
|
||||
}
|
||||
|
||||
func Example_isSubsetOf() {
|
||||
func ExampleSet_Diff() {
|
||||
s1 := gset.NewFrom(g.Slice{1, 2, 3})
|
||||
s2 := gset.NewFrom(g.Slice{4, 5, 6})
|
||||
s3 := gset.NewFrom(g.Slice{1, 2, 3, 4, 5, 6, 7})
|
||||
|
||||
fmt.Println(s3.Intersect(s1).Slice())
|
||||
fmt.Println(s3.Diff(s1).Slice())
|
||||
fmt.Println(s1.Union(s2).Slice())
|
||||
fmt.Println(s1.Complement(s3).Slice())
|
||||
|
||||
// May Output:
|
||||
// [2 3 1]
|
||||
// [5 6 7 4]
|
||||
// [6 1 2 3 4 5]
|
||||
// [4 5 6 7]
|
||||
}
|
||||
|
||||
func ExampleSet_Union() {
|
||||
s1 := gset.NewFrom(g.Slice{1, 2, 3})
|
||||
s2 := gset.NewFrom(g.Slice{4, 5, 6})
|
||||
s3 := gset.NewFrom(g.Slice{1, 2, 3, 4, 5, 6, 7})
|
||||
|
||||
fmt.Println(s3.Intersect(s1).Slice())
|
||||
fmt.Println(s3.Diff(s1).Slice())
|
||||
fmt.Println(s1.Union(s2).Slice())
|
||||
fmt.Println(s1.Complement(s3).Slice())
|
||||
|
||||
// May Output:
|
||||
// [2 3 1]
|
||||
// [5 6 7 4]
|
||||
// [6 1 2 3 4 5]
|
||||
// [4 5 6 7]
|
||||
}
|
||||
|
||||
func ExampleSet_Complement() {
|
||||
s1 := gset.NewFrom(g.Slice{1, 2, 3})
|
||||
s2 := gset.NewFrom(g.Slice{4, 5, 6})
|
||||
s3 := gset.NewFrom(g.Slice{1, 2, 3, 4, 5, 6, 7})
|
||||
|
||||
fmt.Println(s3.Intersect(s1).Slice())
|
||||
fmt.Println(s3.Diff(s1).Slice())
|
||||
fmt.Println(s1.Union(s2).Slice())
|
||||
fmt.Println(s1.Complement(s3).Slice())
|
||||
|
||||
// May Output:
|
||||
// [2 3 1]
|
||||
// [5 6 7 4]
|
||||
// [6 1 2 3 4 5]
|
||||
// [4 5 6 7]
|
||||
}
|
||||
|
||||
func ExampleSet_IsSubsetOf() {
|
||||
var s1, s2 gset.Set
|
||||
s1.Add(g.Slice{1, 2, 3}...)
|
||||
s2.Add(g.Slice{2, 3}...)
|
||||
@ -41,7 +92,7 @@ func Example_isSubsetOf() {
|
||||
// true
|
||||
}
|
||||
|
||||
func Example_addIfNotExist() {
|
||||
func ExampleSet_AddIfNotExist() {
|
||||
var set gset.Set
|
||||
fmt.Println(set.AddIfNotExist(1))
|
||||
fmt.Println(set.AddIfNotExist(1))
|
||||
@ -53,7 +104,7 @@ func Example_addIfNotExist() {
|
||||
// [1]
|
||||
}
|
||||
|
||||
func Example_pop() {
|
||||
func ExampleSet_Pop() {
|
||||
var set gset.Set
|
||||
set.Add(1, 2, 3, 4)
|
||||
fmt.Println(set.Pop())
|
||||
@ -66,7 +117,20 @@ func Example_pop() {
|
||||
// 1
|
||||
}
|
||||
|
||||
func Example_join() {
|
||||
func ExampleSet_Pops() {
|
||||
var set gset.Set
|
||||
set.Add(1, 2, 3, 4)
|
||||
fmt.Println(set.Pop())
|
||||
fmt.Println(set.Pops(2))
|
||||
fmt.Println(set.Size())
|
||||
|
||||
// May Output:
|
||||
// 1
|
||||
// [2 3]
|
||||
// 1
|
||||
}
|
||||
|
||||
func ExampleSet_Join() {
|
||||
var set gset.Set
|
||||
set.Add("a", "b", "c", "d")
|
||||
fmt.Println(set.Join(","))
|
||||
@ -75,7 +139,7 @@ func Example_join() {
|
||||
// a,b,c,d
|
||||
}
|
||||
|
||||
func Example_contains() {
|
||||
func ExampleSet_Contains() {
|
||||
var set gset.StrSet
|
||||
set.Add("a")
|
||||
fmt.Println(set.Contains("a"))
|
||||
@ -88,7 +152,7 @@ func Example_contains() {
|
||||
// true
|
||||
}
|
||||
|
||||
func Example_Contains() {
|
||||
func ExampleSet_ContainsI() {
|
||||
var set gset.StrSet
|
||||
set.Add("a")
|
||||
fmt.Println(set.Contains("a"))
|
||||
@ -100,20 +164,3 @@ func Example_Contains() {
|
||||
// false
|
||||
// true
|
||||
}
|
||||
|
||||
func Example_walk() {
|
||||
var (
|
||||
set gset.StrSet
|
||||
names = g.SliceStr{"user", "user_detail"}
|
||||
prefix = "gf_"
|
||||
)
|
||||
set.Add(names...)
|
||||
// Add prefix for given table names.
|
||||
set.Walk(func(item string) string {
|
||||
return prefix + item
|
||||
})
|
||||
fmt.Println(set.Slice())
|
||||
|
||||
// May Output:
|
||||
// [gf_user gf_user_detail]
|
||||
}
|
||||
23
container/gset/gset_z_example_int_test.go
Normal file
23
container/gset/gset_z_example_int_test.go
Normal file
@ -0,0 +1,23 @@
|
||||
// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with gm file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gset_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/container/gset"
|
||||
)
|
||||
|
||||
func ExampleIntSet_Contains() {
|
||||
var set gset.IntSet
|
||||
set.Add(1)
|
||||
fmt.Println(set.Contains(1))
|
||||
fmt.Println(set.Contains(2))
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// false
|
||||
}
|
||||
43
container/gset/gset_z_example_str_test.go
Normal file
43
container/gset/gset_z_example_str_test.go
Normal file
@ -0,0 +1,43 @@
|
||||
// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with gm file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gset_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/container/gset"
|
||||
"github.com/gogf/gf/frame/g"
|
||||
)
|
||||
|
||||
func ExampleStrSet_Contains() {
|
||||
var set gset.StrSet
|
||||
set.Add("a")
|
||||
fmt.Println(set.Contains("a"))
|
||||
fmt.Println(set.Contains("A"))
|
||||
fmt.Println(set.ContainsI("A"))
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// false
|
||||
// true
|
||||
}
|
||||
|
||||
func ExampleStrSet_Walk() {
|
||||
var (
|
||||
set gset.StrSet
|
||||
names = g.SliceStr{"user", "user_detail"}
|
||||
prefix = "gf_"
|
||||
)
|
||||
set.Add(names...)
|
||||
// Add prefix for given table names.
|
||||
set.Walk(func(item string) string {
|
||||
return prefix + item
|
||||
})
|
||||
fmt.Println(set.Slice())
|
||||
|
||||
// May Output:
|
||||
// [gf_user gf_user_detail]
|
||||
}
|
||||
@ -9,8 +9,8 @@
|
||||
package gset_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
@ -9,8 +9,8 @@
|
||||
package gset_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
@ -9,8 +9,8 @@
|
||||
package gset_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
@ -7,8 +7,8 @@
|
||||
package gtree
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
|
||||
|
||||
@ -8,8 +8,8 @@ package gtree
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
|
||||
@ -7,8 +7,8 @@
|
||||
package gtree
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"github.com/gogf/gf/util/gutil"
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
package gtype
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
@ -9,8 +9,8 @@
|
||||
package gtype_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gogf/gf/container/gtype"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
package gtype_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"testing"
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
package gtype_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
package gtype_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"testing"
|
||||
|
||||
|
||||
@ -7,8 +7,8 @@
|
||||
package gtype_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gogf/gf/container/gtype"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"math"
|
||||
|
||||
@ -7,8 +7,8 @@
|
||||
package gtype_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gogf/gf/container/gtype"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"math"
|
||||
|
||||
@ -7,8 +7,8 @@
|
||||
package gtype_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gogf/gf/container/gtype"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"math"
|
||||
|
||||
@ -7,8 +7,8 @@
|
||||
package gtype_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gogf/gf/container/gtype"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"math"
|
||||
|
||||
@ -7,8 +7,8 @@
|
||||
package gtype_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gogf/gf/container/gtype"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"sync"
|
||||
|
||||
@ -7,8 +7,8 @@
|
||||
package gtype_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gogf/gf/container/gtype"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"testing"
|
||||
|
||||
@ -7,8 +7,8 @@
|
||||
package gtype_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gogf/gf/container/gtype"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"testing"
|
||||
|
||||
@ -7,8 +7,8 @@
|
||||
package gtype_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gogf/gf/container/gtype"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"math"
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
package gtype_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"math"
|
||||
"sync"
|
||||
|
||||
@ -7,8 +7,8 @@
|
||||
package gtype_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gogf/gf/container/gtype"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"sync"
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
package gvar
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/internal/empty"
|
||||
@ -18,17 +18,23 @@ import (
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
)
|
||||
|
||||
// Var is an universal variable type.
|
||||
// Var is an universal variable type implementer.
|
||||
type Var struct {
|
||||
value interface{} // Underlying value.
|
||||
safe bool // Concurrent safe or not.
|
||||
}
|
||||
|
||||
// New creates and returns a new *Var with given <value>.
|
||||
// New creates and returns a new Var with given <value>.
|
||||
// The optional parameter <safe> specifies whether Var is used in concurrent-safety,
|
||||
// which is false in default.
|
||||
func New(value interface{}, safe ...bool) *Var {
|
||||
v := Create(value, safe...)
|
||||
v := Var{}
|
||||
if len(safe) > 0 && !safe[0] {
|
||||
v.safe = true
|
||||
v.value = gtype.NewInterface(value)
|
||||
} else {
|
||||
v.value = value
|
||||
}
|
||||
return &v
|
||||
}
|
||||
|
||||
@ -202,7 +208,7 @@ func (v *Var) Array() []interface{} {
|
||||
return v.Interfaces()
|
||||
}
|
||||
|
||||
// Vars converts and returns <v> as []*Var.
|
||||
// Vars converts and returns <v> as []Var.
|
||||
func (v *Var) Vars() []*Var {
|
||||
array := gconv.Interfaces(v.Val())
|
||||
if len(array) == 0 {
|
||||
|
||||
25
container/gvar/gvar_list.go
Normal file
25
container/gvar/gvar_list.go
Normal file
@ -0,0 +1,25 @@
|
||||
// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gvar
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/util/gutil"
|
||||
)
|
||||
|
||||
// ListItemValues retrieves and returns the elements of all item struct/map with key <key>.
|
||||
// Note that the parameter <list> should be type of slice which contains elements of map or struct,
|
||||
// or else it returns an empty slice.
|
||||
func (v *Var) ListItemValues(key interface{}) (values []interface{}) {
|
||||
return gutil.ListItemValues(v.Val(), key)
|
||||
}
|
||||
|
||||
// ListItemValuesUnique retrieves and returns the unique elements of all struct/map with key <key>.
|
||||
// Note that the parameter <list> should be type of slice which contains elements of map or struct,
|
||||
// or else it returns an empty slice.
|
||||
func (v *Var) ListItemValuesUnique(key string) []interface{} {
|
||||
return gutil.ListItemValuesUnique(v.Val(), key)
|
||||
}
|
||||
@ -13,12 +13,17 @@ func (v *Var) Map(tags ...string) map[string]interface{} {
|
||||
return gconv.Map(v.Val(), tags...)
|
||||
}
|
||||
|
||||
// MapStrAny is like function Map, but implements the interface of MapStrAny.
|
||||
func (v *Var) MapStrAny() map[string]interface{} {
|
||||
return v.Map()
|
||||
}
|
||||
|
||||
// MapStrStr converts and returns <v> as map[string]string.
|
||||
func (v *Var) MapStrStr(tags ...string) map[string]string {
|
||||
return gconv.MapStrStr(v.Val(), tags...)
|
||||
}
|
||||
|
||||
// MapStrVar converts and returns <v> as map[string]*Var.
|
||||
// MapStrVar converts and returns <v> as map[string]Var.
|
||||
func (v *Var) MapStrVar(tags ...string) map[string]*Var {
|
||||
m := v.Map(tags...)
|
||||
if len(m) > 0 {
|
||||
@ -62,15 +67,15 @@ func (v *Var) Maps(tags ...string) []map[string]interface{} {
|
||||
|
||||
// MapToMap converts any map type variable <params> to another map type variable <pointer>.
|
||||
// See gconv.MapToMap.
|
||||
func (v *Var) MapToMap(pointer interface{}) (err error) {
|
||||
return gconv.MapToMap(v.Val(), pointer)
|
||||
func (v *Var) MapToMap(pointer interface{}, mapping ...map[string]string) (err error) {
|
||||
return gconv.MapToMap(v.Val(), pointer, mapping...)
|
||||
}
|
||||
|
||||
// MapToMapDeep converts any map type variable <params> to another map type variable
|
||||
// <pointer> recursively.
|
||||
// See gconv.MapToMapDeep.
|
||||
func (v *Var) MapToMapDeep(pointer interface{}) (err error) {
|
||||
return gconv.MapToMapDeep(v.Val(), pointer)
|
||||
func (v *Var) MapToMapDeep(pointer interface{}, mapping ...map[string]string) (err error) {
|
||||
return gconv.MapToMapDeep(v.Val(), pointer, mapping...)
|
||||
}
|
||||
|
||||
// MapToMaps converts any map type variable <params> to another map type variable <pointer>.
|
||||
|
||||
@ -6,7 +6,9 @@
|
||||
|
||||
package gvar
|
||||
|
||||
import "github.com/gogf/gf/util/gconv"
|
||||
import (
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
)
|
||||
|
||||
// Struct maps value of <v> to <pointer>.
|
||||
// The parameter <pointer> should be a pointer to a struct instance.
|
||||
@ -23,11 +25,27 @@ func (v *Var) StructDeep(pointer interface{}, mapping ...map[string]string) erro
|
||||
}
|
||||
|
||||
// Structs converts and returns <v> as given struct slice.
|
||||
func (v *Var) Structs(pointer interface{}, mapping ...map[string]string) (err error) {
|
||||
func (v *Var) Structs(pointer interface{}, mapping ...map[string]string) error {
|
||||
return gconv.Structs(v.Val(), pointer, mapping...)
|
||||
}
|
||||
|
||||
// StructsDeep converts and returns <v> as given struct slice recursively.
|
||||
func (v *Var) StructsDeep(pointer interface{}, mapping ...map[string]string) (err error) {
|
||||
func (v *Var) StructsDeep(pointer interface{}, mapping ...map[string]string) error {
|
||||
return gconv.StructsDeep(v.Val(), pointer, mapping...)
|
||||
}
|
||||
|
||||
// Scan automatically calls Struct or Structs function according to the type of parameter
|
||||
// <pointer> to implement the converting.
|
||||
// It calls function Struct if <pointer> is type of *struct/**struct to do the converting.
|
||||
// It calls function Structs if <pointer> is type of *[]struct/*[]*struct to do the converting.
|
||||
func (v *Var) Scan(pointer interface{}, mapping ...map[string]string) error {
|
||||
return gconv.Scan(v.Val(), pointer, mapping...)
|
||||
}
|
||||
|
||||
// ScanDeep automatically calls StructDeep or StructsDeep function according to the type of
|
||||
// parameter <pointer> to implement the converting.
|
||||
// It calls function StructDeep if <pointer> is type of *struct/**struct to do the converting.
|
||||
// It calls function StructsDeep if <pointer> is type of *[]struct/*[]*struct to do the converting.
|
||||
func (v *Var) ScanDeep(pointer interface{}, mapping ...map[string]string) error {
|
||||
return gconv.ScanDeep(v.Val(), pointer, mapping...)
|
||||
}
|
||||
|
||||
@ -7,8 +7,8 @@
|
||||
package gvar_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gogf/gf/container/gvar"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
"math"
|
||||
"testing"
|
||||
|
||||
115
container/gvar/gvar_z_unit_list_test.go
Normal file
115
container/gvar/gvar_z_unit_list_test.go
Normal file
@ -0,0 +1,115 @@
|
||||
// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gvar_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/container/gvar"
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_ListItemValues_Map(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
listMap := g.List{
|
||||
g.Map{"id": 1, "score": 100},
|
||||
g.Map{"id": 2, "score": 99},
|
||||
g.Map{"id": 3, "score": 99},
|
||||
}
|
||||
t.Assert(gvar.New(listMap).ListItemValues("id"), g.Slice{1, 2, 3})
|
||||
t.Assert(gvar.New(listMap).ListItemValues("score"), g.Slice{100, 99, 99})
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
listMap := g.List{
|
||||
g.Map{"id": 1, "score": 100},
|
||||
g.Map{"id": 2, "score": nil},
|
||||
g.Map{"id": 3, "score": 0},
|
||||
}
|
||||
t.Assert(gvar.New(listMap).ListItemValues("id"), g.Slice{1, 2, 3})
|
||||
t.Assert(gvar.New(listMap).ListItemValues("score"), g.Slice{100, nil, 0})
|
||||
})
|
||||
}
|
||||
|
||||
func Test_ListItemValues_Struct(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type T struct {
|
||||
Id int
|
||||
Score float64
|
||||
}
|
||||
listStruct := g.Slice{
|
||||
T{1, 100},
|
||||
T{2, 99},
|
||||
T{3, 0},
|
||||
}
|
||||
t.Assert(gvar.New(listStruct).ListItemValues("Id"), g.Slice{1, 2, 3})
|
||||
t.Assert(gvar.New(listStruct).ListItemValues("Score"), g.Slice{100, 99, 0})
|
||||
})
|
||||
// Pointer items.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type T struct {
|
||||
Id int
|
||||
Score float64
|
||||
}
|
||||
listStruct := g.Slice{
|
||||
&T{1, 100},
|
||||
&T{2, 99},
|
||||
&T{3, 0},
|
||||
}
|
||||
t.Assert(gvar.New(listStruct).ListItemValues("Id"), g.Slice{1, 2, 3})
|
||||
t.Assert(gvar.New(listStruct).ListItemValues("Score"), g.Slice{100, 99, 0})
|
||||
})
|
||||
// Nil element value.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type T struct {
|
||||
Id int
|
||||
Score interface{}
|
||||
}
|
||||
listStruct := g.Slice{
|
||||
T{1, 100},
|
||||
T{2, nil},
|
||||
T{3, 0},
|
||||
}
|
||||
t.Assert(gvar.New(listStruct).ListItemValues("Id"), g.Slice{1, 2, 3})
|
||||
t.Assert(gvar.New(listStruct).ListItemValues("Score"), g.Slice{100, nil, 0})
|
||||
})
|
||||
}
|
||||
|
||||
func Test_ListItemValuesUnique(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
listMap := g.List{
|
||||
g.Map{"id": 1, "score": 100},
|
||||
g.Map{"id": 2, "score": 100},
|
||||
g.Map{"id": 3, "score": 100},
|
||||
g.Map{"id": 4, "score": 100},
|
||||
g.Map{"id": 5, "score": 100},
|
||||
}
|
||||
t.Assert(gvar.New(listMap).ListItemValuesUnique("id"), g.Slice{1, 2, 3, 4, 5})
|
||||
t.Assert(gvar.New(listMap).ListItemValuesUnique("score"), g.Slice{100})
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
listMap := g.List{
|
||||
g.Map{"id": 1, "score": 100},
|
||||
g.Map{"id": 2, "score": 100},
|
||||
g.Map{"id": 3, "score": 100},
|
||||
g.Map{"id": 4, "score": 100},
|
||||
g.Map{"id": 5, "score": 99},
|
||||
}
|
||||
t.Assert(gvar.New(listMap).ListItemValuesUnique("id"), g.Slice{1, 2, 3, 4, 5})
|
||||
t.Assert(gvar.New(listMap).ListItemValuesUnique("score"), g.Slice{100, 99})
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
listMap := g.List{
|
||||
g.Map{"id": 1, "score": 100},
|
||||
g.Map{"id": 2, "score": 100},
|
||||
g.Map{"id": 3, "score": 0},
|
||||
g.Map{"id": 4, "score": 100},
|
||||
g.Map{"id": 5, "score": 99},
|
||||
}
|
||||
t.Assert(gvar.New(listMap).ListItemValuesUnique("id"), g.Slice{1, 2, 3, 4, 5})
|
||||
t.Assert(gvar.New(listMap).ListItemValuesUnique("score"), g.Slice{100, 0, 99})
|
||||
})
|
||||
}
|
||||
@ -10,6 +10,7 @@ import (
|
||||
"github.com/gogf/gf/container/gvar"
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@ -40,3 +41,36 @@ func Test_Struct(t *testing.T) {
|
||||
t.Assert(o.Test, -25)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Var_Attribute_Struct(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type User struct {
|
||||
Uid int
|
||||
Name string
|
||||
}
|
||||
user := new(User)
|
||||
err := gconv.Struct(
|
||||
g.Map{
|
||||
"uid": gvar.New(1),
|
||||
"name": gvar.New("john"),
|
||||
}, user)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(user.Uid, 1)
|
||||
t.Assert(user.Name, "john")
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type User struct {
|
||||
Uid int
|
||||
Name string
|
||||
}
|
||||
var user *User
|
||||
err := gconv.Struct(
|
||||
g.Map{
|
||||
"uid": gvar.New(1),
|
||||
"name": gvar.New("john"),
|
||||
}, &user)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(user.Uid, 1)
|
||||
t.Assert(user.Name, "john")
|
||||
})
|
||||
}
|
||||
|
||||
@ -25,46 +25,32 @@ import (
|
||||
|
||||
// DB defines the interfaces for ORM operations.
|
||||
type DB interface {
|
||||
// ===========================================================================
|
||||
// Model creation.
|
||||
// ===========================================================================
|
||||
|
||||
// The DB interface is designed not only for
|
||||
// relational databases but also for NoSQL databases in the future. The name
|
||||
// "Table" is not proper for that purpose any more.
|
||||
Table(table ...string) *Model
|
||||
Model(table ...string) *Model
|
||||
Schema(schema string) *Schema
|
||||
|
||||
// Open creates a raw connection object for database with given node configuration.
|
||||
// Note that it is not recommended using the this function manually.
|
||||
Open(config *ConfigNode) (*sql.DB, error)
|
||||
|
||||
// ===========================================================================
|
||||
// Query APIs.
|
||||
// ===========================================================================
|
||||
|
||||
Query(sql string, args ...interface{}) (*sql.Rows, error)
|
||||
Exec(sql string, args ...interface{}) (sql.Result, error)
|
||||
Prepare(sql string, execOnMaster ...bool) (*sql.Stmt, error)
|
||||
|
||||
// Internal APIs for CURD, which can be overwrote for custom CURD implements.
|
||||
DoQuery(link Link, sql string, args ...interface{}) (rows *sql.Rows, err error)
|
||||
DoGetAll(link Link, sql string, args ...interface{}) (result Result, err error)
|
||||
DoExec(link Link, sql string, args ...interface{}) (result sql.Result, err error)
|
||||
DoPrepare(link Link, sql string) (*sql.Stmt, error)
|
||||
DoInsert(link Link, table string, data interface{}, option int, batch ...int) (result sql.Result, err error)
|
||||
DoBatchInsert(link Link, table string, list interface{}, option int, batch ...int) (result sql.Result, err error)
|
||||
DoUpdate(link Link, table string, data interface{}, condition string, args ...interface{}) (result sql.Result, err error)
|
||||
DoDelete(link Link, table string, condition string, args ...interface{}) (result sql.Result, err error)
|
||||
|
||||
// Query APIs for convenience purpose.
|
||||
GetAll(sql string, args ...interface{}) (Result, error)
|
||||
GetOne(sql string, args ...interface{}) (Record, error)
|
||||
GetValue(sql string, args ...interface{}) (Value, error)
|
||||
GetArray(sql string, args ...interface{}) ([]Value, error)
|
||||
GetCount(sql string, args ...interface{}) (int, error)
|
||||
GetStruct(objPointer interface{}, sql string, args ...interface{}) error
|
||||
GetStructs(objPointerSlice interface{}, sql string, args ...interface{}) error
|
||||
GetScan(objPointer interface{}, sql string, args ...interface{}) error
|
||||
|
||||
// Master/Slave specification support.
|
||||
Master() (*sql.DB, error)
|
||||
Slave() (*sql.DB, error)
|
||||
|
||||
// Ping-Pong.
|
||||
PingMaster() error
|
||||
PingSlave() error
|
||||
|
||||
// Transaction.
|
||||
Begin() (*TX, error)
|
||||
Transaction(f func(tx *TX) error) (err error)
|
||||
// ===========================================================================
|
||||
// Common APIs for CURD.
|
||||
// ===========================================================================
|
||||
|
||||
Insert(table string, data interface{}, batch ...int) (sql.Result, error)
|
||||
InsertIgnore(table string, data interface{}, batch ...int) (sql.Result, error)
|
||||
@ -78,12 +64,57 @@ type DB interface {
|
||||
Update(table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error)
|
||||
Delete(table string, condition interface{}, args ...interface{}) (sql.Result, error)
|
||||
|
||||
// Model creation.
|
||||
Table(table ...string) *Model
|
||||
Model(table ...string) *Model
|
||||
Schema(schema string) *Schema
|
||||
// ===========================================================================
|
||||
// Internal APIs for CURD, which can be overwrote for custom CURD implements.
|
||||
// ===========================================================================
|
||||
|
||||
DoQuery(link Link, sql string, args ...interface{}) (rows *sql.Rows, err error)
|
||||
DoGetAll(link Link, sql string, args ...interface{}) (result Result, err error)
|
||||
DoExec(link Link, sql string, args ...interface{}) (result sql.Result, err error)
|
||||
DoPrepare(link Link, sql string) (*sql.Stmt, error)
|
||||
DoInsert(link Link, table string, data interface{}, option int, batch ...int) (result sql.Result, err error)
|
||||
DoBatchInsert(link Link, table string, list interface{}, option int, batch ...int) (result sql.Result, err error)
|
||||
DoUpdate(link Link, table string, data interface{}, condition string, args ...interface{}) (result sql.Result, err error)
|
||||
DoDelete(link Link, table string, condition string, args ...interface{}) (result sql.Result, err error)
|
||||
|
||||
// ===========================================================================
|
||||
// Query APIs for convenience purpose.
|
||||
// ===========================================================================
|
||||
|
||||
GetAll(sql string, args ...interface{}) (Result, error)
|
||||
GetOne(sql string, args ...interface{}) (Record, error)
|
||||
GetValue(sql string, args ...interface{}) (Value, error)
|
||||
GetArray(sql string, args ...interface{}) ([]Value, error)
|
||||
GetCount(sql string, args ...interface{}) (int, error)
|
||||
GetStruct(objPointer interface{}, sql string, args ...interface{}) error
|
||||
GetStructs(objPointerSlice interface{}, sql string, args ...interface{}) error
|
||||
GetScan(objPointer interface{}, sql string, args ...interface{}) error
|
||||
|
||||
// ===========================================================================
|
||||
// Master/Slave specification support.
|
||||
// ===========================================================================
|
||||
|
||||
Master() (*sql.DB, error)
|
||||
Slave() (*sql.DB, error)
|
||||
|
||||
// ===========================================================================
|
||||
// Ping-Pong.
|
||||
// ===========================================================================
|
||||
|
||||
PingMaster() error
|
||||
PingSlave() error
|
||||
|
||||
// ===========================================================================
|
||||
// Transaction.
|
||||
// ===========================================================================
|
||||
|
||||
Begin() (*TX, error)
|
||||
Transaction(f func(tx *TX) error) (err error)
|
||||
|
||||
// ===========================================================================
|
||||
// Configuration methods.
|
||||
// ===========================================================================
|
||||
|
||||
GetCache() *gcache.Cache
|
||||
SetDebug(debug bool)
|
||||
GetDebug() bool
|
||||
@ -99,7 +130,10 @@ type DB interface {
|
||||
SetMaxOpenConnCount(n int)
|
||||
SetMaxConnLifetime(d time.Duration)
|
||||
|
||||
// ===========================================================================
|
||||
// Utility methods.
|
||||
// ===========================================================================
|
||||
|
||||
GetChars() (charLeft string, charRight string)
|
||||
GetMaster(schema ...string) (*sql.DB, error)
|
||||
GetSlave(schema ...string) (*sql.DB, error)
|
||||
@ -115,7 +149,10 @@ type DB interface {
|
||||
// arguments <args> as you wish before they're committed to driver.
|
||||
HandleSqlBeforeCommit(link Link, sql string, args []interface{}) (string, []interface{})
|
||||
|
||||
// ===========================================================================
|
||||
// Internal methods.
|
||||
// ===========================================================================
|
||||
|
||||
filterFields(schema, table string, data map[string]interface{}) map[string]interface{}
|
||||
convertValue(fieldValue []byte, fieldType string) interface{}
|
||||
rowsToResult(rows *sql.Rows) (Result, error)
|
||||
@ -202,8 +239,10 @@ const (
|
||||
var (
|
||||
// ErrNoRows is alias of sql.ErrNoRows.
|
||||
ErrNoRows = sql.ErrNoRows
|
||||
|
||||
// instances is the management map for instances.
|
||||
instances = gmap.NewStrAnyMap(true)
|
||||
|
||||
// driverMap manages all custom registered driver.
|
||||
driverMap = map[string]Driver{
|
||||
"mysql": &DriverMysql{},
|
||||
@ -212,6 +251,14 @@ var (
|
||||
"oracle": &DriverOracle{},
|
||||
"sqlite": &DriverSqlite{},
|
||||
}
|
||||
|
||||
// lastOperatorRegPattern is the regular expression pattern for a string
|
||||
// which has operator at its tail.
|
||||
lastOperatorRegPattern = `[<>=]+\s*$`
|
||||
|
||||
// regularFieldNameRegPattern is the regular expression pattern for a string
|
||||
// which is a regular field name of table.
|
||||
regularFieldNameRegPattern = `^[\w\.\-]+$`
|
||||
)
|
||||
|
||||
// Register registers custom database driver to gdb.
|
||||
|
||||
@ -13,7 +13,6 @@ import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/internal/utils"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/container/gvar"
|
||||
@ -22,16 +21,6 @@ import (
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
)
|
||||
|
||||
const (
|
||||
gPATH_FILTER_KEY = "/database/gdb/gdb"
|
||||
)
|
||||
|
||||
var (
|
||||
// lastOperatorReg is the regular expression object for a string
|
||||
// which has operator at its tail.
|
||||
lastOperatorReg = regexp.MustCompile(`[<>=]+\s*$`)
|
||||
)
|
||||
|
||||
// Master creates and returns a connection from master node if master-slave configured.
|
||||
// It returns the default connection if master-slave not configured.
|
||||
func (c *Core) Master() (*sql.DB, error) {
|
||||
@ -244,12 +233,12 @@ func (c *Core) GetScan(pointer interface{}, sql string, args ...interface{}) err
|
||||
func (c *Core) GetValue(sql string, args ...interface{}) (Value, error) {
|
||||
one, err := c.DB.GetOne(sql, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return gvar.New(nil), err
|
||||
}
|
||||
for _, v := range one {
|
||||
return v, nil
|
||||
}
|
||||
return nil, nil
|
||||
return gvar.New(nil), nil
|
||||
}
|
||||
|
||||
// GetCount queries and returns the count from database.
|
||||
@ -784,8 +773,8 @@ func (c *Core) writeSqlToLogger(v *Sql) {
|
||||
s := fmt.Sprintf("[%3d ms] %s", v.End-v.Start, v.Format)
|
||||
if v.Error != nil {
|
||||
s += "\nError: " + v.Error.Error()
|
||||
c.logger.StackWithFilter(gPATH_FILTER_KEY).Error(s)
|
||||
c.logger.Error(s)
|
||||
} else {
|
||||
c.logger.StackWithFilter(gPATH_FILTER_KEY).Debug(s)
|
||||
c.logger.Debug(s)
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,11 +15,12 @@ import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/internal/intlog"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/internal/intlog"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
|
||||
"github.com/gogf/gf/text/gregex"
|
||||
)
|
||||
|
||||
@ -63,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 "@px".
|
||||
// Convert place holder char '?' to string "@vx".
|
||||
str, _ := gregex.ReplaceStringFunc("\\?", sql, func(s string) string {
|
||||
index++
|
||||
return fmt.Sprintf("@p%d", index)
|
||||
return fmt.Sprintf("@v%d", index)
|
||||
})
|
||||
str, _ = gregex.ReplaceString("\"", "", str)
|
||||
return d.parseSql(str), args
|
||||
@ -206,23 +207,43 @@ func (d *DriverMssql) TableFields(table string, schema ...string) (fields map[st
|
||||
return nil
|
||||
}
|
||||
result, err = d.DB.DoGetAll(link, fmt.Sprintf(`
|
||||
SELECT c.name as FIELD, CASE t.name
|
||||
WHEN 'numeric' THEN t.name + '(' + convert(varchar(20),c.xprec) + ',' + convert(varchar(20),c.xscale) + ')'
|
||||
WHEN 'char' THEN t.name + '(' + convert(varchar(20),c.length)+ ')'
|
||||
WHEN 'varchar' THEN t.name + '(' + convert(varchar(20),c.length)+ ')'
|
||||
ELSE t.name + '(' + convert(varchar(20),c.length)+ ')' END as TYPE
|
||||
FROM systypes t,syscolumns c WHERE t.xtype=c.xtype
|
||||
AND c.id = (SELECT id FROM sysobjects WHERE name='%s')
|
||||
ORDER BY c.colid`, strings.ToUpper(table)))
|
||||
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],
|
||||
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)))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
fields = make(map[string]*TableField)
|
||||
for i, m := range result {
|
||||
fields[strings.ToLower(m["FIELD"].String())] = &TableField{
|
||||
Index: i,
|
||||
Name: strings.ToLower(m["FIELD"].String()),
|
||||
Type: strings.ToLower(m["TYPE"].String()),
|
||||
Index: i,
|
||||
Name: strings.ToLower(m["FIELD"].String()),
|
||||
Type: strings.ToLower(m["TYPE"].String()),
|
||||
Null: m["Null"].Bool(),
|
||||
Key: m["Key"].String(),
|
||||
Default: m["Default"].Val(),
|
||||
Extra: m["Extra"].String(),
|
||||
Comment: m["Comment"].String(),
|
||||
}
|
||||
}
|
||||
return fields
|
||||
|
||||
@ -66,10 +66,10 @@ func (d *DriverOracle) GetChars() (charLeft string, charRight string) {
|
||||
// HandleSqlBeforeCommit deals with the sql string before commits it to underlying sql driver.
|
||||
func (d *DriverOracle) HandleSqlBeforeCommit(link Link, sql string, args []interface{}) (string, []interface{}) {
|
||||
var index int
|
||||
// Convert place holder char '?' to string ":x".
|
||||
// Convert place holder char '?' to string ":vx".
|
||||
str, _ := gregex.ReplaceStringFunc("\\?", sql, func(s string) string {
|
||||
index++
|
||||
return fmt.Sprintf(":%d", index)
|
||||
return fmt.Sprintf(":v%d", index)
|
||||
})
|
||||
str, _ = gregex.ReplaceString("\"", "", str)
|
||||
return d.parseSql(str), args
|
||||
@ -88,10 +88,10 @@ func (d *DriverOracle) parseSql(sql string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
index := 0
|
||||
keyword := strings.TrimSpace(res[index][0])
|
||||
keyword = strings.ToUpper(keyword)
|
||||
|
||||
var (
|
||||
index = 0
|
||||
keyword = strings.ToUpper(strings.TrimSpace(res[index][0]))
|
||||
)
|
||||
index++
|
||||
switch keyword {
|
||||
case "SELECT":
|
||||
|
||||
@ -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 "$x".
|
||||
// Convert place holder char '?' to string "$vx".
|
||||
sql, _ = gregex.ReplaceStringFunc("\\?", sql, func(s string) string {
|
||||
index++
|
||||
return fmt.Sprintf("$%d", index)
|
||||
return fmt.Sprintf("$v%d", index)
|
||||
})
|
||||
sql, _ = gregex.ReplaceString(` LIMIT (\d+),\s*(\d+)`, ` LIMIT $2 OFFSET $1`, sql)
|
||||
return sql, args
|
||||
|
||||
@ -13,6 +13,7 @@ import (
|
||||
"github.com/gogf/gf/internal/empty"
|
||||
"github.com/gogf/gf/internal/utils"
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
"github.com/gogf/gf/util/gutil"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
@ -56,6 +57,12 @@ var (
|
||||
quoteWordReg = regexp.MustCompile(`^[a-zA-Z0-9\-_]+$`)
|
||||
)
|
||||
|
||||
// ListItemValues is alias for gutil.ListItemValues.
|
||||
// See gutil.ListItemValues.
|
||||
func ListItemValues(list interface{}, key interface{}, subKey ...interface{}) (values []interface{}) {
|
||||
return gutil.ListItemValues(list, key, subKey...)
|
||||
}
|
||||
|
||||
// GetInsertOperationByOption returns proper insert option with given parameter <option>.
|
||||
func GetInsertOperationByOption(option int) string {
|
||||
var operator string
|
||||
@ -247,7 +254,7 @@ func GetPrimaryKeyCondition(primary string, where ...interface{}) (newWhereCondi
|
||||
// formatSql formats the sql string and its arguments before executing.
|
||||
// The internal handleArguments function might be called twice during the SQL procedure,
|
||||
// but do not worry about it, it's safe and efficient.
|
||||
func formatSql(sql string, args []interface{}) (newQuery string, newArgs []interface{}) {
|
||||
func formatSql(sql string, args []interface{}) (newSql string, newArgs []interface{}) {
|
||||
sql = gstr.Trim(sql)
|
||||
sql = gstr.Replace(sql, "\n", " ")
|
||||
sql, _ = gregex.ReplaceString(`\s{2,}`, ` `, sql)
|
||||
@ -257,9 +264,11 @@ func formatSql(sql string, args []interface{}) (newQuery string, newArgs []inter
|
||||
// formatWhere formats where statement and its arguments.
|
||||
// TODO []interface{} type support for parameter <where> does not completed yet.
|
||||
func formatWhere(db DB, where interface{}, args []interface{}, omitEmpty bool) (newWhere string, newArgs []interface{}) {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
rv := reflect.ValueOf(where)
|
||||
kind := rv.Kind()
|
||||
var (
|
||||
buffer = bytes.NewBuffer(nil)
|
||||
rv = reflect.ValueOf(where)
|
||||
kind = rv.Kind()
|
||||
)
|
||||
if kind == reflect.Ptr {
|
||||
rv = rv.Elem()
|
||||
kind = rv.Kind()
|
||||
@ -270,7 +279,7 @@ func formatWhere(db DB, where interface{}, args []interface{}, omitEmpty bool) (
|
||||
|
||||
case reflect.Map:
|
||||
for key, value := range DataToMapDeep(where) {
|
||||
if omitEmpty && empty.IsEmpty(value) {
|
||||
if gregex.IsMatchString(regularFieldNameRegPattern, key) && omitEmpty && empty.IsEmpty(value) {
|
||||
continue
|
||||
}
|
||||
newArgs = formatWhereKeyValue(db, buffer, newArgs, key, value)
|
||||
@ -283,10 +292,11 @@ func formatWhere(db DB, where interface{}, args []interface{}, omitEmpty bool) (
|
||||
// which implement apiIterator interface and are index-friendly for where conditions.
|
||||
if iterator, ok := where.(apiIterator); ok {
|
||||
iterator.Iterator(func(key, value interface{}) bool {
|
||||
if omitEmpty && empty.IsEmpty(value) {
|
||||
ketStr := gconv.String(key)
|
||||
if gregex.IsMatchString(regularFieldNameRegPattern, ketStr) && omitEmpty && empty.IsEmpty(value) {
|
||||
return true
|
||||
}
|
||||
newArgs = formatWhereKeyValue(db, buffer, newArgs, gconv.String(key), value)
|
||||
newArgs = formatWhereKeyValue(db, buffer, newArgs, ketStr, value)
|
||||
return true
|
||||
})
|
||||
break
|
||||
@ -309,10 +319,10 @@ func formatWhere(db DB, where interface{}, args []interface{}, omitEmpty bool) (
|
||||
newWhere = buffer.String()
|
||||
if len(newArgs) > 0 {
|
||||
if gstr.Pos(newWhere, "?") == -1 {
|
||||
if lastOperatorReg.MatchString(newWhere) {
|
||||
if gregex.IsMatchString(lastOperatorRegPattern, newWhere) {
|
||||
// Eg: Where/And/Or("uid>=", 1)
|
||||
newWhere += "?"
|
||||
} else if gregex.IsMatchString(`^[\w\.\-]+$`, newWhere) {
|
||||
} else if gregex.IsMatchString(regularFieldNameRegPattern, newWhere) {
|
||||
newWhere = db.QuoteString(newWhere)
|
||||
if len(newArgs) > 0 {
|
||||
if utils.IsArray(newArgs[0]) {
|
||||
@ -334,7 +344,7 @@ func formatWhere(db DB, where interface{}, args []interface{}, omitEmpty bool) (
|
||||
}
|
||||
|
||||
// formatWhereInterfaces formats <where> as []interface{}.
|
||||
// TODO []interface{} type support for parameter <where> does not completed yet.
|
||||
// TODO supporting for parameter <where> with []interface{} type is not completed yet.
|
||||
func formatWhereInterfaces(db DB, where []interface{}, buffer *bytes.Buffer, newArgs []interface{}) []interface{} {
|
||||
var str string
|
||||
var array []interface{}
|
||||
@ -362,8 +372,10 @@ func formatWhereKeyValue(db DB, buffer *bytes.Buffer, newArgs []interface{}, key
|
||||
// If the value is type of slice, and there's only one '?' holder in
|
||||
// the key string, it automatically adds '?' holder chars according to its arguments count
|
||||
// and converts it to "IN" statement.
|
||||
rv := reflect.ValueOf(value)
|
||||
kind := rv.Kind()
|
||||
var (
|
||||
rv = reflect.ValueOf(value)
|
||||
kind = rv.Kind()
|
||||
)
|
||||
switch kind {
|
||||
case reflect.Slice, reflect.Array:
|
||||
count := gstr.Count(quotedKey, "?")
|
||||
@ -379,7 +391,7 @@ func formatWhereKeyValue(db DB, buffer *bytes.Buffer, newArgs []interface{}, key
|
||||
}
|
||||
default:
|
||||
if value == nil || empty.IsNil(rv) {
|
||||
if gregex.IsMatchString(`^[\w\.\-]+$`, key) {
|
||||
if gregex.IsMatchString(regularFieldNameRegPattern, key) {
|
||||
// The key is a single field name.
|
||||
buffer.WriteString(quotedKey + " IS NULL")
|
||||
} else {
|
||||
@ -392,11 +404,24 @@ func formatWhereKeyValue(db DB, buffer *bytes.Buffer, newArgs []interface{}, key
|
||||
if gstr.Pos(quotedKey, "?") == -1 {
|
||||
like := " like"
|
||||
if len(quotedKey) > len(like) && gstr.Equal(quotedKey[len(quotedKey)-len(like):], like) {
|
||||
// Eg: Where(g.Map{"name like": "john%"})
|
||||
buffer.WriteString(quotedKey + " ?")
|
||||
} else if lastOperatorReg.MatchString(quotedKey) {
|
||||
} else if gregex.IsMatchString(lastOperatorRegPattern, quotedKey) {
|
||||
// Eg: Where(g.Map{"age > ": 16})
|
||||
buffer.WriteString(quotedKey + " ?")
|
||||
} else {
|
||||
} else if gregex.IsMatchString(regularFieldNameRegPattern, key) {
|
||||
// The key is a regular field name.
|
||||
buffer.WriteString(quotedKey + "=?")
|
||||
} else {
|
||||
// The key is not a regular field name.
|
||||
// Eg: Where(g.Map{"age > 16": nil})
|
||||
// Issue: https://github.com/gogf/gf/issues/765
|
||||
if empty.IsEmpty(value) {
|
||||
buffer.WriteString(quotedKey)
|
||||
break
|
||||
} else {
|
||||
buffer.WriteString(quotedKey + "=?")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
buffer.WriteString(quotedKey)
|
||||
@ -432,9 +457,26 @@ func handleArguments(sql string, args []interface{}) (newSql string, newArgs []i
|
||||
newArgs = append(newArgs, arg)
|
||||
continue
|
||||
}
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
newArgs = append(newArgs, rv.Index(i).Interface())
|
||||
|
||||
if rv.Len() == 0 {
|
||||
// Empty slice argument, it converts the sql to a false sql.
|
||||
// Eg:
|
||||
// Query("select * from xxx where id in(?)", g.Slice{}) -> select * from xxx where 0=1
|
||||
// Where("id in(?)", g.Slice{}) -> WHERE 0=1
|
||||
if gstr.Contains(newSql, "?") {
|
||||
whereKeyWord := " WHERE "
|
||||
if p := gstr.PosI(newSql, whereKeyWord); p == -1 {
|
||||
return "0=1", []interface{}{}
|
||||
} else {
|
||||
return gstr.SubStr(newSql, 0, p+len(whereKeyWord)) + "0=1", []interface{}{}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
newArgs = append(newArgs, rv.Index(i).Interface())
|
||||
}
|
||||
}
|
||||
|
||||
// If the '?' holder count equals the length of the slice,
|
||||
// it does not implement the arguments splitting logic.
|
||||
// Eg: db.Query("SELECT ?+?", g.Slice{1, 2})
|
||||
@ -511,14 +553,16 @@ func formatError(err error, sql string, args ...interface{}) error {
|
||||
func FormatSqlWithArgs(sql string, args []interface{}) string {
|
||||
index := -1
|
||||
newQuery, _ := gregex.ReplaceStringFunc(
|
||||
`(\?|:\d+|\$\d+|@p\d+)`, sql, func(s string) string {
|
||||
`(\?|:v\d+|\$v\d+|@v\d+)`, sql, func(s string) string {
|
||||
index++
|
||||
if len(args) > index {
|
||||
if args[index] == nil {
|
||||
return "null"
|
||||
}
|
||||
rv := reflect.ValueOf(args[index])
|
||||
kind := rv.Kind()
|
||||
var (
|
||||
rv = reflect.ValueOf(args[index])
|
||||
kind = rv.Kind()
|
||||
)
|
||||
if kind == reflect.Ptr {
|
||||
if rv.IsNil() || !rv.IsValid() {
|
||||
return "null"
|
||||
|
||||
@ -40,9 +40,9 @@ func (m *Model) Delete(where ...interface{}) (result sql.Result, err error) {
|
||||
return m.db.DoUpdate(
|
||||
m.getLink(true),
|
||||
m.tables,
|
||||
fmt.Sprintf(`%s='%s'`, m.db.QuoteString(fieldNameDelete), gtime.Now().String()),
|
||||
fmt.Sprintf(`%s=?`, m.db.QuoteString(fieldNameDelete)),
|
||||
conditionWhere+conditionExtra,
|
||||
conditionArgs...,
|
||||
append([]interface{}{gtime.Now().String()}, conditionArgs...),
|
||||
)
|
||||
}
|
||||
return m.db.DoDelete(m.getLink(true), m.tables, conditionWhere+conditionExtra, conditionArgs...)
|
||||
|
||||
@ -8,6 +8,7 @@ package gdb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/container/gvar"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"reflect"
|
||||
)
|
||||
@ -26,12 +27,23 @@ func (m *Model) Select(where ...interface{}) (Result, error) {
|
||||
// The optional parameter <where> is the same as the parameter of Model.Where function,
|
||||
// see Model.Where.
|
||||
func (m *Model) All(where ...interface{}) (Result, error) {
|
||||
return m.doGetAll(false, where...)
|
||||
}
|
||||
|
||||
// doGetAll does "SELECT FROM ..." statement for the model.
|
||||
// It retrieves the records from table and returns the result as slice type.
|
||||
// It returns nil if there's no record retrieved with the given conditions from table.
|
||||
//
|
||||
// The parameter <limit1> specifies whether limits querying only one record if m.limit is not set.
|
||||
// The optional parameter <where> is the same as the parameter of Model.Where function,
|
||||
// see Model.Where.
|
||||
func (m *Model) doGetAll(limit1 bool, where ...interface{}) (Result, error) {
|
||||
if len(where) > 0 {
|
||||
return m.Where(where[0], where[1:]...).All()
|
||||
}
|
||||
var (
|
||||
softDeletingCondition = m.getConditionForSoftDeleting()
|
||||
conditionWhere, conditionExtra, conditionArgs = m.formatCondition(false)
|
||||
conditionWhere, conditionExtra, conditionArgs = m.formatCondition(limit1)
|
||||
)
|
||||
if !m.unscoped && softDeletingCondition != "" {
|
||||
if conditionWhere == "" {
|
||||
@ -43,7 +55,7 @@ func (m *Model) All(where ...interface{}) (Result, error) {
|
||||
}
|
||||
// DO NOT quote the m.fields where, in case of fields like:
|
||||
// DISTINCT t.user_id uid
|
||||
return m.doGetAll(
|
||||
return m.doGetAllBySql(
|
||||
fmt.Sprintf(
|
||||
"SELECT %s FROM %s%s",
|
||||
m.fields,
|
||||
@ -90,7 +102,7 @@ func (m *Model) One(where ...interface{}) (Record, error) {
|
||||
if len(where) > 0 {
|
||||
return m.Where(where[0], where[1:]...).One()
|
||||
}
|
||||
all, err := m.All()
|
||||
all, err := m.doGetAll(true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -118,12 +130,12 @@ func (m *Model) Value(fieldsAndWhere ...interface{}) (Value, error) {
|
||||
}
|
||||
one, err := m.One()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return gvar.New(nil), err
|
||||
}
|
||||
for _, v := range one {
|
||||
return v, nil
|
||||
}
|
||||
return nil, nil
|
||||
return gvar.New(nil), nil
|
||||
}
|
||||
|
||||
// Array queries and returns data values as slice from database.
|
||||
@ -157,7 +169,7 @@ func (m *Model) Array(fieldsAndWhere ...interface{}) ([]Value, error) {
|
||||
// see Model.Where.
|
||||
//
|
||||
// Note that it returns sql.ErrNoRows if there's no record retrieved with the given conditions
|
||||
// from table.
|
||||
// from table and <pointer> is not nil.
|
||||
//
|
||||
// Eg:
|
||||
// user := new(User)
|
||||
@ -181,14 +193,14 @@ func (m *Model) Struct(pointer interface{}, where ...interface{}) error {
|
||||
// see Model.Where.
|
||||
//
|
||||
// Note that it returns sql.ErrNoRows if there's no record retrieved with the given conditions
|
||||
// from table.
|
||||
// from table and <pointer> is not empty.
|
||||
//
|
||||
// Eg:
|
||||
// users := ([]User)(nil)
|
||||
// err := db.Table("user").Structs(&users)
|
||||
// err := db.Table("user").Structs(&users)
|
||||
//
|
||||
// users := ([]*User)(nil)
|
||||
// err := db.Table("user").Structs(&users)
|
||||
// err := db.Table("user").Structs(&users)
|
||||
func (m *Model) Structs(pointer interface{}, where ...interface{}) error {
|
||||
all, err := m.All(where...)
|
||||
if err != nil {
|
||||
@ -215,10 +227,10 @@ func (m *Model) Structs(pointer interface{}, where ...interface{}) error {
|
||||
// err := db.Table("user").Where("id", 1).Struct(&user)
|
||||
//
|
||||
// users := ([]User)(nil)
|
||||
// err := db.Table("user").Structs(&users)
|
||||
// err := db.Table("user").Structs(&users)
|
||||
//
|
||||
// users := ([]*User)(nil)
|
||||
// err := db.Table("user").Structs(&users)
|
||||
// err := db.Table("user").Structs(&users)
|
||||
func (m *Model) Scan(pointer interface{}, where ...interface{}) error {
|
||||
t := reflect.TypeOf(pointer)
|
||||
k := t.Kind()
|
||||
@ -226,13 +238,41 @@ func (m *Model) Scan(pointer interface{}, where ...interface{}) error {
|
||||
return fmt.Errorf("params should be type of pointer, but got: %v", k)
|
||||
}
|
||||
switch t.Elem().Kind() {
|
||||
case reflect.Array:
|
||||
case reflect.Slice:
|
||||
case reflect.Array, reflect.Slice:
|
||||
return m.Structs(pointer, where...)
|
||||
default:
|
||||
return m.Struct(pointer, where...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ScanList converts <r> to struct slice which contains other complex struct attributes.
|
||||
// Note that the parameter <listPointer> should be type of *[]struct/*[]*struct.
|
||||
// Usage example:
|
||||
//
|
||||
// type Entity struct {
|
||||
// User *EntityUser
|
||||
// UserDetail *EntityUserDetail
|
||||
// UserScores []*EntityUserScores
|
||||
// }
|
||||
// var users []*Entity
|
||||
// or
|
||||
// var users []Entity
|
||||
//
|
||||
// ScanList(&users, "User")
|
||||
// ScanList(&users, "UserDetail", "User", "uid:Uid")
|
||||
// ScanList(&users, "UserScores", "User", "uid:Uid")
|
||||
// The parameters "User"/"UserDetail"/"UserScores" in the example codes specify the target attribute struct
|
||||
// that current result will be bound to.
|
||||
// The "uid" in the example codes is the table field name of the result, and the "Uid" is the relational
|
||||
// struct attribute name. It automatically calculates the HasOne/HasMany relationship with given <relation>
|
||||
// parameter.
|
||||
// See the example or unit testing cases for clear understanding for this function.
|
||||
func (m *Model) ScanList(listPointer interface{}, attributeName string, relation ...string) (err error) {
|
||||
all, err := m.All()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return all.ScanList(listPointer, attributeName, relation...)
|
||||
}
|
||||
|
||||
// Count does "SELECT COUNT(x) FROM ..." statement for the model.
|
||||
@ -265,7 +305,7 @@ func (m *Model) Count(where ...interface{}) (int, error) {
|
||||
if len(m.groupBy) > 0 {
|
||||
s = fmt.Sprintf("SELECT COUNT(1) FROM (%s) count_alias", s)
|
||||
}
|
||||
list, err := m.doGetAll(s, conditionArgs...)
|
||||
list, err := m.doGetAllBySql(s, conditionArgs...)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@ -338,8 +378,8 @@ func (m *Model) FindScan(pointer interface{}, where ...interface{}) error {
|
||||
return m.Scan(pointer)
|
||||
}
|
||||
|
||||
// doGetAll does the select statement on the database.
|
||||
func (m *Model) doGetAll(sql string, args ...interface{}) (result Result, err error) {
|
||||
// doGetAllBySql does the select statement on the database.
|
||||
func (m *Model) doGetAllBySql(sql string, args ...interface{}) (result Result, err error) {
|
||||
cacheKey := ""
|
||||
// Retrieve from cache.
|
||||
if m.cacheEnabled && m.tx == nil {
|
||||
|
||||
@ -157,8 +157,8 @@ func (m *Model) getPrimaryKey() string {
|
||||
// formatCondition formats where arguments of the model and returns a new condition sql and its arguments.
|
||||
// Note that this function does not change any attribute value of the <m>.
|
||||
//
|
||||
// The parameter <limit> specifies whether limits querying only one record if m.limit is not set.
|
||||
func (m *Model) formatCondition(limit bool) (conditionWhere string, conditionExtra string, conditionArgs []interface{}) {
|
||||
// The parameter <limit1> specifies whether limits querying only one record if m.limit is not set.
|
||||
func (m *Model) formatCondition(limit1 bool) (conditionWhere string, conditionExtra string, conditionArgs []interface{}) {
|
||||
if len(m.whereHolder) > 0 {
|
||||
for _, v := range m.whereHolder {
|
||||
switch v.operator {
|
||||
@ -231,7 +231,7 @@ func (m *Model) formatCondition(limit bool) (conditionWhere string, conditionExt
|
||||
} else {
|
||||
conditionExtra += fmt.Sprintf(" LIMIT %d", m.limit)
|
||||
}
|
||||
} else if limit {
|
||||
} else if limit1 {
|
||||
conditionExtra += " LIMIT 1"
|
||||
}
|
||||
if m.offset >= 0 {
|
||||
|
||||
@ -9,12 +9,28 @@ package gdb
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/container/gvar"
|
||||
"math"
|
||||
"reflect"
|
||||
|
||||
"github.com/gogf/gf/encoding/gparser"
|
||||
)
|
||||
|
||||
// IsEmpty checks and returns whether <r> is empty.
|
||||
func (r Result) IsEmpty() bool {
|
||||
return r.Len() == 0
|
||||
}
|
||||
|
||||
// Len returns the length of result list.
|
||||
func (r Result) Len() int {
|
||||
return len(r)
|
||||
}
|
||||
|
||||
// Size is alias of function Len.
|
||||
func (r Result) Size() int {
|
||||
return r.Len()
|
||||
}
|
||||
|
||||
// Chunk splits an Result into multiple Results,
|
||||
// the size of each array is determined by <size>.
|
||||
// The last chunk may contain less than size elements.
|
||||
@ -79,6 +95,34 @@ func (r Result) Array(field ...string) []Value {
|
||||
return array
|
||||
}
|
||||
|
||||
// MapKeyValue converts <r> to a map[string]Value of which key is specified by <key>.
|
||||
// Note that the item value may be type of slice.
|
||||
func (r Result) MapKeyValue(key string) map[string]Value {
|
||||
var (
|
||||
s = ""
|
||||
m = make(map[string]Value)
|
||||
tempMap = make(map[string][]interface{})
|
||||
hasMultiValues bool
|
||||
)
|
||||
for _, item := range r {
|
||||
if k, ok := item[key]; ok {
|
||||
s = k.String()
|
||||
tempMap[s] = append(tempMap[s], item)
|
||||
if len(tempMap[s]) > 1 {
|
||||
hasMultiValues = true
|
||||
}
|
||||
}
|
||||
}
|
||||
for k, v := range tempMap {
|
||||
if hasMultiValues {
|
||||
m[k] = gvar.New(v)
|
||||
} else {
|
||||
m[k] = gvar.New(v[0])
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// MapKeyStr converts <r> to a map[string]Map of which key is specified by <key>.
|
||||
func (r Result) MapKeyStr(key string) map[string]Map {
|
||||
m := make(map[string]Map)
|
||||
@ -197,8 +241,3 @@ func (r Result) Structs(pointer interface{}) (err error) {
|
||||
reflect.ValueOf(pointer).Elem().Set(array)
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsEmpty checks and returns whether <r> is empty.
|
||||
func (r Result) IsEmpty() bool {
|
||||
return len(r) == 0
|
||||
}
|
||||
|
||||
245
database/gdb/gdb_type_result_scanlist.go
Normal file
245
database/gdb/gdb_type_result_scanlist.go
Normal file
@ -0,0 +1,245 @@
|
||||
// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gdb
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"github.com/gogf/gf/util/gutil"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// ScanList converts <r> to struct slice which contains other complex struct attributes.
|
||||
// Note that the parameter <listPointer> should be type of *[]struct/*[]*struct.
|
||||
// Usage example:
|
||||
//
|
||||
// type Entity struct {
|
||||
// User *EntityUser
|
||||
// UserDetail *EntityUserDetail
|
||||
// UserScores []*EntityUserScores
|
||||
// }
|
||||
// var users []*Entity
|
||||
// or
|
||||
// var users []Entity
|
||||
//
|
||||
// ScanList(&users, "User")
|
||||
// ScanList(&users, "UserDetail", "User", "uid:Uid")
|
||||
// ScanList(&users, "UserScores", "User", "uid:Uid")
|
||||
// The parameters "User"/"UserDetail"/"UserScores" in the example codes specify the target attribute struct
|
||||
// that current result will be bound to.
|
||||
// The "uid" in the example codes is the table field name of the result, and the "Uid" is the relational
|
||||
// struct attribute name. It automatically calculates the HasOne/HasMany relationship with given <relation>
|
||||
// parameter.
|
||||
// See the example or unit testing cases for clear understanding for this function.
|
||||
func (r Result) ScanList(listPointer interface{}, attributeName string, relation ...string) (err error) {
|
||||
// Necessary checks for parameters.
|
||||
if attributeName == "" {
|
||||
return errors.New(`attributeName should not be empty`)
|
||||
}
|
||||
if len(relation) > 0 {
|
||||
if len(relation) < 2 {
|
||||
return errors.New(`relation name and key should are both necessary`)
|
||||
}
|
||||
if relation[0] == "" || relation[1] == "" {
|
||||
return errors.New(`relation name and key should not be empty`)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
reflectValue = reflect.ValueOf(listPointer)
|
||||
reflectKind = reflectValue.Kind()
|
||||
)
|
||||
if reflectKind == reflect.Interface {
|
||||
reflectValue = reflectValue.Elem()
|
||||
reflectKind = reflectValue.Kind()
|
||||
}
|
||||
if reflectKind != reflect.Ptr {
|
||||
return fmt.Errorf("parameter should be type of *[]struct/*[]*struct, but got: %v", reflectKind)
|
||||
}
|
||||
reflectValue = reflectValue.Elem()
|
||||
reflectKind = reflectValue.Kind()
|
||||
if reflectKind != reflect.Slice && reflectKind != reflect.Array {
|
||||
return fmt.Errorf("parameter should be type of *[]struct/*[]*struct, but got: %v", reflectKind)
|
||||
}
|
||||
length := len(r)
|
||||
if length == 0 {
|
||||
// The pointed slice is not empty.
|
||||
if reflectValue.Len() > 0 {
|
||||
// It here checks if it has struct item, which is already initialized.
|
||||
// It then returns error to warn the developer its empty and no conversion.
|
||||
if v := reflectValue.Index(0); v.Kind() != reflect.Ptr {
|
||||
return sql.ErrNoRows
|
||||
}
|
||||
}
|
||||
// Do nothing for empty struct slice.
|
||||
return nil
|
||||
}
|
||||
var (
|
||||
arrayValue reflect.Value // Like: []*Entity
|
||||
arrayItemType reflect.Type // Like: *Entity
|
||||
reflectType = reflect.TypeOf(listPointer)
|
||||
)
|
||||
if reflectValue.Len() > 0 {
|
||||
arrayValue = reflectValue
|
||||
} else {
|
||||
arrayValue = reflect.MakeSlice(reflectType.Elem(), length, length)
|
||||
}
|
||||
|
||||
// Slice element item.
|
||||
arrayItemType = arrayValue.Index(0).Type()
|
||||
|
||||
// Relation variables.
|
||||
var (
|
||||
relationDataMap map[string]Value
|
||||
relationFieldName string
|
||||
relationAttrName string
|
||||
)
|
||||
if len(relation) > 0 {
|
||||
array := gstr.Split(relation[1], ":")
|
||||
if len(array) > 1 {
|
||||
// Defined table field to relation attribute name.
|
||||
// Like:
|
||||
// uid:Uid
|
||||
// uid:UserId
|
||||
relationFieldName = array[0]
|
||||
relationAttrName = array[1]
|
||||
} else {
|
||||
relationAttrName = relation[1]
|
||||
// Find the possible map key by given only struct attribute name.
|
||||
// Like:
|
||||
// Uid
|
||||
if k, _ := gutil.MapPossibleItemByKey(r[0].Map(), relation[1]); k != "" {
|
||||
relationFieldName = k
|
||||
}
|
||||
}
|
||||
if relationFieldName != "" {
|
||||
relationDataMap = r.MapKeyValue(relationFieldName)
|
||||
}
|
||||
if len(relationDataMap) == 0 {
|
||||
return fmt.Errorf(`cannot find the relation data map, maybe invalid relation key given: %s`, relation[1])
|
||||
}
|
||||
}
|
||||
// Bind to target attribute.
|
||||
var (
|
||||
ok bool
|
||||
attrValue reflect.Value
|
||||
attrKind reflect.Kind
|
||||
attrType reflect.Type
|
||||
attrField reflect.StructField
|
||||
)
|
||||
if arrayItemType.Kind() == reflect.Ptr {
|
||||
if attrField, ok = arrayItemType.Elem().FieldByName(attributeName); !ok {
|
||||
return fmt.Errorf(`invalid field name: %s`, attributeName)
|
||||
}
|
||||
} else {
|
||||
if attrField, ok = arrayItemType.FieldByName(attributeName); !ok {
|
||||
return fmt.Errorf(`invalid field name: %s`, attributeName)
|
||||
}
|
||||
}
|
||||
attrType = attrField.Type
|
||||
attrKind = attrType.Kind()
|
||||
|
||||
// Bind to relation conditions.
|
||||
var (
|
||||
relationValue reflect.Value
|
||||
relationField reflect.Value
|
||||
)
|
||||
for i := 0; i < arrayValue.Len(); i++ {
|
||||
arrayElemValue := arrayValue.Index(i)
|
||||
// The FieldByName should be called on non-pointer reflect.Value.
|
||||
if arrayElemValue.Kind() == reflect.Ptr {
|
||||
// Like: []*Entity
|
||||
arrayElemValue = arrayElemValue.Elem()
|
||||
if !arrayElemValue.IsValid() {
|
||||
// The element is nil, then create one and set it to the slice.
|
||||
// The "reflect.New(itemType.Elem())" creates a new element and returns the address of it.
|
||||
// For example:
|
||||
// reflect.New(itemType.Elem()) => *Entity
|
||||
// reflect.New(itemType.Elem()).Elem() => Entity
|
||||
arrayElemValue = reflect.New(arrayItemType.Elem()).Elem()
|
||||
arrayValue.Index(i).Set(arrayElemValue.Addr())
|
||||
}
|
||||
} else {
|
||||
// Like: []Entity
|
||||
}
|
||||
attrValue = arrayElemValue.FieldByName(attributeName)
|
||||
if len(relation) > 0 {
|
||||
relationValue = arrayElemValue.FieldByName(relation[0])
|
||||
if relationValue.Kind() == reflect.Ptr {
|
||||
relationValue = relationValue.Elem()
|
||||
}
|
||||
}
|
||||
if len(relationDataMap) > 0 && !relationValue.IsValid() {
|
||||
return fmt.Errorf(`invalid relation: %s, %s`, relation[0], relation[1])
|
||||
}
|
||||
switch attrKind {
|
||||
case reflect.Array, reflect.Slice:
|
||||
if len(relationDataMap) > 0 {
|
||||
relationField = relationValue.FieldByName(relationAttrName)
|
||||
if relationField.IsValid() {
|
||||
if err = gconv.Structs(
|
||||
relationDataMap[gconv.String(relationField.Interface())],
|
||||
attrValue.Addr(),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// May be the attribute does not exist yet.
|
||||
return fmt.Errorf(`invalid relation: %s, %s`, relation[0], relation[1])
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf(`relationKey should not be empty as field "%s" is slice`, attributeName)
|
||||
}
|
||||
|
||||
case reflect.Ptr:
|
||||
e := reflect.New(attrType.Elem()).Elem()
|
||||
if len(relationDataMap) > 0 {
|
||||
relationField = relationValue.FieldByName(relationAttrName)
|
||||
if relationField.IsValid() {
|
||||
if err = gconv.Struct(relationDataMap[gconv.String(relationField.Interface())], e); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// May be the attribute does not exist yet.
|
||||
return fmt.Errorf(`invalid relation: %s, %s`, relation[0], relation[1])
|
||||
}
|
||||
} else {
|
||||
if err = gconv.Struct(r[i], e); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
attrValue.Set(e.Addr())
|
||||
|
||||
case reflect.Struct:
|
||||
e := reflect.New(attrType).Elem()
|
||||
if len(relationDataMap) > 0 {
|
||||
relationField = relationValue.FieldByName(relationAttrName)
|
||||
if relationField.IsValid() {
|
||||
if err = gconv.Struct(relationDataMap[gconv.String(relationField.Interface())], e); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// May be the attribute does not exist yet.
|
||||
return fmt.Errorf(`invalid relation: %s, %s`, relation[0], relation[1])
|
||||
}
|
||||
} else {
|
||||
if err = gconv.Struct(r[i], e); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
attrValue.Set(e)
|
||||
|
||||
default:
|
||||
return fmt.Errorf(`unsupport attribute type: %s`, attrKind.String())
|
||||
}
|
||||
}
|
||||
reflect.ValueOf(listPointer).Elem().Set(arrayValue)
|
||||
return nil
|
||||
}
|
||||
@ -56,8 +56,8 @@ func Test_Custom_Driver(t *testing.T) {
|
||||
gdb.AddConfigNode("driver-test", gdb.ConfigNode{
|
||||
Host: "127.0.0.1",
|
||||
Port: "3306",
|
||||
User: "root",
|
||||
Pass: "12345678",
|
||||
User: USER,
|
||||
Pass: PASS,
|
||||
Name: "test",
|
||||
Type: customDriverName,
|
||||
Role: "master",
|
||||
|
||||
@ -23,6 +23,8 @@ const (
|
||||
SCHEMA1 = "test1"
|
||||
SCHEMA2 = "test2"
|
||||
PREFIX1 = "gf_"
|
||||
USER = "root"
|
||||
PASS = "12345678"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -40,8 +42,8 @@ func init() {
|
||||
configNode = gdb.ConfigNode{
|
||||
Host: "127.0.0.1",
|
||||
Port: "3306",
|
||||
User: "root",
|
||||
Pass: "12345678",
|
||||
User: USER,
|
||||
Pass: PASS,
|
||||
Name: parser.GetOpt("name", ""),
|
||||
Type: parser.GetOpt("type", "mysql"),
|
||||
Role: "master",
|
||||
|
||||
@ -16,6 +16,8 @@ import (
|
||||
|
||||
const (
|
||||
SCHEMA = "test_internal"
|
||||
USER = "root"
|
||||
PASS = "12345678"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -32,8 +34,8 @@ func init() {
|
||||
configNode = ConfigNode{
|
||||
Host: "127.0.0.1",
|
||||
Port: "3306",
|
||||
User: "root",
|
||||
Pass: "12345678",
|
||||
User: USER,
|
||||
Pass: PASS,
|
||||
Name: parser.GetOpt("name", ""),
|
||||
Type: parser.GetOpt("type", "mysql"),
|
||||
Role: "master",
|
||||
@ -73,19 +75,19 @@ func Test_Func_FormatSqlWithArgs(t *testing.T) {
|
||||
// mssql
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var s string
|
||||
s = FormatSqlWithArgs("select * from table where id>=@p1 and sex=@p2", []interface{}{100, 1})
|
||||
s = FormatSqlWithArgs("select * from table where id>=@v1 and sex=@v2", []interface{}{100, 1})
|
||||
t.Assert(s, "select * from table where id>=100 and sex=1")
|
||||
})
|
||||
// pgsql
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var s string
|
||||
s = FormatSqlWithArgs("select * from table where id>=$1 and sex=$2", []interface{}{100, 1})
|
||||
s = FormatSqlWithArgs("select * from table where id>=$v1 and sex=$v2", []interface{}{100, 1})
|
||||
t.Assert(s, "select * from table where id>=100 and sex=1")
|
||||
})
|
||||
// oracle
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var s string
|
||||
s = FormatSqlWithArgs("select * from table where id>=:1 and sex=:2", []interface{}{100, 1})
|
||||
s = FormatSqlWithArgs("select * from table where id>=:v1 and sex=:v2", []interface{}{100, 1})
|
||||
t.Assert(s, "select * from table where id>=100 and sex=1")
|
||||
})
|
||||
}
|
||||
|
||||
@ -1186,26 +1186,26 @@ func Test_Model_InnerJoin(t *testing.T) {
|
||||
|
||||
res, err := db.Table(table1).Where("id > ?", 5).Delete()
|
||||
if err != nil {
|
||||
gtest.Fatal(err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
n, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
gtest.Fatal(err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Assert(n, 5)
|
||||
|
||||
result, err := db.Table(table1+" u1").InnerJoin(table2+" u2", "u1.id = u2.id").OrderBy("u1.id").Select()
|
||||
if err != nil {
|
||||
gtest.Fatal(err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Assert(len(result), 5)
|
||||
|
||||
result, err = db.Table(table1+" u1").InnerJoin(table2+" u2", "u1.id = u2.id").Where("u1.id > ?", 1).OrderBy("u1.id").Select()
|
||||
if err != nil {
|
||||
gtest.Fatal(err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Assert(len(result), 4)
|
||||
@ -1222,26 +1222,26 @@ func Test_Model_LeftJoin(t *testing.T) {
|
||||
|
||||
res, err := db.Table(table2).Where("id > ?", 3).Delete()
|
||||
if err != nil {
|
||||
gtest.Fatal(err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
n, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
gtest.Fatal(err)
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
t.Assert(n, 7)
|
||||
}
|
||||
|
||||
result, err := db.Table(table1+" u1").LeftJoin(table2+" u2", "u1.id = u2.id").Select()
|
||||
if err != nil {
|
||||
gtest.Fatal(err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Assert(len(result), 10)
|
||||
|
||||
result, err = db.Table(table1+" u1").LeftJoin(table2+" u2", "u1.id = u2.id").Where("u1.id > ? ", 2).Select()
|
||||
if err != nil {
|
||||
gtest.Fatal(err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Assert(len(result), 8)
|
||||
@ -1258,26 +1258,36 @@ func Test_Model_RightJoin(t *testing.T) {
|
||||
|
||||
res, err := db.Table(table1).Where("id > ?", 3).Delete()
|
||||
if err != nil {
|
||||
gtest.Fatal(err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
n, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
gtest.Fatal(err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Assert(n, 7)
|
||||
|
||||
result, err := db.Table(table1+" u1").RightJoin(table2+" u2", "u1.id = u2.id").Select()
|
||||
if err != nil {
|
||||
gtest.Fatal(err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Assert(len(result), 10)
|
||||
|
||||
result, err = db.Table(table1+" u1").RightJoin(table2+" u2", "u1.id = u2.id").Where("u1.id > 2").Select()
|
||||
if err != nil {
|
||||
gtest.Fatal(err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Assert(len(result), 1)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Empty_Slice_Argument(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, err := db.GetAll(fmt.Sprintf(`select * from %s where id in(?)`, table), g.Slice{})
|
||||
t.Assert(err, nil)
|
||||
t.Assert(len(result), 0)
|
||||
})
|
||||
}
|
||||
|
||||
@ -824,6 +824,34 @@ func Test_Model_Structs(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_StructsWithJsonTag(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type User struct {
|
||||
Uid int `json:"id"`
|
||||
Passport string
|
||||
Password string
|
||||
Name string `json:"nick_name"`
|
||||
Time gtime.Time `json:"create_time"`
|
||||
}
|
||||
var users []User
|
||||
err := db.Table(table).Order("id asc").Structs(&users)
|
||||
if err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
t.Assert(len(users), SIZE)
|
||||
t.Assert(users[0].Uid, 1)
|
||||
t.Assert(users[1].Uid, 2)
|
||||
t.Assert(users[2].Uid, 3)
|
||||
t.Assert(users[0].Name, "name_1")
|
||||
t.Assert(users[1].Name, "name_2")
|
||||
t.Assert(users[2].Name, "name_3")
|
||||
t.Assert(users[0].Time.String(), "2018-10-24 10:00:00")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_Scan(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
@ -1258,6 +1286,29 @@ func Test_Model_Where_ISNULL_2(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_Where_OmitEmpty(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
conditions := g.Map{
|
||||
"id < 4": "",
|
||||
}
|
||||
result, err := db.Table(table).WherePri(conditions).Order("id asc").All()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(len(result), 3)
|
||||
t.Assert(result[0]["id"].Int(), 1)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
conditions := g.Map{
|
||||
"id < 4": "",
|
||||
}
|
||||
result, err := db.Table(table).WherePri(conditions).OmitEmpty().Order("id asc").All()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(len(result), 3)
|
||||
t.Assert(result[0]["id"].Int(), 1)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_Where_GTime(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
@ -2371,3 +2422,18 @@ func Test_Model_NullField(t *testing.T) {
|
||||
t.Assert(user.Passport, data["passport"])
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_Empty_Slice_Argument(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, err := db.Model(table).Where(`id`, g.Slice{}).All()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(len(result), 0)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, err := db.Model(table).Where(`id in(?)`, g.Slice{}).All()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(len(result), 0)
|
||||
})
|
||||
}
|
||||
|
||||
329
database/gdb/gdb_z_mysql_relation_test.go
Normal file
329
database/gdb/gdb_z_mysql_relation_test.go
Normal file
@ -0,0 +1,329 @@
|
||||
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gdb_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/database/gdb"
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
)
|
||||
|
||||
func Test_Table_Relation(t *testing.T) {
|
||||
var (
|
||||
tableUser = "user_" + gtime.TimestampMicroStr()
|
||||
tableUserDetail = "user_detail_" + gtime.TimestampMicroStr()
|
||||
tableUserScores = "user_scores_" + gtime.TimestampMicroStr()
|
||||
)
|
||||
if _, err := db.Exec(fmt.Sprintf(`
|
||||
CREATE TABLE %s (
|
||||
uid int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
name varchar(45) NOT NULL,
|
||||
PRIMARY KEY (uid)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
`, tableUser)); err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
defer dropTable(tableUser)
|
||||
|
||||
if _, err := db.Exec(fmt.Sprintf(`
|
||||
CREATE TABLE %s (
|
||||
uid int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
address varchar(45) NOT NULL,
|
||||
PRIMARY KEY (uid)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
`, tableUserDetail)); err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
defer dropTable(tableUserDetail)
|
||||
|
||||
if _, err := db.Exec(fmt.Sprintf(`
|
||||
CREATE TABLE %s (
|
||||
id int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
uid int(10) unsigned NOT NULL,
|
||||
score int(10) unsigned NOT NULL,
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
`, tableUserScores)); err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
defer dropTable(tableUserScores)
|
||||
|
||||
type EntityUser struct {
|
||||
Uid int `json:"uid"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
type EntityUserDetail struct {
|
||||
Uid int `json:"uid"`
|
||||
Address string `json:"address"`
|
||||
}
|
||||
type EntityUserScores struct {
|
||||
Id int `json:"id"`
|
||||
Uid int `json:"uid"`
|
||||
Score int `json:"score"`
|
||||
}
|
||||
type Entity struct {
|
||||
User *EntityUser
|
||||
UserDetail *EntityUserDetail
|
||||
UserScores []*EntityUserScores
|
||||
}
|
||||
|
||||
// Initialize the data.
|
||||
var err error
|
||||
for i := 1; i <= 5; i++ {
|
||||
// User.
|
||||
_, err = db.Insert(tableUser, g.Map{
|
||||
"uid": i,
|
||||
"name": fmt.Sprintf(`name_%d`, i),
|
||||
})
|
||||
gtest.Assert(err, nil)
|
||||
// Detail.
|
||||
_, err = db.Insert(tableUserDetail, g.Map{
|
||||
"uid": i,
|
||||
"address": fmt.Sprintf(`address_%d`, i),
|
||||
})
|
||||
gtest.Assert(err, nil)
|
||||
// Scores.
|
||||
for j := 1; j <= 5; j++ {
|
||||
_, err = db.Insert(tableUserScores, g.Map{
|
||||
"uid": i,
|
||||
"score": j,
|
||||
})
|
||||
gtest.Assert(err, nil)
|
||||
}
|
||||
}
|
||||
// MapKeyValue.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
all, err := db.Table(tableUser).Where("uid", g.Slice{3, 4}).Order("uid asc").All()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(all.Len(), 2)
|
||||
t.Assert(len(all.MapKeyValue("uid")), 2)
|
||||
t.Assert(all.MapKeyValue("uid")["3"].Map()["uid"], 3)
|
||||
t.Assert(all.MapKeyValue("uid")["4"].Map()["uid"], 4)
|
||||
all, err = db.Table(tableUserScores).Where("uid", g.Slice{3, 4}).Order("id asc").All()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(all.Len(), 10)
|
||||
t.Assert(len(all.MapKeyValue("uid")), 2)
|
||||
t.Assert(len(all.MapKeyValue("uid")["3"].Slice()), 5)
|
||||
t.Assert(len(all.MapKeyValue("uid")["4"].Slice()), 5)
|
||||
t.Assert(gconv.Map(all.MapKeyValue("uid")["3"].Slice()[0])["uid"], 3)
|
||||
t.Assert(gconv.Map(all.MapKeyValue("uid")["3"].Slice()[0])["score"], 1)
|
||||
t.Assert(gconv.Map(all.MapKeyValue("uid")["3"].Slice()[4])["uid"], 3)
|
||||
t.Assert(gconv.Map(all.MapKeyValue("uid")["3"].Slice()[4])["score"], 5)
|
||||
})
|
||||
// Result ScanList with struct elements and pointer attributes.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var users []Entity
|
||||
// User
|
||||
all, err := db.Table(tableUser).Where("uid", g.Slice{3, 4}).Order("uid asc").All()
|
||||
t.Assert(err, nil)
|
||||
err = all.ScanList(&users, "User")
|
||||
t.Assert(err, nil)
|
||||
t.Assert(len(users), 2)
|
||||
t.Assert(users[0].User, &EntityUser{3, "name_3"})
|
||||
t.Assert(users[1].User, &EntityUser{4, "name_4"})
|
||||
// Detail
|
||||
all, err = db.Table(tableUserDetail).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("uid asc").All()
|
||||
gtest.Assert(err, nil)
|
||||
err = all.ScanList(&users, "UserDetail", "User", "uid:Uid")
|
||||
t.Assert(err, nil)
|
||||
t.Assert(users[0].UserDetail, &EntityUserDetail{3, "address_3"})
|
||||
t.Assert(users[1].UserDetail, &EntityUserDetail{4, "address_4"})
|
||||
// Scores
|
||||
all, err = db.Table(tableUserScores).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("id asc").All()
|
||||
gtest.Assert(err, nil)
|
||||
err = all.ScanList(&users, "UserScores", "User", "uid:Uid")
|
||||
t.Assert(err, nil)
|
||||
t.Assert(len(users[0].UserScores), 5)
|
||||
t.Assert(len(users[1].UserScores), 5)
|
||||
t.Assert(users[0].UserScores[0].Uid, 3)
|
||||
t.Assert(users[0].UserScores[0].Score, 1)
|
||||
t.Assert(users[0].UserScores[4].Score, 5)
|
||||
t.Assert(users[1].UserScores[0].Uid, 4)
|
||||
t.Assert(users[1].UserScores[0].Score, 1)
|
||||
t.Assert(users[1].UserScores[4].Score, 5)
|
||||
})
|
||||
|
||||
// Result ScanList with pointer elements and pointer attributes.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var users []*Entity
|
||||
// User
|
||||
all, err := db.Table(tableUser).Where("uid", g.Slice{3, 4}).Order("uid asc").All()
|
||||
t.Assert(err, nil)
|
||||
err = all.ScanList(&users, "User")
|
||||
t.Assert(err, nil)
|
||||
t.Assert(len(users), 2)
|
||||
t.Assert(users[0].User, &EntityUser{3, "name_3"})
|
||||
t.Assert(users[1].User, &EntityUser{4, "name_4"})
|
||||
// Detail
|
||||
all, err = db.Table(tableUserDetail).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("uid asc").All()
|
||||
gtest.Assert(err, nil)
|
||||
err = all.ScanList(&users, "UserDetail", "User", "uid:Uid")
|
||||
t.Assert(err, nil)
|
||||
t.Assert(users[0].UserDetail, &EntityUserDetail{3, "address_3"})
|
||||
t.Assert(users[1].UserDetail, &EntityUserDetail{4, "address_4"})
|
||||
// Scores
|
||||
all, err = db.Table(tableUserScores).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("id asc").All()
|
||||
gtest.Assert(err, nil)
|
||||
err = all.ScanList(&users, "UserScores", "User", "uid:Uid")
|
||||
t.Assert(err, nil)
|
||||
t.Assert(len(users[0].UserScores), 5)
|
||||
t.Assert(len(users[1].UserScores), 5)
|
||||
t.Assert(users[0].UserScores[0].Uid, 3)
|
||||
t.Assert(users[0].UserScores[0].Score, 1)
|
||||
t.Assert(users[0].UserScores[4].Score, 5)
|
||||
t.Assert(users[1].UserScores[0].Uid, 4)
|
||||
t.Assert(users[1].UserScores[0].Score, 1)
|
||||
t.Assert(users[1].UserScores[4].Score, 5)
|
||||
})
|
||||
|
||||
// Result ScanList with struct elements and struct attributes.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type EntityUser struct {
|
||||
Uid int `json:"uid"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
type EntityUserDetail struct {
|
||||
Uid int `json:"uid"`
|
||||
Address string `json:"address"`
|
||||
}
|
||||
type EntityUserScores struct {
|
||||
Id int `json:"id"`
|
||||
Uid int `json:"uid"`
|
||||
Score int `json:"score"`
|
||||
}
|
||||
type Entity struct {
|
||||
User EntityUser
|
||||
UserDetail EntityUserDetail
|
||||
UserScores []EntityUserScores
|
||||
}
|
||||
var users []Entity
|
||||
// User
|
||||
all, err := db.Table(tableUser).Where("uid", g.Slice{3, 4}).Order("uid asc").All()
|
||||
t.Assert(err, nil)
|
||||
err = all.ScanList(&users, "User")
|
||||
t.Assert(err, nil)
|
||||
t.Assert(len(users), 2)
|
||||
t.Assert(users[0].User, &EntityUser{3, "name_3"})
|
||||
t.Assert(users[1].User, &EntityUser{4, "name_4"})
|
||||
// Detail
|
||||
all, err = db.Table(tableUserDetail).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("uid asc").All()
|
||||
gtest.Assert(err, nil)
|
||||
err = all.ScanList(&users, "UserDetail", "User", "uid:Uid")
|
||||
t.Assert(err, nil)
|
||||
t.Assert(users[0].UserDetail, &EntityUserDetail{3, "address_3"})
|
||||
t.Assert(users[1].UserDetail, &EntityUserDetail{4, "address_4"})
|
||||
// Scores
|
||||
all, err = db.Table(tableUserScores).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("id asc").All()
|
||||
gtest.Assert(err, nil)
|
||||
err = all.ScanList(&users, "UserScores", "User", "uid:Uid")
|
||||
t.Assert(err, nil)
|
||||
t.Assert(len(users[0].UserScores), 5)
|
||||
t.Assert(len(users[1].UserScores), 5)
|
||||
t.Assert(users[0].UserScores[0].Uid, 3)
|
||||
t.Assert(users[0].UserScores[0].Score, 1)
|
||||
t.Assert(users[0].UserScores[4].Score, 5)
|
||||
t.Assert(users[1].UserScores[0].Uid, 4)
|
||||
t.Assert(users[1].UserScores[0].Score, 1)
|
||||
t.Assert(users[1].UserScores[4].Score, 5)
|
||||
})
|
||||
|
||||
// Result ScanList with pointer elements and struct attributes.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type EntityUser struct {
|
||||
Uid int `json:"uid"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
type EntityUserDetail struct {
|
||||
Uid int `json:"uid"`
|
||||
Address string `json:"address"`
|
||||
}
|
||||
type EntityUserScores struct {
|
||||
Id int `json:"id"`
|
||||
Uid int `json:"uid"`
|
||||
Score int `json:"score"`
|
||||
}
|
||||
type Entity struct {
|
||||
User EntityUser
|
||||
UserDetail EntityUserDetail
|
||||
UserScores []EntityUserScores
|
||||
}
|
||||
var users []*Entity
|
||||
|
||||
// User
|
||||
all, err := db.Table(tableUser).Where("uid", g.Slice{3, 4}).Order("uid asc").All()
|
||||
t.Assert(err, nil)
|
||||
err = all.ScanList(&users, "User")
|
||||
t.Assert(err, nil)
|
||||
t.Assert(len(users), 2)
|
||||
t.Assert(users[0].User, &EntityUser{3, "name_3"})
|
||||
t.Assert(users[1].User, &EntityUser{4, "name_4"})
|
||||
// Detail
|
||||
all, err = db.Table(tableUserDetail).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("uid asc").All()
|
||||
gtest.Assert(err, nil)
|
||||
err = all.ScanList(&users, "UserDetail", "User", "uid:Uid")
|
||||
t.Assert(err, nil)
|
||||
t.Assert(users[0].UserDetail, &EntityUserDetail{3, "address_3"})
|
||||
t.Assert(users[1].UserDetail, &EntityUserDetail{4, "address_4"})
|
||||
// Scores
|
||||
all, err = db.Table(tableUserScores).Where("uid", gdb.ListItemValues(users, "User", "Uid")).Order("id asc").All()
|
||||
gtest.Assert(err, nil)
|
||||
err = all.ScanList(&users, "UserScores", "User", "uid:Uid")
|
||||
t.Assert(err, nil)
|
||||
t.Assert(len(users[0].UserScores), 5)
|
||||
t.Assert(len(users[1].UserScores), 5)
|
||||
t.Assert(users[0].UserScores[0].Uid, 3)
|
||||
t.Assert(users[0].UserScores[0].Score, 1)
|
||||
t.Assert(users[0].UserScores[4].Score, 5)
|
||||
t.Assert(users[1].UserScores[0].Uid, 4)
|
||||
t.Assert(users[1].UserScores[0].Score, 1)
|
||||
t.Assert(users[1].UserScores[4].Score, 5)
|
||||
})
|
||||
|
||||
// Model ScanList with pointer elements and pointer attributes.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var users []*Entity
|
||||
// User
|
||||
err := db.Table(tableUser).
|
||||
Where("uid", g.Slice{3, 4}).
|
||||
Order("uid asc").
|
||||
ScanList(&users, "User")
|
||||
t.Assert(err, nil)
|
||||
// Detail
|
||||
err = db.Table(tableUserDetail).
|
||||
Where("uid", gdb.ListItemValues(users, "User", "Uid")).
|
||||
Order("uid asc").
|
||||
ScanList(&users, "UserDetail", "User", "uid:Uid")
|
||||
gtest.Assert(err, nil)
|
||||
// Scores
|
||||
err = db.Table(tableUserScores).
|
||||
Where("uid", gdb.ListItemValues(users, "User", "Uid")).
|
||||
Order("id asc").
|
||||
ScanList(&users, "UserScores", "User", "uid:Uid")
|
||||
t.Assert(err, nil)
|
||||
|
||||
t.Assert(len(users), 2)
|
||||
t.Assert(users[0].User, &EntityUser{3, "name_3"})
|
||||
t.Assert(users[1].User, &EntityUser{4, "name_4"})
|
||||
|
||||
t.Assert(users[0].UserDetail, &EntityUserDetail{3, "address_3"})
|
||||
t.Assert(users[1].UserDetail, &EntityUserDetail{4, "address_4"})
|
||||
|
||||
t.Assert(len(users[0].UserScores), 5)
|
||||
t.Assert(len(users[1].UserScores), 5)
|
||||
t.Assert(users[0].UserScores[0].Uid, 3)
|
||||
t.Assert(users[0].UserScores[0].Score, 1)
|
||||
t.Assert(users[0].UserScores[4].Score, 5)
|
||||
t.Assert(users[1].UserScores[0].Uid, 4)
|
||||
t.Assert(users[1].UserScores[0].Score, 1)
|
||||
t.Assert(users[1].UserScores[4].Score, 5)
|
||||
})
|
||||
}
|
||||
@ -17,7 +17,7 @@ import (
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
)
|
||||
|
||||
func Test_CreateUpdateDeleteTime(t *testing.T) {
|
||||
func Test_SoftCreateUpdateDeleteTime(t *testing.T) {
|
||||
table := "time_test_table"
|
||||
if _, err := db.Exec(fmt.Sprintf(`
|
||||
CREATE TABLE %s (
|
||||
@ -52,6 +52,7 @@ CREATE TABLE %s (
|
||||
t.AssertGE(oneInsert["create_at"].GTime().Timestamp(), gtime.Timestamp()-2)
|
||||
t.AssertGE(oneInsert["update_at"].GTime().Timestamp(), gtime.Timestamp())
|
||||
|
||||
// For time asserting purpose.
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
// Save
|
||||
@ -73,11 +74,11 @@ CREATE TABLE %s (
|
||||
t.AssertNE(oneSave["update_at"].GTime().Timestamp(), oneInsert["update_at"].GTime().Timestamp())
|
||||
t.AssertGE(oneSave["update_at"].GTime().Timestamp(), gtime.Now().Timestamp()-2)
|
||||
|
||||
// For time asserting purpose.
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
// Update
|
||||
dataUpdate := g.Map{
|
||||
"id": 1,
|
||||
"name": "name_1000",
|
||||
}
|
||||
r, err = db.Table(table).Data(dataUpdate).WherePri(1).Update()
|
||||
@ -111,6 +112,7 @@ CREATE TABLE %s (
|
||||
t.AssertGE(oneReplace["create_at"].GTime().Timestamp(), oneInsert["create_at"].GTime().Timestamp())
|
||||
t.AssertGE(oneReplace["update_at"].GTime().Timestamp(), oneInsert["update_at"].GTime().Timestamp())
|
||||
|
||||
// For time asserting purpose.
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
// Delete
|
||||
@ -148,6 +150,114 @@ CREATE TABLE %s (
|
||||
})
|
||||
}
|
||||
|
||||
func Test_SoftUpdateTime(t *testing.T) {
|
||||
table := "time_test_table"
|
||||
if _, err := db.Exec(fmt.Sprintf(`
|
||||
CREATE TABLE %s (
|
||||
id int(11) NOT NULL,
|
||||
num int(11) DEFAULT NULL,
|
||||
create_at datetime DEFAULT NULL,
|
||||
update_at datetime DEFAULT NULL,
|
||||
delete_at datetime DEFAULT NULL,
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
`, table)); err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Insert
|
||||
dataInsert := g.Map{
|
||||
"id": 1,
|
||||
"num": 10,
|
||||
}
|
||||
r, err := db.Table(table).Data(dataInsert).Insert()
|
||||
t.Assert(err, nil)
|
||||
n, _ := r.RowsAffected()
|
||||
t.Assert(n, 1)
|
||||
|
||||
oneInsert, err := db.Table(table).FindOne(1)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(oneInsert["id"].Int(), 1)
|
||||
t.Assert(oneInsert["num"].Int(), 10)
|
||||
|
||||
// Update.
|
||||
r, err = db.Table(table).Data("num=num+1").Where("id=?", 1).Update()
|
||||
t.Assert(err, nil)
|
||||
n, _ = r.RowsAffected()
|
||||
t.Assert(n, 1)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_SoftDelete(t *testing.T) {
|
||||
table := "time_test_table"
|
||||
if _, err := db.Exec(fmt.Sprintf(`
|
||||
CREATE TABLE %s (
|
||||
id int(11) NOT NULL,
|
||||
name varchar(45) DEFAULT NULL,
|
||||
create_at datetime DEFAULT NULL,
|
||||
update_at datetime DEFAULT NULL,
|
||||
delete_at datetime DEFAULT NULL,
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
`, table)); err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
defer dropTable(table)
|
||||
// db.SetDebug(true)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
for i := 1; i <= 10; i++ {
|
||||
data := g.Map{
|
||||
"id": i,
|
||||
"name": fmt.Sprintf("name_%d", i),
|
||||
}
|
||||
r, err := db.Table(table).Data(data).Insert()
|
||||
t.Assert(err, nil)
|
||||
n, _ := r.RowsAffected()
|
||||
t.Assert(n, 1)
|
||||
}
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
one, err := db.Table(table).FindOne(1)
|
||||
t.Assert(err, nil)
|
||||
t.AssertNE(one["create_at"].String(), "")
|
||||
t.AssertNE(one["update_at"].String(), "")
|
||||
t.Assert(one["delete_at"].String(), "")
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
one, err := db.Table(table).FindOne(10)
|
||||
t.Assert(err, nil)
|
||||
t.AssertNE(one["create_at"].String(), "")
|
||||
t.AssertNE(one["update_at"].String(), "")
|
||||
t.Assert(one["delete_at"].String(), "")
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
ids := g.SliceInt{1, 3, 5}
|
||||
r, err := db.Table(table).Where("id", ids).Delete()
|
||||
t.Assert(err, nil)
|
||||
n, _ := r.RowsAffected()
|
||||
t.Assert(n, 3)
|
||||
|
||||
count, err := db.Table(table).FindCount(ids)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(count, 0)
|
||||
|
||||
all, err := db.Table(table).Unscoped().FindAll(ids)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(len(all), 3)
|
||||
t.AssertNE(all[0]["create_at"].String(), "")
|
||||
t.AssertNE(all[0]["update_at"].String(), "")
|
||||
t.AssertNE(all[0]["delete_at"].String(), "")
|
||||
t.AssertNE(all[1]["create_at"].String(), "")
|
||||
t.AssertNE(all[1]["update_at"].String(), "")
|
||||
t.AssertNE(all[1]["delete_at"].String(), "")
|
||||
t.AssertNE(all[2]["create_at"].String(), "")
|
||||
t.AssertNE(all[2]["update_at"].String(), "")
|
||||
t.AssertNE(all[2]["delete_at"].String(), "")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_SoftDelete_Join(t *testing.T) {
|
||||
table1 := "time_test_table1"
|
||||
if _, err := db.Exec(fmt.Sprintf(`
|
||||
@ -7,11 +7,11 @@
|
||||
package gdb_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_Model_Inherit_Insert(t *testing.T) {
|
||||
@ -322,3 +322,40 @@ func Test_Structs_Empty(t *testing.T) {
|
||||
t.Assert(all.Structs(&users), nil)
|
||||
})
|
||||
}
|
||||
|
||||
type MyTime struct {
|
||||
gtime.Time
|
||||
}
|
||||
|
||||
type MyTimeSt struct {
|
||||
CreateTime MyTime
|
||||
}
|
||||
|
||||
func (st *MyTimeSt) UnmarshalValue(v interface{}) error {
|
||||
m := gconv.Map(v)
|
||||
t, err := gtime.StrToTime(gconv.String(m["create_time"]))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
st.CreateTime = MyTime{*t}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Test_Model_Scan_CustomType(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
st := new(MyTimeSt)
|
||||
err := db.Table(table).Fields("create_time").Scan(st)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(st.CreateTime.String(), "2018-10-24 10:00:00")
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var stSlice []*MyTimeSt
|
||||
err := db.Table(table).Fields("create_time").Scan(&stSlice)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(len(stSlice), SIZE)
|
||||
t.Assert(stSlice[0].CreateTime.String(), "2018-10-24 10:00:00")
|
||||
t.Assert(stSlice[9].CreateTime.String(), "2018-10-24 10:00:00")
|
||||
})
|
||||
}
|
||||
|
||||
@ -210,7 +210,7 @@ func (r *Redis) Do(command string, args ...interface{}) (interface{}, error) {
|
||||
return conn.Do(command, args...)
|
||||
}
|
||||
|
||||
// DoVar returns value from Do as *gvar.Var.
|
||||
// DoVar returns value from Do as gvar.Var.
|
||||
func (r *Redis) DoVar(command string, args ...interface{}) (*gvar.Var, error) {
|
||||
v, err := r.Do(command, args...)
|
||||
if result, ok := v.([]byte); ok {
|
||||
|
||||
@ -7,8 +7,8 @@
|
||||
package gredis
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gogf/gf/container/gvar"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user