Compare commits

..

98 Commits

Author SHA1 Message Date
edb745ed92 version updates 2020-05-07 11:31:34 +08:00
61f4e0da6f improve package gcache 2020-05-06 21:09:02 +08:00
767afa3106 improve package gcache 2020-05-06 19:06:49 +08:00
16779359ee remove package gchan 2020-05-06 18:43:19 +08:00
770653fafa enable the route dump for unit testing of logging feature for ghttp.Server 2020-05-05 00:09:39 +08:00
0c021b6da7 add unit testing case of status handler feature for ghttp.Server 2020-05-05 00:01:09 +08:00
ec92b08f25 add function Maps for package gvar; add function GetMaps for package gjson; improve MapToMap* functions for package gconv 2020-05-04 23:42:51 +08:00
13e2353729 fix lock issue in function search for package garray 2020-05-03 23:08:18 +08:00
90d19a84ce improve package garray/gset, adding function ContainsI 2020-05-03 22:52:04 +08:00
b4c0b95d8a add support for old version of gres 2020-05-03 22:16:12 +08:00
b7e8255066 add support for old version of gre 2020-05-03 22:08:08 +08:00
5f8e6ad9ed comment update for package ghttp 2020-05-01 03:31:04 +08:00
c8d253eb56 change binary content from hex string to base64 string for package gres 2020-05-01 02:35:24 +08:00
4814624cff change binary content from hex string to base64 string for package gres 2020-05-01 02:16:42 +08:00
cc67f3d388 improve package gcompress 2020-05-01 01:47:02 +08:00
f7c2a51c9f fix issue in zip feature for package gcompress; improve package gres 2020-05-01 00:18:45 +08:00
3db83e1159 improve package gtimer 2020-04-30 22:22:35 +08:00
3bb002796c donator update 2020-04-30 20:46:27 +08:00
45170bc53e add ClientMaxBodySize configuration for ghttp.Server 2020-04-30 20:37:09 +08:00
b79ff84c6f add struct slice conversion for request parameters for ghttp.Request; improve package gconv 2020-04-30 16:53:47 +08:00
938c46fec9 readme update 2020-04-29 19:33:14 +08:00
8a13d94526 improve Record.Struct for package gdb 2020-04-29 09:12:13 +08:00
1e844d505a comment update for package ghttp/gconv; readme update 2020-04-29 00:14:29 +08:00
a123a2c086 improve struct conversion of empty result/record for package gdb 2020-04-28 21:03:25 +08:00
1eeeeb853e improve unit testing case of error logging for ghttp.Server 2020-04-28 15:21:17 +08:00
6e7224e306 improve error handling for gconv.Struct/ghttp.Server; add NewSkip/NewfSkip function for package gerror 2020-04-28 15:04:07 +08:00
9e064e2651 donator updates 2020-04-27 23:34:22 +08:00
8d9dd17eac add Walk function for package gset; improve fields handling feature for package gdb 2020-04-27 21:18:42 +08:00
cf1d3d3d2b improve package gdebug; add more unit testing case for package gdb 2020-04-27 17:56:04 +08:00
9480ffcdc0 improve function SetPath/AddPath for package gview 2020-04-27 17:07:00 +08:00
5db10add4a fix issue in unnacessary quoting of fields in select statement of gdb.Model 2020-04-27 16:30:53 +08:00
fa66bf5d9d improve cache feature of package gdb.Model 2020-04-26 21:31:55 +08:00
f69da3ace1 add function Transaction for package gdb 2020-04-26 17:47:19 +08:00
e01bfa05c3 listening ports change for unit testing cases of ghttp.Server 2020-04-26 17:13:48 +08:00
231238c157 improve parameter handing for ghttp.Request 2020-04-26 17:08:07 +08:00
7edec099ab improve raw request/response content dump for ghttp.Client 2020-04-24 00:00:52 +08:00
83eb8be064 Merge pull request #609 from kirileec/master
add Raw* method in ClientResponse to get request and response string
2020-04-23 22:58:04 +08:00
7af30df494 Merge branch 'master' into master 2020-04-23 22:57:23 +08:00
f026686fda fix issue in dupicated expiration handling in response cookies og ghttp.Client 2020-04-23 21:12:32 +08:00
35a50b9c6c readme update 2020-04-23 21:06:42 +08:00
010e2f951a downgrade the required golang version from v1.13 to v1.11 2020-04-23 20:38:25 +08:00
f7f86ad65a downgrade the required golang version from v1.13 to v1.11 2020-04-23 20:25:59 +08:00
1e19f447d1 improve ghttp.Client in context handling 2020-04-23 20:23:23 +08:00
8cc378331d add one unit testing case for ghttp.Server 2020-04-23 20:10:10 +08:00
4721f68fd8 add performance testing result 2020-04-23 19:51:08 +08:00
0d11c0a1f8 add performance testing result 2020-04-23 19:41:34 +08:00
5076613a8f add performance testing result 2020-04-23 17:25:11 +08:00
c5a44daa65 add performance testing result 2020-04-23 17:23:57 +08:00
520970b71f add performance testing result 2020-04-23 17:19:08 +08:00
ebfb08ee3f add performance testing result 2020-04-23 17:18:15 +08:00
9e38b2cb90 add performance testing result 2020-04-23 17:14:11 +08:00
71b1f00dc5 improve package gdb/gstr/gvalid 2020-04-20 22:36:28 +08:00
59d6830ab1 version update 2020-04-18 13:42:06 +08:00
8779a3f211 add function Walk for package garray; improve comment for package garray 2020-04-18 13:30:49 +08:00
c10149baa0 fix issue in stack trace for package gdebug; improve package gsmtp 2020-04-18 10:17:54 +08:00
4f87668780 improve retry feature for ghttp.Client 2020-04-16 15:43:21 +08:00
63f33d1d8c improve package gdb supporting gtime.Time parameter for Where condition 2020-04-15 18:02:32 +08:00
734aa5a6fe improve create_at,update_at,delete_at feature for package gdb 2020-04-15 12:56:41 +08:00
371aef224d donator updates 2020-04-15 11:41:44 +08:00
362e380ada improve SetConfigWithMap function for package ghttp/glog/gview; reveal some logger functions for default logger of package glog 2020-04-15 11:04:39 +08:00
e995bd8c9a add unit testing cases of https feature for ghttp.Server 2020-04-15 09:55:44 +08:00
81b211dd1a improve select operation details handling for package gdb 2020-04-15 09:37:46 +08:00
0515fc94cb add MapMerge/MapMargeCopy functions for package gutil; improve template view feature for indefinite parameters 2020-04-14 21:11:12 +08:00
46ee070f0a fix issue in package gfsnotify when gset changes 2020-04-14 00:20:39 +08:00
b17e3a6804 improve package gset 2020-04-13 23:44:43 +08:00
9160bee1af change comments 2020-04-11 12:16:53 +08:00
8e6018cfff improve soft deleting feature for package gdb 2020-04-11 10:14:49 +08:00
c08739b5a3 add function Having for gdb.Model 2020-04-11 10:11:52 +08:00
385a503d31 add soft deleting feature, improve chars handling for package gdb 2020-04-11 09:09:25 +08:00
ef286b0c15 add unit testing case for auto time and soft deleting features for package gdb 2020-04-09 22:48:21 +08:00
53aba2d4b8 add unit testing case of cache feature for package gdb 2020-04-09 22:00:02 +08:00
f22da4ba3a improve gconv.MapDeep with anonymous checks for attribute struct field 2020-04-09 15:34:23 +08:00
23c2f12672 improve MapDeep function for package gconv; improve gjson.New function for loading struct parameter 2020-04-09 13:37:27 +08:00
7fd53673ce add automatic time and soft deleting features: create_at/update_at/delete_at for package gdb; improve schema changing feature for package gdb 2020-04-08 21:26:14 +08:00
e64fd088b9 fix when no response 2020-04-08 20:11:06 +08:00
15672e7a09 #591 add Raw* method in ClientResponse to get request and response string 2020-04-08 19:24:03 +08:00
2ea1d2c7b2 update build-in template function substr/strlimit for package gview 2020-04-07 23:53:17 +08:00
90d751f98d add function SubStrRune/StrLimitRune/PosRune/PosIRune/PosRRune/PosRIRune for package gstr; remove unicode support in function SubStr/StrLimit for package gstr 2020-04-07 23:51:48 +08:00
25a91c732c improve String/Map converting for package gconv 2020-04-07 21:29:41 +08:00
01995f5501 improve function Marge for package garray using interface feature; improve slice converting using interface feature for package gconv 2020-04-07 21:25:52 +08:00
68abb3cf3d improve package glist for var initilalization feature 2020-04-07 20:58:58 +08:00
77cc323d0e improve package gset/gmap for initialization 2020-04-07 20:41:49 +08:00
c7a9c03495 improve package garray/gmap for initialization 2020-04-07 20:06:26 +08:00
f7850e3ed3 improve package garray 2020-04-07 11:29:42 +08:00
82125416a2 comment update for package ghttp 2020-04-06 22:31:45 +08:00
2c1e2155e3 improve function Server.Handler for package ghttp 2020-04-06 20:29:35 +08:00
2d30a53c3a remove function From for package gdb; add function g.Table; add ServeHTTP interface implements for ghttp.Server 2020-04-04 22:46:52 +08:00
ccceeae29c change third party package for yaml parsing to gopkg.in/yaml.v3 2020-04-03 19:51:33 +08:00
f22b98456f add default value of MaxActive configuration for package gredis 2020-04-03 15:18:35 +08:00
e7f1bd692b remove Content-Type header set in Response.WriteStatus for package ghttp 2020-04-03 14:16:26 +08:00
f82f7ab199 change default value of safe from false to true for gdb.Model 2020-04-03 10:16:57 +08:00
4d33b527b6 readme update 2020-04-03 09:32:04 +08:00
7bcc596308 add GetVar function for package genv; add DryRun feature for package gdb 2020-04-02 20:52:37 +08:00
be2d4b080e Merge pull request #601 from bdliyq/master
Get ghttp a chance to add custom host.
2020-04-01 19:55:25 +08:00
c96e5f5a9e readme update 2020-04-01 18:08:20 +08:00
3c23766674 readme update 2020-04-01 18:05:17 +08:00
718089fc11 Get ghttp a chance to add custom host. 2020-03-31 23:28:21 +08:00
8ab44dcb44 Get ghttp a chance to add custom host. 2020-03-31 21:50:15 +08:00
228 changed files with 9132 additions and 4273 deletions

View File

@ -1,16 +1,68 @@
package main
import (
"archive/zip"
"fmt"
"github.com/gogf/gf/encoding/gcompress"
"io"
"os"
"path/filepath"
"strings"
)
func main() {
err := gcompress.ZipPath(
`D:\Workspace\Go\GOPATH\src\github.com\gogf\gf\geg`,
`D:\Workspace\Go\GOPATH\src\github.com\gogf\gf\geg\encoding\gcompress\data.zip`,
"my-dir",
)
fmt.Println(err)
// srcFile could be a single file or a directory
func Zip(srcFile string, destZip string) error {
zipfile, err := os.Create(destZip)
if err != nil {
return err
}
defer zipfile.Close()
archive := zip.NewWriter(zipfile)
defer archive.Close()
filepath.Walk(srcFile, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
header, err := zip.FileInfoHeader(info)
if err != nil {
return err
}
header.Name = strings.TrimPrefix(path, filepath.Dir(srcFile)+"/")
// header.Name = path
if info.IsDir() {
header.Name += "/"
} else {
header.Method = zip.Deflate
}
writer, err := archive.CreateHeader(header)
if err != nil {
return err
}
if !info.IsDir() {
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
_, err = io.Copy(writer, file)
}
return err
})
return err
}
func main() {
src := `/Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/test`
dst := `/Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/test.zip`
//src := `/Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/README.MD`
//dst := `/Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/README.MD.zip`
fmt.Println(gcompress.ZipPath(src, dst))
//fmt.Println(Zip(src, dst))
}

View File

@ -9,7 +9,7 @@ import (
func Upload(r *ghttp.Request) {
saveDirPath := "/tmp/"
files := r.GetUploadFiles("upload-file")
if err := files.Save(saveDirPath); err != nil {
if _, err := files.Save(saveDirPath); err != nil {
r.Response.WriteExit(err)
}
r.Response.WriteExit("upload successfully")

View File

@ -9,7 +9,7 @@ import (
func Upload(r *ghttp.Request) {
saveDirPath := "/tmp/"
files := r.GetUploadFiles("upload-file")
if err := files.Save(saveDirPath); err != nil {
if _, err := files.Save(saveDirPath); err != nil {
r.Response.WriteExit(err)
}
r.Response.WriteExit("upload successfully")

View File

@ -1,21 +1,29 @@
package main
import (
"encoding/hex"
"fmt"
"github.com/gogf/gf/encoding/gbase64"
"github.com/gogf/gf/util/gconv"
"github.com/gogf/gf/util/grand"
)
func Test() (int, int) {
return 1, 1
}
func Assert(v1, v2, v3 interface{}) {
fmt.Println(v1)
}
func F(v ...interface{}) []interface{} {
return v
// bytesToHexString converts binary content to hex string content.
func bytesToHexStr(b []byte) string {
dst := make([]byte, hex.EncodedLen(len(b)))
hex.Encode(dst, b)
return gconv.UnsafeBytesToStr(dst)
}
func main() {
Assert(F(Test()), 2, 3)
b := make([]byte, 1024)
for i := 0; i < 1024; i++ {
b[i] = byte(grand.N(0, 255))
}
fmt.Println(bytesToHexStr(b))
fmt.Println(len(b))
fmt.Println(len(bytesToHexStr(b)))
fmt.Println(gbase64.EncodeToString(b))
fmt.Println(len(gbase64.EncodeToString(b)))
}

View File

@ -1,6 +1,8 @@
language: go
go:
- "1.11.x"
- "1.12.x"
- "1.13.x"
- "1.14.x"

View File

@ -69,6 +69,14 @@ We currently accept donation by Alipay/WechatPay, please note your github/gitee
|1*1x|wechat|¥100.00|
|[ywanbing](https://github.com/ywanbing)|wechat|¥66.66|
|[侯哥](http://www.macnie.com)|wechat|¥10.00|
|如果🍋|alipay|¥100.00| 错过的奶茶^_^
|蔡蔡|wechat|¥666.00| gf真强大让项目省心
|jack|wechat|¥100.00|
|sbilly|wechat|¥100.00| 祝好!
|米司|wechat|¥166.66| 大佬加油!
|*秦|wechat|¥20.00| 给群主献上一杯咖啡...
|[zhuhuan12](https://gitee.com/zhuhuan12)|wechat|¥50.00|
|faddei|qq|¥9.99|
<img src="https://goframe.org/images/donate.png"/>

View File

@ -9,13 +9,13 @@
English | [简体中文](README_ZH.MD)
`GF(GoFrame)` is a modular, loose-coupled, full-featured and production-ready application development framework of golang,
providing a series of core components and dozens of practical modules,
such as: memcache, configure, validator, logging, array/queue/set/map containers,
timer/timing tasks, file/memory lock, object pool, database ORM, etc,
supporting web server integrated with router, cookie, session, middleware, logger,
template, https, hooks, rewrites and many more features.
`GF(GoFrame)` is a modular, powerful, high-performance and production-ready application development framework
of golang. Providing a series of core components and dozens of practical modules, such as:
cache, logging, containers, timer, resource, validator, database orm, etc.
Supporting web server integrated with router, cookie, session, middleware, logger, configure,
template, https, hooks, rewrites and many more features.
> If you're a newbie to `Go`, you may consider `GoFrame` easy and great as `Laravel` in `PHP`, `SpringBoot` in `Java` or `Django` in `Python`.
# Installation
```
@ -28,13 +28,72 @@ require github.com/gogf/gf latest
# Limitation
```
golang version >= 1.13
golang version >= 1.11
```
# 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
## Environment
OS : Ubuntu 18.04 amd64
CPU : AMD A8-6600K x 4
MEM : 32GB
GO : v1.13.4
## Testing Tool
`ab`: Apache HTTP server benchmarking tool.
Command:
```
ab -t 10 -c 100 http://127.0.0.1:3000/hello
ab -t 10 -c 100 http://127.0.0.1:3000/query?id=10000
ab -t 10 -c 100 http://127.0.0.1:3000/json
```
The concurrency starts from `100` to `10000`.
> Run `5` times for each case of each project and pick up the best testing result.
## 1. Hello World
<table>
<tr>
<th>Throughputs</th>
<th>Mean Latency</th>
<th>P99 Latency</th>
</tr>
<tr>
<td width="30%"><img src="http://gfcdn.johng.cn/images/performance/throughputs1.jpeg"></td>
<td width="30%"><img src="http://gfcdn.johng.cn/images/performance/meanlatency1.jpeg"></td>
<td width="30%"><img src="http://gfcdn.johng.cn/images/performance/p99latency1.jpeg"></td>
</tr>
</table>
## 2. Json Response
<table>
<tr>
<th>Throughputs</th>
<th>Mean Latency</th>
<th>P99 Latency</th>
</tr>
<tr>
<td width="30%"><img src="http://gfcdn.johng.cn/images/performance/throughputs3.jpeg"></td>
<td width="30%"><img src="http://gfcdn.johng.cn/images/performance/meanlatency3.jpeg"></td>
<td width="30%"><img src="http://gfcdn.johng.cn/images/performance/p99latency3.jpeg"></td>
</tr>
</table>
# Documentation
* [APIDoc](https://godoc.org/github.com/gogf/gf)
* [中文文档](https://goframe.org)
* 中文官网: https://goframe.org
* GoDoc API: https://godoc.org/github.com/gogf/gf
# Discussion
- QQ Group[116707870](//shang.qq.com/wpa/qunwpa?idkey=195f91eceeb5d7fa76009b7cd5a4641f70bf4897b7f5a520635eb26ff17adfe7)
@ -43,12 +102,6 @@ golang version >= 1.13
> It's recommended learning `GoFrame` through its awesome source codes and API reference.
# Architecture
<div align=center>
<img src="https://goframe.org/images/arch.png?v=11"/>
</div>
# License
`GF` is licensed under the [MIT License](LICENSE), 100% free and open-source, forever.

View File

@ -15,16 +15,17 @@
并提供了Web服务开发的系列核心组件Router、Cookie、Session、Middleware、服务注册、模板引擎等等
支持热重启、热更新、域名绑定、TLS/HTTPS、Rewrite等特性。
> 如果您初识`Go`语言,您可以将`GoFrame`类似于`PHP`中的`Laravel`, `Java`中的`SpringBoot`或者`Python`中的`Django`。
# 特点
* 模块化、松耦合设计;
* 模块丰富开箱即用;
* 简便易用易于维护;
* 社区活跃,大牛谦逊低调脾气好;
* 模块丰富开箱即用;
* 简便易用易于维护;
* 高代码质量、高单元测试覆盖率;
* 社区活跃,大牛谦逊低调脾气好;
* 详尽的开发文档及示例;
* 完善的本地中文化支持;
* 更适合企业及团队使用;
* 设计为团队及企业使用;
# 地址
- **主库**https://github.com/gogf/gf
@ -41,7 +42,7 @@ require github.com/gogf/gf latest
# 限制
```shell
golang版本 >= 1.13
golang版本 >= 1.11
```
# 架构
@ -49,13 +50,66 @@ golang版本 >= 1.13
<img src="https://goframe.org/images/arch.png?v=11"/>
</div>
# 性能
以下是目前最流行的`WEB Server` Golang框架/类库性能测试结果。
性能测试用例源代码仓库: https://github.com/gogf/gf-performance
## 环境:
OS : Ubuntu 18.04 amd64
CPU : AMD A8-6600K x 4
MEM : 32GB
GO : v1.13.4
## 工具
`ab`: Apache HTTP server benchmarking tool.
测试命令:
```
ab -t 10 -c 100 http://127.0.0.1:3000/hello
ab -t 10 -c 100 http://127.0.0.1:3000/query?id=10000
ab -t 10 -c 100 http://127.0.0.1:3000/json
```
并发客户端数量从 `100` 递增到 `10000`。
> 每个项目的每个用例均运行`5`次,取最优的结果展示。
## 1. Hello World
<table>
<tr>
<th>Throughputs</th>
<th>Mean Latency</th>
<th>P99 Latency</th>
</tr>
<tr>
<td width="30%"><img src="http://gfcdn.johng.cn/images/performance/throughputs1.jpeg"></td>
<td width="30%"><img src="http://gfcdn.johng.cn/images/performance/meanlatency1.jpeg"></td>
<td width="30%"><img src="http://gfcdn.johng.cn/images/performance/p99latency1.jpeg"></td>
</tr>
</table>
## 2. Json Response
<table>
<tr>
<th>Throughputs</th>
<th>Mean Latency</th>
<th>P99 Latency</th>
</tr>
<tr>
<td width="30%"><img src="http://gfcdn.johng.cn/images/performance/throughputs3.jpeg"></td>
<td width="30%"><img src="http://gfcdn.johng.cn/images/performance/meanlatency3.jpeg"></td>
<td width="30%"><img src="http://gfcdn.johng.cn/images/performance/p99latency3.jpeg"></td>
</tr>
</table>
# 文档
开发文档:[https://goframe.org](https://goframe.org)
开发文档https://goframe.org
接口文档:[https://godoc.org/github.com/gogf/gf](https://godoc.org/github.com/gogf/gf)
接口文档https://godoc.org/github.com/gogf/gf
# 帮助
- QQ交流群[116707870](//shang.qq.com/wpa/qunwpa?idkey=195f91eceeb5d7fa76009b7cd5a4641f70bf4897b7f5a520635eb26ff17adfe7)

View File

@ -8,6 +8,12 @@ package garray
import "strings"
// apiInterfaces is used for type assert api for Interfaces.
type apiInterfaces interface {
Interfaces() []interface{}
}
// defaultComparatorInt for int comparison.
func defaultComparatorInt(a, b int) int {
if a < b {
return -1
@ -18,6 +24,7 @@ func defaultComparatorInt(a, b int) int {
return 0
}
// defaultComparatorStr for string comparison.
func defaultComparatorStr(a, b string) int {
return strings.Compare(a, b)
}

View File

@ -23,7 +23,7 @@ import (
// Array is a golang array with rich features.
type Array struct {
mu *rwmutex.RWMutex
mu rwmutex.RWMutex
array []interface{}
}
@ -44,7 +44,7 @@ func NewArray(safe ...bool) *Array {
// which is false in default.
func NewArraySize(size int, cap int, safe ...bool) *Array {
return &Array{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
array: make([]interface{}, size, cap),
}
}
@ -79,7 +79,7 @@ func NewFromCopy(array []interface{}, safe ...bool) *Array {
// which is false in default.
func NewArrayFrom(array []interface{}, safe ...bool) *Array {
return &Array{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
array: array,
}
}
@ -91,7 +91,7 @@ func NewArrayFromCopy(array []interface{}, safe ...bool) *Array {
newArray := make([]interface{}, len(array))
copy(newArray, array)
return &Array{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
array: newArray,
}
}
@ -473,18 +473,7 @@ func (a *Array) Contains(value interface{}) bool {
// or returns -1 if not exists.
func (a *Array) Search(value interface{}) int {
a.mu.RLock()
result := -1
for index, v := range a.array {
if v == value {
result = index
break
}
}
a.mu.RUnlock()
return result
}
func (a *Array) doSearch(value interface{}) int {
defer a.mu.RUnlock()
if len(a.array) == 0 {
return -1
}
@ -533,23 +522,7 @@ func (a *Array) RLockFunc(f func(array []interface{})) *Array {
// The difference between Merge and Append is Append supports only specified slice type,
// but Merge supports more parameter types.
func (a *Array) Merge(array interface{}) *Array {
switch v := array.(type) {
case *Array:
a.Append(gconv.Interfaces(v.Slice())...)
case *IntArray:
a.Append(gconv.Interfaces(v.Slice())...)
case *StrArray:
a.Append(gconv.Interfaces(v.Slice())...)
case *SortedArray:
a.Append(gconv.Interfaces(v.Slice())...)
case *SortedIntArray:
a.Append(gconv.Interfaces(v.Slice())...)
case *SortedStrArray:
a.Append(gconv.Interfaces(v.Slice())...)
default:
a.Append(gconv.Interfaces(array)...)
}
return a
return a.Append(gconv.Interfaces(array)...)
}
// Fill fills an array with num entries of the value <value>,
@ -668,6 +641,9 @@ func (a *Array) Reverse() *Array {
func (a *Array) Join(glue string) string {
a.mu.RLock()
defer a.mu.RUnlock()
if len(a.array) == 0 {
return ""
}
buffer := bytes.NewBuffer(nil)
for k, v := range a.array {
buffer.WriteString(gconv.String(v))
@ -694,7 +670,7 @@ func (a *Array) Iterator(f func(k int, v interface{}) bool) {
a.IteratorAsc(f)
}
// IteratorAsc iterates the array in ascending order with given callback function <f>.
// IteratorAsc iterates the array readonly in ascending order with given callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
func (a *Array) IteratorAsc(f func(k int, v interface{}) bool) {
a.mu.RLock()
@ -706,7 +682,7 @@ func (a *Array) IteratorAsc(f func(k int, v interface{}) bool) {
}
}
// IteratorDesc iterates the array in descending order with given callback function <f>.
// 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.
func (a *Array) IteratorDesc(f func(k int, v interface{}) bool) {
a.mu.RLock()
@ -749,8 +725,7 @@ func (a *Array) MarshalJSON() ([]byte, error) {
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
func (a *Array) UnmarshalJSON(b []byte) error {
if a.mu == nil {
a.mu = rwmutex.New()
if a.array == nil {
a.array = make([]interface{}, 0)
}
a.mu.Lock()
@ -763,9 +738,6 @@ func (a *Array) UnmarshalJSON(b []byte) error {
// UnmarshalValue is an interface implement which sets any type of value for array.
func (a *Array) UnmarshalValue(value interface{}) error {
if a.mu == nil {
a.mu = rwmutex.New()
}
a.mu.Lock()
defer a.mu.Unlock()
switch value.(type) {
@ -806,6 +778,16 @@ func (a *Array) FilterEmpty() *Array {
return a
}
// Walk applies a user supplied function <f> to every item of array.
func (a *Array) Walk(f func(value interface{}) interface{}) *Array {
a.mu.Lock()
defer a.mu.Unlock()
for i, v := range a.array {
a.array[i] = f(v)
}
return a
}
// IsEmpty checks whether the array is empty.
func (a *Array) IsEmpty() bool {
return a.Len() == 0

View File

@ -21,7 +21,7 @@ import (
// IntArray is a golang int array with rich features.
type IntArray struct {
mu *rwmutex.RWMutex
mu rwmutex.RWMutex
array []int
}
@ -37,7 +37,7 @@ func NewIntArray(safe ...bool) *IntArray {
// which is false in default.
func NewIntArraySize(size int, cap int, safe ...bool) *IntArray {
return &IntArray{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
array: make([]int, size, cap),
}
}
@ -62,7 +62,7 @@ func NewIntArrayRange(start, end, step int, safe ...bool) *IntArray {
// which is false in default.
func NewIntArrayFrom(array []int, safe ...bool) *IntArray {
return &IntArray{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
array: array,
}
}
@ -74,7 +74,7 @@ func NewIntArrayFromCopy(array []int, safe ...bool) *IntArray {
newArray := make([]int, len(array))
copy(newArray, array)
return &IntArray{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
array: newArray,
}
}
@ -489,6 +489,10 @@ func (a *IntArray) Contains(value int) bool {
// or returns -1 if not exists.
func (a *IntArray) Search(value int) int {
a.mu.RLock()
defer a.mu.RUnlock()
if len(a.array) == 0 {
return -1
}
result := -1
for index, v := range a.array {
if v == value {
@ -496,7 +500,6 @@ func (a *IntArray) Search(value int) int {
break
}
}
a.mu.RUnlock()
return result
}
@ -535,23 +538,7 @@ func (a *IntArray) RLockFunc(f func(array []int)) *IntArray {
// The difference between Merge and Append is Append supports only specified slice type,
// but Merge supports more parameter types.
func (a *IntArray) Merge(array interface{}) *IntArray {
switch v := array.(type) {
case *Array:
a.Append(gconv.Ints(v.Slice())...)
case *IntArray:
a.Append(gconv.Ints(v.Slice())...)
case *StrArray:
a.Append(gconv.Ints(v.Slice())...)
case *SortedArray:
a.Append(gconv.Ints(v.Slice())...)
case *SortedIntArray:
a.Append(gconv.Ints(v.Slice())...)
case *SortedStrArray:
a.Append(gconv.Ints(v.Slice())...)
default:
a.Append(gconv.Ints(array)...)
}
return a
return a.Append(gconv.Ints(array)...)
}
// Fill fills an array with num entries of the value <value>,
@ -670,6 +657,9 @@ func (a *IntArray) Reverse() *IntArray {
func (a *IntArray) Join(glue string) string {
a.mu.RLock()
defer a.mu.RUnlock()
if len(a.array) == 0 {
return ""
}
buffer := bytes.NewBuffer(nil)
for k, v := range a.array {
buffer.WriteString(gconv.String(v))
@ -696,7 +686,7 @@ func (a *IntArray) Iterator(f func(k int, v int) bool) {
a.IteratorAsc(f)
}
// IteratorAsc iterates the array in ascending order with given callback function <f>.
// IteratorAsc iterates the array readonly in ascending order with given callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
func (a *IntArray) IteratorAsc(f func(k int, v int) bool) {
a.mu.RLock()
@ -708,7 +698,7 @@ func (a *IntArray) IteratorAsc(f func(k int, v int) bool) {
}
}
// IteratorDesc iterates the array in descending order with given callback function <f>.
// 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.
func (a *IntArray) IteratorDesc(f func(k int, v int) bool) {
a.mu.RLock()
@ -734,8 +724,7 @@ func (a *IntArray) MarshalJSON() ([]byte, error) {
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
func (a *IntArray) UnmarshalJSON(b []byte) error {
if a.mu == nil {
a.mu = rwmutex.New()
if a.array == nil {
a.array = make([]int, 0)
}
a.mu.Lock()
@ -748,9 +737,6 @@ func (a *IntArray) UnmarshalJSON(b []byte) error {
// UnmarshalValue is an interface implement which sets any type of value for array.
func (a *IntArray) UnmarshalValue(value interface{}) error {
if a.mu == nil {
a.mu = rwmutex.New()
}
a.mu.Lock()
defer a.mu.Unlock()
switch value.(type) {
@ -776,6 +762,16 @@ func (a *IntArray) FilterEmpty() *IntArray {
return a
}
// Walk applies a user supplied function <f> to every item of array.
func (a *IntArray) Walk(f func(value int) int) *IntArray {
a.mu.Lock()
defer a.mu.Unlock()
for i, v := range a.array {
a.array[i] = f(v)
}
return a
}
// IsEmpty checks whether the array is empty.
func (a *IntArray) IsEmpty() bool {
return a.Len() == 0

View File

@ -23,7 +23,7 @@ import (
// StrArray is a golang string array with rich features.
type StrArray struct {
mu *rwmutex.RWMutex
mu rwmutex.RWMutex
array []string
}
@ -39,7 +39,7 @@ func NewStrArray(safe ...bool) *StrArray {
// which is false in default.
func NewStrArraySize(size int, cap int, safe ...bool) *StrArray {
return &StrArray{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
array: make([]string, size, cap),
}
}
@ -49,7 +49,7 @@ func NewStrArraySize(size int, cap int, safe ...bool) *StrArray {
// which is false in default.
func NewStrArrayFrom(array []string, safe ...bool) *StrArray {
return &StrArray{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
array: array,
}
}
@ -61,7 +61,7 @@ func NewStrArrayFromCopy(array []string, safe ...bool) *StrArray {
newArray := make([]string, len(array))
copy(newArray, array)
return &StrArray{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
array: newArray,
}
}
@ -473,13 +473,30 @@ func (a *StrArray) Contains(value string) bool {
return a.Search(value) != -1
}
// ContainsI checks whether a value exists in the array with case-insensitively.
// Note that it internally iterates the whole array to do the comparison with case-insensitively.
func (a *StrArray) ContainsI(value string) bool {
a.mu.RLock()
defer a.mu.RUnlock()
if len(a.array) == 0 {
return false
}
for _, v := range a.array {
if strings.EqualFold(v, value) {
return true
}
}
return false
}
// Search searches array by <value>, returns the index of <value>,
// or returns -1 if not exists.
func (a *StrArray) Search(value string) int {
a.mu.RLock()
defer a.mu.RUnlock()
if len(a.array) == 0 {
return -1
}
a.mu.RLock()
result := -1
for index, v := range a.array {
if strings.Compare(v, value) == 0 {
@ -487,7 +504,6 @@ func (a *StrArray) Search(value string) int {
break
}
}
a.mu.RUnlock()
return result
}
@ -526,23 +542,7 @@ func (a *StrArray) RLockFunc(f func(array []string)) *StrArray {
// The difference between Merge and Append is Append supports only specified slice type,
// but Merge supports more parameter types.
func (a *StrArray) Merge(array interface{}) *StrArray {
switch v := array.(type) {
case *Array:
a.Append(gconv.Strings(v.Slice())...)
case *IntArray:
a.Append(gconv.Strings(v.Slice())...)
case *StrArray:
a.Append(gconv.Strings(v.Slice())...)
case *SortedArray:
a.Append(gconv.Strings(v.Slice())...)
case *SortedIntArray:
a.Append(gconv.Strings(v.Slice())...)
case *SortedStrArray:
a.Append(gconv.Strings(v.Slice())...)
default:
a.Append(gconv.Strings(array)...)
}
return a
return a.Append(gconv.Strings(array)...)
}
// Fill fills an array with num entries of the value <value>,
@ -661,6 +661,9 @@ func (a *StrArray) Reverse() *StrArray {
func (a *StrArray) Join(glue string) string {
a.mu.RLock()
defer a.mu.RUnlock()
if len(a.array) == 0 {
return ""
}
buffer := bytes.NewBuffer(nil)
for k, v := range a.array {
buffer.WriteString(v)
@ -687,7 +690,7 @@ func (a *StrArray) Iterator(f func(k int, v string) bool) {
a.IteratorAsc(f)
}
// IteratorAsc iterates the array in ascending order with given callback function <f>.
// IteratorAsc iterates the array readonly in ascending order with given callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
func (a *StrArray) IteratorAsc(f func(k int, v string) bool) {
a.mu.RLock()
@ -699,7 +702,7 @@ func (a *StrArray) IteratorAsc(f func(k int, v string) bool) {
}
}
// IteratorDesc iterates the array in descending order with given callback function <f>.
// 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.
func (a *StrArray) IteratorDesc(f func(k int, v string) bool) {
a.mu.RLock()
@ -736,8 +739,7 @@ func (a *StrArray) MarshalJSON() ([]byte, error) {
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
func (a *StrArray) UnmarshalJSON(b []byte) error {
if a.mu == nil {
a.mu = rwmutex.New()
if a.array == nil {
a.array = make([]string, 0)
}
a.mu.Lock()
@ -750,9 +752,6 @@ func (a *StrArray) UnmarshalJSON(b []byte) error {
// UnmarshalValue is an interface implement which sets any type of value for array.
func (a *StrArray) UnmarshalValue(value interface{}) error {
if a.mu == nil {
a.mu = rwmutex.New()
}
a.mu.Lock()
defer a.mu.Unlock()
switch value.(type) {
@ -778,6 +777,16 @@ func (a *StrArray) FilterEmpty() *StrArray {
return a
}
// Walk applies a user supplied function <f> to every item of array.
func (a *StrArray) Walk(f func(value string) string) *StrArray {
a.mu.Lock()
defer a.mu.Unlock()
for i, v := range a.array {
a.array[i] = f(v)
}
return a
}
// IsEmpty checks whether the array is empty.
func (a *StrArray) IsEmpty() bool {
return a.Len() == 0

View File

@ -16,7 +16,6 @@ import (
"math"
"sort"
"github.com/gogf/gf/container/gtype"
"github.com/gogf/gf/internal/rwmutex"
"github.com/gogf/gf/util/gconv"
"github.com/gogf/gf/util/grand"
@ -25,9 +24,9 @@ import (
// SortedArray is a golang sorted array with rich features.
// It's using increasing order in default.
type SortedArray struct {
mu *rwmutex.RWMutex
mu rwmutex.RWMutex
array []interface{}
unique *gtype.Bool // Whether enable unique feature(false)
unique bool // Whether enable unique feature(false)
comparator func(a, b interface{}) int // Comparison function(it returns -1: a < b; 0: a == b; 1: a > b)
}
@ -46,8 +45,7 @@ func NewSortedArray(comparator func(a, b interface{}) int, safe ...bool) *Sorted
// which is false in default.
func NewSortedArraySize(cap int, comparator func(a, b interface{}) int, safe ...bool) *SortedArray {
return &SortedArray{
mu: rwmutex.New(safe...),
unique: gtype.NewBool(),
mu: rwmutex.Create(safe...),
array: make([]interface{}, 0, cap),
comparator: comparator,
}
@ -75,7 +73,7 @@ func NewSortedArrayFrom(array []interface{}, comparator func(a, b interface{}) i
a := NewSortedArraySize(0, comparator, safe...)
a.array = array
sort.Slice(a.array, func(i, j int) bool {
return a.comparator(a.array[i], a.array[j]) < 0
return a.getComparator()(a.array[i], a.array[j]) < 0
})
return a
}
@ -95,19 +93,19 @@ func (a *SortedArray) SetArray(array []interface{}) *SortedArray {
defer a.mu.Unlock()
a.array = array
sort.Slice(a.array, func(i, j int) bool {
return a.comparator(a.array[i], a.array[j]) < 0
return a.getComparator()(a.array[i], a.array[j]) < 0
})
return a
}
// SetComparator sets/changes the comparator for sorting.
// It resorts the array as the comparator is changed.
func (a *SortedArray) SetComparator(comparator func(a, b interface{}) int) {
a.mu.Lock()
defer a.mu.Unlock()
a.comparator = comparator
// Resort the array if comparator is changed.
sort.Slice(a.array, func(i, j int) bool {
return a.comparator(a.array[i], a.array[j]) < 0
return a.getComparator()(a.array[i], a.array[j]) < 0
})
}
@ -118,13 +116,19 @@ func (a *SortedArray) Sort() *SortedArray {
a.mu.Lock()
defer a.mu.Unlock()
sort.Slice(a.array, func(i, j int) bool {
return a.comparator(a.array[i], a.array[j]) < 0
return a.getComparator()(a.array[i], a.array[j]) < 0
})
return a
}
// Add adds one or multiple values to sorted array, the array always keeps sorted.
// It's alias of function Append, see Append.
func (a *SortedArray) Add(values ...interface{}) *SortedArray {
return a.Append(values...)
}
// Append adds one or multiple values to sorted array, the array always keeps sorted.
func (a *SortedArray) Append(values ...interface{}) *SortedArray {
if len(values) == 0 {
return a
}
@ -132,7 +136,7 @@ func (a *SortedArray) Add(values ...interface{}) *SortedArray {
defer a.mu.Unlock()
for _, value := range values {
index, cmp := a.binSearch(value, false)
if a.unique.Val() && cmp == 0 {
if a.unique && cmp == 0 {
continue
}
if index < 0 {
@ -390,7 +394,7 @@ func (a *SortedArray) Len() int {
// Note that, if it's in concurrent-safe usage, it returns a copy of underlying data,
// or else a pointer to the underlying data.
func (a *SortedArray) Slice() []interface{} {
array := ([]interface{})(nil)
var array []interface{}
if a.mu.IsSafe() {
a.mu.RLock()
defer a.mu.RUnlock()
@ -427,20 +431,20 @@ func (a *SortedArray) Search(value interface{}) (index int) {
// If <result> lesser than 0, it means the value at <index> is lesser than <value>.
// If <result> greater than 0, it means the value at <index> is greater than <value>.
func (a *SortedArray) binSearch(value interface{}, lock bool) (index int, result int) {
if len(a.array) == 0 {
return -1, -2
}
if lock {
a.mu.RLock()
defer a.mu.RUnlock()
}
if len(a.array) == 0 {
return -1, -2
}
min := 0
max := len(a.array) - 1
mid := 0
cmp := -2
for min <= max {
mid = int((min + max) / 2)
cmp = a.comparator(value, a.array[mid])
mid = (min + max) / 2
cmp = a.getComparator()(value, a.array[mid])
switch {
case cmp < 0:
max = mid - 1
@ -457,8 +461,8 @@ func (a *SortedArray) binSearch(value interface{}, lock bool) (index int, result
// which means it does not contain any repeated items.
// It also do unique check, remove all repeated items.
func (a *SortedArray) SetUnique(unique bool) *SortedArray {
oldUnique := a.unique.Val()
a.unique.Set(unique)
oldUnique := a.unique
a.unique = unique
if unique && oldUnique != unique {
a.Unique()
}
@ -477,7 +481,7 @@ func (a *SortedArray) Unique() *SortedArray {
if i == len(a.array)-1 {
break
}
if a.comparator(a.array[i], a.array[i+1]) == 0 {
if a.getComparator()(a.array[i], a.array[i+1]) == 0 {
a.array = append(a.array[:i+1], a.array[i+1+1:]...)
} else {
i++
@ -509,6 +513,12 @@ func (a *SortedArray) Clear() *SortedArray {
func (a *SortedArray) LockFunc(f func(array []interface{})) *SortedArray {
a.mu.Lock()
defer a.mu.Unlock()
// Keep the array always sorted.
defer sort.Slice(a.array, func(i, j int) bool {
return a.getComparator()(a.array[i], a.array[j]) < 0
})
f(a.array)
return a
}
@ -526,23 +536,7 @@ func (a *SortedArray) RLockFunc(f func(array []interface{})) *SortedArray {
// The difference between Merge and Append is Append supports only specified slice type,
// but Merge supports more parameter types.
func (a *SortedArray) Merge(array interface{}) *SortedArray {
switch v := array.(type) {
case *Array:
a.Add(gconv.Interfaces(v.Slice())...)
case *IntArray:
a.Add(gconv.Interfaces(v.Slice())...)
case *StrArray:
a.Add(gconv.Interfaces(v.Slice())...)
case *SortedArray:
a.Add(gconv.Interfaces(v.Slice())...)
case *SortedIntArray:
a.Add(gconv.Interfaces(v.Slice())...)
case *SortedStrArray:
a.Add(gconv.Interfaces(v.Slice())...)
default:
a.Add(gconv.Interfaces(array)...)
}
return a
return a.Add(gconv.Interfaces(array)...)
}
// Chunk splits an array into multiple arrays,
@ -596,6 +590,9 @@ func (a *SortedArray) Rands(size int) []interface{} {
func (a *SortedArray) Join(glue string) string {
a.mu.RLock()
defer a.mu.RUnlock()
if len(a.array) == 0 {
return ""
}
buffer := bytes.NewBuffer(nil)
for k, v := range a.array {
buffer.WriteString(gconv.String(v))
@ -622,7 +619,7 @@ func (a *SortedArray) Iterator(f func(k int, v interface{}) bool) {
a.IteratorAsc(f)
}
// IteratorAsc iterates the array in ascending order with given callback function <f>.
// IteratorAsc iterates the array readonly in ascending order with given callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
func (a *SortedArray) IteratorAsc(f func(k int, v interface{}) bool) {
a.mu.RLock()
@ -634,7 +631,7 @@ func (a *SortedArray) IteratorAsc(f func(k int, v interface{}) bool) {
}
}
// IteratorDesc iterates the array in descending order with given callback function <f>.
// 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.
func (a *SortedArray) IteratorDesc(f func(k int, v interface{}) bool) {
a.mu.RLock()
@ -676,12 +673,10 @@ func (a *SortedArray) MarshalJSON() ([]byte, error) {
}
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
// Note that the comparator is set as string comparator in default.
func (a *SortedArray) UnmarshalJSON(b []byte) error {
if a.mu == nil {
a.mu = rwmutex.New()
if a.comparator == nil {
a.array = make([]interface{}, 0)
a.unique = gtype.NewBool()
// Note that the comparator is string comparator in default.
a.comparator = gutil.ComparatorString
}
a.mu.Lock()
@ -698,11 +693,9 @@ func (a *SortedArray) UnmarshalJSON(b []byte) error {
}
// UnmarshalValue is an interface implement which sets any type of value for array.
// Note that the comparator is set as string comparator in default.
func (a *SortedArray) UnmarshalValue(value interface{}) (err error) {
if a.mu == nil {
a.mu = rwmutex.New()
a.unique = gtype.NewBool()
// Note that the comparator is string comparator in default.
if a.comparator == nil {
a.comparator = gutil.ComparatorString
}
a.mu.Lock()
@ -764,7 +757,30 @@ func (a *SortedArray) FilterEmpty() *SortedArray {
return a
}
// Walk applies a user supplied function <f> to every item of array.
func (a *SortedArray) Walk(f func(value interface{}) interface{}) *SortedArray {
a.mu.Lock()
defer a.mu.Unlock()
// Keep the array always sorted.
defer sort.Slice(a.array, func(i, j int) bool {
return a.getComparator()(a.array[i], a.array[j]) < 0
})
for i, v := range a.array {
a.array[i] = f(v)
}
return a
}
// IsEmpty checks whether the array is empty.
func (a *SortedArray) IsEmpty() bool {
return a.Len() == 0
}
// getComparator returns the comparator if it's previously set,
// or else it panics.
func (a *SortedArray) getComparator() func(a, b interface{}) int {
if a.comparator == nil {
panic("comparator is missing for sorted array")
}
return a.comparator
}

View File

@ -13,7 +13,6 @@ import (
"math"
"sort"
"github.com/gogf/gf/container/gtype"
"github.com/gogf/gf/internal/rwmutex"
"github.com/gogf/gf/util/gconv"
"github.com/gogf/gf/util/grand"
@ -22,9 +21,9 @@ import (
// SortedIntArray is a golang sorted int array with rich features.
// It's using increasing order in default.
type SortedIntArray struct {
mu *rwmutex.RWMutex
mu rwmutex.RWMutex
array []int
unique *gtype.Bool // Whether enable unique feature(false)
unique bool // Whether enable unique feature(false)
comparator func(a, b int) int // Comparison function(it returns -1: a < b; 0: a == b; 1: a > b)
}
@ -48,9 +47,8 @@ func NewSortedIntArrayComparator(comparator func(a, b int) int, safe ...bool) *S
// which is false in default.
func NewSortedIntArraySize(cap int, safe ...bool) *SortedIntArray {
return &SortedIntArray{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
array: make([]int, 0, cap),
unique: gtype.NewBool(),
comparator: defaultComparatorInt,
}
}
@ -94,7 +92,7 @@ func (a *SortedIntArray) SetArray(array []int) *SortedIntArray {
a.mu.Lock()
defer a.mu.Unlock()
a.array = array
sort.Ints(a.array)
quickSortInt(a.array, a.getComparator())
return a
}
@ -104,12 +102,18 @@ func (a *SortedIntArray) SetArray(array []int) *SortedIntArray {
func (a *SortedIntArray) Sort() *SortedIntArray {
a.mu.Lock()
defer a.mu.Unlock()
sort.Ints(a.array)
quickSortInt(a.array, a.getComparator())
return a
}
// Add adds one or multiple values to sorted array, the array always keeps sorted.
// It's alias of function Append, see Append.
func (a *SortedIntArray) Add(values ...int) *SortedIntArray {
return a.Append(values...)
}
// Append adds one or multiple values to sorted array, the array always keeps sorted.
func (a *SortedIntArray) Append(values ...int) *SortedIntArray {
if len(values) == 0 {
return a
}
@ -117,7 +121,7 @@ func (a *SortedIntArray) Add(values ...int) *SortedIntArray {
defer a.mu.Unlock()
for _, value := range values {
index, cmp := a.binSearch(value, false)
if a.unique.Val() && cmp == 0 {
if a.unique && cmp == 0 {
continue
}
if index < 0 {
@ -424,20 +428,20 @@ func (a *SortedIntArray) Search(value int) (index int) {
// If <result> lesser than 0, it means the value at <index> is lesser than <value>.
// If <result> greater than 0, it means the value at <index> is greater than <value>.
func (a *SortedIntArray) binSearch(value int, lock bool) (index int, result int) {
if len(a.array) == 0 {
return -1, -2
}
if lock {
a.mu.RLock()
defer a.mu.RUnlock()
}
if len(a.array) == 0 {
return -1, -2
}
min := 0
max := len(a.array) - 1
mid := 0
cmp := -2
for min <= max {
mid = int((min + max) / 2)
cmp = a.comparator(value, a.array[mid])
mid = (min + max) / 2
cmp = a.getComparator()(value, a.array[mid])
switch {
case cmp < 0:
max = mid - 1
@ -454,8 +458,8 @@ func (a *SortedIntArray) binSearch(value int, lock bool) (index int, result int)
// which means it does not contain any repeated items.
// It also do unique check, remove all repeated items.
func (a *SortedIntArray) SetUnique(unique bool) *SortedIntArray {
oldUnique := a.unique.Val()
a.unique.Set(unique)
oldUnique := a.unique
a.unique = unique
if unique && oldUnique != unique {
a.Unique()
}
@ -474,7 +478,7 @@ func (a *SortedIntArray) Unique() *SortedIntArray {
if i == len(a.array)-1 {
break
}
if a.comparator(a.array[i], a.array[i+1]) == 0 {
if a.getComparator()(a.array[i], a.array[i+1]) == 0 {
a.array = append(a.array[:i+1], a.array[i+1+1:]...)
} else {
i++
@ -523,23 +527,7 @@ func (a *SortedIntArray) RLockFunc(f func(array []int)) *SortedIntArray {
// The difference between Merge and Append is Append supports only specified slice type,
// but Merge supports more parameter types.
func (a *SortedIntArray) Merge(array interface{}) *SortedIntArray {
switch v := array.(type) {
case *Array:
a.Add(gconv.Ints(v.Slice())...)
case *IntArray:
a.Add(gconv.Ints(v.Slice())...)
case *StrArray:
a.Add(gconv.Ints(v.Slice())...)
case *SortedArray:
a.Add(gconv.Ints(v.Slice())...)
case *SortedIntArray:
a.Add(gconv.Ints(v.Slice())...)
case *SortedStrArray:
a.Add(gconv.Ints(v.Slice())...)
default:
a.Add(gconv.Ints(array)...)
}
return a
return a.Add(gconv.Ints(array)...)
}
// Chunk splits an array into multiple arrays,
@ -593,6 +581,9 @@ func (a *SortedIntArray) Rands(size int) []int {
func (a *SortedIntArray) Join(glue string) string {
a.mu.RLock()
defer a.mu.RUnlock()
if len(a.array) == 0 {
return ""
}
buffer := bytes.NewBuffer(nil)
for k, v := range a.array {
buffer.WriteString(gconv.String(v))
@ -619,7 +610,7 @@ func (a *SortedIntArray) Iterator(f func(k int, v int) bool) {
a.IteratorAsc(f)
}
// IteratorAsc iterates the array in ascending order with given callback function <f>.
// IteratorAsc iterates the array readonly in ascending order with given callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
func (a *SortedIntArray) IteratorAsc(f func(k int, v int) bool) {
a.mu.RLock()
@ -631,7 +622,7 @@ func (a *SortedIntArray) IteratorAsc(f func(k int, v int) bool) {
}
}
// IteratorDesc iterates the array in descending order with given callback function <f>.
// 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.
func (a *SortedIntArray) IteratorDesc(f func(k int, v int) bool) {
a.mu.RLock()
@ -657,10 +648,8 @@ func (a *SortedIntArray) MarshalJSON() ([]byte, error) {
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
func (a *SortedIntArray) UnmarshalJSON(b []byte) error {
if a.mu == nil {
a.mu = rwmutex.New()
if a.comparator == nil {
a.array = make([]int, 0)
a.unique = gtype.NewBool()
a.comparator = defaultComparatorInt
}
a.mu.Lock()
@ -676,10 +665,7 @@ func (a *SortedIntArray) UnmarshalJSON(b []byte) error {
// UnmarshalValue is an interface implement which sets any type of value for array.
func (a *SortedIntArray) UnmarshalValue(value interface{}) (err error) {
if a.mu == nil {
a.mu = rwmutex.New()
a.unique = gtype.NewBool()
// Note that the comparator is string comparator in default.
if a.comparator == nil {
a.comparator = defaultComparatorInt
}
a.mu.Lock()
@ -717,7 +703,30 @@ func (a *SortedIntArray) FilterEmpty() *SortedIntArray {
return a
}
// Walk applies a user supplied function <f> to every item of array.
func (a *SortedIntArray) Walk(f func(value int) int) *SortedIntArray {
a.mu.Lock()
defer a.mu.Unlock()
// Keep the array always sorted.
defer quickSortInt(a.array, a.getComparator())
for i, v := range a.array {
a.array[i] = f(v)
}
return a
}
// IsEmpty checks whether the array is empty.
func (a *SortedIntArray) IsEmpty() bool {
return a.Len() == 0
}
// getComparator returns the comparator if it's previously set,
// or else it returns a default comparator.
func (a *SortedIntArray) getComparator() func(a, b int) int {
if a.comparator == nil {
return defaultComparatorInt
}
return a.comparator
}

View File

@ -12,8 +12,8 @@ import (
"github.com/gogf/gf/text/gstr"
"math"
"sort"
"strings"
"github.com/gogf/gf/container/gtype"
"github.com/gogf/gf/internal/rwmutex"
"github.com/gogf/gf/util/gconv"
"github.com/gogf/gf/util/grand"
@ -22,9 +22,9 @@ import (
// SortedStrArray is a golang sorted string array with rich features.
// It's using increasing order in default.
type SortedStrArray struct {
mu *rwmutex.RWMutex
mu rwmutex.RWMutex
array []string
unique *gtype.Bool // Whether enable unique feature(false)
unique bool // Whether enable unique feature(false)
comparator func(a, b string) int // Comparison function(it returns -1: a < b; 0: a == b; 1: a > b)
}
@ -48,9 +48,8 @@ func NewSortedStrArrayComparator(comparator func(a, b string) int, safe ...bool)
// which is false in default.
func NewSortedStrArraySize(cap int, safe ...bool) *SortedStrArray {
return &SortedStrArray{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
array: make([]string, 0, cap),
unique: gtype.NewBool(),
comparator: defaultComparatorStr,
}
}
@ -61,7 +60,7 @@ func NewSortedStrArraySize(cap int, safe ...bool) *SortedStrArray {
func NewSortedStrArrayFrom(array []string, safe ...bool) *SortedStrArray {
a := NewSortedStrArraySize(0, safe...)
a.array = array
quickSortStr(a.array, a.comparator)
quickSortStr(a.array, a.getComparator())
return a
}
@ -79,7 +78,7 @@ func (a *SortedStrArray) SetArray(array []string) *SortedStrArray {
a.mu.Lock()
defer a.mu.Unlock()
a.array = array
quickSortStr(a.array, a.comparator)
quickSortStr(a.array, a.getComparator())
return a
}
@ -89,12 +88,18 @@ func (a *SortedStrArray) SetArray(array []string) *SortedStrArray {
func (a *SortedStrArray) Sort() *SortedStrArray {
a.mu.Lock()
defer a.mu.Unlock()
quickSortStr(a.array, a.comparator)
quickSortStr(a.array, a.getComparator())
return a
}
// Add adds one or multiple values to sorted array, the array always keeps sorted.
// It's alias of function Append, see Append.
func (a *SortedStrArray) Add(values ...string) *SortedStrArray {
return a.Append(values...)
}
// Append adds one or multiple values to sorted array, the array always keeps sorted.
func (a *SortedStrArray) Append(values ...string) *SortedStrArray {
if len(values) == 0 {
return a
}
@ -102,7 +107,7 @@ func (a *SortedStrArray) Add(values ...string) *SortedStrArray {
defer a.mu.Unlock()
for _, value := range values {
index, cmp := a.binSearch(value, false)
if a.unique.Val() && cmp == 0 {
if a.unique && cmp == 0 {
continue
}
if index < 0 {
@ -394,6 +399,22 @@ func (a *SortedStrArray) Contains(value string) bool {
return a.Search(value) != -1
}
// ContainsI checks whether a value exists in the array with case-insensitively.
// Note that it internally iterates the whole array to do the comparison with case-insensitively.
func (a *SortedStrArray) ContainsI(value string) bool {
a.mu.RLock()
defer a.mu.RUnlock()
if len(a.array) == 0 {
return false
}
for _, v := range a.array {
if strings.EqualFold(v, value) {
return true
}
}
return false
}
// Search searches array by <value>, returns the index of <value>,
// or returns -1 if not exists.
func (a *SortedStrArray) Search(value string) (index int) {
@ -409,20 +430,20 @@ func (a *SortedStrArray) Search(value string) (index int) {
// If <result> lesser than 0, it means the value at <index> is lesser than <value>.
// If <result> greater than 0, it means the value at <index> is greater than <value>.
func (a *SortedStrArray) binSearch(value string, lock bool) (index int, result int) {
if len(a.array) == 0 {
return -1, -2
}
if lock {
a.mu.RLock()
defer a.mu.RUnlock()
}
if len(a.array) == 0 {
return -1, -2
}
min := 0
max := len(a.array) - 1
mid := 0
cmp := -2
for min <= max {
mid = int((min + max) / 2)
cmp = a.comparator(value, a.array[mid])
mid = (min + max) / 2
cmp = a.getComparator()(value, a.array[mid])
switch {
case cmp < 0:
max = mid - 1
@ -439,8 +460,8 @@ func (a *SortedStrArray) binSearch(value string, lock bool) (index int, result i
// which means it does not contain any repeated items.
// It also do unique check, remove all repeated items.
func (a *SortedStrArray) SetUnique(unique bool) *SortedStrArray {
oldUnique := a.unique.Val()
a.unique.Set(unique)
oldUnique := a.unique
a.unique = unique
if unique && oldUnique != unique {
a.Unique()
}
@ -459,7 +480,7 @@ func (a *SortedStrArray) Unique() *SortedStrArray {
if i == len(a.array)-1 {
break
}
if a.comparator(a.array[i], a.array[i+1]) == 0 {
if a.getComparator()(a.array[i], a.array[i+1]) == 0 {
a.array = append(a.array[:i+1], a.array[i+1+1:]...)
} else {
i++
@ -508,23 +529,7 @@ func (a *SortedStrArray) RLockFunc(f func(array []string)) *SortedStrArray {
// The difference between Merge and Append is Append supports only specified slice type,
// but Merge supports more parameter types.
func (a *SortedStrArray) Merge(array interface{}) *SortedStrArray {
switch v := array.(type) {
case *Array:
a.Add(gconv.Strings(v.Slice())...)
case *IntArray:
a.Add(gconv.Strings(v.Slice())...)
case *StrArray:
a.Add(gconv.Strings(v.Slice())...)
case *SortedArray:
a.Add(gconv.Strings(v.Slice())...)
case *SortedIntArray:
a.Add(gconv.Strings(v.Slice())...)
case *SortedStrArray:
a.Add(gconv.Strings(v.Slice())...)
default:
a.Add(gconv.Strings(array)...)
}
return a
return a.Add(gconv.Strings(array)...)
}
// Chunk splits an array into multiple arrays,
@ -578,6 +583,9 @@ func (a *SortedStrArray) Rands(size int) []string {
func (a *SortedStrArray) Join(glue string) string {
a.mu.RLock()
defer a.mu.RUnlock()
if len(a.array) == 0 {
return ""
}
buffer := bytes.NewBuffer(nil)
for k, v := range a.array {
buffer.WriteString(v)
@ -604,7 +612,7 @@ func (a *SortedStrArray) Iterator(f func(k int, v string) bool) {
a.IteratorAsc(f)
}
// IteratorAsc iterates the array in ascending order with given callback function <f>.
// IteratorAsc iterates the array readonly in ascending order with given callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
func (a *SortedStrArray) IteratorAsc(f func(k int, v string) bool) {
a.mu.RLock()
@ -616,7 +624,7 @@ func (a *SortedStrArray) IteratorAsc(f func(k int, v string) bool) {
}
}
// IteratorDesc iterates the array in descending order with given callback function <f>.
// 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.
func (a *SortedStrArray) IteratorDesc(f func(k int, v string) bool) {
a.mu.RLock()
@ -653,10 +661,8 @@ func (a *SortedStrArray) MarshalJSON() ([]byte, error) {
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
func (a *SortedStrArray) UnmarshalJSON(b []byte) error {
if a.mu == nil {
a.mu = rwmutex.New()
if a.comparator == nil {
a.array = make([]string, 0)
a.unique = gtype.NewBool()
a.comparator = defaultComparatorStr
}
a.mu.Lock()
@ -672,10 +678,7 @@ func (a *SortedStrArray) UnmarshalJSON(b []byte) error {
// UnmarshalValue is an interface implement which sets any type of value for array.
func (a *SortedStrArray) UnmarshalValue(value interface{}) (err error) {
if a.mu == nil {
a.mu = rwmutex.New()
a.unique = gtype.NewBool()
// Note that the comparator is string comparator in default.
if a.comparator == nil {
a.comparator = defaultComparatorStr
}
a.mu.Lock()
@ -713,7 +716,30 @@ func (a *SortedStrArray) FilterEmpty() *SortedStrArray {
return a
}
// Walk applies a user supplied function <f> to every item of array.
func (a *SortedStrArray) Walk(f func(value string) string) *SortedStrArray {
a.mu.Lock()
defer a.mu.Unlock()
// Keep the array always sorted.
defer quickSortStr(a.array, a.getComparator())
for i, v := range a.array {
a.array[i] = f(v)
}
return a
}
// IsEmpty checks whether the array is empty.
func (a *SortedStrArray) IsEmpty() bool {
return a.Len() == 0
}
// getComparator returns the comparator if it's previously set,
// or else it returns a default comparator.
func (a *SortedStrArray) getComparator() func(a, b string) int {
if a.comparator == nil {
return defaultComparatorStr
}
return a.comparator
}

View File

@ -9,6 +9,7 @@
package garray_test
import (
"github.com/gogf/gf/util/gutil"
"strings"
"testing"
@ -17,6 +18,55 @@ import (
"github.com/gogf/gf/util/gconv"
)
func Test_Array_Var(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var array garray.Array
expect := []int{2, 3, 1}
array.Append(2, 3, 1)
t.Assert(array.Slice(), expect)
})
gtest.C(t, func(t *gtest.T) {
var array garray.IntArray
expect := []int{2, 3, 1}
array.Append(2, 3, 1)
t.Assert(array.Slice(), expect)
})
gtest.C(t, func(t *gtest.T) {
var array garray.StrArray
expect := []string{"b", "a"}
array.Append("b", "a")
t.Assert(array.Slice(), expect)
})
gtest.C(t, func(t *gtest.T) {
var array garray.SortedArray
array.SetComparator(gutil.ComparatorInt)
expect := []int{1, 2, 3}
array.Add(2, 3, 1)
t.Assert(array.Slice(), expect)
})
gtest.C(t, func(t *gtest.T) {
var array garray.SortedIntArray
expect := []int{1, 2, 3}
array.Add(2, 3, 1)
t.Assert(array.Slice(), expect)
})
gtest.C(t, func(t *gtest.T) {
var array garray.SortedStrArray
expect := []string{"a", "b", "c"}
array.Add("c", "a", "b")
t.Assert(array.Slice(), expect)
})
}
func Test_SortedIntArray_Var(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var array garray.SortedIntArray
expect := []int{1, 2, 3}
array.Add(2, 3, 1)
t.Assert(array.Slice(), expect)
})
}
func Test_IntArray_Unique(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
expect := []int{1, 2, 3, 4, 5, 6}

View File

@ -650,3 +650,12 @@ func TestArray_FilterEmpty(t *testing.T) {
t.Assert(array.FilterEmpty(), g.Slice{1, 2, 3, 4})
})
}
func TestArray_Walk(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
array := garray.NewArrayFrom(g.Slice{"1", "2"})
t.Assert(array.Walk(func(value interface{}) interface{} {
return "key-" + gconv.String(value)
}), g.Slice{"key-1", "key-2"})
})
}

View File

@ -683,3 +683,12 @@ func TestIntArray_FilterEmpty(t *testing.T) {
t.Assert(array.FilterEmpty(), g.SliceInt{1, 2, 3, 4})
})
}
func TestIntArray_Walk(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
array := garray.NewIntArrayFrom(g.SliceInt{1, 2})
t.Assert(array.Walk(func(value int) int {
return 10 + value
}), g.Slice{11, 12})
})
}

View File

@ -64,6 +64,16 @@ func Test_StrArray_Basic(t *testing.T) {
})
}
func TestStrArray_ContainsI(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := garray.NewStrArray()
s.Append("a", "b", "C")
t.Assert(s.Contains("A"), false)
t.Assert(s.Contains("a"), true)
t.Assert(s.ContainsI("A"), true)
})
}
func TestStrArray_Sort(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
expect1 := []string{"0", "1", "2", "3"}
@ -671,3 +681,12 @@ func TestStrArray_FilterEmpty(t *testing.T) {
t.Assert(array.FilterEmpty(), g.SliceStr{"1", "2"})
})
}
func TestStrArray_Walk(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
array := garray.NewStrArrayFrom(g.SliceStr{"1", "2"})
t.Assert(array.Walk(func(value string) string {
return "key-" + value
}), g.Slice{"key-1", "key-2"})
})
}

View File

@ -779,3 +779,12 @@ func TestSortedArray_FilterEmpty(t *testing.T) {
t.Assert(array.FilterEmpty(), g.Slice{1, 2, 3, 4})
})
}
func TestSortedArray_Walk(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
array := garray.NewSortedArrayFrom(g.Slice{"1", "2"}, gutil.ComparatorString)
t.Assert(array.Walk(func(value interface{}) interface{} {
return "key-" + gconv.String(value)
}), g.Slice{"key-1", "key-2"})
})
}

View File

@ -642,3 +642,12 @@ func TestSortedIntArray_FilterEmpty(t *testing.T) {
t.Assert(array.FilterEmpty(), g.SliceInt{1, 2, 3, 4})
})
}
func TestSortedIntArray_Walk(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
array := garray.NewSortedIntArrayFrom(g.SliceInt{1, 2})
t.Assert(array.Walk(func(value int) int {
return 10 + value
}), g.Slice{11, 12})
})
}

View File

@ -51,6 +51,16 @@ func TestSortedStrArray_SetArray(t *testing.T) {
})
}
func TestSortedStrArray_ContainsI(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := garray.NewSortedStrArray()
s.Append("a", "b", "C")
t.Assert(s.Contains("A"), false)
t.Assert(s.Contains("a"), true)
t.Assert(s.ContainsI("A"), true)
})
}
func TestSortedStrArray_Sort(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
a1 := []string{"a", "d", "c", "b"}
@ -652,3 +662,12 @@ func TestSortedStrArray_FilterEmpty(t *testing.T) {
t.Assert(array.FilterEmpty(), g.SliceStr{"1", "2"})
})
}
func TestSortedStrArray_Walk(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
array := garray.NewSortedStrArrayFrom(g.SliceStr{"1", "2"})
t.Assert(array.Walk(func(value string) string {
return "key-" + value
}), g.Slice{"key-1", "key-2"})
})
}

View File

@ -1,70 +0,0 @@
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
// Package gchan provides graceful channel for no panic operations.
//
// It's safe to call Chan.Push/Close functions repeatedly.
package gchan
import (
"errors"
"github.com/gogf/gf/container/gtype"
)
// Graceful channel.
type Chan struct {
channel chan interface{}
closed *gtype.Bool
}
// New creates a graceful channel with given <limit>.
func New(limit int) *Chan {
return &Chan{
channel: make(chan interface{}, limit),
closed: gtype.NewBool(),
}
}
// Push pushes <value> to channel.
// It is safe to be called repeatedly.
func (c *Chan) Push(value interface{}) error {
if c.closed.Val() {
return errors.New("channel is closed")
}
c.channel <- value
return nil
}
// Pop pops value from channel.
// If there's no value in channel, it would block to wait.
// If the channel is closed, it will return a nil value immediately.
func (c *Chan) Pop() interface{} {
return <-c.channel
}
// Close closes the channel.
// It is safe to be called repeatedly.
func (c *Chan) Close() {
if !c.closed.Set(true) {
close(c.channel)
}
}
// See Len.
func (c *Chan) Size() int {
return c.Len()
}
// Len returns the length of the channel.
func (c *Chan) Len() int {
return len(c.channel)
}
// Cap returns the capacity of the channel.
func (c *Chan) Cap() int {
return cap(c.channel)
}

View File

@ -1,30 +0,0 @@
// 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 gchan_test
import (
"fmt"
"github.com/gogf/gf/container/gchan"
)
func Example_basic() {
n := 10
c := gchan.New(n)
for i := 0; i < n; i++ {
c.Push(i)
}
fmt.Println(c.Len(), c.Cap())
for i := 0; i < n; i++ {
fmt.Print(c.Pop())
}
c.Close()
// Output:
//10 10
//0123456789
}

View File

@ -1,47 +0,0 @@
package gchan_test
import (
"errors"
"testing"
"github.com/gogf/gf/container/gchan"
"github.com/gogf/gf/test/gtest"
)
func Test_Gchan(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
ch := gchan.New(10)
t.Assert(ch.Cap(), 10)
t.Assert(ch.Push(1), nil)
t.Assert(ch.Len(), 1)
t.Assert(ch.Size(), 1)
ch.Pop()
t.Assert(ch.Len(), 0)
t.Assert(ch.Size(), 0)
ch.Close()
t.Assert(ch.Push(1), errors.New("channel is closed"))
ch = gchan.New(0)
ch1 := gchan.New(0)
go func() {
var i = 0
for {
v := ch.Pop()
if v == nil {
ch1.Push(i)
break
}
t.Assert(v, i)
i++
}
}()
for index := 0; index < 10; index++ {
ch.Push(index)
}
ch.Close()
t.Assert(ch1.Pop(), 10)
ch1.Close()
})
}

View File

@ -20,8 +20,8 @@ import (
type (
List struct {
mu *rwmutex.RWMutex
list *list.List
mu rwmutex.RWMutex
list list.List
}
Element = list.Element
@ -30,8 +30,8 @@ type (
// New creates and returns a new empty doubly linked list.
func New(safe ...bool) *List {
return &List{
mu: rwmutex.New(safe...),
list: list.New(),
mu: rwmutex.Create(safe...),
list: list.List{},
}
}
@ -39,12 +39,12 @@ func New(safe ...bool) *List {
// The parameter <safe> is used to specify whether using list in concurrent-safety,
// which is false in default.
func NewFrom(array []interface{}, safe ...bool) *List {
l := list.New()
l := list.List{}
for _, v := range array {
l.PushBack(v)
}
return &List{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
list: l,
}
}
@ -273,7 +273,7 @@ func (l *List) PushBackList(other *List) {
defer other.mu.RUnlock()
}
l.mu.Lock()
l.list.PushBackList(other.list)
l.list.PushBackList(&other.list)
l.mu.Unlock()
}
@ -285,7 +285,7 @@ func (l *List) PushFrontList(other *List) {
defer other.mu.RUnlock()
}
l.mu.Lock()
l.list.PushFrontList(other.list)
l.list.PushFrontList(&other.list)
l.mu.Unlock()
}
@ -332,7 +332,7 @@ func (l *List) Removes(es []*Element) {
// RemoveAll removes all elements from list <l>.
func (l *List) RemoveAll() {
l.mu.Lock()
l.list = list.New()
l.list = list.List{}
l.mu.Unlock()
}
@ -345,14 +345,14 @@ func (l *List) Clear() {
func (l *List) RLockFunc(f func(list *list.List)) {
l.mu.RLock()
defer l.mu.RUnlock()
f(l.list)
f(&l.list)
}
// LockFunc locks writing with given callback function <f> within RWMutex.Lock.
func (l *List) LockFunc(f func(list *list.List)) {
l.mu.Lock()
defer l.mu.Unlock()
f(l.list)
f(&l.list)
}
// Iterator is alias of IteratorAsc.
@ -360,7 +360,7 @@ func (l *List) Iterator(f func(e *Element) bool) {
l.IteratorAsc(f)
}
// IteratorAsc iterates the list in ascending order with given callback function <f>.
// IteratorAsc iterates the list readonly in ascending order with given callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
func (l *List) IteratorAsc(f func(e *Element) bool) {
l.mu.RLock()
@ -375,7 +375,7 @@ func (l *List) IteratorAsc(f func(e *Element) bool) {
}
}
// IteratorDesc iterates the list in descending order with given callback function <f>.
// IteratorDesc iterates the list readonly in descending order with given callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
func (l *List) IteratorDesc(f func(e *Element) bool) {
l.mu.RLock()
@ -425,10 +425,6 @@ func (l *List) MarshalJSON() ([]byte, error) {
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
func (l *List) UnmarshalJSON(b []byte) error {
if l.mu == nil {
l.mu = rwmutex.New()
l.list = list.New()
}
l.mu.Lock()
defer l.mu.Unlock()
var array []interface{}
@ -441,10 +437,6 @@ func (l *List) UnmarshalJSON(b []byte) error {
// UnmarshalValue is an interface implement which sets any type of value for list.
func (l *List) UnmarshalValue(value interface{}) (err error) {
if l.mu == nil {
l.mu = rwmutex.New()
l.list = list.New()
}
l.mu.Lock()
defer l.mu.Unlock()
var array []interface{}

View File

@ -41,6 +41,44 @@ func checkListPointers(t *gtest.T, l *List, es []*Element) {
})
}
func TestVar(t *testing.T) {
var l List
l.PushFront(1)
l.PushFront(2)
if v := l.PopBack(); v != 1 {
t.Errorf("EXPECT %v, GOT %v", 1, v)
} else {
//fmt.Println(v)
}
if v := l.PopBack(); v != 2 {
t.Errorf("EXPECT %v, GOT %v", 2, v)
} else {
//fmt.Println(v)
}
if v := l.PopBack(); v != nil {
t.Errorf("EXPECT %v, GOT %v", nil, v)
} else {
//fmt.Println(v)
}
l.PushBack(1)
l.PushBack(2)
if v := l.PopFront(); v != 1 {
t.Errorf("EXPECT %v, GOT %v", 1, v)
} else {
//fmt.Println(v)
}
if v := l.PopFront(); v != 2 {
t.Errorf("EXPECT %v, GOT %v", 2, v)
} else {
//fmt.Println(v)
}
if v := l.PopFront(); v != nil {
t.Errorf("EXPECT %v, GOT %v", nil, v)
} else {
//fmt.Println(v)
}
}
func TestBasic(t *testing.T) {
l := New()
l.PushFront(1)

View File

@ -7,9 +7,10 @@
// Package gmap provides concurrent-safe/unsafe map containers.
package gmap
// Map based on hash table, alias of AnyAnyMap.
type Map = AnyAnyMap
type HashMap = AnyAnyMap
type (
Map = AnyAnyMap // Map is alias of AnyAnyMap.
HashMap = AnyAnyMap // HashMap is alias of AnyAnyMap.
)
// New creates and returns an empty hash map.
// The parameter <safe> is used to specify whether using map in concurrent-safety,

View File

@ -18,7 +18,7 @@ import (
)
type AnyAnyMap struct {
mu *rwmutex.RWMutex
mu rwmutex.RWMutex
data map[interface{}]interface{}
}
@ -27,7 +27,7 @@ type AnyAnyMap struct {
// which is false in default.
func NewAnyAnyMap(safe ...bool) *AnyAnyMap {
return &AnyAnyMap{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
data: make(map[interface{}]interface{}),
}
}
@ -37,12 +37,12 @@ func NewAnyAnyMap(safe ...bool) *AnyAnyMap {
// there might be some concurrent-safe issues when changing the map outside.
func NewAnyAnyMapFrom(data map[interface{}]interface{}, safe ...bool) *AnyAnyMap {
return &AnyAnyMap{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
data: data,
}
}
// Iterator iterates the hash map with custom callback function <f>.
// Iterator iterates the hash map readonly with custom callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
func (m *AnyAnyMap) Iterator(f func(k interface{}, v interface{}) bool) {
m.mu.RLock()
@ -89,37 +89,44 @@ func (m *AnyAnyMap) MapCopy() map[interface{}]interface{} {
// MapStrAny returns a copy of the underlying data of the map as map[string]interface{}.
func (m *AnyAnyMap) MapStrAny() map[string]interface{} {
m.mu.RLock()
defer m.mu.RUnlock()
data := make(map[string]interface{}, len(m.data))
for k, v := range m.data {
data[gconv.String(k)] = v
}
m.mu.RUnlock()
return data
}
// FilterEmpty deletes all key-value pair of which the value is empty.
func (m *AnyAnyMap) FilterEmpty() {
m.mu.Lock()
defer m.mu.Unlock()
for k, v := range m.data {
if empty.IsEmpty(v) {
delete(m.data, k)
}
}
m.mu.Unlock()
}
// Set sets key-value to the hash map.
func (m *AnyAnyMap) Set(key interface{}, val interface{}) {
func (m *AnyAnyMap) Set(key interface{}, value interface{}) {
m.mu.Lock()
m.data[key] = val
if m.data == nil {
m.data = make(map[interface{}]interface{})
}
m.data[key] = value
m.mu.Unlock()
}
// Sets batch sets key-values to the hash map.
func (m *AnyAnyMap) Sets(data map[interface{}]interface{}) {
m.mu.Lock()
for k, v := range data {
m.data[k] = v
if m.data == nil {
m.data = data
} else {
for k, v := range data {
m.data[k] = v
}
}
m.mu.Unlock()
}
@ -128,17 +135,21 @@ func (m *AnyAnyMap) Sets(data map[interface{}]interface{}) {
// Second return parameter <found> is true if key was found, otherwise false.
func (m *AnyAnyMap) Search(key interface{}) (value interface{}, found bool) {
m.mu.RLock()
value, found = m.data[key]
if m.data != nil {
value, found = m.data[key]
}
m.mu.RUnlock()
return
}
// Get returns the value by given <key>.
func (m *AnyAnyMap) Get(key interface{}) interface{} {
func (m *AnyAnyMap) Get(key interface{}) (value interface{}) {
m.mu.RLock()
val, _ := m.data[key]
if m.data != nil {
value, _ = m.data[key]
}
m.mu.RUnlock()
return val
return
}
// Pop retrieves and deletes an item from the map.
@ -163,8 +174,10 @@ func (m *AnyAnyMap) Pops(size int) map[interface{}]interface{} {
if size == 0 {
return nil
}
index := 0
newMap := make(map[interface{}]interface{}, size)
var (
index = 0
newMap = make(map[interface{}]interface{}, size)
)
for k, v := range m.data {
delete(m.data, k)
newMap[k] = v
@ -188,6 +201,9 @@ func (m *AnyAnyMap) Pops(size int) map[interface{}]interface{} {
func (m *AnyAnyMap) doSetWithLockCheck(key interface{}, value interface{}) interface{} {
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[interface{}]interface{})
}
if v, ok := m.data[key]; ok {
return v
}
@ -235,26 +251,26 @@ func (m *AnyAnyMap) GetOrSetFuncLock(key interface{}, f func() interface{}) inte
}
}
// GetVar returns a gvar.Var with the value by given <key>.
// The returned gvar.Var is un-concurrent safe.
// GetVar returns a Var with the value by given <key>.
// The returned Var is un-concurrent safe.
func (m *AnyAnyMap) GetVar(key interface{}) *gvar.Var {
return gvar.New(m.Get(key))
}
// GetVarOrSet returns a gvar.Var with result from GetVarOrSet.
// The returned gvar.Var is un-concurrent safe.
// GetVarOrSet returns a Var with result from GetVarOrSet.
// The returned Var is un-concurrent safe.
func (m *AnyAnyMap) GetVarOrSet(key interface{}, value interface{}) *gvar.Var {
return gvar.New(m.GetOrSet(key, value))
}
// GetVarOrSetFunc returns a gvar.Var with result from GetOrSetFunc.
// The returned gvar.Var is un-concurrent safe.
// GetVarOrSetFunc returns a Var with result from GetOrSetFunc.
// The returned Var is un-concurrent safe.
func (m *AnyAnyMap) GetVarOrSetFunc(key interface{}, f func() interface{}) *gvar.Var {
return gvar.New(m.GetOrSetFunc(key, f))
}
// GetVarOrSetFuncLock returns a gvar.Var with result from GetOrSetFuncLock.
// The returned gvar.Var is un-concurrent safe.
// GetVarOrSetFuncLock returns a Var with result from GetOrSetFuncLock.
// The returned Var is un-concurrent safe.
func (m *AnyAnyMap) GetVarOrSetFuncLock(key interface{}, f func() interface{}) *gvar.Var {
return gvar.New(m.GetOrSetFuncLock(key, f))
}
@ -293,21 +309,25 @@ func (m *AnyAnyMap) SetIfNotExistFuncLock(key interface{}, f func() interface{})
}
// Remove deletes value from map by given <key>, and return this deleted value.
func (m *AnyAnyMap) Remove(key interface{}) interface{} {
func (m *AnyAnyMap) Remove(key interface{}) (value interface{}) {
m.mu.Lock()
val, exists := m.data[key]
if exists {
delete(m.data, key)
if m.data != nil {
var ok bool
if value, ok = m.data[key]; ok {
delete(m.data, key)
}
}
m.mu.Unlock()
return val
return
}
// Removes batch deletes values of the map by keys.
func (m *AnyAnyMap) Removes(keys []interface{}) {
m.mu.Lock()
for _, key := range keys {
delete(m.data, key)
if m.data != nil {
for _, key := range keys {
delete(m.data, key)
}
}
m.mu.Unlock()
}
@ -315,36 +335,43 @@ func (m *AnyAnyMap) Removes(keys []interface{}) {
// Keys returns all keys of the map as a slice.
func (m *AnyAnyMap) Keys() []interface{} {
m.mu.RLock()
keys := make([]interface{}, len(m.data))
index := 0
defer m.mu.RUnlock()
var (
keys = make([]interface{}, len(m.data))
index = 0
)
for key := range m.data {
keys[index] = key
index++
}
m.mu.RUnlock()
return keys
}
// Values returns all values of the map as a slice.
func (m *AnyAnyMap) Values() []interface{} {
m.mu.RLock()
values := make([]interface{}, len(m.data))
index := 0
defer m.mu.RUnlock()
var (
values = make([]interface{}, len(m.data))
index = 0
)
for _, value := range m.data {
values[index] = value
index++
}
m.mu.RUnlock()
return values
}
// Contains checks whether a key exists.
// It returns true if the <key> exists, or else false.
func (m *AnyAnyMap) Contains(key interface{}) bool {
var ok bool
m.mu.RLock()
_, exists := m.data[key]
if m.data != nil {
_, ok = m.data[key]
}
m.mu.RUnlock()
return exists
return ok
}
// Size returns the size of the map.
@ -405,6 +432,10 @@ func (m *AnyAnyMap) Flip() {
func (m *AnyAnyMap) Merge(other *AnyAnyMap) {
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = other.MapCopy()
return
}
if other != m {
other.mu.RLock()
defer other.mu.RUnlock()
@ -421,12 +452,11 @@ func (m *AnyAnyMap) MarshalJSON() ([]byte, error) {
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
func (m *AnyAnyMap) UnmarshalJSON(b []byte) error {
if m.mu == nil {
m.mu = rwmutex.New()
m.data = make(map[interface{}]interface{})
}
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[interface{}]interface{})
}
var data map[string]interface{}
if err := json.Unmarshal(b, &data); err != nil {
return err
@ -439,12 +469,11 @@ func (m *AnyAnyMap) UnmarshalJSON(b []byte) error {
// UnmarshalValue is an interface implement which sets any type of value for map.
func (m *AnyAnyMap) UnmarshalValue(value interface{}) (err error) {
if m.mu == nil {
m.mu = rwmutex.New()
m.data = make(map[interface{}]interface{})
}
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[interface{}]interface{})
}
for k, v := range gconv.Map(value) {
m.data[k] = v
}

View File

@ -18,7 +18,7 @@ import (
)
type IntAnyMap struct {
mu *rwmutex.RWMutex
mu rwmutex.RWMutex
data map[int]interface{}
}
@ -27,7 +27,7 @@ type IntAnyMap struct {
// which is false in default.
func NewIntAnyMap(safe ...bool) *IntAnyMap {
return &IntAnyMap{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
data: make(map[int]interface{}),
}
}
@ -37,12 +37,12 @@ func NewIntAnyMap(safe ...bool) *IntAnyMap {
// there might be some concurrent-safe issues when changing the map outside.
func NewIntAnyMapFrom(data map[int]interface{}, safe ...bool) *IntAnyMap {
return &IntAnyMap{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
data: data,
}
}
// Iterator iterates the hash map with custom callback function <f>.
// Iterator iterates the hash map readonly with custom callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
func (m *IntAnyMap) Iterator(f func(k int, v interface{}) bool) {
m.mu.RLock()
@ -111,6 +111,9 @@ func (m *IntAnyMap) FilterEmpty() {
// Set sets key-value to the hash map.
func (m *IntAnyMap) Set(key int, val interface{}) {
m.mu.Lock()
if m.data == nil {
m.data = make(map[int]interface{})
}
m.data[key] = val
m.mu.Unlock()
}
@ -118,8 +121,12 @@ func (m *IntAnyMap) Set(key int, val interface{}) {
// Sets batch sets key-values to the hash map.
func (m *IntAnyMap) Sets(data map[int]interface{}) {
m.mu.Lock()
for k, v := range data {
m.data[k] = v
if m.data == nil {
m.data = data
} else {
for k, v := range data {
m.data[k] = v
}
}
m.mu.Unlock()
}
@ -128,17 +135,21 @@ func (m *IntAnyMap) Sets(data map[int]interface{}) {
// Second return parameter <found> is true if key was found, otherwise false.
func (m *IntAnyMap) Search(key int) (value interface{}, found bool) {
m.mu.RLock()
value, found = m.data[key]
if m.data != nil {
value, found = m.data[key]
}
m.mu.RUnlock()
return
}
// Get returns the value by given <key>.
func (m *IntAnyMap) Get(key int) interface{} {
func (m *IntAnyMap) Get(key int) (value interface{}) {
m.mu.RLock()
val, _ := m.data[key]
if m.data != nil {
value, _ = m.data[key]
}
m.mu.RUnlock()
return val
return
}
// Pop retrieves and deletes an item from the map.
@ -163,8 +174,10 @@ func (m *IntAnyMap) Pops(size int) map[int]interface{} {
if size == 0 {
return nil
}
index := 0
newMap := make(map[int]interface{}, size)
var (
index = 0
newMap = make(map[int]interface{}, size)
)
for k, v := range m.data {
delete(m.data, k)
newMap[k] = v
@ -188,6 +201,9 @@ func (m *IntAnyMap) Pops(size int) map[int]interface{} {
func (m *IntAnyMap) doSetWithLockCheck(key int, value interface{}) interface{} {
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[int]interface{})
}
if v, ok := m.data[key]; ok {
return v
}
@ -233,26 +249,26 @@ func (m *IntAnyMap) GetOrSetFuncLock(key int, f func() interface{}) interface{}
}
}
// GetVar returns a gvar.Var with the value by given <key>.
// The returned gvar.Var is un-concurrent safe.
// GetVar returns a Var with the value by given <key>.
// The returned Var is un-concurrent safe.
func (m *IntAnyMap) GetVar(key int) *gvar.Var {
return gvar.New(m.Get(key))
}
// GetVarOrSet returns a gvar.Var with result from GetVarOrSet.
// The returned gvar.Var is un-concurrent safe.
// GetVarOrSet returns a Var with result from GetVarOrSet.
// The returned Var is un-concurrent safe.
func (m *IntAnyMap) GetVarOrSet(key int, value interface{}) *gvar.Var {
return gvar.New(m.GetOrSet(key, value))
}
// GetVarOrSetFunc returns a gvar.Var with result from GetOrSetFunc.
// The returned gvar.Var is un-concurrent safe.
// GetVarOrSetFunc returns a Var with result from GetOrSetFunc.
// The returned Var is un-concurrent safe.
func (m *IntAnyMap) GetVarOrSetFunc(key int, f func() interface{}) *gvar.Var {
return gvar.New(m.GetOrSetFunc(key, f))
}
// GetVarOrSetFuncLock returns a gvar.Var with result from GetOrSetFuncLock.
// The returned gvar.Var is un-concurrent safe.
// GetVarOrSetFuncLock returns a Var with result from GetOrSetFuncLock.
// The returned Var is un-concurrent safe.
func (m *IntAnyMap) GetVarOrSetFuncLock(key int, f func() interface{}) *gvar.Var {
return gvar.New(m.GetOrSetFuncLock(key, f))
}
@ -293,28 +309,34 @@ func (m *IntAnyMap) SetIfNotExistFuncLock(key int, f func() interface{}) bool {
// Removes batch deletes values of the map by keys.
func (m *IntAnyMap) Removes(keys []int) {
m.mu.Lock()
for _, key := range keys {
delete(m.data, key)
if m.data != nil {
for _, key := range keys {
delete(m.data, key)
}
}
m.mu.Unlock()
}
// Remove deletes value from map by given <key>, and return this deleted value.
func (m *IntAnyMap) Remove(key int) interface{} {
func (m *IntAnyMap) Remove(key int) (value interface{}) {
m.mu.Lock()
val, exists := m.data[key]
if exists {
delete(m.data, key)
if m.data != nil {
var ok bool
if value, ok = m.data[key]; ok {
delete(m.data, key)
}
}
m.mu.Unlock()
return val
return
}
// Keys returns all keys of the map as a slice.
func (m *IntAnyMap) Keys() []int {
m.mu.RLock()
keys := make([]int, len(m.data))
index := 0
var (
keys = make([]int, len(m.data))
index = 0
)
for key := range m.data {
keys[index] = key
index++
@ -326,8 +348,10 @@ func (m *IntAnyMap) Keys() []int {
// Values returns all values of the map as a slice.
func (m *IntAnyMap) Values() []interface{} {
m.mu.RLock()
values := make([]interface{}, len(m.data))
index := 0
var (
values = make([]interface{}, len(m.data))
index = 0
)
for _, value := range m.data {
values[index] = value
index++
@ -339,10 +363,13 @@ func (m *IntAnyMap) Values() []interface{} {
// Contains checks whether a key exists.
// It returns true if the <key> exists, or else false.
func (m *IntAnyMap) Contains(key int) bool {
var ok bool
m.mu.RLock()
_, exists := m.data[key]
if m.data != nil {
_, ok = m.data[key]
}
m.mu.RUnlock()
return exists
return ok
}
// Size returns the size of the map.
@ -403,6 +430,10 @@ func (m *IntAnyMap) Flip() {
func (m *IntAnyMap) Merge(other *IntAnyMap) {
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = other.MapCopy()
return
}
if other != m {
other.mu.RLock()
defer other.mu.RUnlock()
@ -421,12 +452,11 @@ func (m *IntAnyMap) MarshalJSON() ([]byte, error) {
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
func (m *IntAnyMap) UnmarshalJSON(b []byte) error {
if m.mu == nil {
m.mu = rwmutex.New()
m.data = make(map[int]interface{})
}
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[int]interface{})
}
if err := json.Unmarshal(b, &m.data); err != nil {
return err
}
@ -435,12 +465,11 @@ func (m *IntAnyMap) UnmarshalJSON(b []byte) error {
// UnmarshalValue is an interface implement which sets any type of value for map.
func (m *IntAnyMap) UnmarshalValue(value interface{}) (err error) {
if m.mu == nil {
m.mu = rwmutex.New()
m.data = make(map[int]interface{})
}
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[int]interface{})
}
switch value.(type) {
case string, []byte:
return json.Unmarshal(gconv.Bytes(value), &m.data)

View File

@ -16,7 +16,7 @@ import (
)
type IntIntMap struct {
mu *rwmutex.RWMutex
mu rwmutex.RWMutex
data map[int]int
}
@ -25,7 +25,7 @@ type IntIntMap struct {
// which is false in default.
func NewIntIntMap(safe ...bool) *IntIntMap {
return &IntIntMap{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
data: make(map[int]int),
}
}
@ -35,12 +35,12 @@ func NewIntIntMap(safe ...bool) *IntIntMap {
// there might be some concurrent-safe issues when changing the map outside.
func NewIntIntMapFrom(data map[int]int, safe ...bool) *IntIntMap {
return &IntIntMap{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
data: data,
}
}
// Iterator iterates the hash map with custom callback function <f>.
// Iterator iterates the hash map readonly with custom callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
func (m *IntIntMap) Iterator(f func(k int, v int) bool) {
m.mu.RLock()
@ -109,6 +109,9 @@ func (m *IntIntMap) FilterEmpty() {
// Set sets key-value to the hash map.
func (m *IntIntMap) Set(key int, val int) {
m.mu.Lock()
if m.data == nil {
m.data = make(map[int]int)
}
m.data[key] = val
m.mu.Unlock()
}
@ -116,8 +119,12 @@ func (m *IntIntMap) Set(key int, val int) {
// Sets batch sets key-values to the hash map.
func (m *IntIntMap) Sets(data map[int]int) {
m.mu.Lock()
for k, v := range data {
m.data[k] = v
if m.data == nil {
m.data = data
} else {
for k, v := range data {
m.data[k] = v
}
}
m.mu.Unlock()
}
@ -126,17 +133,21 @@ func (m *IntIntMap) Sets(data map[int]int) {
// Second return parameter <found> is true if key was found, otherwise false.
func (m *IntIntMap) Search(key int) (value int, found bool) {
m.mu.RLock()
value, found = m.data[key]
if m.data != nil {
value, found = m.data[key]
}
m.mu.RUnlock()
return
}
// Get returns the value by given <key>.
func (m *IntIntMap) Get(key int) int {
func (m *IntIntMap) Get(key int) (value int) {
m.mu.RLock()
val, _ := m.data[key]
if m.data != nil {
value, _ = m.data[key]
}
m.mu.RUnlock()
return val
return
}
// Pop retrieves and deletes an item from the map.
@ -161,8 +172,10 @@ func (m *IntIntMap) Pops(size int) map[int]int {
if size == 0 {
return nil
}
index := 0
newMap := make(map[int]int, size)
var (
index = 0
newMap = make(map[int]int, size)
)
for k, v := range m.data {
delete(m.data, k)
newMap[k] = v
@ -181,12 +194,14 @@ func (m *IntIntMap) Pops(size int) map[int]int {
// It returns value with given <key>.
func (m *IntIntMap) doSetWithLockCheck(key int, value int) int {
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[int]int)
}
if v, ok := m.data[key]; ok {
m.mu.Unlock()
return v
}
m.data[key] = value
m.mu.Unlock()
return value
}
@ -219,6 +234,9 @@ func (m *IntIntMap) GetOrSetFuncLock(key int, f func() int) int {
if v, ok := m.Search(key); !ok {
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[int]int)
}
if v, ok = m.data[key]; ok {
return v
}
@ -259,6 +277,9 @@ func (m *IntIntMap) SetIfNotExistFuncLock(key int, f func() int) bool {
if !m.Contains(key) {
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[int]int)
}
if _, ok := m.data[key]; !ok {
m.data[key] = f()
}
@ -270,28 +291,34 @@ func (m *IntIntMap) SetIfNotExistFuncLock(key int, f func() int) bool {
// Removes batch deletes values of the map by keys.
func (m *IntIntMap) Removes(keys []int) {
m.mu.Lock()
for _, key := range keys {
delete(m.data, key)
if m.data != nil {
for _, key := range keys {
delete(m.data, key)
}
}
m.mu.Unlock()
}
// Remove deletes value from map by given <key>, and return this deleted value.
func (m *IntIntMap) Remove(key int) int {
func (m *IntIntMap) Remove(key int) (value int) {
m.mu.Lock()
val, exists := m.data[key]
if exists {
delete(m.data, key)
if m.data != nil {
var ok bool
if value, ok = m.data[key]; ok {
delete(m.data, key)
}
}
m.mu.Unlock()
return val
return
}
// Keys returns all keys of the map as a slice.
func (m *IntIntMap) Keys() []int {
m.mu.RLock()
keys := make([]int, len(m.data))
index := 0
var (
keys = make([]int, len(m.data))
index = 0
)
for key := range m.data {
keys[index] = key
index++
@ -303,8 +330,10 @@ func (m *IntIntMap) Keys() []int {
// Values returns all values of the map as a slice.
func (m *IntIntMap) Values() []int {
m.mu.RLock()
values := make([]int, len(m.data))
index := 0
var (
values = make([]int, len(m.data))
index = 0
)
for _, value := range m.data {
values[index] = value
index++
@ -316,10 +345,13 @@ func (m *IntIntMap) Values() []int {
// Contains checks whether a key exists.
// It returns true if the <key> exists, or else false.
func (m *IntIntMap) Contains(key int) bool {
var ok bool
m.mu.RLock()
_, exists := m.data[key]
if m.data != nil {
_, ok = m.data[key]
}
m.mu.RUnlock()
return exists
return ok
}
// Size returns the size of the map.
@ -380,6 +412,10 @@ func (m *IntIntMap) Flip() {
func (m *IntIntMap) Merge(other *IntIntMap) {
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = other.MapCopy()
return
}
if other != m {
other.mu.RLock()
defer other.mu.RUnlock()
@ -398,12 +434,11 @@ func (m *IntIntMap) MarshalJSON() ([]byte, error) {
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
func (m *IntIntMap) UnmarshalJSON(b []byte) error {
if m.mu == nil {
m.mu = rwmutex.New()
m.data = make(map[int]int)
}
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[int]int)
}
if err := json.Unmarshal(b, &m.data); err != nil {
return err
}
@ -412,12 +447,11 @@ func (m *IntIntMap) UnmarshalJSON(b []byte) error {
// UnmarshalValue is an interface implement which sets any type of value for map.
func (m *IntIntMap) UnmarshalValue(value interface{}) (err error) {
if m.mu == nil {
m.mu = rwmutex.New()
m.data = make(map[int]int)
}
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[int]int)
}
switch value.(type) {
case string, []byte:
return json.Unmarshal(gconv.Bytes(value), &m.data)

View File

@ -16,7 +16,7 @@ import (
)
type IntStrMap struct {
mu *rwmutex.RWMutex
mu rwmutex.RWMutex
data map[int]string
}
@ -25,7 +25,7 @@ type IntStrMap struct {
// which is false in default.
func NewIntStrMap(safe ...bool) *IntStrMap {
return &IntStrMap{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
data: make(map[int]string),
}
}
@ -35,12 +35,12 @@ func NewIntStrMap(safe ...bool) *IntStrMap {
// there might be some concurrent-safe issues when changing the map outside.
func NewIntStrMapFrom(data map[int]string, safe ...bool) *IntStrMap {
return &IntStrMap{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
data: data,
}
}
// Iterator iterates the hash map with custom callback function <f>.
// Iterator iterates the hash map readonly with custom callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
func (m *IntStrMap) Iterator(f func(k int, v string) bool) {
m.mu.RLock()
@ -109,6 +109,9 @@ func (m *IntStrMap) FilterEmpty() {
// Set sets key-value to the hash map.
func (m *IntStrMap) Set(key int, val string) {
m.mu.Lock()
if m.data == nil {
m.data = make(map[int]string)
}
m.data[key] = val
m.mu.Unlock()
}
@ -116,8 +119,12 @@ func (m *IntStrMap) Set(key int, val string) {
// Sets batch sets key-values to the hash map.
func (m *IntStrMap) Sets(data map[int]string) {
m.mu.Lock()
for k, v := range data {
m.data[k] = v
if m.data == nil {
m.data = data
} else {
for k, v := range data {
m.data[k] = v
}
}
m.mu.Unlock()
}
@ -126,17 +133,21 @@ func (m *IntStrMap) Sets(data map[int]string) {
// Second return parameter <found> is true if key was found, otherwise false.
func (m *IntStrMap) Search(key int) (value string, found bool) {
m.mu.RLock()
value, found = m.data[key]
if m.data != nil {
value, found = m.data[key]
}
m.mu.RUnlock()
return
}
// Get returns the value by given <key>.
func (m *IntStrMap) Get(key int) string {
func (m *IntStrMap) Get(key int) (value string) {
m.mu.RLock()
val, _ := m.data[key]
if m.data != nil {
value, _ = m.data[key]
}
m.mu.RUnlock()
return val
return
}
// Pop retrieves and deletes an item from the map.
@ -161,8 +172,10 @@ func (m *IntStrMap) Pops(size int) map[int]string {
if size == 0 {
return nil
}
index := 0
newMap := make(map[int]string, size)
var (
index = 0
newMap = make(map[int]string, size)
)
for k, v := range m.data {
delete(m.data, k)
newMap[k] = v
@ -182,6 +195,9 @@ func (m *IntStrMap) Pops(size int) map[int]string {
func (m *IntStrMap) doSetWithLockCheck(key int, value string) string {
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[int]string)
}
if v, ok := m.data[key]; ok {
return v
}
@ -218,13 +234,14 @@ func (m *IntStrMap) GetOrSetFuncLock(key int, f func() string) string {
if v, ok := m.Search(key); !ok {
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[int]string)
}
if v, ok = m.data[key]; ok {
return v
}
v = f()
if v != "" {
m.data[key] = v
}
m.data[key] = v
return v
} else {
return v
@ -260,6 +277,9 @@ func (m *IntStrMap) SetIfNotExistFuncLock(key int, f func() string) bool {
if !m.Contains(key) {
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[int]string)
}
if _, ok := m.data[key]; !ok {
m.data[key] = f()
}
@ -271,28 +291,34 @@ func (m *IntStrMap) SetIfNotExistFuncLock(key int, f func() string) bool {
// Removes batch deletes values of the map by keys.
func (m *IntStrMap) Removes(keys []int) {
m.mu.Lock()
for _, key := range keys {
delete(m.data, key)
if m.data != nil {
for _, key := range keys {
delete(m.data, key)
}
}
m.mu.Unlock()
}
// Remove deletes value from map by given <key>, and return this deleted value.
func (m *IntStrMap) Remove(key int) string {
func (m *IntStrMap) Remove(key int) (value string) {
m.mu.Lock()
val, exists := m.data[key]
if exists {
delete(m.data, key)
if m.data != nil {
var ok bool
if value, ok = m.data[key]; ok {
delete(m.data, key)
}
}
m.mu.Unlock()
return val
return
}
// Keys returns all keys of the map as a slice.
func (m *IntStrMap) Keys() []int {
m.mu.RLock()
keys := make([]int, len(m.data))
index := 0
var (
keys = make([]int, len(m.data))
index = 0
)
for key := range m.data {
keys[index] = key
index++
@ -304,8 +330,10 @@ func (m *IntStrMap) Keys() []int {
// Values returns all values of the map as a slice.
func (m *IntStrMap) Values() []string {
m.mu.RLock()
values := make([]string, len(m.data))
index := 0
var (
values = make([]string, len(m.data))
index = 0
)
for _, value := range m.data {
values[index] = value
index++
@ -317,10 +345,13 @@ func (m *IntStrMap) Values() []string {
// Contains checks whether a key exists.
// It returns true if the <key> exists, or else false.
func (m *IntStrMap) Contains(key int) bool {
var ok bool
m.mu.RLock()
_, exists := m.data[key]
if m.data != nil {
_, ok = m.data[key]
}
m.mu.RUnlock()
return exists
return ok
}
// Size returns the size of the map.
@ -381,6 +412,10 @@ func (m *IntStrMap) Flip() {
func (m *IntStrMap) Merge(other *IntStrMap) {
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = other.MapCopy()
return
}
if other != m {
other.mu.RLock()
defer other.mu.RUnlock()
@ -399,12 +434,11 @@ func (m *IntStrMap) MarshalJSON() ([]byte, error) {
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
func (m *IntStrMap) UnmarshalJSON(b []byte) error {
if m.mu == nil {
m.mu = rwmutex.New()
m.data = make(map[int]string)
}
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[int]string)
}
if err := json.Unmarshal(b, &m.data); err != nil {
return err
}
@ -413,12 +447,11 @@ func (m *IntStrMap) UnmarshalJSON(b []byte) error {
// UnmarshalValue is an interface implement which sets any type of value for map.
func (m *IntStrMap) UnmarshalValue(value interface{}) (err error) {
if m.mu == nil {
m.mu = rwmutex.New()
m.data = make(map[int]string)
}
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[int]string)
}
switch value.(type) {
case string, []byte:
return json.Unmarshal(gconv.Bytes(value), &m.data)

View File

@ -18,7 +18,7 @@ import (
)
type StrAnyMap struct {
mu *rwmutex.RWMutex
mu rwmutex.RWMutex
data map[string]interface{}
}
@ -27,7 +27,7 @@ type StrAnyMap struct {
// which is false in default.
func NewStrAnyMap(safe ...bool) *StrAnyMap {
return &StrAnyMap{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
data: make(map[string]interface{}),
}
}
@ -37,12 +37,12 @@ func NewStrAnyMap(safe ...bool) *StrAnyMap {
// there might be some concurrent-safe issues when changing the map outside.
func NewStrAnyMapFrom(data map[string]interface{}, safe ...bool) *StrAnyMap {
return &StrAnyMap{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
data: data,
}
}
// Iterator iterates the hash map with custom callback function <f>.
// Iterator iterates the hash map readonly with custom callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
func (m *StrAnyMap) Iterator(f func(k string, v interface{}) bool) {
m.mu.RLock()
@ -105,6 +105,9 @@ func (m *StrAnyMap) FilterEmpty() {
// Set sets key-value to the hash map.
func (m *StrAnyMap) Set(key string, val interface{}) {
m.mu.Lock()
if m.data == nil {
m.data = make(map[string]interface{})
}
m.data[key] = val
m.mu.Unlock()
}
@ -112,8 +115,12 @@ func (m *StrAnyMap) Set(key string, val interface{}) {
// Sets batch sets key-values to the hash map.
func (m *StrAnyMap) Sets(data map[string]interface{}) {
m.mu.Lock()
for k, v := range data {
m.data[k] = v
if m.data == nil {
m.data = data
} else {
for k, v := range data {
m.data[k] = v
}
}
m.mu.Unlock()
}
@ -122,17 +129,21 @@ func (m *StrAnyMap) Sets(data map[string]interface{}) {
// Second return parameter <found> is true if key was found, otherwise false.
func (m *StrAnyMap) Search(key string) (value interface{}, found bool) {
m.mu.RLock()
value, found = m.data[key]
if m.data != nil {
value, found = m.data[key]
}
m.mu.RUnlock()
return
}
// Get returns the value by given <key>.
func (m *StrAnyMap) Get(key string) interface{} {
func (m *StrAnyMap) Get(key string) (value interface{}) {
m.mu.RLock()
val, _ := m.data[key]
if m.data != nil {
value, _ = m.data[key]
}
m.mu.RUnlock()
return val
return
}
// Pop retrieves and deletes an item from the map.
@ -157,8 +168,10 @@ func (m *StrAnyMap) Pops(size int) map[string]interface{} {
if size == 0 {
return nil
}
index := 0
newMap := make(map[string]interface{}, size)
var (
index = 0
newMap = make(map[string]interface{}, size)
)
for k, v := range m.data {
delete(m.data, k)
newMap[k] = v
@ -182,6 +195,9 @@ func (m *StrAnyMap) Pops(size int) map[string]interface{} {
func (m *StrAnyMap) doSetWithLockCheck(key string, value interface{}) interface{} {
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[string]interface{})
}
if v, ok := m.data[key]; ok {
return v
}
@ -229,26 +245,26 @@ func (m *StrAnyMap) GetOrSetFuncLock(key string, f func() interface{}) interface
}
}
// GetVar returns a gvar.Var with the value by given <key>.
// The returned gvar.Var is un-concurrent safe.
// GetVar returns a Var with the value by given <key>.
// The returned Var is un-concurrent safe.
func (m *StrAnyMap) GetVar(key string) *gvar.Var {
return gvar.New(m.Get(key))
}
// GetVarOrSet returns a gvar.Var with result from GetVarOrSet.
// The returned gvar.Var is un-concurrent safe.
// GetVarOrSet returns a Var with result from GetVarOrSet.
// The returned Var is un-concurrent safe.
func (m *StrAnyMap) GetVarOrSet(key string, value interface{}) *gvar.Var {
return gvar.New(m.GetOrSet(key, value))
}
// GetVarOrSetFunc returns a gvar.Var with result from GetOrSetFunc.
// The returned gvar.Var is un-concurrent safe.
// GetVarOrSetFunc returns a Var with result from GetOrSetFunc.
// The returned Var is un-concurrent safe.
func (m *StrAnyMap) GetVarOrSetFunc(key string, f func() interface{}) *gvar.Var {
return gvar.New(m.GetOrSetFunc(key, f))
}
// GetVarOrSetFuncLock returns a gvar.Var with result from GetOrSetFuncLock.
// The returned gvar.Var is un-concurrent safe.
// GetVarOrSetFuncLock returns a Var with result from GetOrSetFuncLock.
// The returned Var is un-concurrent safe.
func (m *StrAnyMap) GetVarOrSetFuncLock(key string, f func() interface{}) *gvar.Var {
return gvar.New(m.GetOrSetFuncLock(key, f))
}
@ -289,28 +305,34 @@ func (m *StrAnyMap) SetIfNotExistFuncLock(key string, f func() interface{}) bool
// Removes batch deletes values of the map by keys.
func (m *StrAnyMap) Removes(keys []string) {
m.mu.Lock()
for _, key := range keys {
delete(m.data, key)
if m.data != nil {
for _, key := range keys {
delete(m.data, key)
}
}
m.mu.Unlock()
}
// Remove deletes value from map by given <key>, and return this deleted value.
func (m *StrAnyMap) Remove(key string) interface{} {
func (m *StrAnyMap) Remove(key string) (value interface{}) {
m.mu.Lock()
val, exists := m.data[key]
if exists {
delete(m.data, key)
if m.data != nil {
var ok bool
if value, ok = m.data[key]; ok {
delete(m.data, key)
}
}
m.mu.Unlock()
return val
return
}
// Keys returns all keys of the map as a slice.
func (m *StrAnyMap) Keys() []string {
m.mu.RLock()
keys := make([]string, len(m.data))
index := 0
var (
keys = make([]string, len(m.data))
index = 0
)
for key := range m.data {
keys[index] = key
index++
@ -322,8 +344,10 @@ func (m *StrAnyMap) Keys() []string {
// Values returns all values of the map as a slice.
func (m *StrAnyMap) Values() []interface{} {
m.mu.RLock()
values := make([]interface{}, len(m.data))
index := 0
var (
values = make([]interface{}, len(m.data))
index = 0
)
for _, value := range m.data {
values[index] = value
index++
@ -335,10 +359,13 @@ func (m *StrAnyMap) Values() []interface{} {
// Contains checks whether a key exists.
// It returns true if the <key> exists, or else false.
func (m *StrAnyMap) Contains(key string) bool {
var ok bool
m.mu.RLock()
_, exists := m.data[key]
if m.data != nil {
_, ok = m.data[key]
}
m.mu.RUnlock()
return exists
return ok
}
// Size returns the size of the map.
@ -399,6 +426,10 @@ func (m *StrAnyMap) Flip() {
func (m *StrAnyMap) Merge(other *StrAnyMap) {
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = other.MapCopy()
return
}
if other != m {
other.mu.RLock()
defer other.mu.RUnlock()
@ -417,12 +448,11 @@ func (m *StrAnyMap) MarshalJSON() ([]byte, error) {
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
func (m *StrAnyMap) UnmarshalJSON(b []byte) error {
if m.mu == nil {
m.mu = rwmutex.New()
m.data = make(map[string]interface{})
}
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[string]interface{})
}
if err := json.Unmarshal(b, &m.data); err != nil {
return err
}
@ -431,10 +461,6 @@ func (m *StrAnyMap) UnmarshalJSON(b []byte) error {
// UnmarshalValue is an interface implement which sets any type of value for map.
func (m *StrAnyMap) UnmarshalValue(value interface{}) (err error) {
if m.mu == nil {
m.mu = rwmutex.New()
m.data = make(map[string]interface{})
}
m.mu.Lock()
defer m.mu.Unlock()
m.data = gconv.Map(value)

View File

@ -16,7 +16,7 @@ import (
)
type StrIntMap struct {
mu *rwmutex.RWMutex
mu rwmutex.RWMutex
data map[string]int
}
@ -25,7 +25,7 @@ type StrIntMap struct {
// which is false in default.
func NewStrIntMap(safe ...bool) *StrIntMap {
return &StrIntMap{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
data: make(map[string]int),
}
}
@ -35,12 +35,12 @@ func NewStrIntMap(safe ...bool) *StrIntMap {
// there might be some concurrent-safe issues when changing the map outside.
func NewStrIntMapFrom(data map[string]int, safe ...bool) *StrIntMap {
return &StrIntMap{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
data: data,
}
}
// Iterator iterates the hash map with custom callback function <f>.
// Iterator iterates the hash map readonly with custom callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
func (m *StrIntMap) Iterator(f func(k string, v int) bool) {
m.mu.RLock()
@ -76,11 +76,11 @@ func (m *StrIntMap) Map() map[string]int {
// MapStrAny returns a copy of the underlying data of the map as map[string]interface{}.
func (m *StrIntMap) MapStrAny() map[string]interface{} {
m.mu.RLock()
defer m.mu.RUnlock()
data := make(map[string]interface{}, len(m.data))
for k, v := range m.data {
data[k] = v
}
m.mu.RUnlock()
return data
}
@ -109,6 +109,9 @@ func (m *StrIntMap) FilterEmpty() {
// Set sets key-value to the hash map.
func (m *StrIntMap) Set(key string, val int) {
m.mu.Lock()
if m.data == nil {
m.data = make(map[string]int)
}
m.data[key] = val
m.mu.Unlock()
}
@ -116,8 +119,12 @@ func (m *StrIntMap) Set(key string, val int) {
// Sets batch sets key-values to the hash map.
func (m *StrIntMap) Sets(data map[string]int) {
m.mu.Lock()
for k, v := range data {
m.data[k] = v
if m.data == nil {
m.data = data
} else {
for k, v := range data {
m.data[k] = v
}
}
m.mu.Unlock()
}
@ -126,17 +133,21 @@ func (m *StrIntMap) Sets(data map[string]int) {
// Second return parameter <found> is true if key was found, otherwise false.
func (m *StrIntMap) Search(key string) (value int, found bool) {
m.mu.RLock()
value, found = m.data[key]
if m.data != nil {
value, found = m.data[key]
}
m.mu.RUnlock()
return
}
// Get returns the value by given <key>.
func (m *StrIntMap) Get(key string) int {
func (m *StrIntMap) Get(key string) (value int) {
m.mu.RLock()
val, _ := m.data[key]
if m.data != nil {
value, _ = m.data[key]
}
m.mu.RUnlock()
return val
return
}
// Pop retrieves and deletes an item from the map.
@ -161,8 +172,10 @@ func (m *StrIntMap) Pops(size int) map[string]int {
if size == 0 {
return nil
}
index := 0
newMap := make(map[string]int, size)
var (
index = 0
newMap = make(map[string]int, size)
)
for k, v := range m.data {
delete(m.data, k)
newMap[k] = v
@ -181,6 +194,9 @@ func (m *StrIntMap) Pops(size int) map[string]int {
// It returns value with given <key>.
func (m *StrIntMap) doSetWithLockCheck(key string, value int) int {
m.mu.Lock()
if m.data == nil {
m.data = make(map[string]int)
}
if v, ok := m.data[key]; ok {
m.mu.Unlock()
return v
@ -221,6 +237,9 @@ func (m *StrIntMap) GetOrSetFuncLock(key string, f func() int) int {
if v, ok := m.Search(key); !ok {
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[string]int)
}
if v, ok = m.data[key]; ok {
return v
}
@ -261,6 +280,9 @@ func (m *StrIntMap) SetIfNotExistFuncLock(key string, f func() int) bool {
if !m.Contains(key) {
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[string]int)
}
if _, ok := m.data[key]; !ok {
m.data[key] = f()
}
@ -272,28 +294,34 @@ func (m *StrIntMap) SetIfNotExistFuncLock(key string, f func() int) bool {
// Removes batch deletes values of the map by keys.
func (m *StrIntMap) Removes(keys []string) {
m.mu.Lock()
for _, key := range keys {
delete(m.data, key)
if m.data != nil {
for _, key := range keys {
delete(m.data, key)
}
}
m.mu.Unlock()
}
// Remove deletes value from map by given <key>, and return this deleted value.
func (m *StrIntMap) Remove(key string) int {
func (m *StrIntMap) Remove(key string) (value int) {
m.mu.Lock()
val, exists := m.data[key]
if exists {
delete(m.data, key)
if m.data != nil {
var ok bool
if value, ok = m.data[key]; ok {
delete(m.data, key)
}
}
m.mu.Unlock()
return val
return
}
// Keys returns all keys of the map as a slice.
func (m *StrIntMap) Keys() []string {
m.mu.RLock()
keys := make([]string, len(m.data))
index := 0
var (
keys = make([]string, len(m.data))
index = 0
)
for key := range m.data {
keys[index] = key
index++
@ -305,8 +333,10 @@ func (m *StrIntMap) Keys() []string {
// Values returns all values of the map as a slice.
func (m *StrIntMap) Values() []int {
m.mu.RLock()
values := make([]int, len(m.data))
index := 0
var (
values = make([]int, len(m.data))
index = 0
)
for _, value := range m.data {
values[index] = value
index++
@ -318,10 +348,13 @@ func (m *StrIntMap) Values() []int {
// Contains checks whether a key exists.
// It returns true if the <key> exists, or else false.
func (m *StrIntMap) Contains(key string) bool {
var ok bool
m.mu.RLock()
_, exists := m.data[key]
if m.data != nil {
_, ok = m.data[key]
}
m.mu.RUnlock()
return exists
return ok
}
// Size returns the size of the map.
@ -382,6 +415,10 @@ func (m *StrIntMap) Flip() {
func (m *StrIntMap) Merge(other *StrIntMap) {
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = other.MapCopy()
return
}
if other != m {
other.mu.RLock()
defer other.mu.RUnlock()
@ -400,12 +437,11 @@ func (m *StrIntMap) MarshalJSON() ([]byte, error) {
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
func (m *StrIntMap) UnmarshalJSON(b []byte) error {
if m.mu == nil {
m.mu = rwmutex.New()
m.data = make(map[string]int)
}
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[string]int)
}
if err := json.Unmarshal(b, &m.data); err != nil {
return err
}
@ -414,12 +450,11 @@ func (m *StrIntMap) UnmarshalJSON(b []byte) error {
// UnmarshalValue is an interface implement which sets any type of value for map.
func (m *StrIntMap) UnmarshalValue(value interface{}) (err error) {
if m.mu == nil {
m.mu = rwmutex.New()
m.data = make(map[string]int)
}
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[string]int)
}
switch value.(type) {
case string, []byte:
return json.Unmarshal(gconv.Bytes(value), &m.data)

View File

@ -17,7 +17,7 @@ import (
)
type StrStrMap struct {
mu *rwmutex.RWMutex
mu rwmutex.RWMutex
data map[string]string
}
@ -27,7 +27,7 @@ type StrStrMap struct {
func NewStrStrMap(safe ...bool) *StrStrMap {
return &StrStrMap{
data: make(map[string]string),
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
}
}
@ -36,12 +36,12 @@ func NewStrStrMap(safe ...bool) *StrStrMap {
// there might be some concurrent-safe issues when changing the map outside.
func NewStrStrMapFrom(data map[string]string, safe ...bool) *StrStrMap {
return &StrStrMap{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
data: data,
}
}
// Iterator iterates the hash map with custom callback function <f>.
// Iterator iterates the hash map readonly with custom callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
func (m *StrStrMap) Iterator(f func(k string, v string) bool) {
m.mu.RLock()
@ -110,6 +110,9 @@ func (m *StrStrMap) FilterEmpty() {
// Set sets key-value to the hash map.
func (m *StrStrMap) Set(key string, val string) {
m.mu.Lock()
if m.data == nil {
m.data = make(map[string]string)
}
m.data[key] = val
m.mu.Unlock()
}
@ -117,8 +120,12 @@ func (m *StrStrMap) Set(key string, val string) {
// Sets batch sets key-values to the hash map.
func (m *StrStrMap) Sets(data map[string]string) {
m.mu.Lock()
for k, v := range data {
m.data[k] = v
if m.data == nil {
m.data = data
} else {
for k, v := range data {
m.data[k] = v
}
}
m.mu.Unlock()
}
@ -127,17 +134,21 @@ func (m *StrStrMap) Sets(data map[string]string) {
// Second return parameter <found> is true if key was found, otherwise false.
func (m *StrStrMap) Search(key string) (value string, found bool) {
m.mu.RLock()
value, found = m.data[key]
if m.data != nil {
value, found = m.data[key]
}
m.mu.RUnlock()
return
}
// Get returns the value by given <key>.
func (m *StrStrMap) Get(key string) string {
func (m *StrStrMap) Get(key string) (value string) {
m.mu.RLock()
val, _ := m.data[key]
if m.data != nil {
value, _ = m.data[key]
}
m.mu.RUnlock()
return val
return
}
// Pop retrieves and deletes an item from the map.
@ -162,8 +173,10 @@ func (m *StrStrMap) Pops(size int) map[string]string {
if size == 0 {
return nil
}
index := 0
newMap := make(map[string]string, size)
var (
index = 0
newMap = make(map[string]string, size)
)
for k, v := range m.data {
delete(m.data, k)
newMap[k] = v
@ -183,6 +196,9 @@ func (m *StrStrMap) Pops(size int) map[string]string {
func (m *StrStrMap) doSetWithLockCheck(key string, value string) string {
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[string]string)
}
if v, ok := m.data[key]; ok {
return v
}
@ -221,13 +237,14 @@ func (m *StrStrMap) GetOrSetFuncLock(key string, f func() string) string {
if v, ok := m.Search(key); !ok {
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[string]string)
}
if v, ok = m.data[key]; ok {
return v
}
v = f()
if v != "" {
m.data[key] = v
}
m.data[key] = v
return v
} else {
return v
@ -263,6 +280,9 @@ func (m *StrStrMap) SetIfNotExistFuncLock(key string, f func() string) bool {
if !m.Contains(key) {
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[string]string)
}
if _, ok := m.data[key]; !ok {
m.data[key] = f()
}
@ -274,28 +294,34 @@ func (m *StrStrMap) SetIfNotExistFuncLock(key string, f func() string) bool {
// Removes batch deletes values of the map by keys.
func (m *StrStrMap) Removes(keys []string) {
m.mu.Lock()
for _, key := range keys {
delete(m.data, key)
if m.data != nil {
for _, key := range keys {
delete(m.data, key)
}
}
m.mu.Unlock()
}
// Remove deletes value from map by given <key>, and return this deleted value.
func (m *StrStrMap) Remove(key string) string {
func (m *StrStrMap) Remove(key string) (value string) {
m.mu.Lock()
val, exists := m.data[key]
if exists {
delete(m.data, key)
if m.data != nil {
var ok bool
if value, ok = m.data[key]; ok {
delete(m.data, key)
}
}
m.mu.Unlock()
return val
return
}
// Keys returns all keys of the map as a slice.
func (m *StrStrMap) Keys() []string {
m.mu.RLock()
keys := make([]string, len(m.data))
index := 0
var (
keys = make([]string, len(m.data))
index = 0
)
for key := range m.data {
keys[index] = key
index++
@ -307,8 +333,10 @@ func (m *StrStrMap) Keys() []string {
// Values returns all values of the map as a slice.
func (m *StrStrMap) Values() []string {
m.mu.RLock()
values := make([]string, len(m.data))
index := 0
var (
values = make([]string, len(m.data))
index = 0
)
for _, value := range m.data {
values[index] = value
index++
@ -320,10 +348,13 @@ func (m *StrStrMap) Values() []string {
// Contains checks whether a key exists.
// It returns true if the <key> exists, or else false.
func (m *StrStrMap) Contains(key string) bool {
var ok bool
m.mu.RLock()
_, exists := m.data[key]
if m.data != nil {
_, ok = m.data[key]
}
m.mu.RUnlock()
return exists
return ok
}
// Size returns the size of the map.
@ -384,6 +415,10 @@ func (m *StrStrMap) Flip() {
func (m *StrStrMap) Merge(other *StrStrMap) {
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = other.MapCopy()
return
}
if other != m {
other.mu.RLock()
defer other.mu.RUnlock()
@ -402,12 +437,11 @@ func (m *StrStrMap) MarshalJSON() ([]byte, error) {
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
func (m *StrStrMap) UnmarshalJSON(b []byte) error {
if m.mu == nil {
m.mu = rwmutex.New()
m.data = make(map[string]string)
}
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[string]string)
}
if err := json.Unmarshal(b, &m.data); err != nil {
return err
}
@ -416,9 +450,6 @@ func (m *StrStrMap) UnmarshalJSON(b []byte) error {
// UnmarshalValue is an interface implement which sets any type of value for map.
func (m *StrStrMap) UnmarshalValue(value interface{}) (err error) {
if m.mu == nil {
m.mu = rwmutex.New()
}
m.mu.Lock()
defer m.mu.Unlock()
m.data = gconv.MapStrStr(value)

View File

@ -19,7 +19,7 @@ import (
)
type ListMap struct {
mu *rwmutex.RWMutex
mu rwmutex.RWMutex
data map[interface{}]*glist.Element
list *glist.List
}
@ -35,7 +35,7 @@ type gListMapNode struct {
// which is false in default.
func NewListMap(safe ...bool) *ListMap {
return &ListMap{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
data: make(map[interface{}]*glist.Element),
list: glist.New(),
}
@ -55,28 +55,32 @@ func (m *ListMap) Iterator(f func(key, value interface{}) bool) {
m.IteratorAsc(f)
}
// IteratorAsc iterates the map in ascending order with given callback function <f>.
// IteratorAsc iterates the map readonly in ascending order with given callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
func (m *ListMap) IteratorAsc(f func(key interface{}, value interface{}) bool) {
m.mu.RLock()
defer m.mu.RUnlock()
node := (*gListMapNode)(nil)
m.list.IteratorAsc(func(e *glist.Element) bool {
node = e.Value.(*gListMapNode)
return f(node.key, node.value)
})
if m.list != nil {
node := (*gListMapNode)(nil)
m.list.IteratorAsc(func(e *glist.Element) bool {
node = e.Value.(*gListMapNode)
return f(node.key, node.value)
})
}
}
// IteratorDesc iterates the map in descending order with given callback function <f>.
// IteratorDesc iterates the map readonly in descending order with given callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
func (m *ListMap) IteratorDesc(f func(key interface{}, value interface{}) bool) {
m.mu.RLock()
defer m.mu.RUnlock()
node := (*gListMapNode)(nil)
m.list.IteratorDesc(func(e *glist.Element) bool {
node = e.Value.(*gListMapNode)
return f(node.key, node.value)
})
if m.list != nil {
node := (*gListMapNode)(nil)
m.list.IteratorDesc(func(e *glist.Element) bool {
node = e.Value.(*gListMapNode)
return f(node.key, node.value)
})
}
}
// Clone returns a new link map with copy of current map data.
@ -110,13 +114,16 @@ func (m *ListMap) Replace(data map[interface{}]interface{}) {
// Map returns a copy of the underlying data of the map.
func (m *ListMap) Map() map[interface{}]interface{} {
m.mu.RLock()
node := (*gListMapNode)(nil)
data := make(map[interface{}]interface{}, len(m.data))
m.list.IteratorAsc(func(e *glist.Element) bool {
node = e.Value.(*gListMapNode)
data[node.key] = node.value
return true
})
var node *gListMapNode
var data map[interface{}]interface{}
if m.list != nil {
data = make(map[interface{}]interface{}, len(m.data))
m.list.IteratorAsc(func(e *glist.Element) bool {
node = e.Value.(*gListMapNode)
data[node.key] = node.value
return true
})
}
m.mu.RUnlock()
return data
}
@ -124,13 +131,16 @@ func (m *ListMap) Map() map[interface{}]interface{} {
// MapStrAny returns a copy of the underlying data of the map as map[string]interface{}.
func (m *ListMap) MapStrAny() map[string]interface{} {
m.mu.RLock()
node := (*gListMapNode)(nil)
data := make(map[string]interface{}, len(m.data))
m.list.IteratorAsc(func(e *glist.Element) bool {
node = e.Value.(*gListMapNode)
data[gconv.String(node.key)] = node.value
return true
})
var node *gListMapNode
var data map[string]interface{}
if m.list != nil {
data = make(map[string]interface{}, len(m.data))
m.list.IteratorAsc(func(e *glist.Element) bool {
node = e.Value.(*gListMapNode)
data[gconv.String(node.key)] = node.value
return true
})
}
m.mu.RUnlock()
return data
}
@ -138,20 +148,22 @@ func (m *ListMap) MapStrAny() map[string]interface{} {
// FilterEmpty deletes all key-value pair of which the value is empty.
func (m *ListMap) FilterEmpty() {
m.mu.Lock()
keys := make([]interface{}, 0)
node := (*gListMapNode)(nil)
m.list.IteratorAsc(func(e *glist.Element) bool {
node = e.Value.(*gListMapNode)
if empty.IsEmpty(node.value) {
keys = append(keys, node.key)
}
return true
})
if len(keys) > 0 {
for _, key := range keys {
if e, ok := m.data[key]; ok {
delete(m.data, key)
m.list.Remove(e)
if m.list != nil {
keys := make([]interface{}, 0)
node := (*gListMapNode)(nil)
m.list.IteratorAsc(func(e *glist.Element) bool {
node = e.Value.(*gListMapNode)
if empty.IsEmpty(node.value) {
keys = append(keys, node.key)
}
return true
})
if len(keys) > 0 {
for _, key := range keys {
if e, ok := m.data[key]; ok {
delete(m.data, key)
m.list.Remove(e)
}
}
}
}
@ -161,6 +173,10 @@ func (m *ListMap) FilterEmpty() {
// Set sets key-value to the map.
func (m *ListMap) Set(key interface{}, value interface{}) {
m.mu.Lock()
if m.data == nil {
m.data = make(map[interface{}]*glist.Element)
m.list = glist.New()
}
if e, ok := m.data[key]; !ok {
m.data[key] = m.list.PushBack(&gListMapNode{key, value})
} else {
@ -172,6 +188,10 @@ func (m *ListMap) Set(key interface{}, value interface{}) {
// Sets batch sets key-values to the map.
func (m *ListMap) Sets(data map[interface{}]interface{}) {
m.mu.Lock()
if m.data == nil {
m.data = make(map[interface{}]*glist.Element)
m.list = glist.New()
}
for key, value := range data {
if e, ok := m.data[key]; !ok {
m.data[key] = m.list.PushBack(&gListMapNode{key, value})
@ -186,9 +206,11 @@ func (m *ListMap) Sets(data map[interface{}]interface{}) {
// Second return parameter <found> is true if key was found, otherwise false.
func (m *ListMap) Search(key interface{}) (value interface{}, found bool) {
m.mu.RLock()
if e, ok := m.data[key]; ok {
value = e.Value.(*gListMapNode).value
found = ok
if m.data != nil {
if e, ok := m.data[key]; ok {
value = e.Value.(*gListMapNode).value
found = ok
}
}
m.mu.RUnlock()
return
@ -197,8 +219,10 @@ func (m *ListMap) Search(key interface{}) (value interface{}, found bool) {
// Get returns the value by given <key>.
func (m *ListMap) Get(key interface{}) (value interface{}) {
m.mu.RLock()
if e, ok := m.data[key]; ok {
value = e.Value.(*gListMapNode).value
if m.data != nil {
if e, ok := m.data[key]; ok {
value = e.Value.(*gListMapNode).value
}
}
m.mu.RUnlock()
return
@ -255,6 +279,10 @@ func (m *ListMap) Pops(size int) map[interface{}]interface{} {
func (m *ListMap) doSetWithLockCheck(key interface{}, value interface{}) interface{} {
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[interface{}]*glist.Element)
m.list = glist.New()
}
if e, ok := m.data[key]; ok {
return e.Value.(*gListMapNode).value
}
@ -302,26 +330,26 @@ func (m *ListMap) GetOrSetFuncLock(key interface{}, f func() interface{}) interf
}
}
// GetVar returns a gvar.Var with the value by given <key>.
// The returned gvar.Var is un-concurrent safe.
// GetVar returns a Var with the value by given <key>.
// The returned Var is un-concurrent safe.
func (m *ListMap) GetVar(key interface{}) *gvar.Var {
return gvar.New(m.Get(key))
}
// GetVarOrSet returns a gvar.Var with result from GetVarOrSet.
// The returned gvar.Var is un-concurrent safe.
// GetVarOrSet returns a Var with result from GetVarOrSet.
// The returned Var is un-concurrent safe.
func (m *ListMap) GetVarOrSet(key interface{}, value interface{}) *gvar.Var {
return gvar.New(m.GetOrSet(key, value))
}
// GetVarOrSetFunc returns a gvar.Var with result from GetOrSetFunc.
// The returned gvar.Var is un-concurrent safe.
// GetVarOrSetFunc returns a Var with result from GetOrSetFunc.
// The returned Var is un-concurrent safe.
func (m *ListMap) GetVarOrSetFunc(key interface{}, f func() interface{}) *gvar.Var {
return gvar.New(m.GetOrSetFunc(key, f))
}
// GetVarOrSetFuncLock returns a gvar.Var with result from GetOrSetFuncLock.
// The returned gvar.Var is un-concurrent safe.
// GetVarOrSetFuncLock returns a Var with result from GetOrSetFuncLock.
// The returned Var is un-concurrent safe.
func (m *ListMap) GetVarOrSetFuncLock(key interface{}, f func() interface{}) *gvar.Var {
return gvar.New(m.GetOrSetFuncLock(key, f))
}
@ -362,10 +390,12 @@ func (m *ListMap) SetIfNotExistFuncLock(key interface{}, f func() interface{}) b
// Remove deletes value from map by given <key>, and return this deleted value.
func (m *ListMap) Remove(key interface{}) (value interface{}) {
m.mu.Lock()
if e, ok := m.data[key]; ok {
value = e.Value.(*gListMapNode).value
delete(m.data, key)
m.list.Remove(e)
if m.data != nil {
if e, ok := m.data[key]; ok {
value = e.Value.(*gListMapNode).value
delete(m.data, key)
m.list.Remove(e)
}
}
m.mu.Unlock()
return
@ -374,10 +404,12 @@ func (m *ListMap) Remove(key interface{}) (value interface{}) {
// Removes batch deletes values of the map by keys.
func (m *ListMap) Removes(keys []interface{}) {
m.mu.Lock()
for _, key := range keys {
if e, ok := m.data[key]; ok {
delete(m.data, key)
m.list.Remove(e)
if m.data != nil {
for _, key := range keys {
if e, ok := m.data[key]; ok {
delete(m.data, key)
m.list.Remove(e)
}
}
}
m.mu.Unlock()
@ -386,13 +418,17 @@ func (m *ListMap) Removes(keys []interface{}) {
// Keys returns all keys of the map as a slice in ascending order.
func (m *ListMap) Keys() []interface{} {
m.mu.RLock()
keys := make([]interface{}, m.list.Len())
index := 0
m.list.IteratorAsc(func(e *glist.Element) bool {
keys[index] = e.Value.(*gListMapNode).key
index++
return true
})
var (
keys = make([]interface{}, m.list.Len())
index = 0
)
if m.list != nil {
m.list.IteratorAsc(func(e *glist.Element) bool {
keys[index] = e.Value.(*gListMapNode).key
index++
return true
})
}
m.mu.RUnlock()
return keys
}
@ -400,13 +436,17 @@ func (m *ListMap) Keys() []interface{} {
// Values returns all values of the map as a slice.
func (m *ListMap) Values() []interface{} {
m.mu.RLock()
values := make([]interface{}, m.list.Len())
index := 0
m.list.IteratorAsc(func(e *glist.Element) bool {
values[index] = e.Value.(*gListMapNode).value
index++
return true
})
var (
values = make([]interface{}, m.list.Len())
index = 0
)
if m.list != nil {
m.list.IteratorAsc(func(e *glist.Element) bool {
values[index] = e.Value.(*gListMapNode).value
index++
return true
})
}
m.mu.RUnlock()
return values
}
@ -415,7 +455,9 @@ func (m *ListMap) Values() []interface{} {
// It returns true if the <key> exists, or else false.
func (m *ListMap) Contains(key interface{}) (ok bool) {
m.mu.RLock()
_, ok = m.data[key]
if m.data != nil {
_, ok = m.data[key]
}
m.mu.RUnlock()
return
}
@ -448,6 +490,10 @@ func (m *ListMap) Flip() {
func (m *ListMap) Merge(other *ListMap) {
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[interface{}]*glist.Element)
m.list = glist.New()
}
if other != m {
other.mu.RLock()
defer other.mu.RUnlock()
@ -471,13 +517,12 @@ func (m *ListMap) MarshalJSON() ([]byte, error) {
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
func (m *ListMap) UnmarshalJSON(b []byte) error {
if m.mu == nil {
m.mu = rwmutex.New()
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[interface{}]*glist.Element)
m.list = glist.New()
}
m.mu.Lock()
defer m.mu.Unlock()
var data map[string]interface{}
if err := json.Unmarshal(b, &data); err != nil {
return err
@ -494,13 +539,12 @@ func (m *ListMap) UnmarshalJSON(b []byte) error {
// UnmarshalValue is an interface implement which sets any type of value for map.
func (m *ListMap) UnmarshalValue(value interface{}) (err error) {
if m.mu == nil {
m.mu = rwmutex.New()
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[interface{}]*glist.Element)
m.list = glist.New()
}
m.mu.Lock()
defer m.mu.Unlock()
for k, v := range gconv.Map(value) {
if e, ok := m.data[k]; !ok {
m.data[k] = m.list.PushBack(&gListMapNode{k, v})

View File

@ -7,6 +7,7 @@
package gmap_test
import (
"github.com/gogf/gf/util/gutil"
"testing"
"github.com/gogf/gf/container/gmap"
@ -17,6 +18,55 @@ func getValue() interface{} {
return 3
}
func Test_Map_Var(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var m gmap.Map
m.Set(1, 11)
t.Assert(m.Get(1), 11)
})
gtest.C(t, func(t *gtest.T) {
var m gmap.IntAnyMap
m.Set(1, 11)
t.Assert(m.Get(1), 11)
})
gtest.C(t, func(t *gtest.T) {
var m gmap.IntIntMap
m.Set(1, 11)
t.Assert(m.Get(1), 11)
})
gtest.C(t, func(t *gtest.T) {
var m gmap.IntStrMap
m.Set(1, "11")
t.Assert(m.Get(1), "11")
})
gtest.C(t, func(t *gtest.T) {
var m gmap.StrAnyMap
m.Set("1", "11")
t.Assert(m.Get("1"), "11")
})
gtest.C(t, func(t *gtest.T) {
var m gmap.StrStrMap
m.Set("1", "11")
t.Assert(m.Get("1"), "11")
})
gtest.C(t, func(t *gtest.T) {
var m gmap.StrIntMap
m.Set("1", 11)
t.Assert(m.Get("1"), 11)
})
gtest.C(t, func(t *gtest.T) {
var m gmap.ListMap
m.Set("1", 11)
t.Assert(m.Get("1"), 11)
})
gtest.C(t, func(t *gtest.T) {
var m gmap.TreeMap
m.SetComparator(gutil.ComparatorString)
m.Set("1", 11)
t.Assert(m.Get("1"), 11)
})
}
func Test_Map_Basic(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
m := gmap.New()

View File

@ -17,8 +17,34 @@ import (
"github.com/gogf/gf/test/gtest"
)
func anyAnyCallBack(int, interface{}) bool {
return true
func Test_AnyAnyMap_Var(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var m gmap.AnyAnyMap
m.Set(1, 1)
t.Assert(m.Get(1), 1)
t.Assert(m.Size(), 1)
t.Assert(m.IsEmpty(), false)
t.Assert(m.GetOrSet(2, "2"), "2")
t.Assert(m.SetIfNotExist(2, "2"), false)
t.Assert(m.SetIfNotExist(3, 3), true)
t.Assert(m.Remove(2), "2")
t.Assert(m.Contains(2), false)
t.AssertIN(3, m.Keys())
t.AssertIN(1, m.Keys())
t.AssertIN(3, m.Values())
t.AssertIN(1, m.Values())
m.Flip()
t.Assert(m.Map(), map[interface{}]int{1: 1, 3: 3})
m.Clear()
t.Assert(m.Size(), 0)
t.Assert(m.IsEmpty(), true)
})
}
func Test_AnyAnyMap_Basic(t *testing.T) {

View File

@ -20,9 +20,37 @@ import (
func getAny() interface{} {
return 123
}
func intAnyCallBack(int, interface{}) bool {
return true
func Test_IntAnyMap_Var(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var m gmap.IntAnyMap
m.Set(1, 1)
t.Assert(m.Get(1), 1)
t.Assert(m.Size(), 1)
t.Assert(m.IsEmpty(), false)
t.Assert(m.GetOrSet(2, "2"), "2")
t.Assert(m.SetIfNotExist(2, "2"), false)
t.Assert(m.SetIfNotExist(3, 3), true)
t.Assert(m.Remove(2), "2")
t.Assert(m.Contains(2), false)
t.AssertIN(3, m.Keys())
t.AssertIN(1, m.Keys())
t.AssertIN(3, m.Values())
t.AssertIN(1, m.Values())
m.Flip()
t.Assert(m.Map(), map[interface{}]int{1: 1, 3: 3})
m.Clear()
t.Assert(m.Size(), 0)
t.Assert(m.IsEmpty(), true)
})
}
func Test_IntAnyMap_Basic(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
m := gmap.NewIntAnyMap()
@ -55,6 +83,7 @@ func Test_IntAnyMap_Basic(t *testing.T) {
t.Assert(m2.Map(), map[int]interface{}{1: 1, 2: "2"})
})
}
func Test_IntAnyMap_Set_Fun(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
m := gmap.NewIntAnyMap()

View File

@ -20,9 +20,41 @@ import (
func getInt() int {
return 123
}
func intIntCallBack(int, int) bool {
return true
}
func Test_IntIntMap_Var(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var m gmap.IntIntMap
m.Set(1, 1)
t.Assert(m.Get(1), 1)
t.Assert(m.Size(), 1)
t.Assert(m.IsEmpty(), false)
t.Assert(m.GetOrSet(2, 2), 2)
t.Assert(m.SetIfNotExist(2, 2), false)
t.Assert(m.SetIfNotExist(3, 3), true)
t.Assert(m.Remove(2), 2)
t.Assert(m.Contains(2), false)
t.AssertIN(3, m.Keys())
t.AssertIN(1, m.Keys())
t.AssertIN(3, m.Values())
t.AssertIN(1, m.Values())
m.Flip()
t.Assert(m.Map(), map[int]int{1: 1, 3: 3})
m.Clear()
t.Assert(m.Size(), 0)
t.Assert(m.IsEmpty(), true)
})
}
func Test_IntIntMap_Basic(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
m := gmap.NewIntIntMap()
@ -55,6 +87,7 @@ func Test_IntIntMap_Basic(t *testing.T) {
t.Assert(m2.Map(), map[int]int{1: 1, 2: 2})
})
}
func Test_IntIntMap_Set_Fun(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
m := gmap.NewIntIntMap()

View File

@ -20,9 +20,40 @@ import (
func getStr() string {
return "z"
}
func intStrCallBack(int, string) bool {
return true
func Test_IntStrMap_Var(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var m gmap.IntStrMap
m.Set(1, "a")
t.Assert(m.Get(1), "a")
t.Assert(m.Size(), 1)
t.Assert(m.IsEmpty(), false)
t.Assert(m.GetOrSet(2, "b"), "b")
t.Assert(m.SetIfNotExist(2, "b"), false)
t.Assert(m.SetIfNotExist(3, "c"), true)
t.Assert(m.Remove(2), "b")
t.Assert(m.Contains(2), false)
t.AssertIN(3, m.Keys())
t.AssertIN(1, m.Keys())
t.AssertIN("a", m.Values())
t.AssertIN("c", m.Values())
m_f := gmap.NewIntStrMap()
m_f.Set(1, "2")
m_f.Flip()
t.Assert(m_f.Map(), map[int]string{2: "1"})
m.Clear()
t.Assert(m.Size(), 0)
t.Assert(m.IsEmpty(), true)
})
}
func Test_IntStrMap_Basic(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
m := gmap.NewIntStrMap()
@ -60,6 +91,7 @@ func Test_IntStrMap_Basic(t *testing.T) {
t.Assert(m2.Map(), map[int]string{1: "a", 2: "b"})
})
}
func Test_IntStrMap_Set_Fun(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
m := gmap.NewIntStrMap()

View File

@ -17,6 +17,38 @@ import (
"github.com/gogf/gf/test/gtest"
)
func Test_ListMap_Var(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var m gmap.ListMap
m.Set("key1", "val1")
t.Assert(m.Keys(), []interface{}{"key1"})
t.Assert(m.Get("key1"), "val1")
t.Assert(m.Size(), 1)
t.Assert(m.IsEmpty(), false)
t.Assert(m.GetOrSet("key2", "val2"), "val2")
t.Assert(m.SetIfNotExist("key2", "val2"), false)
t.Assert(m.SetIfNotExist("key3", "val3"), true)
t.Assert(m.Remove("key2"), "val2")
t.Assert(m.Contains("key2"), false)
t.AssertIN("key3", m.Keys())
t.AssertIN("key1", m.Keys())
t.AssertIN("val3", m.Values())
t.AssertIN("val1", m.Values())
m.Flip()
t.Assert(m.Map(), map[interface{}]interface{}{"val3": "key3", "val1": "key1"})
m.Clear()
t.Assert(m.Size(), 0)
t.Assert(m.IsEmpty(), true)
})
}
func Test_ListMap_Basic(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
m := gmap.NewListMap()
@ -51,6 +83,7 @@ func Test_ListMap_Basic(t *testing.T) {
t.Assert(m2.Map(), map[interface{}]interface{}{1: 1, "key1": "val1"})
})
}
func Test_ListMap_Set_Fun(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
m := gmap.NewListMap()

View File

@ -17,9 +17,37 @@ import (
"github.com/gogf/gf/test/gtest"
)
func stringAnyCallBack(string, interface{}) bool {
return true
func Test_StrAnyMap_Var(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var m gmap.StrAnyMap
m.Set("a", 1)
t.Assert(m.Get("a"), 1)
t.Assert(m.Size(), 1)
t.Assert(m.IsEmpty(), false)
t.Assert(m.GetOrSet("b", "2"), "2")
t.Assert(m.SetIfNotExist("b", "2"), false)
t.Assert(m.SetIfNotExist("c", 3), true)
t.Assert(m.Remove("b"), "2")
t.Assert(m.Contains("b"), false)
t.AssertIN("c", m.Keys())
t.AssertIN("a", m.Keys())
t.AssertIN(3, m.Values())
t.AssertIN(1, m.Values())
m.Flip()
t.Assert(m.Map(), map[string]interface{}{"1": "a", "3": "c"})
m.Clear()
t.Assert(m.Size(), 0)
t.Assert(m.IsEmpty(), true)
})
}
func Test_StrAnyMap_Basic(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
m := gmap.NewStrAnyMap()
@ -53,6 +81,7 @@ func Test_StrAnyMap_Basic(t *testing.T) {
t.Assert(m2.Map(), map[string]interface{}{"a": 1, "b": "2"})
})
}
func Test_StrAnyMap_Set_Fun(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
m := gmap.NewStrAnyMap()

View File

@ -17,9 +17,39 @@ import (
"github.com/gogf/gf/test/gtest"
)
func stringIntCallBack(string, int) bool {
return true
func Test_StrIntMap_Var(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var m gmap.StrIntMap
m.Set("a", 1)
t.Assert(m.Get("a"), 1)
t.Assert(m.Size(), 1)
t.Assert(m.IsEmpty(), false)
t.Assert(m.GetOrSet("b", 2), 2)
t.Assert(m.SetIfNotExist("b", 2), false)
t.Assert(m.SetIfNotExist("c", 3), true)
t.Assert(m.Remove("b"), 2)
t.Assert(m.Contains("b"), false)
t.AssertIN("c", m.Keys())
t.AssertIN("a", m.Keys())
t.AssertIN(3, m.Values())
t.AssertIN(1, m.Values())
m_f := gmap.NewStrIntMap()
m_f.Set("1", 2)
m_f.Flip()
t.Assert(m_f.Map(), map[string]int{"2": 1})
m.Clear()
t.Assert(m.Size(), 0)
t.Assert(m.IsEmpty(), true)
})
}
func Test_StrIntMap_Basic(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
m := gmap.NewStrIntMap()
@ -55,6 +85,7 @@ func Test_StrIntMap_Basic(t *testing.T) {
t.Assert(m2.Map(), map[string]int{"a": 1, "b": 2})
})
}
func Test_StrIntMap_Set_Fun(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
m := gmap.NewStrIntMap()

View File

@ -17,9 +17,38 @@ import (
"github.com/gogf/gf/test/gtest"
)
func stringStrCallBack(string, string) bool {
return true
func Test_StrStrMap_Var(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var m gmap.StrStrMap
m.Set("a", "a")
t.Assert(m.Get("a"), "a")
t.Assert(m.Size(), 1)
t.Assert(m.IsEmpty(), false)
t.Assert(m.GetOrSet("b", "b"), "b")
t.Assert(m.SetIfNotExist("b", "b"), false)
t.Assert(m.SetIfNotExist("c", "c"), true)
t.Assert(m.Remove("b"), "b")
t.Assert(m.Contains("b"), false)
t.AssertIN("c", m.Keys())
t.AssertIN("a", m.Keys())
t.AssertIN("a", m.Values())
t.AssertIN("c", m.Values())
m.Flip()
t.Assert(m.Map(), map[string]string{"a": "a", "c": "c"})
m.Clear()
t.Assert(m.Size(), 0)
t.Assert(m.IsEmpty(), true)
})
}
func Test_StrStrMap_Basic(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
m := gmap.NewStrStrMap()
@ -54,6 +83,7 @@ func Test_StrStrMap_Basic(t *testing.T) {
t.Assert(m2.Map(), map[string]string{"a": "a", "b": "b"})
})
}
func Test_StrStrMap_Set_Fun(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
m := gmap.NewStrStrMap()

View File

@ -17,6 +17,39 @@ import (
"github.com/gogf/gf/util/gutil"
)
func Test_TreeMap_Var(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var m gmap.TreeMap
m.SetComparator(gutil.ComparatorString)
m.Set("key1", "val1")
t.Assert(m.Keys(), []interface{}{"key1"})
t.Assert(m.Get("key1"), "val1")
t.Assert(m.Size(), 1)
t.Assert(m.IsEmpty(), false)
t.Assert(m.GetOrSet("key2", "val2"), "val2")
t.Assert(m.SetIfNotExist("key2", "val2"), false)
t.Assert(m.SetIfNotExist("key3", "val3"), true)
t.Assert(m.Remove("key2"), "val2")
t.Assert(m.Contains("key2"), false)
t.AssertIN("key3", m.Keys())
t.AssertIN("key1", m.Keys())
t.AssertIN("val3", m.Values())
t.AssertIN("val1", m.Values())
m.Flip()
t.Assert(m.Map(), map[interface{}]interface{}{"val3": "key3", "val1": "key1"})
m.Clear()
t.Assert(m.Size(), 0)
t.Assert(m.IsEmpty(), true)
})
}
func Test_TreeMap_Basic(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
m := gmap.NewTreeMap(gutil.ComparatorString)
@ -51,6 +84,7 @@ func Test_TreeMap_Basic(t *testing.T) {
t.Assert(m2.Map(), map[interface{}]interface{}{1: 1, "key1": "val1"})
})
}
func Test_TreeMap_Set_Fun(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
m := gmap.NewTreeMap(gutil.ComparatorString)

View File

@ -14,6 +14,7 @@ import (
"github.com/gogf/gf/internal/rwmutex"
)
// Ring is a struct of ring structure.
type Ring struct {
mu *rwmutex.RWMutex
ring *ring.Ring // Underlying ring.
@ -22,6 +23,9 @@ type Ring struct {
dirty *gtype.Bool // Dirty, which means the len and cap should be recalculated. It's marked dirty when the size of ring changes.
}
// New creates and returns a Ring structure of <cap> elements.
// The optional parameter <safe> specifies whether using this structure in concurrent safety,
// which is false in default.
func New(cap int, safe ...bool) *Ring {
return &Ring{
mu: rwmutex.New(safe...),

View File

@ -1,3 +1,9 @@
// 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 gring_test
import (

View File

@ -16,7 +16,7 @@ import (
)
type Set struct {
mu *rwmutex.RWMutex
mu rwmutex.RWMutex
data map[interface{}]struct{}
}
@ -31,7 +31,7 @@ func New(safe ...bool) *Set {
func NewSet(safe ...bool) *Set {
return &Set{
data: make(map[interface{}]struct{}),
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
}
}
@ -44,13 +44,13 @@ func NewFrom(items interface{}, safe ...bool) *Set {
}
return &Set{
data: m,
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
}
}
// Iterator iterates the set with given callback function <f>,
// Iterator iterates the set readonly with given callback function <f>,
// if <f> returns true then continue iterating; or false to stop.
func (set *Set) Iterator(f func(v interface{}) bool) *Set {
func (set *Set) Iterator(f func(v interface{}) bool) {
set.mu.RLock()
defer set.mu.RUnlock()
for k, _ := range set.data {
@ -58,77 +58,113 @@ func (set *Set) Iterator(f func(v interface{}) bool) *Set {
break
}
}
return set
}
// Add adds one or multiple items to the set.
func (set *Set) Add(item ...interface{}) *Set {
func (set *Set) Add(item ...interface{}) {
set.mu.Lock()
if set.data == nil {
set.data = make(map[interface{}]struct{})
}
for _, v := range item {
set.data[v] = struct{}{}
}
set.mu.Unlock()
return set
}
// AddIfNotExistFunc adds the returned value of callback function <f> to the set
// if <item> does not exit in the set.
func (set *Set) AddIfNotExistFunc(item interface{}, f func() interface{}) *Set {
if !set.Contains(item) {
set.doAddWithLockCheck(item, f())
// AddIfNotExist checks whether item exists in the set,
// it adds the item to set and returns true if it does not exists in the set,
// or else it does nothing and returns false.
//
// Note that, if <item> is nil, it does nothing and returns false.
func (set *Set) AddIfNotExist(item interface{}) bool {
if item == nil {
return false
}
return set
}
// AddIfNotExistFuncLock adds the returned value of callback function <f> to the set
// if <item> does not exit in the set.
//
// Note that the callback function <f> is executed in the mutex.Lock of the set.
func (set *Set) AddIfNotExistFuncLock(item interface{}, f func() interface{}) *Set {
if !set.Contains(item) {
set.doAddWithLockCheck(item, f)
}
return set
}
// doAddWithLockCheck checks whether item exists with mutex.Lock,
// if not exists, it adds item to the set or else just returns the existing value.
//
// If <value> is type of <func() interface {}>,
// it will be executed with mutex.Lock of the set,
// and its return value will be added to the set.
//
// It returns item successfully added..
func (set *Set) doAddWithLockCheck(item interface{}, value interface{}) interface{} {
set.mu.Lock()
defer set.mu.Unlock()
if _, ok := set.data[item]; !ok && value != nil {
if f, ok := value.(func() interface{}); ok {
item = f()
} else {
item = value
set.mu.Lock()
defer set.mu.Unlock()
if set.data == nil {
set.data = make(map[interface{}]struct{})
}
if _, ok := set.data[item]; !ok {
set.data[item] = struct{}{}
return true
}
}
if item != nil {
set.data[item] = struct{}{}
return false
}
// AddIfNotExistFunc checks whether item exists in the set,
// it adds the item to set and returns true if it does not exists in the set and
// function <f> returns true, or else it does nothing and returns false.
//
// Note that, if <item> is nil, it does nothing and returns false. The function <f>
// is executed without writing lock.
func (set *Set) AddIfNotExistFunc(item interface{}, f func() bool) bool {
if item == nil {
return false
}
return item
if !set.Contains(item) {
if f() {
set.mu.Lock()
defer set.mu.Unlock()
if set.data == nil {
set.data = make(map[interface{}]struct{})
}
if _, ok := set.data[item]; !ok {
set.data[item] = struct{}{}
return true
}
}
}
return false
}
// AddIfNotExistFunc checks whether item exists in the set,
// it adds the item to set and returns true if it does not exists in the set and
// function <f> returns true, or else it does nothing and returns false.
//
// Note that, if <item> is nil, it does nothing and returns false. The function <f>
// is executed within writing lock.
func (set *Set) AddIfNotExistFuncLock(item interface{}, f func() bool) bool {
if item == nil {
return false
}
if !set.Contains(item) {
set.mu.Lock()
defer set.mu.Unlock()
if set.data == nil {
set.data = make(map[interface{}]struct{})
}
if f() {
if _, ok := set.data[item]; !ok {
set.data[item] = struct{}{}
return true
}
}
}
return false
}
// Contains checks whether the set contains <item>.
func (set *Set) Contains(item interface{}) bool {
var ok bool
set.mu.RLock()
_, exists := set.data[item]
if set.data != nil {
_, ok = set.data[item]
}
set.mu.RUnlock()
return exists
return ok
}
// Remove deletes <item> from set.
func (set *Set) Remove(item interface{}) *Set {
func (set *Set) Remove(item interface{}) {
set.mu.Lock()
delete(set.data, item)
if set.data != nil {
delete(set.data, item)
}
set.mu.Unlock()
return set
}
// Size returns the size of the set.
@ -140,18 +176,19 @@ func (set *Set) Size() int {
}
// Clear deletes all items of the set.
func (set *Set) Clear() *Set {
func (set *Set) Clear() {
set.mu.Lock()
set.data = make(map[interface{}]struct{})
set.mu.Unlock()
return set
}
// Slice returns the a of items of the set as slice.
func (set *Set) Slice() []interface{} {
set.mu.RLock()
i := 0
ret := make([]interface{}, len(set.data))
var (
i = 0
ret = make([]interface{}, len(set.data))
)
for item := range set.data {
ret[i] = item
i++
@ -164,9 +201,14 @@ func (set *Set) Slice() []interface{} {
func (set *Set) Join(glue string) string {
set.mu.RLock()
defer set.mu.RUnlock()
buffer := bytes.NewBuffer(nil)
l := len(set.data)
i := 0
if len(set.data) == 0 {
return ""
}
var (
l = len(set.data)
i = 0
buffer = bytes.NewBuffer(nil)
)
for k, _ := range set.data {
buffer.WriteString(gconv.String(k))
if i != l-1 {
@ -181,11 +223,13 @@ func (set *Set) Join(glue string) string {
func (set *Set) String() string {
set.mu.RLock()
defer set.mu.RUnlock()
buffer := bytes.NewBuffer(nil)
var (
s = ""
l = len(set.data)
i = 0
buffer = bytes.NewBuffer(nil)
)
buffer.WriteByte('[')
s := ""
l := len(set.data)
i := 0
for k, _ := range set.data {
s = gconv.String(k)
if gstr.IsNumeric(s) {
@ -256,7 +300,7 @@ func (set *Set) IsSubsetOf(other *Set) bool {
// Union returns a new set which is the union of <set> and <others>.
// Which means, all the items in <newSet> are in <set> or in <others>.
func (set *Set) Union(others ...*Set) (newSet *Set) {
newSet = NewSet(true)
newSet = NewSet()
set.mu.RLock()
defer set.mu.RUnlock()
for _, other := range others {
@ -282,7 +326,7 @@ func (set *Set) Union(others ...*Set) (newSet *Set) {
// Diff returns a new set which is the difference set from <set> to <others>.
// Which means, all the items in <newSet> are in <set> but not in <others>.
func (set *Set) Diff(others ...*Set) (newSet *Set) {
newSet = NewSet(true)
newSet = NewSet()
set.mu.RLock()
defer set.mu.RUnlock()
for _, other := range others {
@ -303,7 +347,7 @@ func (set *Set) Diff(others ...*Set) (newSet *Set) {
// Intersect returns a new set which is the intersection from <set> to <others>.
// Which means, all the items in <newSet> are in <set> and also in <others>.
func (set *Set) Intersect(others ...*Set) (newSet *Set) {
newSet = NewSet(true)
newSet = NewSet()
set.mu.RLock()
defer set.mu.RUnlock()
for _, other := range others {
@ -328,7 +372,7 @@ func (set *Set) Intersect(others ...*Set) (newSet *Set) {
// It returns the difference between <full> and <set>
// if the given set <full> is not the full set of <set>.
func (set *Set) Complement(full *Set) (newSet *Set) {
newSet = NewSet(true)
newSet = NewSet()
set.mu.RLock()
defer set.mu.RUnlock()
if set != full {
@ -408,6 +452,16 @@ func (set *Set) Pops(size int) []interface{} {
return array
}
// Walk applies a user supplied function <f> to every item of set.
func (set *Set) Walk(f func(item interface{}) interface{}) *Set {
set.mu.Lock()
defer set.mu.Unlock()
for k, v := range set.data {
set.data[f(k)] = v
}
return set
}
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
func (set *Set) MarshalJSON() ([]byte, error) {
return json.Marshal(set.Slice())
@ -415,12 +469,11 @@ func (set *Set) MarshalJSON() ([]byte, error) {
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
func (set *Set) UnmarshalJSON(b []byte) error {
if set.mu == nil {
set.mu = rwmutex.New()
set.data = make(map[interface{}]struct{})
}
set.mu.Lock()
defer set.mu.Unlock()
if set.data == nil {
set.data = make(map[interface{}]struct{})
}
var array []interface{}
if err := json.Unmarshal(b, &array); err != nil {
return err
@ -433,12 +486,11 @@ func (set *Set) UnmarshalJSON(b []byte) error {
// UnmarshalValue is an interface implement which sets any type of value for set.
func (set *Set) UnmarshalValue(value interface{}) (err error) {
if set.mu == nil {
set.mu = rwmutex.New()
set.data = make(map[interface{}]struct{})
}
set.mu.Lock()
defer set.mu.Unlock()
if set.data == nil {
set.data = make(map[interface{}]struct{})
}
var array []interface{}
switch value.(type) {
case string, []byte:

View File

@ -15,7 +15,7 @@ import (
)
type IntSet struct {
mu *rwmutex.RWMutex
mu rwmutex.RWMutex
data map[int]struct{}
}
@ -24,7 +24,7 @@ type IntSet struct {
// which is false in default.
func NewIntSet(safe ...bool) *IntSet {
return &IntSet{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
data: make(map[int]struct{}),
}
}
@ -36,14 +36,14 @@ func NewIntSetFrom(items []int, safe ...bool) *IntSet {
m[v] = struct{}{}
}
return &IntSet{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
data: m,
}
}
// Iterator iterates the set with given callback function <f>,
// Iterator iterates the set readonly with given callback function <f>,
// if <f> returns true then continue iterating; or false to stop.
func (set *IntSet) Iterator(f func(v int) bool) *IntSet {
func (set *IntSet) Iterator(f func(v int) bool) {
set.mu.RLock()
defer set.mu.RUnlock()
for k, _ := range set.data {
@ -51,75 +51,102 @@ func (set *IntSet) Iterator(f func(v int) bool) *IntSet {
break
}
}
return set
}
// Add adds one or multiple items to the set.
func (set *IntSet) Add(item ...int) *IntSet {
func (set *IntSet) Add(item ...int) {
set.mu.Lock()
if set.data == nil {
set.data = make(map[int]struct{})
}
for _, v := range item {
set.data[v] = struct{}{}
}
set.mu.Unlock()
return set
}
// AddIfNotExistFunc adds the returned value of callback function <f> to the set
// if <item> does not exit in the set.
func (set *IntSet) AddIfNotExistFunc(item int, f func() int) *IntSet {
// AddIfNotExist checks whether item exists in the set,
// it adds the item to set and returns true if it does not exists in the set,
// or else it does nothing and returns false.
//
// Note that, if <item> is nil, it does nothing and returns false.
func (set *IntSet) AddIfNotExist(item int) bool {
if !set.Contains(item) {
set.doAddWithLockCheck(item, f())
}
return set
}
// AddIfNotExistFuncLock adds the returned value of callback function <f> to the set
// if <item> does not exit in the set.
//
// Note that the callback function <f> is executed in the mutex.Lock of the set.
func (set *IntSet) AddIfNotExistFuncLock(item int, f func() int) *IntSet {
if !set.Contains(item) {
set.doAddWithLockCheck(item, f)
}
return set
}
// doAddWithLockCheck checks whether item exists with mutex.Lock,
// if not exists, it adds item to the set or else just returns the existing value.
//
// If <value> is type of <func() interface {}>,
// it will be executed with mutex.Lock of the set,
// and its return value will be added to the set.
//
// It returns item successfully added..
func (set *IntSet) doAddWithLockCheck(item int, value interface{}) int {
set.mu.Lock()
defer set.mu.Unlock()
if _, ok := set.data[item]; !ok && value != nil {
if f, ok := value.(func() int); ok {
item = f()
} else {
item = value.(int)
set.mu.Lock()
defer set.mu.Unlock()
if set.data == nil {
set.data = make(map[int]struct{})
}
if _, ok := set.data[item]; !ok {
set.data[item] = struct{}{}
return true
}
}
set.data[item] = struct{}{}
return item
return false
}
// AddIfNotExistFunc checks whether item exists in the set,
// it adds the item to set and returns true if it does not exists in the set and
// function <f> returns true, or else it does nothing and returns false.
//
// Note that, the function <f> is executed without writing lock.
func (set *IntSet) AddIfNotExistFunc(item int, f func() bool) bool {
if !set.Contains(item) {
if f() {
set.mu.Lock()
defer set.mu.Unlock()
if set.data == nil {
set.data = make(map[int]struct{})
}
if _, ok := set.data[item]; !ok {
set.data[item] = struct{}{}
return true
}
}
}
return false
}
// AddIfNotExistFunc checks whether item exists in the set,
// it adds the item to set and returns true if it does not exists in the set and
// function <f> returns true, or else it does nothing and returns false.
//
// Note that, the function <f> is executed without writing lock.
func (set *IntSet) AddIfNotExistFuncLock(item int, f func() bool) bool {
if !set.Contains(item) {
set.mu.Lock()
defer set.mu.Unlock()
if set.data == nil {
set.data = make(map[int]struct{})
}
if f() {
if _, ok := set.data[item]; !ok {
set.data[item] = struct{}{}
return true
}
}
}
return false
}
// Contains checks whether the set contains <item>.
func (set *IntSet) Contains(item int) bool {
var ok bool
set.mu.RLock()
_, exists := set.data[item]
if set.data != nil {
_, ok = set.data[item]
}
set.mu.RUnlock()
return exists
return ok
}
// Remove deletes <item> from set.
func (set *IntSet) Remove(item int) *IntSet {
func (set *IntSet) Remove(item int) {
set.mu.Lock()
delete(set.data, item)
if set.data != nil {
delete(set.data, item)
}
set.mu.Unlock()
return set
}
// Size returns the size of the set.
@ -131,18 +158,19 @@ func (set *IntSet) Size() int {
}
// Clear deletes all items of the set.
func (set *IntSet) Clear() *IntSet {
func (set *IntSet) Clear() {
set.mu.Lock()
set.data = make(map[int]struct{})
set.mu.Unlock()
return set
}
// Slice returns the a of items of the set as slice.
func (set *IntSet) Slice() []int {
set.mu.RLock()
ret := make([]int, len(set.data))
i := 0
var (
i = 0
ret = make([]int, len(set.data))
)
for k, _ := range set.data {
ret[i] = k
i++
@ -155,9 +183,14 @@ func (set *IntSet) Slice() []int {
func (set *IntSet) Join(glue string) string {
set.mu.RLock()
defer set.mu.RUnlock()
buffer := bytes.NewBuffer(nil)
l := len(set.data)
i := 0
if len(set.data) == 0 {
return ""
}
var (
l = len(set.data)
i = 0
buffer = bytes.NewBuffer(nil)
)
for k, _ := range set.data {
buffer.WriteString(gconv.String(k))
if i != l-1 {
@ -227,7 +260,7 @@ func (set *IntSet) IsSubsetOf(other *IntSet) bool {
// Union returns a new set which is the union of <set> and <other>.
// Which means, all the items in <newSet> are in <set> or in <other>.
func (set *IntSet) Union(others ...*IntSet) (newSet *IntSet) {
newSet = NewIntSet(true)
newSet = NewIntSet()
set.mu.RLock()
defer set.mu.RUnlock()
for _, other := range others {
@ -253,7 +286,7 @@ func (set *IntSet) Union(others ...*IntSet) (newSet *IntSet) {
// Diff returns a new set which is the difference set from <set> to <other>.
// Which means, all the items in <newSet> are in <set> but not in <other>.
func (set *IntSet) Diff(others ...*IntSet) (newSet *IntSet) {
newSet = NewIntSet(true)
newSet = NewIntSet()
set.mu.RLock()
defer set.mu.RUnlock()
for _, other := range others {
@ -274,7 +307,7 @@ func (set *IntSet) Diff(others ...*IntSet) (newSet *IntSet) {
// Intersect returns a new set which is the intersection from <set> to <other>.
// Which means, all the items in <newSet> are in <set> and also in <other>.
func (set *IntSet) Intersect(others ...*IntSet) (newSet *IntSet) {
newSet = NewIntSet(true)
newSet = NewIntSet()
set.mu.RLock()
defer set.mu.RUnlock()
for _, other := range others {
@ -299,7 +332,7 @@ func (set *IntSet) Intersect(others ...*IntSet) (newSet *IntSet) {
// It returns the difference between <full> and <set>
// if the given set <full> is not the full set of <set>.
func (set *IntSet) Complement(full *IntSet) (newSet *IntSet) {
newSet = NewIntSet(true)
newSet = NewIntSet()
set.mu.RLock()
defer set.mu.RUnlock()
if set != full {
@ -379,6 +412,16 @@ func (set *IntSet) Pops(size int) []int {
return array
}
// Walk applies a user supplied function <f> to every item of set.
func (set *IntSet) Walk(f func(item int) int) *IntSet {
set.mu.Lock()
defer set.mu.Unlock()
for k, v := range set.data {
set.data[f(k)] = v
}
return set
}
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
func (set *IntSet) MarshalJSON() ([]byte, error) {
return json.Marshal(set.Slice())
@ -386,12 +429,11 @@ func (set *IntSet) MarshalJSON() ([]byte, error) {
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
func (set *IntSet) UnmarshalJSON(b []byte) error {
if set.mu == nil {
set.mu = rwmutex.New()
set.data = make(map[int]struct{})
}
set.mu.Lock()
defer set.mu.Unlock()
if set.data == nil {
set.data = make(map[int]struct{})
}
var array []int
if err := json.Unmarshal(b, &array); err != nil {
return err
@ -404,12 +446,11 @@ func (set *IntSet) UnmarshalJSON(b []byte) error {
// UnmarshalValue is an interface implement which sets any type of value for set.
func (set *IntSet) UnmarshalValue(value interface{}) (err error) {
if set.mu == nil {
set.mu = rwmutex.New()
set.data = make(map[int]struct{})
}
set.mu.Lock()
defer set.mu.Unlock()
if set.data == nil {
set.data = make(map[int]struct{})
}
var array []int
switch value.(type) {
case string, []byte:

View File

@ -13,10 +13,11 @@ import (
"github.com/gogf/gf/internal/rwmutex"
"github.com/gogf/gf/text/gstr"
"github.com/gogf/gf/util/gconv"
"strings"
)
type StrSet struct {
mu *rwmutex.RWMutex
mu rwmutex.RWMutex
data map[string]struct{}
}
@ -25,7 +26,7 @@ type StrSet struct {
// which is false in default.
func NewStrSet(safe ...bool) *StrSet {
return &StrSet{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
data: make(map[string]struct{}),
}
}
@ -37,14 +38,14 @@ func NewStrSetFrom(items []string, safe ...bool) *StrSet {
m[v] = struct{}{}
}
return &StrSet{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
data: m,
}
}
// Iterator iterates the set with given callback function <f>,
// Iterator iterates the set readonly with given callback function <f>,
// if <f> returns true then continue iterating; or false to stop.
func (set *StrSet) Iterator(f func(v string) bool) *StrSet {
func (set *StrSet) Iterator(f func(v string) bool) {
set.mu.RLock()
defer set.mu.RUnlock()
for k, _ := range set.data {
@ -52,77 +53,113 @@ func (set *StrSet) Iterator(f func(v string) bool) *StrSet {
break
}
}
return set
}
// Add adds one or multiple items to the set.
func (set *StrSet) Add(item ...string) *StrSet {
func (set *StrSet) Add(item ...string) {
set.mu.Lock()
if set.data == nil {
set.data = make(map[string]struct{})
}
for _, v := range item {
set.data[v] = struct{}{}
}
set.mu.Unlock()
return set
}
// AddIfNotExistFunc adds the returned value of callback function <f> to the set
// if <item> does not exit in the set.
func (set *StrSet) AddIfNotExistFunc(item string, f func() string) *StrSet {
// AddIfNotExist checks whether item exists in the set,
// it adds the item to set and returns true if it does not exists in the set,
// or else it does nothing and returns false.
func (set *StrSet) AddIfNotExist(item string) bool {
if !set.Contains(item) {
set.doAddWithLockCheck(item, f())
}
return set
}
// AddIfNotExistFuncLock adds the returned value of callback function <f> to the set
// if <item> does not exit in the set.
//
// Note that the callback function <f> is executed in the mutex.Lock of the set.
func (set *StrSet) AddIfNotExistFuncLock(item string, f func() string) *StrSet {
if !set.Contains(item) {
set.doAddWithLockCheck(item, f)
}
return set
}
// doAddWithLockCheck checks whether item exists with mutex.Lock,
// if not exists, it adds item to the set or else just returns the existing value.
//
// If <value> is type of <func() interface {}>,
// it will be executed with mutex.Lock of the set,
// and its return value will be added to the set.
//
// It returns item successfully added..
func (set *StrSet) doAddWithLockCheck(item string, value interface{}) string {
set.mu.Lock()
defer set.mu.Unlock()
if _, ok := set.data[item]; !ok && value != nil {
if f, ok := value.(func() string); ok {
item = f()
} else {
item = value.(string)
set.mu.Lock()
defer set.mu.Unlock()
if set.data == nil {
set.data = make(map[string]struct{})
}
if _, ok := set.data[item]; !ok {
set.data[item] = struct{}{}
return true
}
}
if item != "" {
set.data[item] = struct{}{}
return false
}
// AddIfNotExistFunc checks whether item exists in the set,
// it adds the item to set and returns true if it does not exists in the set and
// function <f> returns true, or else it does nothing and returns false.
//
// Note that, the function <f> is executed without writing lock.
func (set *StrSet) AddIfNotExistFunc(item string, f func() bool) bool {
if !set.Contains(item) {
if f() {
set.mu.Lock()
defer set.mu.Unlock()
if set.data == nil {
set.data = make(map[string]struct{})
}
if _, ok := set.data[item]; !ok {
set.data[item] = struct{}{}
return true
}
}
}
return item
return false
}
// AddIfNotExistFunc checks whether item exists in the set,
// it adds the item to set and returns true if it does not exists in the set and
// function <f> returns true, or else it does nothing and returns false.
//
// Note that, the function <f> is executed without writing lock.
func (set *StrSet) AddIfNotExistFuncLock(item string, f func() bool) bool {
if !set.Contains(item) {
set.mu.Lock()
defer set.mu.Unlock()
if set.data == nil {
set.data = make(map[string]struct{})
}
if f() {
if _, ok := set.data[item]; !ok {
set.data[item] = struct{}{}
return true
}
}
}
return false
}
// Contains checks whether the set contains <item>.
func (set *StrSet) Contains(item string) bool {
var ok bool
set.mu.RLock()
_, exists := set.data[item]
if set.data != nil {
_, ok = set.data[item]
}
set.mu.RUnlock()
return exists
return ok
}
// ContainsI checks whether a value exists in the set with case-insensitively.
// Note that it internally iterates the whole set to do the comparison with case-insensitively.
func (set *StrSet) ContainsI(item string) bool {
set.mu.RLock()
defer set.mu.RUnlock()
for k, _ := range set.data {
if strings.EqualFold(k, item) {
return true
}
}
return false
}
// Remove deletes <item> from set.
func (set *StrSet) Remove(item string) *StrSet {
func (set *StrSet) Remove(item string) {
set.mu.Lock()
delete(set.data, item)
if set.data != nil {
delete(set.data, item)
}
set.mu.Unlock()
return set
}
// Size returns the size of the set.
@ -134,18 +171,19 @@ func (set *StrSet) Size() int {
}
// Clear deletes all items of the set.
func (set *StrSet) Clear() *StrSet {
func (set *StrSet) Clear() {
set.mu.Lock()
set.data = make(map[string]struct{})
set.mu.Unlock()
return set
}
// Slice returns the a of items of the set as slice.
func (set *StrSet) Slice() []string {
set.mu.RLock()
ret := make([]string, len(set.data))
i := 0
var (
i = 0
ret = make([]string, len(set.data))
)
for item := range set.data {
ret[i] = item
i++
@ -159,9 +197,14 @@ func (set *StrSet) Slice() []string {
func (set *StrSet) Join(glue string) string {
set.mu.RLock()
defer set.mu.RUnlock()
buffer := bytes.NewBuffer(nil)
l := len(set.data)
i := 0
if len(set.data) == 0 {
return ""
}
var (
l = len(set.data)
i = 0
buffer = bytes.NewBuffer(nil)
)
for k, _ := range set.data {
buffer.WriteString(k)
if i != l-1 {
@ -176,9 +219,11 @@ func (set *StrSet) Join(glue string) string {
func (set *StrSet) String() string {
set.mu.RLock()
defer set.mu.RUnlock()
buffer := bytes.NewBuffer(nil)
l := len(set.data)
i := 0
var (
l = len(set.data)
i = 0
buffer = bytes.NewBuffer(nil)
)
for k, _ := range set.data {
buffer.WriteString(`"` + gstr.QuoteMeta(k, `"\`) + `"`)
if i != l-1 {
@ -243,7 +288,7 @@ func (set *StrSet) IsSubsetOf(other *StrSet) bool {
// Union returns a new set which is the union of <set> and <other>.
// Which means, all the items in <newSet> are in <set> or in <other>.
func (set *StrSet) Union(others ...*StrSet) (newSet *StrSet) {
newSet = NewStrSet(true)
newSet = NewStrSet()
set.mu.RLock()
defer set.mu.RUnlock()
for _, other := range others {
@ -269,7 +314,7 @@ func (set *StrSet) Union(others ...*StrSet) (newSet *StrSet) {
// Diff returns a new set which is the difference set from <set> to <other>.
// Which means, all the items in <newSet> are in <set> but not in <other>.
func (set *StrSet) Diff(others ...*StrSet) (newSet *StrSet) {
newSet = NewStrSet(true)
newSet = NewStrSet()
set.mu.RLock()
defer set.mu.RUnlock()
for _, other := range others {
@ -290,7 +335,7 @@ func (set *StrSet) Diff(others ...*StrSet) (newSet *StrSet) {
// Intersect returns a new set which is the intersection from <set> to <other>.
// Which means, all the items in <newSet> are in <set> and also in <other>.
func (set *StrSet) Intersect(others ...*StrSet) (newSet *StrSet) {
newSet = NewStrSet(true)
newSet = NewStrSet()
set.mu.RLock()
defer set.mu.RUnlock()
for _, other := range others {
@ -315,7 +360,7 @@ func (set *StrSet) Intersect(others ...*StrSet) (newSet *StrSet) {
// It returns the difference between <full> and <set>
// if the given set <full> is not the full set of <set>.
func (set *StrSet) Complement(full *StrSet) (newSet *StrSet) {
newSet = NewStrSet(true)
newSet = NewStrSet()
set.mu.RLock()
defer set.mu.RUnlock()
if set != full {
@ -395,6 +440,16 @@ func (set *StrSet) Pops(size int) []string {
return array
}
// Walk applies a user supplied function <f> to every item of set.
func (set *StrSet) Walk(f func(item string) string) *StrSet {
set.mu.Lock()
defer set.mu.Unlock()
for k, v := range set.data {
set.data[f(k)] = v
}
return set
}
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
func (set *StrSet) MarshalJSON() ([]byte, error) {
return json.Marshal(set.Slice())
@ -402,12 +457,11 @@ func (set *StrSet) MarshalJSON() ([]byte, error) {
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
func (set *StrSet) UnmarshalJSON(b []byte) error {
if set.mu == nil {
set.mu = rwmutex.New()
set.data = make(map[string]struct{})
}
set.mu.Lock()
defer set.mu.Unlock()
if set.data == nil {
set.data = make(map[string]struct{})
}
var array []string
if err := json.Unmarshal(b, &array); err != nil {
return err
@ -420,12 +474,11 @@ func (set *StrSet) UnmarshalJSON(b []byte) error {
// UnmarshalValue is an interface implement which sets any type of value for set.
func (set *StrSet) UnmarshalValue(value interface{}) (err error) {
if set.mu == nil {
set.mu = rwmutex.New()
set.data = make(map[string]struct{})
}
set.mu.Lock()
defer set.mu.Unlock()
if set.data == nil {
set.data = make(map[string]struct{})
}
var array []string
switch value.(type) {
case string, []byte:

View File

@ -13,6 +13,8 @@ import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/util/gconv"
"strings"
"sync"
"time"
"github.com/gogf/gf/container/garray"
"github.com/gogf/gf/container/gset"
@ -21,10 +23,30 @@ import (
"testing"
)
func TestSet_Var(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var s gset.Set
s.Add(1, 1, 2)
s.Add([]interface{}{3, 4}...)
t.Assert(s.Size(), 4)
t.AssertIN(1, s.Slice())
t.AssertIN(2, s.Slice())
t.AssertIN(3, s.Slice())
t.AssertIN(4, s.Slice())
t.AssertNI(0, s.Slice())
t.Assert(s.Contains(4), true)
t.Assert(s.Contains(5), false)
s.Remove(1)
t.Assert(s.Size(), 3)
s.Clear()
t.Assert(s.Size(), 0)
})
}
func TestSet_New(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := gset.New()
s.Add(1).Add(1).Add(2)
s.Add(1, 1, 2)
s.Add([]interface{}{3, 4}...)
t.Assert(s.Size(), 4)
t.AssertIN(1, s.Slice())
@ -44,7 +66,7 @@ func TestSet_New(t *testing.T) {
func TestSet_Basic(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := gset.NewSet()
s.Add(1).Add(1).Add(2)
s.Add(1, 1, 2)
s.Add([]interface{}{3, 4}...)
t.Assert(s.Size(), 4)
t.AssertIN(1, s.Slice())
@ -64,7 +86,7 @@ func TestSet_Basic(t *testing.T) {
func TestSet_Iterator(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := gset.NewSet()
s.Add(1).Add(2).Add(3)
s.Add(1, 2, 3)
t.Assert(s.Size(), 3)
a1 := garray.New(true)
@ -85,7 +107,7 @@ func TestSet_Iterator(t *testing.T) {
func TestSet_LockFunc(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := gset.NewSet()
s.Add(1).Add(2).Add(3)
s.Add(1, 2, 3)
t.Assert(s.Size(), 3)
s.LockFunc(func(m map[interface{}]struct{}) {
delete(m, 1)
@ -105,9 +127,9 @@ func TestSet_Equal(t *testing.T) {
s1 := gset.NewSet()
s2 := gset.NewSet()
s3 := gset.NewSet()
s1.Add(1).Add(2).Add(3)
s2.Add(1).Add(2).Add(3)
s3.Add(1).Add(2).Add(3).Add(4)
s1.Add(1, 2, 3)
s2.Add(1, 2, 3)
s3.Add(1, 2, 3, 4)
t.Assert(s1.Equal(s2), true)
t.Assert(s1.Equal(s3), false)
})
@ -118,9 +140,9 @@ func TestSet_IsSubsetOf(t *testing.T) {
s1 := gset.NewSet()
s2 := gset.NewSet()
s3 := gset.NewSet()
s1.Add(1).Add(2)
s2.Add(1).Add(2).Add(3)
s3.Add(1).Add(2).Add(3).Add(4)
s1.Add(1, 2)
s2.Add(1, 2, 3)
s3.Add(1, 2, 3, 4)
t.Assert(s1.IsSubsetOf(s2), true)
t.Assert(s2.IsSubsetOf(s3), true)
t.Assert(s1.IsSubsetOf(s3), true)
@ -133,8 +155,8 @@ func TestSet_Union(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s1 := gset.NewSet()
s2 := gset.NewSet()
s1.Add(1).Add(2)
s2.Add(3).Add(4)
s1.Add(1, 2)
s2.Add(3, 4)
s3 := s1.Union(s2)
t.Assert(s3.Contains(1), true)
t.Assert(s3.Contains(2), true)
@ -147,8 +169,8 @@ func TestSet_Diff(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s1 := gset.NewSet()
s2 := gset.NewSet()
s1.Add(1).Add(2).Add(3)
s2.Add(3).Add(4).Add(5)
s1.Add(1, 2, 3)
s2.Add(3, 4, 5)
s3 := s1.Diff(s2)
t.Assert(s3.Contains(1), true)
t.Assert(s3.Contains(2), true)
@ -161,8 +183,8 @@ func TestSet_Intersect(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s1 := gset.NewSet()
s2 := gset.NewSet()
s1.Add(1).Add(2).Add(3)
s2.Add(3).Add(4).Add(5)
s1.Add(1, 2, 3)
s2.Add(3, 4, 5)
s3 := s1.Intersect(s2)
t.Assert(s3.Contains(1), false)
t.Assert(s3.Contains(2), false)
@ -175,8 +197,8 @@ func TestSet_Complement(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s1 := gset.NewSet()
s2 := gset.NewSet()
s1.Add(1).Add(2).Add(3)
s2.Add(3).Add(4).Add(5)
s1.Add(1, 2, 3)
s2.Add(3, 4, 5)
s3 := s1.Complement(s2)
t.Assert(s3.Contains(1), false)
t.Assert(s3.Contains(2), false)
@ -203,9 +225,9 @@ func TestNewFrom(t *testing.T) {
func TestNew(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s1 := gset.New()
s1.Add("a").Add(2)
s1.Add("a", 2)
s2 := gset.New(true)
s2.Add("b").Add(3)
s2.Add("b", 3)
t.Assert(s1.Contains("a"), true)
})
@ -214,13 +236,13 @@ func TestNew(t *testing.T) {
func TestSet_Join(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s1 := gset.New(true)
s1.Add("a").Add("a1").Add("b").Add("c")
s1.Add("a", "a1", "b", "c")
str1 := s1.Join(",")
t.Assert(strings.Contains(str1, "a1"), true)
})
gtest.C(t, func(t *gtest.T) {
s1 := gset.New(true)
s1.Add("a").Add(`"b"`).Add(`\c`)
s1.Add("a", `"b"`, `\c`)
str1 := s1.Join(",")
t.Assert(strings.Contains(str1, `"b"`), true)
t.Assert(strings.Contains(str1, `\c`), true)
@ -231,7 +253,7 @@ func TestSet_Join(t *testing.T) {
func TestSet_String(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s1 := gset.New(true)
s1.Add("a").Add("a2").Add("b").Add("c")
s1.Add("a", "a2", "b", "c")
str1 := s1.String()
t.Assert(strings.Contains(str1, "["), true)
t.Assert(strings.Contains(str1, "]"), true)
@ -243,8 +265,8 @@ func TestSet_Merge(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s1 := gset.New(true)
s2 := gset.New(true)
s1.Add("a").Add("a2").Add("b").Add("c")
s2.Add("b").Add("b1").Add("e").Add("f")
s1.Add("a", "a2", "b", "c")
s2.Add("b", "b1", "e", "f")
ss := s1.Merge(s2)
t.Assert(ss.Contains("a2"), true)
t.Assert(ss.Contains("b1"), true)
@ -255,7 +277,7 @@ func TestSet_Merge(t *testing.T) {
func TestSet_Sum(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s1 := gset.New(true)
s1.Add(1).Add(2).Add(3).Add(4)
s1.Add(1, 2, 3, 4)
t.Assert(s1.Sum(), int(10))
})
@ -264,7 +286,7 @@ func TestSet_Sum(t *testing.T) {
func TestSet_Pop(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := gset.New(true)
s.Add(1).Add(2).Add(3).Add(4)
s.Add(1, 2, 3, 4)
t.Assert(s.Size(), 4)
t.AssertIN(s.Pop(), []int{1, 2, 3, 4})
t.Assert(s.Size(), 3)
@ -274,7 +296,7 @@ func TestSet_Pop(t *testing.T) {
func TestSet_Pops(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := gset.New(true)
s.Add(1).Add(2).Add(3).Add(4)
s.Add(1, 2, 3, 4)
t.Assert(s.Size(), 4)
t.Assert(s.Pops(0), nil)
t.AssertIN(s.Pops(1), []int{1, 2, 3, 4})
@ -324,43 +346,71 @@ func TestSet_Json(t *testing.T) {
})
}
func TestSet_AddIfNotExist(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := gset.New(true)
s.Add(1)
t.Assert(s.Contains(1), true)
t.Assert(s.AddIfNotExist(1), false)
t.Assert(s.AddIfNotExist(2), true)
t.Assert(s.Contains(2), true)
t.Assert(s.AddIfNotExist(2), false)
t.Assert(s.Contains(2), true)
})
}
func TestSet_AddIfNotExistFunc(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := gset.New(true)
s.Add(1)
t.Assert(s.Contains(1), true)
t.Assert(s.Contains(2), false)
s.AddIfNotExistFunc(2, func() interface{} {
return 3
})
t.Assert(s.AddIfNotExistFunc(2, func() bool { return false }), false)
t.Assert(s.Contains(2), false)
t.Assert(s.Contains(3), true)
s.AddIfNotExistFunc(3, func() interface{} {
return 4
})
t.Assert(s.Contains(3), true)
t.Assert(s.Contains(4), false)
t.Assert(s.AddIfNotExistFunc(2, func() bool { return true }), true)
t.Assert(s.Contains(2), true)
t.Assert(s.AddIfNotExistFunc(2, func() bool { return true }), false)
t.Assert(s.Contains(2), true)
})
gtest.C(t, func(t *gtest.T) {
s := gset.New(true)
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
defer wg.Done()
r := s.AddIfNotExistFunc(1, func() bool {
time.Sleep(100 * time.Millisecond)
return true
})
t.Assert(r, false)
}()
s.Add(1)
t.Assert(s.Contains(1), true)
t.Assert(s.Contains(2), false)
wg.Wait()
})
}
s.AddIfNotExistFuncLock(2, func() interface{} {
return 3
})
t.Assert(s.Contains(2), false)
t.Assert(s.Contains(3), true)
s.AddIfNotExistFuncLock(3, func() interface{} {
return 4
})
t.Assert(s.Contains(3), true)
t.Assert(s.Contains(4), false)
func TestSet_AddIfNotExistFuncLock(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := gset.New(true)
wg := sync.WaitGroup{}
wg.Add(2)
go func() {
defer wg.Done()
r := s.AddIfNotExistFuncLock(1, func() bool {
time.Sleep(500 * time.Millisecond)
return true
})
t.Assert(r, true)
}()
time.Sleep(100 * time.Millisecond)
go func() {
defer wg.Done()
r := s.AddIfNotExistFuncLock(1, func() bool {
return true
})
t.Assert(r, false)
}()
wg.Wait()
})
}

View File

@ -13,17 +13,39 @@ import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/util/gconv"
"strings"
"sync"
"testing"
"time"
"github.com/gogf/gf/container/garray"
"github.com/gogf/gf/container/gset"
"github.com/gogf/gf/test/gtest"
)
func TestIntSet_Var(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var s gset.IntSet
s.Add(1, 1, 2)
s.Add([]int{3, 4}...)
t.Assert(s.Size(), 4)
t.AssertIN(1, s.Slice())
t.AssertIN(2, s.Slice())
t.AssertIN(3, s.Slice())
t.AssertIN(4, s.Slice())
t.AssertNI(0, s.Slice())
t.Assert(s.Contains(4), true)
t.Assert(s.Contains(5), false)
s.Remove(1)
t.Assert(s.Size(), 3)
s.Clear()
t.Assert(s.Size(), 0)
})
}
func TestIntSet_Basic(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := gset.NewIntSet()
s.Add(1).Add(1).Add(2)
s.Add(1, 1, 2)
s.Add([]int{3, 4}...)
t.Assert(s.Size(), 4)
t.AssertIN(1, s.Slice())
@ -43,7 +65,7 @@ func TestIntSet_Basic(t *testing.T) {
func TestIntSet_Iterator(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := gset.NewIntSet()
s.Add(1).Add(2).Add(3)
s.Add(1, 2, 3)
t.Assert(s.Size(), 3)
a1 := garray.New(true)
@ -64,7 +86,7 @@ func TestIntSet_Iterator(t *testing.T) {
func TestIntSet_LockFunc(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := gset.NewIntSet()
s.Add(1).Add(2).Add(3)
s.Add(1, 2, 3)
t.Assert(s.Size(), 3)
s.LockFunc(func(m map[int]struct{}) {
delete(m, 1)
@ -84,9 +106,9 @@ func TestIntSet_Equal(t *testing.T) {
s1 := gset.NewIntSet()
s2 := gset.NewIntSet()
s3 := gset.NewIntSet()
s1.Add(1).Add(2).Add(3)
s2.Add(1).Add(2).Add(3)
s3.Add(1).Add(2).Add(3).Add(4)
s1.Add(1, 2, 3)
s2.Add(1, 2, 3)
s3.Add(1, 2, 3, 4)
t.Assert(s1.Equal(s2), true)
t.Assert(s1.Equal(s3), false)
})
@ -97,9 +119,9 @@ func TestIntSet_IsSubsetOf(t *testing.T) {
s1 := gset.NewIntSet()
s2 := gset.NewIntSet()
s3 := gset.NewIntSet()
s1.Add(1).Add(2)
s2.Add(1).Add(2).Add(3)
s3.Add(1).Add(2).Add(3).Add(4)
s1.Add(1, 2)
s2.Add(1, 2, 3)
s3.Add(1, 2, 3, 4)
t.Assert(s1.IsSubsetOf(s2), true)
t.Assert(s2.IsSubsetOf(s3), true)
t.Assert(s1.IsSubsetOf(s3), true)
@ -112,8 +134,8 @@ func TestIntSet_Union(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s1 := gset.NewIntSet()
s2 := gset.NewIntSet()
s1.Add(1).Add(2)
s2.Add(3).Add(4)
s1.Add(1, 2)
s2.Add(3, 4)
s3 := s1.Union(s2)
t.Assert(s3.Contains(1), true)
t.Assert(s3.Contains(2), true)
@ -126,8 +148,8 @@ func TestIntSet_Diff(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s1 := gset.NewIntSet()
s2 := gset.NewIntSet()
s1.Add(1).Add(2).Add(3)
s2.Add(3).Add(4).Add(5)
s1.Add(1, 2, 3)
s2.Add(3, 4, 5)
s3 := s1.Diff(s2)
t.Assert(s3.Contains(1), true)
t.Assert(s3.Contains(2), true)
@ -140,8 +162,8 @@ func TestIntSet_Intersect(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s1 := gset.NewIntSet()
s2 := gset.NewIntSet()
s1.Add(1).Add(2).Add(3)
s2.Add(3).Add(4).Add(5)
s1.Add(1, 2, 3)
s2.Add(3, 4, 5)
s3 := s1.Intersect(s2)
t.Assert(s3.Contains(1), false)
t.Assert(s3.Contains(2), false)
@ -154,8 +176,8 @@ func TestIntSet_Complement(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s1 := gset.NewIntSet()
s2 := gset.NewIntSet()
s1.Add(1).Add(2).Add(3)
s2.Add(3).Add(4).Add(5)
s1.Add(1, 2, 3)
s2.Add(3, 4, 5)
s3 := s1.Complement(s2)
t.Assert(s3.Contains(1), false)
t.Assert(s3.Contains(2), false)
@ -167,7 +189,7 @@ func TestIntSet_Complement(t *testing.T) {
func TestIntSet_Size(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s1 := gset.NewIntSet(true)
s1.Add(1).Add(2).Add(3)
s1.Add(1, 2, 3)
t.Assert(s1.Size(), 3)
})
@ -178,8 +200,8 @@ func TestIntSet_Merge(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s1 := gset.NewIntSet()
s2 := gset.NewIntSet()
s1.Add(1).Add(2).Add(3)
s2.Add(3).Add(4).Add(5)
s1.Add(1, 2, 3)
s2.Add(3, 4, 5)
s3 := s1.Merge(s2)
t.Assert(s3.Contains(1), true)
t.Assert(s3.Contains(5), true)
@ -190,7 +212,7 @@ func TestIntSet_Merge(t *testing.T) {
func TestIntSet_Join(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s1 := gset.NewIntSet()
s1.Add(1).Add(2).Add(3)
s1.Add(1, 2, 3)
s3 := s1.Join(",")
t.Assert(strings.Contains(s3, "1"), true)
t.Assert(strings.Contains(s3, "2"), true)
@ -201,7 +223,7 @@ func TestIntSet_Join(t *testing.T) {
func TestIntSet_String(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s1 := gset.NewIntSet()
s1.Add(1).Add(2).Add(3)
s1.Add(1, 2, 3)
s3 := s1.String()
t.Assert(strings.Contains(s3, "["), true)
t.Assert(strings.Contains(s3, "]"), true)
@ -214,9 +236,9 @@ func TestIntSet_String(t *testing.T) {
func TestIntSet_Sum(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s1 := gset.NewIntSet()
s1.Add(1).Add(2).Add(3)
s1.Add(1, 2, 3)
s2 := gset.NewIntSet()
s2.Add(5).Add(6).Add(7)
s2.Add(5, 6, 7)
t.Assert(s2.Sum(), 18)
})
@ -226,7 +248,7 @@ func TestIntSet_Sum(t *testing.T) {
func TestIntSet_Pop(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := gset.NewIntSet()
s.Add(4).Add(2).Add(3)
s.Add(4, 2, 3)
t.Assert(s.Size(), 3)
t.AssertIN(s.Pop(), []int{4, 2, 3})
t.AssertIN(s.Pop(), []int{4, 2, 3})
@ -237,7 +259,7 @@ func TestIntSet_Pop(t *testing.T) {
func TestIntSet_Pops(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := gset.NewIntSet()
s.Add(1).Add(4).Add(2).Add(3)
s.Add(1, 4, 2, 3)
t.Assert(s.Size(), 4)
t.Assert(s.Pops(0), nil)
t.AssertIN(s.Pops(1), []int{1, 4, 2, 3})
@ -258,6 +280,74 @@ func TestIntSet_Pops(t *testing.T) {
})
}
func TestIntSet_AddIfNotExist(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := gset.NewIntSet(true)
s.Add(1)
t.Assert(s.Contains(1), true)
t.Assert(s.AddIfNotExist(1), false)
t.Assert(s.AddIfNotExist(2), true)
t.Assert(s.Contains(2), true)
t.Assert(s.AddIfNotExist(2), false)
t.Assert(s.Contains(2), true)
})
}
func TestIntSet_AddIfNotExistFunc(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := gset.NewIntSet(true)
s.Add(1)
t.Assert(s.Contains(1), true)
t.Assert(s.Contains(2), false)
t.Assert(s.AddIfNotExistFunc(2, func() bool { return false }), false)
t.Assert(s.Contains(2), false)
t.Assert(s.AddIfNotExistFunc(2, func() bool { return true }), true)
t.Assert(s.Contains(2), true)
t.Assert(s.AddIfNotExistFunc(2, func() bool { return true }), false)
t.Assert(s.Contains(2), true)
})
gtest.C(t, func(t *gtest.T) {
s := gset.NewIntSet(true)
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
defer wg.Done()
r := s.AddIfNotExistFunc(1, func() bool {
time.Sleep(100 * time.Millisecond)
return true
})
t.Assert(r, false)
}()
s.Add(1)
wg.Wait()
})
}
func TestIntSet_AddIfNotExistFuncLock(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := gset.NewIntSet(true)
wg := sync.WaitGroup{}
wg.Add(2)
go func() {
defer wg.Done()
r := s.AddIfNotExistFuncLock(1, func() bool {
time.Sleep(500 * time.Millisecond)
return true
})
t.Assert(r, true)
}()
time.Sleep(100 * time.Millisecond)
go func() {
defer wg.Done()
r := s.AddIfNotExistFuncLock(1, func() bool {
return true
})
t.Assert(r, false)
}()
wg.Wait()
})
}
func TestIntSet_Json(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s1 := []int{1, 3, 2, 4}
@ -287,46 +377,6 @@ func TestIntSet_Json(t *testing.T) {
})
}
func TestIntSet_AddIfNotExistFunc(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := gset.NewIntSet(true)
s.Add(1)
t.Assert(s.Contains(1), true)
t.Assert(s.Contains(2), false)
s.AddIfNotExistFunc(2, func() int {
return 3
})
t.Assert(s.Contains(2), false)
t.Assert(s.Contains(3), true)
s.AddIfNotExistFunc(3, func() int {
return 4
})
t.Assert(s.Contains(3), true)
t.Assert(s.Contains(4), false)
})
gtest.C(t, func(t *gtest.T) {
s := gset.NewIntSet(true)
s.Add(1)
t.Assert(s.Contains(1), true)
t.Assert(s.Contains(2), false)
s.AddIfNotExistFuncLock(2, func() int {
return 3
})
t.Assert(s.Contains(2), false)
t.Assert(s.Contains(3), true)
s.AddIfNotExistFuncLock(3, func() int {
return 4
})
t.Assert(s.Contains(3), true)
t.Assert(s.Contains(4), false)
})
}
func TestIntSet_UnmarshalValue(t *testing.T) {
type V struct {
Name string

View File

@ -13,17 +13,19 @@ import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/util/gconv"
"strings"
"sync"
"testing"
"time"
"github.com/gogf/gf/container/garray"
"github.com/gogf/gf/container/gset"
"github.com/gogf/gf/test/gtest"
)
func TestStrSet_Basic(t *testing.T) {
func TestStrSet_Var(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := gset.NewStrSet()
s.Add("1").Add("1").Add("2")
var s gset.StrSet
s.Add("1", "1", "2")
s.Add([]string{"3", "4"}...)
t.Assert(s.Size(), 4)
t.AssertIN("1", s.Slice())
@ -40,10 +42,40 @@ func TestStrSet_Basic(t *testing.T) {
})
}
func TestStrSet_Basic(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := gset.NewStrSet()
s.Add("1", "1", "2")
s.Add([]string{"3", "4"}...)
t.Assert(s.Size(), 4)
t.AssertIN("1", s.Slice())
t.AssertIN("2", s.Slice())
t.AssertIN("3", s.Slice())
t.AssertIN("4", s.Slice())
t.AssertNI("0", s.Slice())
t.Assert(s.Contains("4"), true)
t.Assert(s.Contains("5"), false)
s.Remove("1")
t.Assert(s.Size(), 3)
s.Clear()
t.Assert(s.Size(), 0)
})
}
func TestStrSet_ContainsI(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := gset.NewStrSet()
s.Add("a", "b", "C")
t.Assert(s.Contains("A"), false)
t.Assert(s.Contains("a"), true)
t.Assert(s.ContainsI("A"), true)
})
}
func TestStrSet_Iterator(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := gset.NewStrSet()
s.Add("1").Add("2").Add("3")
s.Add("1", "2", "3")
t.Assert(s.Size(), 3)
a1 := garray.New(true)
@ -64,7 +96,7 @@ func TestStrSet_Iterator(t *testing.T) {
func TestStrSet_LockFunc(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := gset.NewStrSet()
s.Add("1").Add("2").Add("3")
s.Add("1", "2", "3")
t.Assert(s.Size(), 3)
s.LockFunc(func(m map[string]struct{}) {
delete(m, "1")
@ -84,9 +116,9 @@ func TestStrSet_Equal(t *testing.T) {
s1 := gset.NewStrSet()
s2 := gset.NewStrSet()
s3 := gset.NewStrSet()
s1.Add("1").Add("2").Add("3")
s2.Add("1").Add("2").Add("3")
s3.Add("1").Add("2").Add("3").Add("4")
s1.Add("1", "2", "3")
s2.Add("1", "2", "3")
s3.Add("1", "2", "3", "4")
t.Assert(s1.Equal(s2), true)
t.Assert(s1.Equal(s3), false)
})
@ -97,9 +129,9 @@ func TestStrSet_IsSubsetOf(t *testing.T) {
s1 := gset.NewStrSet()
s2 := gset.NewStrSet()
s3 := gset.NewStrSet()
s1.Add("1").Add("2")
s2.Add("1").Add("2").Add("3")
s3.Add("1").Add("2").Add("3").Add("4")
s1.Add("1", "2")
s2.Add("1", "2", "3")
s3.Add("1", "2", "3", "4")
t.Assert(s1.IsSubsetOf(s2), true)
t.Assert(s2.IsSubsetOf(s3), true)
t.Assert(s1.IsSubsetOf(s3), true)
@ -112,8 +144,8 @@ func TestStrSet_Union(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s1 := gset.NewStrSet()
s2 := gset.NewStrSet()
s1.Add("1").Add("2")
s2.Add("3").Add("4")
s1.Add("1", "2")
s2.Add("3", "4")
s3 := s1.Union(s2)
t.Assert(s3.Contains("1"), true)
t.Assert(s3.Contains("2"), true)
@ -126,8 +158,8 @@ func TestStrSet_Diff(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s1 := gset.NewStrSet()
s2 := gset.NewStrSet()
s1.Add("1").Add("2").Add("3")
s2.Add("3").Add("4").Add("5")
s1.Add("1", "2", "3")
s2.Add("3", "4", "5")
s3 := s1.Diff(s2)
t.Assert(s3.Contains("1"), true)
t.Assert(s3.Contains("2"), true)
@ -140,8 +172,8 @@ func TestStrSet_Intersect(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s1 := gset.NewStrSet()
s2 := gset.NewStrSet()
s1.Add("1").Add("2").Add("3")
s2.Add("3").Add("4").Add("5")
s1.Add("1", "2", "3")
s2.Add("3", "4", "5")
s3 := s1.Intersect(s2)
t.Assert(s3.Contains("1"), false)
t.Assert(s3.Contains("2"), false)
@ -154,8 +186,8 @@ func TestStrSet_Complement(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s1 := gset.NewStrSet()
s2 := gset.NewStrSet()
s1.Add("1").Add("2").Add("3")
s2.Add("3").Add("4").Add("5")
s1.Add("1", "2", "3")
s2.Add("3", "4", "5")
s3 := s1.Complement(s2)
t.Assert(s3.Contains("1"), false)
t.Assert(s3.Contains("2"), false)
@ -179,8 +211,8 @@ func TestStrSet_Merge(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s1 := gset.NewStrSet()
s2 := gset.NewStrSet()
s1.Add("1").Add("2").Add("3")
s2.Add("3").Add("4").Add("5")
s1.Add("1", "2", "3")
s2.Add("3", "4", "5")
s3 := s1.Merge(s2)
t.Assert(s3.Contains("1"), true)
t.Assert(s3.Contains("6"), false)
@ -207,7 +239,7 @@ func TestStrSet_Join(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s1 := gset.NewStrSet()
s1.Add("a").Add(`"b"`).Add(`\c`)
s1.Add("a", `"b"`, `\c`)
str1 := s1.Join(",")
t.Assert(strings.Contains(str1, `"b"`), true)
t.Assert(strings.Contains(str1, `\c`), true)
@ -225,7 +257,7 @@ func TestStrSet_String(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s1 := gset.New(true)
s1.Add("a").Add("a2").Add("b").Add("c")
s1.Add("a", "a2", "b", "c")
str1 := s1.String()
t.Assert(strings.Contains(str1, "["), true)
t.Assert(strings.Contains(str1, "]"), true)
@ -253,7 +285,7 @@ func TestStrSet_Size(t *testing.T) {
func TestStrSet_Remove(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s1 := gset.NewStrSetFrom([]string{"a", "b", "c"}, true)
s1 = s1.Remove("b")
s1.Remove("b")
t.Assert(s1.Contains("b"), false)
t.Assert(s1.Contains("c"), true)
})
@ -294,6 +326,74 @@ func TestStrSet_Pops(t *testing.T) {
})
}
func TestStrSet_AddIfNotExist(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := gset.NewStrSet(true)
s.Add("1")
t.Assert(s.Contains("1"), true)
t.Assert(s.AddIfNotExist("1"), false)
t.Assert(s.AddIfNotExist("2"), true)
t.Assert(s.Contains("2"), true)
t.Assert(s.AddIfNotExist("2"), false)
t.Assert(s.Contains("2"), true)
})
}
func TestStrSet_AddIfNotExistFunc(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := gset.NewStrSet(true)
s.Add("1")
t.Assert(s.Contains("1"), true)
t.Assert(s.Contains("2"), false)
t.Assert(s.AddIfNotExistFunc("2", func() bool { return false }), false)
t.Assert(s.Contains("2"), false)
t.Assert(s.AddIfNotExistFunc("2", func() bool { return true }), true)
t.Assert(s.Contains("2"), true)
t.Assert(s.AddIfNotExistFunc("2", func() bool { return true }), false)
t.Assert(s.Contains("2"), true)
})
gtest.C(t, func(t *gtest.T) {
s := gset.NewStrSet(true)
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
defer wg.Done()
r := s.AddIfNotExistFunc("1", func() bool {
time.Sleep(100 * time.Millisecond)
return true
})
t.Assert(r, false)
}()
s.Add("1")
wg.Wait()
})
}
func TestStrSet_AddIfNotExistFuncLock(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := gset.NewStrSet(true)
wg := sync.WaitGroup{}
wg.Add(2)
go func() {
defer wg.Done()
r := s.AddIfNotExistFuncLock("1", func() bool {
time.Sleep(500 * time.Millisecond)
return true
})
t.Assert(r, true)
}()
time.Sleep(100 * time.Millisecond)
go func() {
defer wg.Done()
r := s.AddIfNotExistFuncLock("1", func() bool {
return true
})
t.Assert(r, false)
}()
wg.Wait()
})
}
func TestStrSet_Json(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s1 := []string{"a", "b", "d", "c"}
@ -323,46 +423,6 @@ func TestStrSet_Json(t *testing.T) {
})
}
func TestStrSet_AddIfNotExistFunc(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := gset.NewStrSet(true)
s.Add("1")
t.Assert(s.Contains("1"), true)
t.Assert(s.Contains("2"), false)
s.AddIfNotExistFunc("2", func() string {
return "3"
})
t.Assert(s.Contains("2"), false)
t.Assert(s.Contains("3"), true)
s.AddIfNotExistFunc("3", func() string {
return "4"
})
t.Assert(s.Contains("3"), true)
t.Assert(s.Contains("4"), false)
})
gtest.C(t, func(t *gtest.T) {
s := gset.NewStrSet(true)
s.Add("1")
t.Assert(s.Contains("1"), true)
t.Assert(s.Contains("2"), false)
s.AddIfNotExistFuncLock("2", func() string {
return "3"
})
t.Assert(s.Contains("2"), false)
t.Assert(s.Contains("3"), true)
s.AddIfNotExistFuncLock("3", func() string {
return "4"
})
t.Assert(s.Contains("3"), true)
t.Assert(s.Contains("4"), false)
})
}
func TestStrSet_UnmarshalValue(t *testing.T) {
type V struct {
Name string

View File

@ -18,7 +18,7 @@ import (
// AVLTree holds elements of the AVL tree.
type AVLTree struct {
mu *rwmutex.RWMutex
mu rwmutex.RWMutex
root *AVLTreeNode
comparator func(v1, v2 interface{}) int
size int
@ -38,7 +38,7 @@ type AVLTreeNode struct {
// which is false in default.
func NewAVLTree(comparator func(v1, v2 interface{}) int, safe ...bool) *AVLTree {
return &AVLTree{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
comparator: comparator,
}
}
@ -55,7 +55,7 @@ func NewAVLTreeFrom(comparator func(v1, v2 interface{}) int, data map[interface{
}
// Clone returns a new tree with a copy of current tree.
func (tree *AVLTree) Clone(safe ...bool) *AVLTree {
func (tree *AVLTree) Clone() *AVLTree {
newTree := NewAVLTree(tree.comparator, !tree.mu.IsSafe())
newTree.Sets(tree.Map())
return newTree
@ -93,7 +93,7 @@ func (tree *AVLTree) Search(key interface{}) (value interface{}, found bool) {
func (tree *AVLTree) doSearch(key interface{}) (node *AVLTreeNode, found bool) {
node = tree.root
for node != nil {
cmp := tree.comparator(key, node.Key)
cmp := tree.getComparator()(key, node.Key)
switch {
case cmp == 0:
return node, true
@ -331,7 +331,7 @@ func (tree *AVLTree) Floor(key interface{}) (floor *AVLTreeNode, found bool) {
defer tree.mu.RUnlock()
n := tree.root
for n != nil {
c := tree.comparator(key, n.Key)
c := tree.getComparator()(key, n.Key)
switch {
case c == 0:
return n, true
@ -361,7 +361,7 @@ func (tree *AVLTree) Ceiling(key interface{}) (ceiling *AVLTreeNode, found bool)
defer tree.mu.RUnlock()
n := tree.root
for n != nil {
c := tree.comparator(key, n.Key)
c := tree.getComparator()(key, n.Key)
switch {
case c == 0:
return n, true
@ -465,7 +465,7 @@ func (tree *AVLTree) IteratorFrom(key interface{}, match bool, f func(key, value
tree.IteratorAscFrom(key, match, f)
}
// IteratorAsc iterates the tree in ascending order with given callback function <f>.
// IteratorAsc iterates the tree readonly in ascending order with given callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
func (tree *AVLTree) IteratorAsc(f func(key, value interface{}) bool) {
tree.mu.RLock()
@ -473,7 +473,7 @@ func (tree *AVLTree) IteratorAsc(f func(key, value interface{}) bool) {
tree.doIteratorAsc(tree.bottom(0), f)
}
// IteratorAscFrom iterates the tree in ascending order with given callback function <f>.
// IteratorAscFrom iterates the tree readonly in ascending order with given callback function <f>.
// The parameter <key> specifies the start entry for iterating. The <match> specifies whether
// starting iterating if the <key> is fully matched, or else using index searching iterating.
// If <f> returns true, then it continues iterating; or false to stop.
@ -499,7 +499,7 @@ func (tree *AVLTree) doIteratorAsc(node *AVLTreeNode, f func(key, value interfac
}
}
// IteratorDesc iterates the tree in descending order with given callback function <f>.
// IteratorDesc iterates the tree readonly in descending order with given callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
func (tree *AVLTree) IteratorDesc(f func(key, value interface{}) bool) {
tree.mu.RLock()
@ -507,7 +507,7 @@ func (tree *AVLTree) IteratorDesc(f func(key, value interface{}) bool) {
tree.doIteratorDesc(tree.bottom(1), f)
}
// IteratorDescFrom iterates the tree in descending order with given callback function <f>.
// IteratorDescFrom iterates the tree readonly in descending order with given callback function <f>.
// The parameter <key> specifies the start entry for iterating. The <match> specifies whether
// starting iterating if the <key> is fully matched, or else using index searching iterating.
// If <f> returns true, then it continues iterating; or false to stop.
@ -541,7 +541,7 @@ func (tree *AVLTree) put(key interface{}, value interface{}, p *AVLTreeNode, qp
return true
}
c := tree.comparator(key, q.Key)
c := tree.getComparator()(key, q.Key)
if c == 0 {
q.Key = key
q.Value = value
@ -566,7 +566,7 @@ func (tree *AVLTree) remove(key interface{}, qp **AVLTreeNode) (value interface{
return nil, false
}
c := tree.comparator(key, q.Key)
c := tree.getComparator()(key, q.Key)
if c == 0 {
tree.size--
value = q.Value
@ -784,3 +784,12 @@ func output(node *AVLTreeNode, prefix string, isTail bool, str *string) {
func (tree *AVLTree) MarshalJSON() ([]byte, error) {
return json.Marshal(tree.Map())
}
// getComparator returns the comparator if it's previously set,
// or else it panics.
func (tree *AVLTree) getComparator() func(a, b interface{}) int {
if tree.comparator == nil {
panic("comparator is missing for tree")
}
return tree.comparator
}

View File

@ -20,7 +20,7 @@ import (
// BTree holds elements of the B-tree.
type BTree struct {
mu *rwmutex.RWMutex
mu rwmutex.RWMutex
root *BTreeNode
comparator func(v1, v2 interface{}) int
size int // Total number of keys in the tree
@ -50,7 +50,7 @@ func NewBTree(m int, comparator func(v1, v2 interface{}) int, safe ...bool) *BTr
}
return &BTree{
comparator: comparator,
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
m: m,
}
}
@ -67,7 +67,7 @@ func NewBTreeFrom(m int, comparator func(v1, v2 interface{}) int, data map[inter
}
// Clone returns a new tree with a copy of current tree.
func (tree *BTree) Clone(safe ...bool) *BTree {
func (tree *BTree) Clone() *BTree {
newTree := NewBTree(tree.m, tree.comparator, !tree.mu.IsSafe())
newTree.Sets(tree.Map())
return newTree
@ -406,7 +406,7 @@ func (tree *BTree) IteratorFrom(key interface{}, match bool, f func(key, value i
tree.IteratorAscFrom(key, match, f)
}
// IteratorAsc iterates the tree in ascending order with given callback function <f>.
// IteratorAsc iterates the tree readonly in ascending order with given callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
func (tree *BTree) IteratorAsc(f func(key, value interface{}) bool) {
tree.mu.RLock()
@ -418,7 +418,7 @@ func (tree *BTree) IteratorAsc(f func(key, value interface{}) bool) {
tree.doIteratorAsc(node, node.Entries[0], 0, f)
}
// IteratorAscFrom iterates the tree in ascending order with given callback function <f>.
// IteratorAscFrom iterates the tree readonly in ascending order with given callback function <f>.
// The parameter <key> specifies the start entry for iterating. The <match> specifies whether
// starting iterating if the <key> is fully matched, or else using index searching iterating.
// If <f> returns true, then it continues iterating; or false to stop.
@ -479,7 +479,7 @@ loop:
}
}
// IteratorDesc iterates the tree in descending order with given callback function <f>.
// IteratorDesc iterates the tree readonly in descending order with given callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
func (tree *BTree) IteratorDesc(f func(key, value interface{}) bool) {
tree.mu.RLock()
@ -493,7 +493,7 @@ func (tree *BTree) IteratorDesc(f func(key, value interface{}) bool) {
tree.doIteratorDesc(node, entry, index, f)
}
// IteratorDescFrom iterates the tree in descending order with given callback function <f>.
// IteratorDescFrom iterates the tree readonly in descending order with given callback function <f>.
// The parameter <key> specifies the start entry for iterating. The <match> specifies whether
// starting iterating if the <key> is fully matched, or else using index searching iterating.
// If <f> returns true, then it continues iterating; or false to stop.
@ -510,7 +510,7 @@ func (tree *BTree) IteratorDescFrom(key interface{}, match bool, f func(key, val
}
}
// IteratorDesc iterates the tree in descending order with given callback function <f>.
// IteratorDesc iterates the tree readonly in descending order with given callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
func (tree *BTree) doIteratorDesc(node *BTreeNode, entry *BTreeEntry, index int, f func(key, value interface{}) bool) {
first := true
@ -621,7 +621,7 @@ func (tree *BTree) search(node *BTreeNode, key interface{}) (index int, found bo
low, mid, high := 0, 0, len(node.Entries)-1
for low <= high {
mid = (high + low) / 2
compare := tree.comparator(key, node.Entries[mid].Key)
compare := tree.getComparator()(key, node.Entries[mid].Key)
switch {
case compare > 0:
low = mid + 1
@ -934,3 +934,12 @@ func (tree *BTree) deleteChild(node *BTreeNode, index int) {
func (tree *BTree) MarshalJSON() ([]byte, error) {
return json.Marshal(tree.Map())
}
// getComparator returns the comparator if it's previously set,
// or else it panics.
func (tree *BTree) getComparator() func(a, b interface{}) int {
if tree.comparator == nil {
panic("comparator is missing for tree")
}
return tree.comparator
}

View File

@ -24,7 +24,7 @@ const (
// RedBlackTree holds elements of the red-black tree.
type RedBlackTree struct {
mu *rwmutex.RWMutex
mu rwmutex.RWMutex
root *RedBlackTreeNode
size int
comparator func(v1, v2 interface{}) int
@ -45,7 +45,7 @@ type RedBlackTreeNode struct {
// which is false in default.
func NewRedBlackTree(comparator func(v1, v2 interface{}) int, safe ...bool) *RedBlackTree {
return &RedBlackTree{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
comparator: comparator,
}
}
@ -82,7 +82,7 @@ func (tree *RedBlackTree) SetComparator(comparator func(a, b interface{}) int) {
}
// Clone returns a new tree with a copy of current tree.
func (tree *RedBlackTree) Clone(safe ...bool) *RedBlackTree {
func (tree *RedBlackTree) Clone() *RedBlackTree {
newTree := NewRedBlackTree(tree.comparator, !tree.mu.IsSafe())
newTree.Sets(tree.Map())
return newTree
@ -109,14 +109,14 @@ func (tree *RedBlackTree) doSet(key interface{}, value interface{}) {
insertedNode := (*RedBlackTreeNode)(nil)
if tree.root == nil {
// Assert key is of comparator's type for initial tree
tree.comparator(key, key)
tree.getComparator()(key, key)
tree.root = &RedBlackTreeNode{Key: key, Value: value, color: red}
insertedNode = tree.root
} else {
node := tree.root
loop := true
for loop {
compare := tree.comparator(key, node.Key)
compare := tree.getComparator()(key, node.Key)
switch {
case compare == 0:
//node.Key = key
@ -337,8 +337,10 @@ func (tree *RedBlackTree) Size() int {
// Keys returns all keys in asc order.
func (tree *RedBlackTree) Keys() []interface{} {
keys := make([]interface{}, tree.Size())
index := 0
var (
keys = make([]interface{}, tree.Size())
index = 0
)
tree.IteratorAsc(func(key, value interface{}) bool {
keys[index] = key
index++
@ -349,8 +351,10 @@ func (tree *RedBlackTree) Keys() []interface{} {
// Values returns all values in asc order based on the key.
func (tree *RedBlackTree) Values() []interface{} {
values := make([]interface{}, tree.Size())
index := 0
var (
values = make([]interface{}, tree.Size())
index = 0
)
tree.IteratorAsc(func(key, value interface{}) bool {
values[index] = value
index++
@ -440,7 +444,7 @@ func (tree *RedBlackTree) Floor(key interface{}) (floor *RedBlackTreeNode, found
defer tree.mu.RUnlock()
n := tree.root
for n != nil {
compare := tree.comparator(key, n.Key)
compare := tree.getComparator()(key, n.Key)
switch {
case compare == 0:
return n, true
@ -468,7 +472,7 @@ func (tree *RedBlackTree) Ceiling(key interface{}) (ceiling *RedBlackTreeNode, f
defer tree.mu.RUnlock()
n := tree.root
for n != nil {
compare := tree.comparator(key, n.Key)
compare := tree.getComparator()(key, n.Key)
switch {
case compare == 0:
return n, true
@ -495,7 +499,7 @@ func (tree *RedBlackTree) IteratorFrom(key interface{}, match bool, f func(key,
tree.IteratorAscFrom(key, match, f)
}
// IteratorAsc iterates the tree in ascending order with given callback function <f>.
// IteratorAsc iterates the tree readonly in ascending order with given callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
func (tree *RedBlackTree) IteratorAsc(f func(key, value interface{}) bool) {
tree.mu.RLock()
@ -503,7 +507,7 @@ func (tree *RedBlackTree) IteratorAsc(f func(key, value interface{}) bool) {
tree.doIteratorAsc(tree.leftNode(), f)
}
// IteratorAscFrom iterates the tree in ascending order with given callback function <f>.
// IteratorAscFrom iterates the tree readonly in ascending order with given callback function <f>.
// The parameter <key> specifies the start entry for iterating. The <match> specifies whether
// starting iterating if the <key> is fully matched, or else using index searching iterating.
// If <f> returns true, then it continues iterating; or false to stop.
@ -539,14 +543,14 @@ loop:
old := node
for node.parent != nil {
node = node.parent
if tree.comparator(old.Key, node.Key) <= 0 {
if tree.getComparator()(old.Key, node.Key) <= 0 {
goto loop
}
}
}
}
// IteratorDesc iterates the tree in descending order with given callback function <f>.
// IteratorDesc iterates the tree readonly in descending order with given callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
func (tree *RedBlackTree) IteratorDesc(f func(key, value interface{}) bool) {
tree.mu.RLock()
@ -554,7 +558,7 @@ func (tree *RedBlackTree) IteratorDesc(f func(key, value interface{}) bool) {
tree.doIteratorDesc(tree.rightNode(), f)
}
// IteratorDescFrom iterates the tree in descending order with given callback function <f>.
// IteratorDescFrom iterates the tree readonly in descending order with given callback function <f>.
// The parameter <key> specifies the start entry for iterating. The <match> specifies whether
// starting iterating if the <key> is fully matched, or else using index searching iterating.
// If <f> returns true, then it continues iterating; or false to stop.
@ -590,7 +594,7 @@ loop:
old := node
for node.parent != nil {
node = node.parent
if tree.comparator(old.Key, node.Key) >= 0 {
if tree.getComparator()(old.Key, node.Key) >= 0 {
goto loop
}
}
@ -699,7 +703,7 @@ func (tree *RedBlackTree) output(node *RedBlackTreeNode, prefix string, isTail b
func (tree *RedBlackTree) doSearch(key interface{}) (node *RedBlackTreeNode, found bool) {
node = tree.root
for node != nil {
compare := tree.comparator(key, node.Key)
compare := tree.getComparator()(key, node.Key)
switch {
case compare == 0:
return node, true
@ -927,12 +931,11 @@ func (tree *RedBlackTree) MarshalJSON() ([]byte, error) {
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
func (tree *RedBlackTree) UnmarshalJSON(b []byte) error {
if tree.mu == nil {
tree.mu = rwmutex.New()
tree.comparator = gutil.ComparatorString
}
tree.mu.Lock()
defer tree.mu.Unlock()
if tree.comparator == nil {
tree.comparator = gutil.ComparatorString
}
var data map[string]interface{}
if err := json.Unmarshal(b, &data); err != nil {
return err
@ -945,14 +948,22 @@ func (tree *RedBlackTree) UnmarshalJSON(b []byte) error {
// UnmarshalValue is an interface implement which sets any type of value for map.
func (tree *RedBlackTree) UnmarshalValue(value interface{}) (err error) {
if tree.mu == nil {
tree.mu = rwmutex.New()
tree.comparator = gutil.ComparatorString
}
tree.mu.Lock()
defer tree.mu.Unlock()
if tree.comparator == nil {
tree.comparator = gutil.ComparatorString
}
for k, v := range gconv.Map(value) {
tree.doSet(k, v)
}
return
}
// getComparator returns the comparator if it's previously set,
// or else it panics.
func (tree *RedBlackTree) getComparator() func(a, b interface{}) int {
if tree.comparator == nil {
panic("comparator is missing for tree")
}
return tree.comparator
}

View File

@ -54,11 +54,13 @@ func (v *Var) Clone() *Var {
// Set sets <value> to <v>, and returns the old value.
func (v *Var) Set(value interface{}) (old interface{}) {
if v.safe {
old = v.value.(*gtype.Interface).Set(value)
} else {
old = v.value
v.value = value
if t, ok := v.value.(*gtype.Interface); ok {
old = t.Set(value)
return
}
}
old = v.value
v.value = value
return
}
@ -68,7 +70,9 @@ func (v *Var) Val() interface{} {
return nil
}
if v.safe {
return v.value.(*gtype.Interface).Val()
if t, ok := v.value.(*gtype.Interface); ok {
return t.Val()
}
}
return v.value
}
@ -231,100 +235,6 @@ func (v *Var) GTime(format ...string) *gtime.Time {
return gconv.GTime(v.Val(), format...)
}
// Map converts <v> to map[string]interface{}.
func (v *Var) Map(tags ...string) map[string]interface{} {
return gconv.Map(v.Val(), tags...)
}
// MapStrStr converts <v> to map[string]string.
func (v *Var) MapStrStr(tags ...string) map[string]string {
return gconv.MapStrStr(v.Val(), tags...)
}
// MapStrVar converts <v> to map[string]*Var.
func (v *Var) MapStrVar(tags ...string) map[string]*Var {
m := v.Map(tags...)
if len(m) > 0 {
vMap := make(map[string]*Var)
for k, v := range m {
vMap[k] = New(v)
}
return vMap
}
return nil
}
// MapDeep converts <v> to map[string]interface{} recursively.
func (v *Var) MapDeep(tags ...string) map[string]interface{} {
return gconv.MapDeep(v.Val(), tags...)
}
// MapDeep converts <v> to map[string]string recursively.
func (v *Var) MapStrStrDeep(tags ...string) map[string]string {
return gconv.MapStrStrDeep(v.Val(), tags...)
}
// MapStrVarDeep converts <v> to map[string]*Var recursively.
func (v *Var) MapStrVarDeep(tags ...string) map[string]*Var {
m := v.MapDeep(tags...)
if len(m) > 0 {
vMap := make(map[string]*Var)
for k, v := range m {
vMap[k] = New(v)
}
return vMap
}
return nil
}
// Struct maps value of <v> to <pointer>.
// The parameter <pointer> should be a pointer to a struct instance.
// The parameter <mapping> is used to specify the key-to-attribute mapping rules.
func (v *Var) Struct(pointer interface{}, mapping ...map[string]string) error {
return gconv.Struct(v.Val(), pointer, mapping...)
}
// Struct maps value of <v> to <pointer> recursively.
// The parameter <pointer> should be a pointer to a struct instance.
// The parameter <mapping> is used to specify the key-to-attribute mapping rules.
func (v *Var) StructDeep(pointer interface{}, mapping ...map[string]string) error {
return gconv.StructDeep(v.Val(), pointer, mapping...)
}
// Structs converts <v> to given struct slice.
func (v *Var) Structs(pointer interface{}, mapping ...map[string]string) (err error) {
return gconv.Structs(v.Val(), pointer, mapping...)
}
// StructsDeep converts <v> to given struct slice recursively.
func (v *Var) StructsDeep(pointer interface{}, mapping ...map[string]string) (err error) {
return gconv.StructsDeep(v.Val(), pointer, mapping...)
}
// MapToMap converts map type variable <params> to another map type variable <pointer>.
// The elements of <pointer> should be type of struct/*struct.
func (v *Var) MapToMap(pointer interface{}, mapping ...map[string]string) (err error) {
return gconv.MapToMap(v.Val(), pointer, mapping...)
}
// MapToMapDeep recursively converts map type variable <params> to another map type variable <pointer>.
// The elements of <pointer> should be type of struct/*struct.
func (v *Var) MapToMapDeep(pointer interface{}, mapping ...map[string]string) (err error) {
return gconv.MapToMapDeep(v.Val(), pointer, mapping...)
}
// MapToMaps converts map type variable <params> to another map type variable <pointer>.
// The elements of <pointer> should be type of []struct/[]*struct.
func (v *Var) MapToMaps(pointer interface{}, mapping ...map[string]string) (err error) {
return gconv.MapToMaps(v.Val(), pointer, mapping...)
}
// MapToMapsDeep recursively converts map type variable <params> to another map type variable <pointer>.
// The elements of <pointer> should be type of []struct/[]*struct.
func (v *Var) MapToMapsDeep(pointer interface{}, mapping ...map[string]string) (err error) {
return gconv.MapToMapsDeep(v.Val(), pointer, mapping...)
}
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
func (v *Var) MarshalJSON() ([]byte, error) {
return json.Marshal(v.Val())

View File

@ -0,0 +1,87 @@
// 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/gconv"
// Map converts and returns <v> as map[string]interface{}.
func (v *Var) Map(tags ...string) map[string]interface{} {
return gconv.Map(v.Val(), tags...)
}
// 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.
func (v *Var) MapStrVar(tags ...string) map[string]*Var {
m := v.Map(tags...)
if len(m) > 0 {
vMap := make(map[string]*Var)
for k, v := range m {
vMap[k] = New(v)
}
return vMap
}
return nil
}
// MapDeep converts and returns <v> as map[string]interface{} recursively.
func (v *Var) MapDeep(tags ...string) map[string]interface{} {
return gconv.MapDeep(v.Val(), tags...)
}
// MapDeep converts and returns <v> as map[string]string recursively.
func (v *Var) MapStrStrDeep(tags ...string) map[string]string {
return gconv.MapStrStrDeep(v.Val(), tags...)
}
// MapStrVarDeep converts and returns <v> as map[string]*Var recursively.
func (v *Var) MapStrVarDeep(tags ...string) map[string]*Var {
m := v.MapDeep(tags...)
if len(m) > 0 {
vMap := make(map[string]*Var)
for k, v := range m {
vMap[k] = New(v)
}
return vMap
}
return nil
}
// Maps converts and returns <v> as map[string]string.
// See gconv.Maps.
func (v *Var) Maps(tags ...string) []map[string]interface{} {
return gconv.Maps(v.Val(), tags...)
}
// MapToMap converts 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)
}
// MapToMapDeep converts 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)
}
// MapToMaps converts map type variable <params> to another map type variable <pointer>.
// See gconv.MapToMaps.
func (v *Var) MapToMaps(pointer interface{}, mapping ...map[string]string) (err error) {
return gconv.MapToMaps(v.Val(), pointer, mapping...)
}
// MapToMapsDeep converts map type variable <params> to another map type variable
// <pointer> recursively.
// See gconv.MapToMapsDeep.
func (v *Var) MapToMapsDeep(pointer interface{}, mapping ...map[string]string) (err error) {
return gconv.MapToMapsDeep(v.Val(), pointer, mapping...)
}

View File

@ -0,0 +1,33 @@
// 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/gconv"
// Struct maps value of <v> to <pointer>.
// The parameter <pointer> should be a pointer to a struct instance.
// The parameter <mapping> is used to specify the key-to-attribute mapping rules.
func (v *Var) Struct(pointer interface{}, mapping ...map[string]string) error {
return gconv.Struct(v.Val(), pointer, mapping...)
}
// Struct maps value of <v> to <pointer> recursively.
// The parameter <pointer> should be a pointer to a struct instance.
// The parameter <mapping> is used to specify the key-to-attribute mapping rules.
func (v *Var) StructDeep(pointer interface{}, mapping ...map[string]string) error {
return gconv.StructDeep(v.Val(), pointer, mapping...)
}
// Structs converts and returns <v> as given struct slice.
func (v *Var) Structs(pointer interface{}, mapping ...map[string]string) (err 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) {
return gconv.StructsDeep(v.Val(), pointer, mapping...)
}

View File

@ -9,14 +9,10 @@ package gvar_test
import (
"bytes"
"encoding/binary"
"encoding/json"
"github.com/gogf/gf/util/gconv"
"math"
"testing"
"time"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/container/gvar"
"github.com/gogf/gf/test/gtest"
)
@ -305,90 +301,6 @@ func Test_Duration(t *testing.T) {
})
}
func Test_Map(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
m := g.Map{
"k1": "v1",
"k2": "v2",
}
objOne := gvar.New(m, true)
t.Assert(objOne.Map()["k1"], m["k1"])
t.Assert(objOne.Map()["k2"], m["k2"])
})
}
func Test_Struct(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
type StTest struct {
Test int
}
Kv := make(map[string]int, 1)
Kv["Test"] = 100
testObj := &StTest{}
objOne := gvar.New(Kv, true)
objOne.Struct(testObj)
t.Assert(testObj.Test, Kv["Test"])
})
gtest.C(t, func(t *gtest.T) {
type StTest struct {
Test int8
}
o := &StTest{}
v := gvar.New(g.Slice{"Test", "-25"})
v.Struct(o)
t.Assert(o.Test, -25)
})
}
func Test_Json(t *testing.T) {
// Marshal
gtest.C(t, func(t *gtest.T) {
s := "i love gf"
v := gvar.New(s)
b1, err1 := json.Marshal(v)
b2, err2 := json.Marshal(s)
t.Assert(err1, err2)
t.Assert(b1, b2)
})
gtest.C(t, func(t *gtest.T) {
s := int64(math.MaxInt64)
v := gvar.New(s)
b1, err1 := json.Marshal(v)
b2, err2 := json.Marshal(s)
t.Assert(err1, err2)
t.Assert(b1, b2)
})
// Unmarshal
gtest.C(t, func(t *gtest.T) {
s := "i love gf"
v := gvar.New(nil)
b, err := json.Marshal(s)
t.Assert(err, nil)
err = json.Unmarshal(b, v)
t.Assert(err, nil)
t.Assert(v.String(), s)
})
gtest.C(t, func(t *gtest.T) {
var v gvar.Var
s := "i love gf"
b, err := json.Marshal(s)
t.Assert(err, nil)
err = json.Unmarshal(b, &v)
t.Assert(err, nil)
t.Assert(v.String(), s)
})
}
func Test_UnmarshalValue(t *testing.T) {
type V struct {
Name string

View File

@ -0,0 +1,59 @@
// 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 (
"encoding/json"
"github.com/gogf/gf/container/gvar"
"github.com/gogf/gf/test/gtest"
"math"
"testing"
)
func Test_Json(t *testing.T) {
// Marshal
gtest.C(t, func(t *gtest.T) {
s := "i love gf"
v := gvar.New(s)
b1, err1 := json.Marshal(v)
b2, err2 := json.Marshal(s)
t.Assert(err1, err2)
t.Assert(b1, b2)
})
gtest.C(t, func(t *gtest.T) {
s := int64(math.MaxInt64)
v := gvar.New(s)
b1, err1 := json.Marshal(v)
b2, err2 := json.Marshal(s)
t.Assert(err1, err2)
t.Assert(b1, b2)
})
// Unmarshal
gtest.C(t, func(t *gtest.T) {
s := "i love gf"
v := gvar.New(nil)
b, err := json.Marshal(s)
t.Assert(err, nil)
err = json.Unmarshal(b, v)
t.Assert(err, nil)
t.Assert(v.String(), s)
})
gtest.C(t, func(t *gtest.T) {
var v gvar.Var
s := "i love gf"
b, err := json.Marshal(s)
t.Assert(err, nil)
err = json.Unmarshal(b, &v)
t.Assert(err, nil)
t.Assert(v.String(), s)
})
}

View File

@ -0,0 +1,26 @@
// 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_Map(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
m := g.Map{
"k1": "v1",
"k2": "v2",
}
objOne := gvar.New(m, true)
t.Assert(objOne.Map()["k1"], m["k1"])
t.Assert(objOne.Map()["k2"], m["k2"])
})
}

View File

@ -0,0 +1,69 @@
// 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_MapToMap(t *testing.T) {
// map[int]int -> map[string]string
// empty original map.
gtest.C(t, func(t *gtest.T) {
m1 := g.MapIntInt{}
m2 := g.MapStrStr{}
t.Assert(gvar.New(m1).MapToMap(&m2), nil)
t.Assert(len(m1), len(m2))
})
// map[int]int -> map[string]string
gtest.C(t, func(t *gtest.T) {
m1 := g.MapIntInt{
1: 100,
2: 200,
}
m2 := g.MapStrStr{}
t.Assert(gvar.New(m1).MapToMap(&m2), nil)
t.Assert(m2["1"], m1[1])
t.Assert(m2["2"], m1[2])
})
// map[string]interface{} -> map[string]string
gtest.C(t, func(t *gtest.T) {
m1 := g.Map{
"k1": "v1",
"k2": "v2",
}
m2 := g.MapStrStr{}
t.Assert(gvar.New(m1).MapToMap(&m2), nil)
t.Assert(m2["k1"], m1["k1"])
t.Assert(m2["k2"], m1["k2"])
})
// map[string]string -> map[string]interface{}
gtest.C(t, func(t *gtest.T) {
m1 := g.MapStrStr{
"k1": "v1",
"k2": "v2",
}
m2 := g.Map{}
t.Assert(gvar.New(m1).MapToMap(&m2), nil)
t.Assert(m2["k1"], m1["k1"])
t.Assert(m2["k2"], m1["k2"])
})
// map[string]interface{} -> map[interface{}]interface{}
gtest.C(t, func(t *gtest.T) {
m1 := g.MapStrStr{
"k1": "v1",
"k2": "v2",
}
m2 := g.MapAnyAny{}
t.Assert(gvar.New(m1).MapToMap(&m2), nil)
t.Assert(m2["k1"], m1["k1"])
t.Assert(m2["k2"], m1["k2"])
})
}

View File

@ -0,0 +1,42 @@
// 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_Struct(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
type StTest struct {
Test int
}
Kv := make(map[string]int, 1)
Kv["Test"] = 100
testObj := &StTest{}
objOne := gvar.New(Kv, true)
objOne.Struct(testObj)
t.Assert(testObj.Test, Kv["Test"])
})
gtest.C(t, func(t *gtest.T) {
type StTest struct {
Test int8
}
o := &StTest{}
v := gvar.New(g.Slice{"Test", "-25"})
v.Struct(o)
t.Assert(o.Test, -25)
})
}

View File

@ -11,6 +11,7 @@ import (
"database/sql"
"errors"
"fmt"
"github.com/gogf/gf/container/gvar"
"github.com/gogf/gf/internal/intlog"
"time"
@ -18,7 +19,6 @@ import (
"github.com/gogf/gf/container/gmap"
"github.com/gogf/gf/container/gtype"
"github.com/gogf/gf/container/gvar"
"github.com/gogf/gf/os/gcache"
"github.com/gogf/gf/util/grand"
)
@ -64,6 +64,7 @@ type DB interface {
// Transaction.
Begin() (*TX, error)
Transaction(f func(tx *TX) error) (err error)
Insert(table string, data interface{}, batch ...int) (sql.Result, error)
InsertIgnore(table string, data interface{}, batch ...int) (sql.Result, error)
@ -78,8 +79,8 @@ type DB interface {
Delete(table string, condition interface{}, args ...interface{}) (sql.Result, error)
// Model creation.
From(tables string) *Model
Table(tables string) *Model
Table(table ...string) *Model
Model(table ...string) *Model
Schema(schema string) *Schema
// Configuration methods.
@ -89,6 +90,9 @@ type DB interface {
SetSchema(schema string)
GetSchema() string
GetPrefix() string
GetGroup() string
SetDryRun(dryrun bool)
GetDryRun() bool
SetLogger(logger *glog.Logger)
GetLogger() *glog.Logger
SetMaxIdleConnCount(n int)
@ -124,6 +128,7 @@ type Core struct {
debug *gtype.Bool // Enable debug mode for the database.
cache *gcache.Cache // Cache manager.
schema *gtype.String // Custom schema for this object.
dryrun *gtype.Bool // Dry run.
prefix string // Table prefix.
logger *glog.Logger // Logger.
maxIdleConnCount int // Max idle connection count.
@ -166,21 +171,23 @@ type Link interface {
Prepare(sql string) (*sql.Stmt, error)
}
// Value is the field value type.
type Value = *gvar.Var
type (
// Value is the field value type.
Value = *gvar.Var
// Record is the row record of the table.
type Record map[string]Value
// Record is the row record of the table.
Record map[string]Value
// Result is the row record array.
type Result []Record
// Result is the row record array.
Result []Record
// Map is alias of map[string]interface{},
// which is the most common usage map type.
type Map = map[string]interface{}
// Map is alias of map[string]interface{},
// which is the most common usage map type.
Map = map[string]interface{}
// List is type of map array.
type List = []Map
// List is type of map array.
List = []Map
)
const (
gINSERT_OPTION_DEFAULT = 0
@ -234,6 +241,7 @@ func New(name ...string) (db DB, err error) {
debug: gtype.NewBool(),
cache: gcache.New(),
schema: gtype.NewString(),
dryrun: gtype.NewBool(),
logger: glog.New(),
prefix: node.Prefix,
maxIdleConnCount: gDEFAULT_CONN_MAX_IDLE_COUNT,
@ -401,5 +409,8 @@ func (c *Core) getSqlDb(master bool, schema ...string) (sqlDb *sql.DB, err error
if node.Debug {
c.DB.SetDebug(node.Debug)
}
if node.Debug {
c.DB.SetDryRun(node.DryRun)
}
return
}

View File

@ -11,6 +11,7 @@ import (
"database/sql"
"errors"
"fmt"
"github.com/gogf/gf/internal/utils"
"reflect"
"regexp"
"strings"
@ -99,7 +100,11 @@ func (c *Core) DoExec(link Link, sql string, args ...interface{}) (result sql.Re
sql, args = c.DB.HandleSqlBeforeCommit(link, sql, args)
if c.DB.GetDebug() {
mTime1 := gtime.TimestampMilli()
result, err = link.Exec(sql, args...)
if !c.DB.GetDryRun() {
result, err = link.Exec(sql, args...)
} else {
result = new(SqlResult)
}
mTime2 := gtime.TimestampMilli()
s := &Sql{
Sql: sql,
@ -111,7 +116,11 @@ func (c *Core) DoExec(link Link, sql string, args ...interface{}) (result sql.Re
}
c.writeSqlToLogger(s)
} else {
result, err = link.Exec(sql, args...)
if !c.DB.GetDryRun() {
result, err = link.Exec(sql, args...)
} else {
result = new(SqlResult)
}
}
return result, formatError(err, sql, args...)
}
@ -149,7 +158,7 @@ func (c *Core) GetAll(sql string, args ...interface{}) (Result, error) {
return c.DB.DoGetAll(nil, sql, args...)
}
// doGetAll queries and returns data records from database.
// DoGetAll queries and returns data records from database.
func (c *Core) DoGetAll(link Link, sql string, args ...interface{}) (result Result, err error) {
if link == nil {
link, err = c.DB.Slave()
@ -194,9 +203,6 @@ func (c *Core) GetStruct(pointer interface{}, sql string, args ...interface{}) e
if err != nil {
return err
}
if len(one) == 0 {
return ErrNoRows
}
return one.Struct(pointer)
}
@ -207,9 +213,6 @@ func (c *Core) GetStructs(pointer interface{}, sql string, args ...interface{})
if err != nil {
return err
}
if len(all) == 0 {
return ErrNoRows
}
return all.Structs(pointer)
}
@ -301,6 +304,34 @@ func (c *Core) Begin() (*TX, error) {
}
}
// Transaction wraps the transaction logic using function <f>.
// It rollbacks the transaction and returns the error from function <f> if
// it returns non-nil error. It commits the transaction and returns nil if
// function <f> returns nil.
//
// Note that, you should not Commit or Rollback the transaction in function <f>
// as it is automatically handled by this function.
func (c *Core) Transaction(f func(tx *TX) error) (err error) {
var tx *TX
tx, err = c.DB.Begin()
if err != nil {
return err
}
defer func() {
if err != nil {
if e := tx.Rollback(); e != nil {
err = e
}
} else {
if e := tx.Commit(); e != nil {
err = e
}
}
}()
err = f(tx)
return
}
// Insert does "INSERT INTO ..." statement for the table.
// If there's already one unique record of the data in the table, it returns error.
//
@ -371,13 +402,15 @@ func (c *Core) Save(table string, data interface{}, batch ...int) (sql.Result, e
// 2: save: if there's unique/primary key in the data, it updates it or else inserts a new one;
// 3: ignore: if there's unique/primary key in the data, it ignores the inserting;
func (c *Core) DoInsert(link Link, table string, data interface{}, option int, batch ...int) (result sql.Result, err error) {
var fields []string
var values []string
var params []interface{}
var dataMap Map
table = c.DB.QuotePrefixTableName(table)
reflectValue := reflect.ValueOf(data)
reflectKind := reflectValue.Kind()
var (
fields []string
values []string
params []interface{}
dataMap Map
reflectValue = reflect.ValueOf(data)
reflectKind = reflectValue.Kind()
)
if reflectKind == reflect.Ptr {
reflectValue = reflectValue.Elem()
reflectKind = reflectValue.Kind()
@ -393,16 +426,23 @@ func (c *Core) DoInsert(link Link, table string, data interface{}, option int, b
if len(dataMap) == 0 {
return nil, errors.New("data cannot be empty")
}
charL, charR := c.DB.GetChars()
var (
charL, charR = c.DB.GetChars()
operation = GetInsertOperationByOption(option)
updateStr = ""
)
for k, v := range dataMap {
fields = append(fields, charL+k+charR)
values = append(values, "?")
params = append(params, v)
}
operation := GetInsertOperationByOption(option)
updateStr := ""
if option == gINSERT_OPTION_SAVE {
for k, _ := range dataMap {
// If it's SAVE operation,
// do not automatically update the creating time.
if utils.EqualFoldWithoutChars(k, gSOFT_FIELD_NAME_CREATE) {
continue
}
if len(updateStr) > 0 {
updateStr += ","
}
@ -454,12 +494,15 @@ func (c *Core) BatchSave(table string, list interface{}, batch ...int) (sql.Resu
return c.DB.DoBatchInsert(nil, table, list, gINSERT_OPTION_SAVE, batch...)
}
// doBatchInsert batch inserts/replaces/saves data.
// DoBatchInsert batch inserts/replaces/saves data.
func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option int, batch ...int) (result sql.Result, err error) {
var keys, values []string
var params []interface{}
table = c.DB.QuotePrefixTableName(table)
listMap := (List)(nil)
var (
keys []string
values []string
params []interface{}
listMap List
)
switch v := list.(type) {
case Result:
listMap = v.List()
@ -470,8 +513,10 @@ func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option i
case Map:
listMap = List{v}
default:
rv := reflect.ValueOf(list)
kind := rv.Kind()
var (
rv = reflect.ValueOf(list)
kind = rv.Kind()
)
if kind == reflect.Ptr {
rv = rv.Elem()
kind = rv.Kind()
@ -484,7 +529,7 @@ func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option i
listMap[i] = DataToMapDeep(rv.Index(i).Interface())
}
case reflect.Map, reflect.Struct:
listMap = List{DataToMapDeep(list)}
listMap = List{DataToMapDeep(v)}
default:
return result, errors.New(fmt.Sprint("unsupported list type:", kind))
}
@ -504,15 +549,21 @@ func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option i
holders = append(holders, "?")
}
// Prepare the batch result pointer.
batchResult := new(SqlResult)
charL, charR := c.DB.GetChars()
keysStr := charL + strings.Join(keys, charR+","+charL) + charR
valueHolderStr := "(" + strings.Join(holders, ",") + ")"
operation := GetInsertOperationByOption(option)
updateStr := ""
var (
charL, charR = c.DB.GetChars()
batchResult = new(SqlResult)
keysStr = charL + strings.Join(keys, charR+","+charL) + charR
valueHolderStr = "(" + strings.Join(holders, ",") + ")"
operation = GetInsertOperationByOption(option)
updateStr = ""
)
if option == gINSERT_OPTION_SAVE {
for _, k := range keys {
// If it's SAVE operation,
// do not automatically update the creating time.
if utils.EqualFoldWithoutChars(k, gSOFT_FIELD_NAME_CREATE) {
continue
}
if len(updateStr) > 0 {
updateStr += ","
}
@ -591,18 +642,25 @@ func (c *Core) Update(table string, data interface{}, condition interface{}, arg
// Also see Update.
func (c *Core) DoUpdate(link Link, table string, data interface{}, condition string, args ...interface{}) (result sql.Result, err error) {
table = c.DB.QuotePrefixTableName(table)
updates := ""
rv := reflect.ValueOf(data)
kind := rv.Kind()
var (
rv = reflect.ValueOf(data)
kind = rv.Kind()
)
if kind == reflect.Ptr {
rv = rv.Elem()
kind = rv.Kind()
}
params := []interface{}(nil)
var (
params []interface{}
updates = ""
)
switch kind {
case reflect.Map, reflect.Struct:
var fields []string
for k, v := range DataToMapDeep(data) {
var (
fields []string
dataMap = DataToMapDeep(data)
)
for k, v := range dataMap {
fields = append(fields, c.DB.QuoteWord(k)+"=?")
params = append(params, v)
}
@ -648,7 +706,7 @@ func (c *Core) Delete(table string, condition interface{}, args ...interface{})
return c.DB.DoDelete(nil, table, newWhere, newArgs...)
}
// doDelete does "DELETE FROM ... " statement for the table.
// DoDelete does "DELETE FROM ... " statement for the table.
// Also see Delete.
func (c *Core) DoDelete(link Link, table string, condition string, args ...interface{}) (result sql.Result, err error) {
if link == nil {

View File

@ -36,6 +36,7 @@ type ConfigNode struct {
Role string // (Optional, "master" in default) Node role, used for master-slave mode: master, slave.
Debug bool // (Optional) Debug mode enables debug information logging and output.
Prefix string // (Optional) Table prefix.
DryRun bool // (Optional) Dry run, which does SELECT but no INSERT/UPDATE/DELETE statements.
Weight int // (Optional) Weight for load balance calculating, it's useless if there's just one node.
Charset string // (Optional, "utf8mb4" in default) Custom charset when operating on database.
LinkInfo string // (Optional) Custom link information, when it is used, configuration Host/Port/User/Pass/Name are ignored.
@ -142,24 +143,19 @@ func (c *Core) SetMaxConnLifetime(d time.Duration) {
// String returns the node as string.
func (node *ConfigNode) String() string {
if node.LinkInfo != "" {
return node.LinkInfo
}
return fmt.Sprintf(
`%s@%s:%s,%s,%s,%s,%s,%v,%d-%d-%d`,
`%s@%s:%s,%s,%s,%s,%s,%v,%d-%d-%d#%s`,
node.User, node.Host, node.Port,
node.Name, node.Type, node.Role, node.Charset, node.Debug,
node.MaxIdleConnCount,
node.MaxOpenConnCount,
node.MaxConnLifetime,
node.LinkInfo,
)
}
// SetDebug enables/disables the debug mode.
func (c *Core) SetDebug(debug bool) {
if c.debug.Val() == debug {
return
}
c.debug.Set(debug)
}
@ -178,6 +174,21 @@ func (c *Core) GetPrefix() string {
return c.prefix
}
// GetGroup returns the group string configured.
func (c *Core) GetGroup() string {
return c.group
}
// SetDryRun enables/disables the DryRun feature.
func (c *Core) SetDryRun(dryrun bool) {
c.dryrun.Set(dryrun)
}
// GetDryRun returns the DryRun value.
func (c *Core) GetDryRun() bool {
return c.dryrun.Val()
}
// SetSchema changes the schema for this database connection object.
// Importantly note that when schema configuration changed for the database,
// it affects all operations on the database object in the future.

View File

@ -13,6 +13,7 @@ package gdb
import (
"database/sql"
"errors"
"fmt"
"github.com/gogf/gf/internal/intlog"
"github.com/gogf/gf/text/gstr"
@ -187,9 +188,10 @@ func (d *DriverMssql) Tables(schema ...string) (tables []string, err error) {
// TableFields retrieves and returns the fields information of specified table of current schema.
func (d *DriverMssql) TableFields(table string, schema ...string) (fields map[string]*TableField, err error) {
table = gstr.Trim(table)
charL, charR := d.GetChars()
table = gstr.Trim(table, charL+charR)
if gstr.Contains(table, " ") {
panic("function TableFields supports only single table operations")
return nil, errors.New("function TableFields supports only single table operations")
}
checkSchema := d.DB.GetSchema()
if len(schema) > 0 && schema[0] != "" {

View File

@ -8,8 +8,10 @@ package gdb
import (
"database/sql"
"errors"
"fmt"
"github.com/gogf/gf/internal/intlog"
"github.com/gogf/gf/text/gregex"
"github.com/gogf/gf/text/gstr"
_ "github.com/go-sql-driver/mysql"
@ -33,6 +35,10 @@ func (d *DriverMysql) Open(config *ConfigNode) (*sql.DB, error) {
var source string
if config.LinkInfo != "" {
source = config.LinkInfo
// Custom changing the schema in runtime.
if config.Name != "" {
source, _ = gregex.ReplaceString(`/([\w\.\-]+)+`, "/"+config.Name, source)
}
} else {
source = fmt.Sprintf(
"%s:%s@tcp(%s:%s)/%s?charset=%s&multiStatements=true&parseTime=true&loc=Local",
@ -77,16 +83,20 @@ func (d *DriverMysql) Tables(schema ...string) (tables []string, err error) {
return
}
// TableFields retrieves and returns the fields information of specified table of current schema.
// TableFields retrieves and returns the fields information of specified table of current
// schema.
//
// Note that it returns a map containing the field name and its corresponding fields.
// As a map is unsorted, the TableField struct has a "Index" field marks its sequence in the fields.
// As a map is unsorted, the TableField struct has a "Index" field marks its sequence in
// the fields.
//
// It's using cache feature to enhance the performance, which is never expired util the process restarts.
// It's using cache feature to enhance the performance, which is never expired util the
// process restarts.
func (d *DriverMysql) TableFields(table string, schema ...string) (fields map[string]*TableField, err error) {
table = gstr.Trim(table)
charL, charR := d.GetChars()
table = gstr.Trim(table, charL+charR)
if gstr.Contains(table, " ") {
panic("function TableFields supports only single table operations")
return nil, errors.New("function TableFields supports only single table operations")
}
checkSchema := d.schema.Val()
if len(schema) > 0 && schema[0] != "" {

View File

@ -148,9 +148,10 @@ func (d *DriverOracle) Tables(schema ...string) (tables []string, err error) {
// TableFields retrieves and returns the fields information of specified table of current schema.
func (d *DriverOracle) TableFields(table string, schema ...string) (fields map[string]*TableField, err error) {
table = gstr.Trim(table)
charL, charR := d.GetChars()
table = gstr.Trim(table, charL+charR)
if gstr.Contains(table, " ") {
panic("function TableFields supports only single table operations")
return nil, errors.New("function TableFields supports only single table operations")
}
checkSchema := d.DB.GetSchema()
if len(schema) > 0 && schema[0] != "" {

View File

@ -13,6 +13,7 @@ package gdb
import (
"database/sql"
"errors"
"fmt"
"github.com/gogf/gf/internal/intlog"
"github.com/gogf/gf/text/gstr"
@ -78,7 +79,6 @@ func (d *DriverPgsql) Tables(schema ...string) (tables []string, err error) {
if err != nil {
return nil, err
}
query := "SELECT TABLENAME FROM PG_TABLES WHERE SCHEMANAME = 'public' ORDER BY TABLENAME"
if len(schema) > 0 && schema[0] != "" {
query = fmt.Sprintf("SELECT TABLENAME FROM PG_TABLES WHERE SCHEMANAME = '%s' ORDER BY TABLENAME", schema[0])
@ -97,9 +97,10 @@ func (d *DriverPgsql) Tables(schema ...string) (tables []string, err error) {
// TableFields retrieves and returns the fields information of specified table of current schema.
func (d *DriverPgsql) TableFields(table string, schema ...string) (fields map[string]*TableField, err error) {
table = gstr.Trim(table)
charL, charR := d.GetChars()
table = gstr.Trim(table, charL+charR)
if gstr.Contains(table, " ") {
panic("function TableFields supports only single table operations")
return nil, errors.New("function TableFields supports only single table operations")
}
table, _ = gregex.ReplaceString("\"", "", table)
checkSchema := d.DB.GetSchema()

View File

@ -12,6 +12,7 @@ package gdb
import (
"database/sql"
"errors"
"fmt"
"github.com/gogf/gf/internal/intlog"
"github.com/gogf/gf/os/gfile"
@ -88,11 +89,11 @@ func (d *DriverSqlite) Tables(schema ...string) (tables []string, err error) {
// TableFields retrieves and returns the fields information of specified table of current schema.
func (d *DriverSqlite) TableFields(table string, schema ...string) (fields map[string]*TableField, err error) {
table = gstr.Trim(table)
charL, charR := d.GetChars()
table = gstr.Trim(table, charL+charR)
if gstr.Contains(table, " ") {
panic("function TableFields supports only single table operations")
return nil, errors.New("function TableFields supports only single table operations")
}
checkSchema := d.DB.GetSchema()
if len(schema) > 0 && schema[0] != "" {
checkSchema = schema[0]

View File

@ -147,8 +147,13 @@ func doQuoteWord(s, charLeft, charRight string) string {
}
// doQuoteString quotes string with quote chars. It handles strings like:
// "user", "user u", "user,user_detail", "user u, user_detail ut",
// "user.user u, user.user_detail ut", "u.id asc".
// "user",
// "user u",
// "user,user_detail",
// "user u, user_detail ut",
// "user.user u, user.user_detail ut",
// "u.id, u.name, u.age",
// "u.id asc".
func doQuoteString(s, charLeft, charRight string) string {
array1 := gstr.SplitAndTrim(s, ",")
for k1, v1 := range array1 {
@ -201,10 +206,16 @@ func GetPrimaryKey(pointer interface{}) string {
// GetPrimaryKeyCondition returns a new where condition by primary field name.
// The optional parameter <where> is like follows:
// 123, []int{1, 2, 3}, "john", []string{"john", "smith"}
// g.Map{"id": g.Slice{1,2,3}}, g.Map{"id": 1, "name": "john"}, etc.
// 123 => primary=123
// []int{1, 2, 3} => primary IN(1,2,3)
// "john" => primary='john'
// []string{"john", "smith"} => primary IN('john','smith')
// g.Map{"id": g.Slice{1,2,3}} => id IN(1,2,3)
// g.Map{"id": 1, "name": "john"} => id=1 AND name='john'
// etc.
//
// Note that it returns the given <where> parameter directly if there's the <primary> is empty.
// Note that it returns the given <where> parameter directly if the <primary> is empty
// or length of <where> > 1.
func GetPrimaryKeyCondition(primary string, where ...interface{}) (newWhereCondition []interface{}) {
if len(where) == 0 {
return nil
@ -221,6 +232,7 @@ func GetPrimaryKeyCondition(primary string, where ...interface{}) (newWhereCondi
}
switch kind {
case reflect.Map, reflect.Struct:
// Ignore the parameter <primary>.
break
default:
@ -443,11 +455,23 @@ func handleArguments(sql string, args []interface{}) (newSql string, newArgs []i
newArgs = append(newArgs, arg)
continue
}
// It converts the struct to string in default
// if it implements the String interface.
if v, ok := arg.(apiString); ok {
switch v := arg.(type) {
case time.Time, *time.Time:
newArgs = append(newArgs, arg)
continue
// Special handling for gtime.Time.
case gtime.Time:
newArgs = append(newArgs, v.String())
continue
default:
// It converts the struct to string in default
// if it implements the String interface.
if v, ok := arg.(apiString); ok {
newArgs = append(newArgs, v.String())
continue
}
}
newArgs = append(newArgs, arg)

View File

@ -7,6 +7,8 @@
package gdb
import (
"fmt"
"github.com/gogf/gf/text/gregex"
"time"
"github.com/gogf/gf/text/gstr"
@ -26,6 +28,7 @@ type Model struct {
whereHolder []*whereHolder // Condition strings for where operation.
groupBy string // Used for "group by" statement.
orderBy string // Used for "order by" statement.
having []interface{} // Used for "having..." statement.
start int // Used for "select ... start, limit ..." statement.
limit int // Used for "select ... start, limit ..." statement.
option int // Option for extra operation features.
@ -37,6 +40,7 @@ type Model struct {
cacheEnabled bool // Enable sql result cache feature.
cacheDuration time.Duration // Cache TTL duration.
cacheName string // Cache name for custom operation.
unscoped bool // Disables soft deleting features when select/delete operations.
safe bool // If true, it clones and returns a new model object whenever operation done; or else it changes the attribute of current model.
}
@ -58,70 +62,70 @@ const (
)
// Table creates and returns a new ORM model from given schema.
// The parameter <tables> can be more than one table names, like :
// "user", "user u", "user, user_detail", "user u, user_detail ud"
func (c *Core) Table(table string) *Model {
table = c.DB.QuotePrefixTableName(table)
// The parameter <table> can be more than one table names, and also alias name, like:
// 1. Table names:
// Table("user")
// Table("user u")
// Table("user, user_detail")
// Table("user u, user_detail ud")
// 2. Table name with alias: Table("user", "u")
func (c *Core) Table(table ...string) *Model {
tables := ""
if len(table) > 1 {
tables = fmt.Sprintf(
`%s AS %s`, c.DB.QuotePrefixTableName(table[0]), c.DB.QuoteWord(table[1]),
)
} else if len(table) == 1 {
tables = c.DB.QuotePrefixTableName(table[0])
} else {
panic("table cannot be empty")
}
return &Model{
db: c.DB,
tablesInit: table,
tables: table,
tablesInit: tables,
tables: tables,
fields: "*",
start: -1,
offset: -1,
safe: false,
option: OPTION_ALLOWEMPTY,
}
}
// Model is alias of Core.Table.
// See Core.Table.
func (c *Core) Model(table string) *Model {
return c.DB.Table(table)
}
// From is alias of Core.Table.
// See Core.Table.
// Deprecated.
func (c *Core) From(table string) *Model {
return c.DB.Table(table)
func (c *Core) Model(table ...string) *Model {
return c.DB.Table(table...)
}
// Table acts like Core.Table except it operates on transaction.
// See Core.Table.
func (tx *TX) Table(table string) *Model {
table = tx.db.QuotePrefixTableName(table)
return &Model{
db: tx.db,
tx: tx,
tablesInit: table,
tables: table,
fields: "*",
start: -1,
offset: -1,
safe: false,
option: OPTION_ALLOWEMPTY,
}
func (tx *TX) Table(table ...string) *Model {
model := tx.db.Table(table...)
model.db = tx.db
model.tx = tx
return model
}
// Model is alias of tx.Table.
// See tx.Table.
func (tx *TX) Model(table string) *Model {
return tx.Table(table)
}
// From is alias of tx.Table.
// See tx.Table.
// Deprecated.
func (tx *TX) From(table string) *Model {
return tx.Table(table)
func (tx *TX) Model(table ...string) *Model {
return tx.Table(table...)
}
// As sets an alias name for current table.
func (m *Model) As(as string) *Model {
if m.tables != "" {
model := m.getModel()
model.tables = gstr.TrimRight(model.tables) + " AS " + as
split := " JOIN "
if gstr.Contains(model.tables, split) {
// For join table.
array := gstr.Split(model.tables, split)
array[len(array)-1], _ = gregex.ReplaceString(`(.+) ON`, fmt.Sprintf(`$1 AS %s ON`, as), array[len(array)-1])
model.tables = gstr.Join(array, split)
} else {
// For base table.
model.tables = gstr.TrimRight(model.tables) + " AS " + as
}
return model
}
return m

View File

@ -18,19 +18,26 @@ import (
// If the parameter <duration> = 0, which means it never expires.
// If the parameter <duration> > 0, which means it expires after <duration>.
//
// The optional parameter <name> is used to bind a name to the cache, which means you can later
// control the cache like changing the <duration> or clearing the cache with specified <name>.
// The optional parameter <name> is used to bind a name to the cache, which means you can
// later control the cache like changing the <duration> or clearing the cache with specified
// <name>.
//
// Note that, the cache feature is disabled if the model is operating on a transaction.
// Note that, the cache feature is disabled if the model is performing select statement
// on a transaction.
func (m *Model) Cache(duration time.Duration, name ...string) *Model {
model := m.getModel()
model.cacheDuration = duration
if len(name) > 0 {
model.cacheName = name[0]
}
// It does not support cache on transaction.
if model.tx == nil {
model.cacheEnabled = true
}
model.cacheEnabled = true
return model
}
// checkAndRemoveCache checks and removes the cache in insert/update/delete statement if
// cache feature is enabled.
func (m *Model) checkAndRemoveCache() {
if m.cacheEnabled && m.cacheDuration < 0 && len(m.cacheName) > 0 {
m.db.GetCache().Remove(m.cacheName)
}
}

View File

@ -6,7 +6,9 @@
package gdb
import "github.com/gogf/gf/util/gconv"
import (
"strings"
)
// Where sets the condition statement for the model. The parameter <where> can be type of
// string/map/gmap/slice/struct/*struct, etc. Note that, if it's called more than one times,
@ -32,10 +34,22 @@ func (m *Model) Where(where interface{}, args ...interface{}) *Model {
return model
}
// Having sets the having statement for the model.
// The parameters of this function usage are as the same as function Where.
// See Where.
func (m *Model) Having(having interface{}, args ...interface{}) *Model {
model := m.getModel()
model.having = []interface{}{
having, args,
}
return model
}
// WherePri does the same logic as Model.Where except that if the parameter <where>
// is a single condition like int/string/float/slice, it treats the condition as the primary
// key value. That is, if primary key is "id" and given <where> parameter as "123", the
// WherePri function treats it as "id=123", but Model.Where treats it as string "123".
// WherePri function treats the condition as "id=123", but Model.Where treats the condition
// as string "123".
func (m *Model) WherePri(where interface{}, args ...interface{}) *Model {
if len(args) > 0 {
return m.Where(where, args...)
@ -87,9 +101,9 @@ func (m *Model) GroupBy(groupBy string) *Model {
}
// Order sets the "ORDER BY" statement for the model.
func (m *Model) Order(orderBy string) *Model {
func (m *Model) Order(orderBy ...string) *Model {
model := m.getModel()
model.orderBy = m.db.QuoteString(orderBy)
model.orderBy = m.db.QuoteString(strings.Join(orderBy, " "))
return model
}
@ -143,28 +157,3 @@ func (m *Model) Page(page, limit int) *Model {
func (m *Model) ForPage(page, limit int) *Model {
return m.Page(page, limit)
}
// getAll does the query from database.
func (m *Model) getAll(sql string, args ...interface{}) (result Result, err error) {
cacheKey := ""
// Retrieve from cache.
if m.cacheEnabled {
cacheKey = m.cacheName
if len(cacheKey) == 0 {
cacheKey = sql + "/" + gconv.String(args)
}
if v := m.db.GetCache().Get(cacheKey); v != nil {
return v.(Result), nil
}
}
result, err = m.db.DoGetAll(m.getLink(false), sql, m.mergeArguments(args)...)
// Cache the result.
if len(cacheKey) > 0 && err == nil {
if m.cacheDuration < 0 {
m.db.GetCache().Remove(cacheKey)
} else {
m.db.GetCache().Set(cacheKey, result, m.cacheDuration)
}
}
return result, err
}

View File

@ -8,8 +8,21 @@ package gdb
import (
"database/sql"
"fmt"
"github.com/gogf/gf/os/gtime"
)
// Unscoped enables/disables the soft deleting feature.
func (m *Model) Unscoped(unscoped ...bool) *Model {
model := m.getModel()
if len(unscoped) > 0 {
model.unscoped = unscoped[0]
} else {
model.unscoped = true
}
return model
}
// Delete does "DELETE FROM ... " statement for the model.
// The optional parameter <where> is the same as the parameter of Model.Where function,
// see Model.Where.
@ -22,6 +35,19 @@ func (m *Model) Delete(where ...interface{}) (result sql.Result, err error) {
m.checkAndRemoveCache()
}
}()
condition, conditionArgs := m.formatCondition(false)
return m.db.DoDelete(m.getLink(true), m.tables, condition, conditionArgs...)
var (
fieldNameDelete = m.getSoftFieldNameDelete()
conditionWhere, conditionExtra, conditionArgs = m.formatCondition(false)
)
// Soft deleting.
if !m.unscoped && fieldNameDelete != "" {
return m.db.DoUpdate(
m.getLink(true),
m.tables,
fmt.Sprintf(`%s='%s'`, m.db.QuoteString(fieldNameDelete), gtime.Now().String()),
conditionWhere+conditionExtra,
conditionArgs...,
)
}
return m.db.DoDelete(m.getLink(true), m.tables, conditionWhere+conditionExtra, conditionArgs...)
}

View File

@ -7,7 +7,7 @@
package gdb
import (
"github.com/gogf/gf/container/garray"
"fmt"
"github.com/gogf/gf/container/gset"
"github.com/gogf/gf/text/gstr"
)
@ -34,21 +34,31 @@ func (m *Model) FieldsEx(fields string) *Model {
if gstr.Contains(m.tables, " ") {
panic("function FieldsEx supports only single table operations")
}
tableFields, err := m.db.TableFields(m.tables)
if err != nil {
panic(err)
}
if len(tableFields) == 0 {
panic(fmt.Sprintf(`empty table fields for table "%s"`, m.tables))
}
model := m.getModel()
model.fieldsEx = fields
fieldsExSet := gset.NewStrSetFrom(gstr.SplitAndTrim(fields, ","))
if m, err := m.db.TableFields(m.tables); err == nil {
model.fields = ""
for k, _ := range m {
if fieldsExSet.Contains(k) {
continue
}
if len(model.fields) > 0 {
model.fields += ","
}
model.fields += k
}
fieldsArray := make([]string, len(tableFields))
for k, v := range tableFields {
fieldsArray[v.Index] = k
}
model.fields = ""
for _, k := range fieldsArray {
if fieldsExSet.Contains(k) {
continue
}
if len(model.fields) > 0 {
model.fields += ","
}
model.fields += k
}
model.fields = model.db.QuoteString(model.fields)
return model
}
@ -59,14 +69,26 @@ func (m *Model) FieldsStr(prefix ...string) string {
if len(prefix) > 0 {
prefixStr = prefix[0]
}
if m, err := m.db.TableFields(m.tables); err == nil {
fieldsArray := garray.NewStrArraySize(len(m), len(m))
for _, field := range m {
fieldsArray.Set(field.Index, prefixStr+field.Name)
}
return fieldsArray.Join(",")
tableFields, err := m.db.TableFields(m.tables)
if err != nil {
panic(err)
}
return ""
if len(tableFields) == 0 {
panic(fmt.Sprintf(`empty table fields for table "%s"`, m.tables))
}
fieldsArray := make([]string, len(tableFields))
for k, v := range tableFields {
fieldsArray[v.Index] = k
}
newFields := ""
for _, k := range fieldsArray {
if len(newFields) > 0 {
newFields += ","
}
newFields += prefixStr + k
}
newFields = m.db.QuoteString(newFields)
return newFields
}
// FieldsExStr retrieves and returns fields which are not in parameter <fields> from the table,
@ -78,17 +100,28 @@ func (m *Model) FieldsExStr(fields string, prefix ...string) string {
if len(prefix) > 0 {
prefixStr = prefix[0]
}
if m, err := m.db.TableFields(m.tables); err == nil {
fieldsArray := garray.NewStrArraySize(len(m), len(m))
fieldsExSet := gset.NewStrSetFrom(gstr.SplitAndTrim(fields, ","))
for _, field := range m {
if fieldsExSet.Contains(field.Name) {
continue
}
fieldsArray.Set(field.Index, prefixStr+field.Name)
}
fieldsArray.FilterEmpty()
return fieldsArray.Join(",")
tableFields, err := m.db.TableFields(m.tables)
if err != nil {
panic(err)
}
return ""
if len(tableFields) == 0 {
panic(fmt.Sprintf(`empty table fields for table "%s"`, m.tables))
}
fieldsExSet := gset.NewStrSetFrom(gstr.SplitAndTrim(fields, ","))
fieldsArray := make([]string, len(tableFields))
for k, v := range tableFields {
fieldsArray[v.Index] = k
}
newFields := ""
for _, k := range fieldsArray {
if fieldsExSet.Contains(k) {
continue
}
if len(newFields) > 0 {
newFields += ","
}
newFields += prefixStr + k
}
newFields = m.db.QuoteString(newFields)
return newFields
}

View File

@ -9,8 +9,10 @@ package gdb
import (
"database/sql"
"errors"
"github.com/gogf/gf/os/gtime"
"github.com/gogf/gf/text/gstr"
"github.com/gogf/gf/util/gconv"
"github.com/gogf/gf/util/gutil"
"reflect"
)
@ -94,6 +96,9 @@ func (m *Model) Data(data ...interface{}) *Model {
// The optional parameter <data> is the same as the parameter of Model.Data function,
// see Model.Data.
func (m *Model) Insert(data ...interface{}) (result sql.Result, err error) {
if len(data) > 0 {
return m.Data(data...).Insert()
}
return m.doInsertWithOption(gINSERT_OPTION_DEFAULT, data...)
}
@ -101,45 +106,10 @@ func (m *Model) Insert(data ...interface{}) (result sql.Result, err error) {
// The optional parameter <data> is the same as the parameter of Model.Data function,
// see Model.Data.
func (m *Model) InsertIgnore(data ...interface{}) (result sql.Result, err error) {
return m.doInsertWithOption(gINSERT_OPTION_IGNORE, data...)
}
// doInsertWithOption inserts data with option parameter.
func (m *Model) doInsertWithOption(option int, data ...interface{}) (result sql.Result, err error) {
if len(data) > 0 {
return m.Data(data...).Insert()
}
defer func() {
if err == nil {
m.checkAndRemoveCache()
}
}()
if m.data == nil {
return nil, errors.New("inserting into table with empty data")
}
if list, ok := m.data.(List); ok {
// Batch insert.
batch := 10
if m.batch > 0 {
batch = m.batch
}
return m.db.DoBatchInsert(
m.getLink(true),
m.tables,
m.filterDataForInsertOrUpdate(list),
option,
batch,
)
} else if data, ok := m.data.(Map); ok {
// Single insert.
return m.db.DoInsert(
m.getLink(true),
m.tables,
m.filterDataForInsertOrUpdate(data),
option,
)
}
return nil, errors.New("inserting into table with invalid data type")
return m.doInsertWithOption(gINSERT_OPTION_IGNORE, data...)
}
// Replace does "REPLACE INTO ..." statement for the model.
@ -149,37 +119,7 @@ func (m *Model) Replace(data ...interface{}) (result sql.Result, err error) {
if len(data) > 0 {
return m.Data(data...).Replace()
}
defer func() {
if err == nil {
m.checkAndRemoveCache()
}
}()
if m.data == nil {
return nil, errors.New("replacing into table with empty data")
}
if list, ok := m.data.(List); ok {
// Batch replace.
batch := 10
if m.batch > 0 {
batch = m.batch
}
return m.db.DoBatchInsert(
m.getLink(true),
m.tables,
m.filterDataForInsertOrUpdate(list),
gINSERT_OPTION_REPLACE,
batch,
)
} else if data, ok := m.data.(Map); ok {
// Single insert.
return m.db.DoInsert(
m.getLink(true),
m.tables,
m.filterDataForInsertOrUpdate(data),
gINSERT_OPTION_REPLACE,
)
}
return nil, errors.New("replacing into table with invalid data type")
return m.doInsertWithOption(gINSERT_OPTION_REPLACE, data...)
}
// Save does "INSERT INTO ... ON DUPLICATE KEY UPDATE..." statement for the model.
@ -192,35 +132,70 @@ func (m *Model) Save(data ...interface{}) (result sql.Result, err error) {
if len(data) > 0 {
return m.Data(data...).Save()
}
return m.doInsertWithOption(gINSERT_OPTION_SAVE, data...)
}
// doInsertWithOption inserts data with option parameter.
func (m *Model) doInsertWithOption(option int, data ...interface{}) (result sql.Result, err error) {
defer func() {
if err == nil {
m.checkAndRemoveCache()
}
}()
if m.data == nil {
return nil, errors.New("saving into table with empty data")
return nil, errors.New("inserting into table with empty data")
}
var (
nowString = gtime.Now().String()
fieldNameCreate = m.getSoftFieldNameCreate()
fieldNameUpdate = m.getSoftFieldNameUpdate()
fieldNameDelete = m.getSoftFieldNameDelete()
)
// Batch operation.
if list, ok := m.data.(List); ok {
// Batch save.
batch := gDEFAULT_BATCH_NUM
if m.batch > 0 {
batch = m.batch
}
// Automatic handling for creating/updating time.
if !m.unscoped && (fieldNameCreate != "" || fieldNameUpdate != "") {
for k, v := range list {
gutil.MapDelete(v, fieldNameCreate, fieldNameUpdate, fieldNameDelete)
if fieldNameCreate != "" {
v[fieldNameCreate] = nowString
}
if fieldNameUpdate != "" {
v[fieldNameUpdate] = nowString
}
list[k] = v
}
}
return m.db.DoBatchInsert(
m.getLink(true),
m.tables,
m.filterDataForInsertOrUpdate(list),
gINSERT_OPTION_SAVE,
option,
batch,
)
} else if data, ok := m.data.(Map); ok {
// Single save.
}
// Single operation.
if data, ok := m.data.(Map); ok {
// Automatic handling for creating/updating time.
if !m.unscoped && (fieldNameCreate != "" || fieldNameUpdate != "") {
gutil.MapDelete(data, fieldNameCreate, fieldNameUpdate, fieldNameDelete)
if fieldNameCreate != "" {
data[fieldNameCreate] = nowString
}
if fieldNameUpdate != "" {
data[fieldNameUpdate] = nowString
}
}
return m.db.DoInsert(
m.getLink(true),
m.tables,
m.filterDataForInsertOrUpdate(data),
gINSERT_OPTION_SAVE,
option,
)
}
return nil, errors.New("saving into table with invalid data type")
return nil, errors.New("inserting into table with invalid data type")
}

View File

@ -9,22 +9,70 @@ package gdb
import "fmt"
// LeftJoin does "LEFT JOIN ... ON ..." statement on the model.
func (m *Model) LeftJoin(table string, on string) *Model {
// The parameter <table> can be joined table and its joined condition,
// and also with its alias name, like:
// Table("user").LeftJoin("user_detail", "user_detail.uid=user.uid")
// Table("user", "u").LeftJoin("user_detail", "ud", "ud.uid=u.uid")
func (m *Model) LeftJoin(table ...string) *Model {
model := m.getModel()
model.tables += fmt.Sprintf(" LEFT JOIN %s ON (%s)", m.db.QuotePrefixTableName(table), on)
if len(table) > 2 {
model.tables += fmt.Sprintf(
" LEFT JOIN %s AS %s ON (%s)",
m.db.QuotePrefixTableName(table[0]), m.db.QuoteWord(table[1]), table[2],
)
} else if len(table) == 2 {
model.tables += fmt.Sprintf(
" LEFT JOIN %s ON (%s)",
m.db.QuotePrefixTableName(table[0]), table[1],
)
} else {
panic("invalid join table parameter")
}
return model
}
// RightJoin does "RIGHT JOIN ... ON ..." statement on the model.
func (m *Model) RightJoin(table string, on string) *Model {
// The parameter <table> can be joined table and its joined condition,
// and also with its alias name, like:
// Table("user").RightJoin("user_detail", "user_detail.uid=user.uid")
// Table("user", "u").RightJoin("user_detail", "ud", "ud.uid=u.uid")
func (m *Model) RightJoin(table ...string) *Model {
model := m.getModel()
model.tables += fmt.Sprintf(" RIGHT JOIN %s ON (%s)", m.db.QuotePrefixTableName(table), on)
if len(table) > 2 {
model.tables += fmt.Sprintf(
" RIGHT JOIN %s AS %s ON (%s)",
m.db.QuotePrefixTableName(table[0]), m.db.QuoteWord(table[1]), table[2],
)
} else if len(table) == 2 {
model.tables += fmt.Sprintf(
" RIGHT JOIN %s ON (%s)",
m.db.QuotePrefixTableName(table[0]), table[1],
)
} else {
panic("invalid join table parameter")
}
return model
}
// InnerJoin does "INNER JOIN ... ON ..." statement on the model.
func (m *Model) InnerJoin(table string, on string) *Model {
// The parameter <table> can be joined table and its joined condition,
// and also with its alias name, like:
// Table("user").InnerJoin("user_detail", "user_detail.uid=user.uid")
// Table("user", "u").InnerJoin("user_detail", "ud", "ud.uid=u.uid")
func (m *Model) InnerJoin(table ...string) *Model {
model := m.getModel()
model.tables += fmt.Sprintf(" INNER JOIN %s ON (%s)", m.db.QuotePrefixTableName(table), on)
if len(table) > 2 {
model.tables += fmt.Sprintf(
" INNER JOIN %s AS %s ON (%s)",
m.db.QuotePrefixTableName(table[0]), m.db.QuoteWord(table[1]), table[2],
)
} else if len(table) == 2 {
model.tables += fmt.Sprintf(
" INNER JOIN %s ON (%s)",
m.db.QuotePrefixTableName(table[0]), table[1],
)
} else {
panic("invalid join table parameter")
}
return model
}

View File

@ -7,7 +7,6 @@
package gdb
import (
"database/sql"
"fmt"
"github.com/gogf/gf/util/gconv"
"reflect"
@ -30,9 +29,27 @@ func (m *Model) All(where ...interface{}) (Result, error) {
if len(where) > 0 {
return m.Where(where[0], where[1:]...).All()
}
condition, conditionArgs := m.formatCondition(false)
return m.getAll(
fmt.Sprintf("SELECT %s FROM %s%s", m.fields, m.tables, condition),
var (
softDeletingCondition = m.getConditionForSoftDeleting()
conditionWhere, conditionExtra, conditionArgs = m.formatCondition(false)
)
if !m.unscoped && softDeletingCondition != "" {
if conditionWhere == "" {
conditionWhere = " WHERE "
} else {
conditionWhere += " AND "
}
conditionWhere += softDeletingCondition
}
// DO NOT quote the m.fields where, in case of fields like:
// DISTINCT t.user_id uid
return m.doGetAll(
fmt.Sprintf(
"SELECT %s FROM %s%s",
m.fields,
m.tables,
conditionWhere+conditionExtra,
),
conditionArgs...,
)
}
@ -73,8 +90,7 @@ func (m *Model) One(where ...interface{}) (Record, error) {
if len(where) > 0 {
return m.Where(where[0], where[1:]...).One()
}
condition, conditionArgs := m.formatCondition(true)
all, err := m.getAll(fmt.Sprintf("SELECT %s FROM %s%s", m.fields, m.tables, condition), conditionArgs...)
all, err := m.All()
if err != nil {
return nil, err
}
@ -154,9 +170,6 @@ func (m *Model) Struct(pointer interface{}, where ...interface{}) error {
if err != nil {
return err
}
if len(one) == 0 {
return sql.ErrNoRows
}
return one.Struct(pointer)
}
@ -181,9 +194,6 @@ func (m *Model) Structs(pointer interface{}, where ...interface{}) error {
if err != nil {
return err
}
if len(all) == 0 {
return sql.ErrNoRows
}
return all.Structs(pointer)
}
@ -234,14 +244,28 @@ func (m *Model) Count(where ...interface{}) (int, error) {
}
countFields := "COUNT(1)"
if m.fields != "" && m.fields != "*" {
// DO NOT quote the m.fields here, in case of fields like:
// DISTINCT t.user_id uid
countFields = fmt.Sprintf(`COUNT(%s)`, m.fields)
}
condition, conditionArgs := m.formatCondition(false)
s := fmt.Sprintf("SELECT %s FROM %s %s", countFields, m.tables, condition)
var (
softDeletingCondition = m.getConditionForSoftDeleting()
conditionWhere, conditionExtra, conditionArgs = m.formatCondition(false)
)
if !m.unscoped && softDeletingCondition != "" {
if conditionWhere == "" {
conditionWhere = " WHERE "
} else {
conditionWhere += " AND "
}
conditionWhere += softDeletingCondition
}
s := fmt.Sprintf("SELECT %s FROM %s%s", countFields, m.tables, conditionWhere+conditionExtra)
if len(m.groupBy) > 0 {
s = fmt.Sprintf("SELECT COUNT(1) FROM (%s) count_alias", s)
}
list, err := m.getAll(s, conditionArgs...)
list, err := m.doGetAll(s, conditionArgs...)
if err != nil {
return 0, err
}
@ -313,3 +337,28 @@ 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) {
cacheKey := ""
// Retrieve from cache.
if m.cacheEnabled && m.tx == nil {
cacheKey = m.cacheName
if len(cacheKey) == 0 {
cacheKey = sql + "/" + gconv.String(args)
}
if v := m.db.GetCache().Get(cacheKey); v != nil {
return v.(Result), nil
}
}
result, err = m.db.DoGetAll(m.getLink(false), sql, m.mergeArguments(args)...)
// Cache the result.
if cacheKey != "" && err == nil {
if m.cacheDuration < 0 {
m.db.GetCache().Remove(cacheKey)
} else {
m.db.GetCache().Set(cacheKey, result, m.cacheDuration)
}
}
return result, err
}

View File

@ -0,0 +1,147 @@
// 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 (
"fmt"
"github.com/gogf/gf/container/garray"
"github.com/gogf/gf/text/gregex"
"github.com/gogf/gf/text/gstr"
"github.com/gogf/gf/util/gconv"
"github.com/gogf/gf/util/gutil"
)
const (
gSOFT_FIELD_NAME_CREATE = "create_at"
gSOFT_FIELD_NAME_UPDATE = "update_at"
gSOFT_FIELD_NAME_DELETE = "delete_at"
)
// getSoftFieldNameCreate checks and returns the field name for record creating time.
// If there's no field name for storing creating time, it returns an empty string.
// It checks the key with or without cases or chars '-'/'_'/'.'/' '.
func (m *Model) getSoftFieldNameCreate(table ...string) string {
tableName := ""
if len(table) > 0 {
tableName = table[0]
} else {
tableName = m.getPrimaryTableName()
}
return m.getSoftFieldName(tableName, gSOFT_FIELD_NAME_CREATE)
}
// getSoftFieldNameUpdate checks and returns the field name for record updating time.
// If there's no field name for storing updating time, it returns an empty string.
// It checks the key with or without cases or chars '-'/'_'/'.'/' '.
func (m *Model) getSoftFieldNameUpdate(table ...string) (field string) {
tableName := ""
if len(table) > 0 {
tableName = table[0]
} else {
tableName = m.getPrimaryTableName()
}
return m.getSoftFieldName(tableName, gSOFT_FIELD_NAME_UPDATE)
}
// getSoftFieldNameDelete checks and returns the field name for record deleting time.
// If there's no field name for storing deleting time, it returns an empty string.
// It checks the key with or without cases or chars '-'/'_'/'.'/' '.
func (m *Model) getSoftFieldNameDelete(table ...string) (field string) {
tableName := ""
if len(table) > 0 {
tableName = table[0]
} else {
tableName = m.getPrimaryTableName()
}
return m.getSoftFieldName(tableName, gSOFT_FIELD_NAME_DELETE)
}
// getSoftFieldName retrieves and returns the field name of the table for possible key.
func (m *Model) getSoftFieldName(table string, key string) (field string) {
fieldsMap, _ := m.db.TableFields(table)
if len(fieldsMap) > 0 {
field, _ = gutil.MapPossibleItemByKey(
gconv.Map(fieldsMap), key,
)
}
return
}
// getConditionForSoftDeleting retrieves and returns the condition string for soft deleting.
// It supports multiple tables string like:
// "user u, user_detail ud"
// "user u LEFT JOIN user_detail ud ON(ud.uid=u.uid)"
// "user LEFT JOIN user_detail ON(user_detail.uid=user.uid)"
// "user u LEFT JOIN user_detail ud ON(ud.uid=u.uid) LEFT JOIN user_stats us ON(us.uid=u.uid)"
func (m *Model) getConditionForSoftDeleting() string {
if m.unscoped {
return ""
}
conditionArray := garray.NewStrArray()
if gstr.Contains(m.tables, " JOIN ") {
// Base table.
match, _ := gregex.MatchString(`(.+?) [A-Z]+ JOIN`, m.tables)
conditionArray.Append(m.getConditionOfTableStringForSoftDeleting(match[1]))
// Multiple joined tables.
matches, _ := gregex.MatchAllString(`JOIN (.+?) ON`, m.tables)
for _, match := range matches {
conditionArray.Append(m.getConditionOfTableStringForSoftDeleting(match[1]))
}
}
if conditionArray.Len() == 0 && gstr.Contains(m.tables, ",") {
// Multiple base tables.
for _, s := range gstr.SplitAndTrim(m.tables, ",") {
conditionArray.Append(m.getConditionOfTableStringForSoftDeleting(s))
}
}
conditionArray.FilterEmpty()
if conditionArray.Len() > 0 {
return conditionArray.Join(" AND ")
}
// Only one table.
if fieldName := m.getSoftFieldNameDelete(); fieldName != "" {
return fmt.Sprintf(`%s IS NULL`, m.db.QuoteWord(fieldName))
}
return ""
}
// getConditionOfTableStringForSoftDeleting does something as its name describes.
func (m *Model) getConditionOfTableStringForSoftDeleting(s string) string {
var (
field = ""
table = ""
array1 = gstr.SplitAndTrim(s, " ")
array2 = gstr.SplitAndTrim(array1[0], ".")
)
if len(array2) >= 2 {
table = array2[1]
} else {
table = array2[0]
}
field = m.getSoftFieldNameDelete(table)
if field == "" {
return ""
}
if len(array1) >= 3 {
return fmt.Sprintf(`%s.%s IS NULL`, m.db.QuoteWord(array1[2]), m.db.QuoteWord(field))
}
if len(array1) >= 2 {
return fmt.Sprintf(`%s.%s IS NULL`, m.db.QuoteWord(array1[1]), m.db.QuoteWord(field))
}
return fmt.Sprintf(`%s.%s IS NULL`, m.db.QuoteWord(table), m.db.QuoteWord(field))
}
// getPrimaryTableName parses and returns the primary table name.
func (m *Model) getPrimaryTableName() string {
array1 := gstr.SplitAndTrim(m.tables, ",")
array2 := gstr.SplitAndTrim(array1[0], " ")
array3 := gstr.SplitAndTrim(array2[0], ".")
if len(array3) >= 2 {
return array3[1]
}
return array3[0]
}

View File

@ -9,6 +9,12 @@ package gdb
import (
"database/sql"
"errors"
"fmt"
"github.com/gogf/gf/os/gtime"
"github.com/gogf/gf/text/gstr"
"github.com/gogf/gf/util/gconv"
"github.com/gogf/gf/util/gutil"
"reflect"
)
// Update does "UPDATE ... " statement for the model.
@ -34,12 +40,44 @@ func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err erro
if m.data == nil {
return nil, errors.New("updating table with empty data")
}
condition, conditionArgs := m.formatCondition(false)
var (
updateData = m.data
fieldNameCreate = m.getSoftFieldNameCreate()
fieldNameUpdate = m.getSoftFieldNameUpdate()
fieldNameDelete = m.getSoftFieldNameDelete()
conditionWhere, conditionExtra, conditionArgs = m.formatCondition(false)
)
// Automatically update the record updating time.
if !m.unscoped && fieldNameUpdate != "" {
var (
refValue = reflect.ValueOf(m.data)
refKind = refValue.Kind()
)
if refKind == reflect.Ptr {
refValue = refValue.Elem()
refKind = refValue.Kind()
}
switch refKind {
case reflect.Map, reflect.Struct:
dataMap := DataToMapDeep(m.data)
gutil.MapDelete(dataMap, fieldNameCreate, fieldNameUpdate, fieldNameDelete)
if fieldNameUpdate != "" {
dataMap[fieldNameUpdate] = gtime.Now().String()
}
updateData = dataMap
default:
updates := gconv.String(m.data)
if fieldNameUpdate != "" && !gstr.Contains(updates, fieldNameUpdate) {
updates += fmt.Sprintf(`,%s='%s'`, fieldNameUpdate, gtime.Now().String())
}
updateData = updates
}
}
return m.db.DoUpdate(
m.getLink(true),
m.tables,
m.filterDataForInsertOrUpdate(m.data),
condition,
m.filterDataForInsertOrUpdate(updateData),
conditionWhere+conditionExtra,
m.mergeArguments(conditionArgs)...,
)
}

View File

@ -12,6 +12,7 @@ import (
"github.com/gogf/gf/internal/empty"
"github.com/gogf/gf/os/gtime"
"github.com/gogf/gf/text/gstr"
"github.com/gogf/gf/util/gconv"
"time"
)
@ -28,15 +29,19 @@ func (m *Model) getModel() *Model {
// filterDataForInsertOrUpdate does filter feature with data for inserting/updating operations.
// Note that, it does not filter list item, which is also type of map, for "omit empty" feature.
func (m *Model) filterDataForInsertOrUpdate(data interface{}) interface{} {
if list, ok := m.data.(List); ok {
for k, item := range list {
list[k] = m.doFilterDataMapForInsertOrUpdate(item, false)
switch value := data.(type) {
case List:
for k, item := range value {
value[k] = m.doFilterDataMapForInsertOrUpdate(item, false)
}
return list
} else if item, ok := m.data.(Map); ok {
return m.doFilterDataMapForInsertOrUpdate(item, true)
return value
case Map:
return m.doFilterDataMapForInsertOrUpdate(value, true)
default:
return data
}
return data
}
// doFilterDataMapForInsertOrUpdate does the filter features for map.
@ -70,8 +75,16 @@ func (m *Model) doFilterDataMapForInsertOrUpdate(data Map, allowOmitEmpty bool)
if len(m.fields) > 0 && m.fields != "*" {
// Keep specified fields.
set := gset.NewStrSetFrom(gstr.SplitAndTrim(m.fields, ","))
var (
set = gset.NewStrSetFrom(gstr.SplitAndTrim(m.fields, ","))
charL, charR = m.db.GetChars()
chars = charL + charR
)
set.Walk(func(item string) string {
return gstr.Trim(item, chars)
})
for k := range data {
k = gstr.Trim(k, chars)
if !set.Contains(k) {
delete(data, k)
}
@ -133,27 +146,21 @@ func (m *Model) getPrimaryKey() string {
return ""
}
// checkAndRemoveCache checks and remove the cache if necessary.
func (m *Model) checkAndRemoveCache() {
if m.cacheEnabled && m.cacheDuration < 0 && len(m.cacheName) > 0 {
m.db.GetCache().Remove(m.cacheName)
}
}
// 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) (condition string, conditionArgs []interface{}) {
var where string
func (m *Model) formatCondition(limit bool) (conditionWhere string, conditionExtra string, conditionArgs []interface{}) {
if len(m.whereHolder) > 0 {
for _, v := range m.whereHolder {
switch v.operator {
case gWHERE_HOLDER_WHERE:
if where == "" {
newWhere, newArgs := formatWhere(m.db, v.where, v.args, m.option&OPTION_OMITEMPTY > 0)
if conditionWhere == "" {
newWhere, newArgs := formatWhere(
m.db, v.where, v.args, m.option&OPTION_OMITEMPTY > 0,
)
if len(newWhere) > 0 {
where = newWhere
conditionWhere = newWhere
conditionArgs = newArgs
}
continue
@ -161,52 +168,69 @@ func (m *Model) formatCondition(limit bool) (condition string, conditionArgs []i
fallthrough
case gWHERE_HOLDER_AND:
newWhere, newArgs := formatWhere(m.db, v.where, v.args, m.option&OPTION_OMITEMPTY > 0)
newWhere, newArgs := formatWhere(
m.db, v.where, v.args, m.option&OPTION_OMITEMPTY > 0,
)
if len(newWhere) > 0 {
if where[0] == '(' {
where = fmt.Sprintf(`%s AND (%s)`, where, newWhere)
if len(conditionWhere) == 0 {
conditionWhere = newWhere
} else if conditionWhere[0] == '(' {
conditionWhere = fmt.Sprintf(`%s AND (%s)`, conditionWhere, newWhere)
} else {
where = fmt.Sprintf(`(%s) AND (%s)`, where, newWhere)
conditionWhere = fmt.Sprintf(`(%s) AND (%s)`, conditionWhere, newWhere)
}
conditionArgs = append(conditionArgs, newArgs...)
}
case gWHERE_HOLDER_OR:
newWhere, newArgs := formatWhere(m.db, v.where, v.args, m.option&OPTION_OMITEMPTY > 0)
newWhere, newArgs := formatWhere(
m.db, v.where, v.args, m.option&OPTION_OMITEMPTY > 0,
)
if len(newWhere) > 0 {
if where[0] == '(' {
where = fmt.Sprintf(`%s OR (%s)`, where, newWhere)
if len(conditionWhere) == 0 {
conditionWhere = newWhere
} else if conditionWhere[0] == '(' {
conditionWhere = fmt.Sprintf(`%s OR (%s)`, conditionWhere, newWhere)
} else {
where = fmt.Sprintf(`(%s) OR (%s)`, where, newWhere)
conditionWhere = fmt.Sprintf(`(%s) OR (%s)`, conditionWhere, newWhere)
}
conditionArgs = append(conditionArgs, newArgs...)
}
}
}
}
if where != "" {
condition += " WHERE " + where
if conditionWhere != "" {
conditionWhere = " WHERE " + conditionWhere
}
if m.groupBy != "" {
condition += " GROUP BY " + m.groupBy
conditionExtra += " GROUP BY " + m.groupBy
}
if m.orderBy != "" {
condition += " ORDER BY " + m.orderBy
conditionExtra += " ORDER BY " + m.orderBy
}
if len(m.having) > 0 {
havingStr, havingArgs := formatWhere(
m.db, m.having[0], gconv.Interfaces(m.having[1]), m.option&OPTION_OMITEMPTY > 0,
)
if len(havingStr) > 0 {
conditionExtra += " HAVING " + havingStr
conditionArgs = append(conditionArgs, havingArgs...)
}
}
if m.limit != 0 {
if m.start >= 0 {
condition += fmt.Sprintf(" LIMIT %d,%d", m.start, m.limit)
conditionExtra += fmt.Sprintf(" LIMIT %d,%d", m.start, m.limit)
} else {
condition += fmt.Sprintf(" LIMIT %d", m.limit)
conditionExtra += fmt.Sprintf(" LIMIT %d", m.limit)
}
} else if limit {
condition += " LIMIT 1"
conditionExtra += " LIMIT 1"
}
if m.offset >= 0 {
condition += fmt.Sprintf(" OFFSET %d", m.offset)
conditionExtra += fmt.Sprintf(" OFFSET %d", m.offset)
}
if m.lockInfo != "" {
condition += " " + m.lockInfo
conditionExtra += " " + m.lockInfo
}
return
}

View File

@ -38,10 +38,16 @@ func (r *SqlResult) RowsAffected() (int64, error) {
if r.affected > 0 {
return r.affected, nil
}
if r.result == nil {
return 0, nil
}
return r.result.RowsAffected()
}
// see sql.Result.LastInsertId
func (r *SqlResult) LastInsertId() (int64, error) {
if r.result == nil {
return 0, nil
}
return r.result.LastInsertId()
}

View File

@ -40,6 +40,14 @@ func (s *Schema) Table(table string) *Model {
} else {
m = s.db.Table(table)
}
// Do not change the schema of the original db,
// it here creates a new db and changes its schema.
db, err := New(m.db.GetGroup())
if err != nil {
panic(err)
}
db.SetSchema(s.schema)
m.db = db
m.schema = s.schema
return m
}

View File

@ -8,10 +8,11 @@ package gdb
import (
"database/sql"
"errors"
"github.com/gogf/gf/container/gmap"
"github.com/gogf/gf/util/gconv"
"github.com/gogf/gf/encoding/gparser"
"github.com/gogf/gf/util/gconv"
"reflect"
)
// Json converts <r> to JSON format content.
@ -42,10 +43,32 @@ func (r Record) GMap() *gmap.StrAnyMap {
// Struct converts <r> to a struct.
// Note that the parameter <pointer> should be type of *struct/**struct.
//
// Note that it returns sql.ErrNoRows if <r> is empty.
func (r Record) Struct(pointer interface{}) error {
if r == nil {
// If the record is empty, it returns error.
if r.IsEmpty() {
return sql.ErrNoRows
}
// Special handling for parameter type: reflect.Value
if _, ok := pointer.(reflect.Value); ok {
return mapToStruct(r.Map(), pointer)
}
var (
reflectValue = reflect.ValueOf(pointer)
reflectKind = reflectValue.Kind()
)
if reflectKind != reflect.Ptr {
return errors.New("parameter should be type of *struct/**struct")
}
reflectValue = reflectValue.Elem()
reflectKind = reflectValue.Kind()
if reflectKind == reflect.Invalid {
return errors.New("parameter is an invalid pointer, maybe nil")
}
if reflectKind != reflect.Ptr && reflectKind != reflect.Struct {
return errors.New("parameter should be type of *struct/**struct")
}
return mapToStruct(r.Map(), pointer)
}

View File

@ -6,36 +6,22 @@
package gdb
import (
"database/sql"
"github.com/gogf/gf/encoding/gparser"
)
// Deprecated.
func (r Record) ToJson() string {
content, _ := gparser.VarToJson(r.Map())
return string(content)
return r.Json()
}
// Deprecated.
func (r Record) ToXml(rootTag ...string) string {
content, _ := gparser.VarToXml(r.Map(), rootTag...)
return string(content)
return r.Xml(rootTag...)
}
// Deprecated.
func (r Record) ToMap() Map {
m := make(map[string]interface{})
for k, v := range r {
m[k] = v.Val()
}
return m
return r.Map()
}
// Deprecated.
func (r Record) ToStruct(pointer interface{}) error {
if r == nil {
return sql.ErrNoRows
}
return mapToStruct(r.Map(), pointer)
return r.Struct(pointer)
}

View File

@ -9,11 +9,33 @@ package gdb
import (
"database/sql"
"fmt"
"math"
"reflect"
"github.com/gogf/gf/encoding/gparser"
)
// 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.
func (r Result) Chunk(size int) []Result {
if size < 1 {
return nil
}
length := len(r)
chunks := int(math.Ceil(float64(length) / float64(size)))
var n []Result
for i, end := 0, 0; chunks > 0; chunks-- {
end = (i + 1) * size
if end > length {
end = length
}
n = append(n, r[i*size:end])
i++
}
return n
}
// Json converts <r> to JSON format content.
func (r Result) Json() string {
content, _ := gparser.VarToJson(r.List())
@ -126,27 +148,48 @@ func (r Result) RecordKeyUint(key string) map[uint]Record {
// Structs converts <r> to struct slice.
// Note that the parameter <pointer> should be type of *[]struct/*[]*struct.
func (r Result) Structs(pointer interface{}) (err error) {
l := len(r)
if l == 0 {
return sql.ErrNoRows
var (
reflectValue = reflect.ValueOf(pointer)
reflectKind = reflectValue.Kind()
)
if reflectKind != reflect.Ptr {
return fmt.Errorf("parameter should be type of *[]struct/*[]*struct, but got: %v", reflectKind)
}
t := reflect.TypeOf(pointer)
if t.Kind() != reflect.Ptr {
return fmt.Errorf("pointer should be type of pointer, but got: %v", t.Kind())
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)
}
array := reflect.MakeSlice(t.Elem(), l, l)
itemType := array.Index(0).Type()
for i := 0; i < l; i++ {
if itemType.Kind() == reflect.Ptr {
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 (
reflectType = reflect.TypeOf(pointer)
array = reflect.MakeSlice(reflectType.Elem(), length, length)
itemType = array.Index(0).Type()
itemKind = itemType.Kind()
)
for i := 0; i < length; i++ {
if itemKind == reflect.Ptr {
e := reflect.New(itemType.Elem()).Elem()
if err = r[i].Struct(e); err != nil {
return err
return fmt.Errorf(`slice element conversion failed: %s`, err.Error())
}
array.Index(i).Set(e.Addr())
} else {
e := reflect.New(itemType).Elem()
if err = r[i].Struct(e); err != nil {
return err
return fmt.Errorf(`slice element conversion failed: %s`, err.Error())
}
array.Index(i).Set(e)
}

View File

@ -6,128 +6,52 @@
package gdb
import (
"database/sql"
"fmt"
"reflect"
"github.com/gogf/gf/encoding/gparser"
)
// Deprecated.
func (r Result) ToJson() string {
content, _ := gparser.VarToJson(r.List())
return string(content)
return r.Json()
}
// Deprecated.
func (r Result) ToXml(rootTag ...string) string {
content, _ := gparser.VarToXml(r.List(), rootTag...)
return string(content)
return r.Xml(rootTag...)
}
// Deprecated.
func (r Result) ToList() List {
l := make(List, len(r))
for k, v := range r {
l[k] = v.Map()
}
return l
return r.List()
}
// Deprecated.
func (r Result) ToStringMap(key string) map[string]Map {
m := make(map[string]Map)
for _, item := range r {
if v, ok := item[key]; ok {
m[v.String()] = item.Map()
}
}
return m
return r.MapKeyStr(key)
}
// Deprecated.
func (r Result) ToIntMap(key string) map[int]Map {
m := make(map[int]Map)
for _, item := range r {
if v, ok := item[key]; ok {
m[v.Int()] = item.Map()
}
}
return m
return r.MapKeyInt(key)
}
// Deprecated.
func (r Result) ToUintMap(key string) map[uint]Map {
m := make(map[uint]Map)
for _, item := range r {
if v, ok := item[key]; ok {
m[v.Uint()] = item.Map()
}
}
return m
return r.MapKeyUint(key)
}
// Deprecated.
func (r Result) ToStringRecord(key string) map[string]Record {
m := make(map[string]Record)
for _, item := range r {
if v, ok := item[key]; ok {
m[v.String()] = item
}
}
return m
return r.RecordKeyStr(key)
}
// Deprecated.
func (r Result) ToIntRecord(key string) map[int]Record {
m := make(map[int]Record)
for _, item := range r {
if v, ok := item[key]; ok {
m[v.Int()] = item
}
}
return m
return r.RecordKeyInt(key)
}
// Deprecated.
func (r Result) ToUintRecord(key string) map[uint]Record {
m := make(map[uint]Record)
for _, item := range r {
if v, ok := item[key]; ok {
m[v.Uint()] = item
}
}
return m
return r.RecordKeyUint(key)
}
// Deprecated.
func (r Result) ToStructs(pointer interface{}) (err error) {
l := len(r)
if l == 0 {
return sql.ErrNoRows
}
t := reflect.TypeOf(pointer)
if t.Kind() != reflect.Ptr {
return fmt.Errorf("pointer should be type of pointer, but got: %v", t.Kind())
}
array := reflect.MakeSlice(t.Elem(), l, l)
itemType := array.Index(0).Type()
for i := 0; i < l; i++ {
if itemType.Kind() == reflect.Ptr {
e := reflect.New(itemType.Elem()).Elem()
if err = r[i].Struct(e); err != nil {
return err
}
array.Index(i).Set(e.Addr())
} else {
e := reflect.New(itemType).Elem()
if err = r[i].Struct(e); err != nil {
return err
}
array.Index(i).Set(e)
}
}
reflect.ValueOf(pointer).Elem().Set(array)
return nil
return r.Structs(pointer)
}

View File

@ -1,117 +0,0 @@
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gdb
import (
"github.com/gogf/gf/test/gtest"
"testing"
)
func Test_Func_FormatSqlWithArgs(t *testing.T) {
// mysql
gtest.C(t, func(t *gtest.T) {
var s string
s = FormatSqlWithArgs("select * from table where id>=? and sex=?", []interface{}{100, 1})
t.Assert(s, "select * from table where id>=100 and sex=1")
})
// 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})
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})
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})
t.Assert(s, "select * from table where id>=100 and sex=1")
})
}
func Test_Func_doQuoteWord(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
array := map[string]string{
"user": "`user`",
"user u": "user u",
"user_detail": "`user_detail`",
"user,user_detail": "user,user_detail",
"user u, user_detail ut": "user u, user_detail ut",
"u.id asc": "u.id asc",
"u.id asc, ut.uid desc": "u.id asc, ut.uid desc",
}
for k, v := range array {
t.Assert(doQuoteWord(k, "`", "`"), v)
}
})
}
func Test_Func_doQuoteString(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
// "user", "user u", "user,user_detail", "user u, user_detail ut", "u.id asc".
array := map[string]string{
"user": "`user`",
"user u": "`user` u",
"user,user_detail": "`user`,`user_detail`",
"user u, user_detail ut": "`user` u,`user_detail` ut",
"u.id asc": "`u`.`id` asc",
"u.id asc, ut.uid desc": "`u`.`id` asc,`ut`.`uid` desc",
"user.user u, user.user_detail ut": "`user`.`user` u,`user`.`user_detail` ut",
// mssql global schema access with double dots.
"user..user u, user.user_detail ut": "`user`..`user` u,`user`.`user_detail` ut",
}
for k, v := range array {
t.Assert(doQuoteString(k, "`", "`"), v)
}
})
}
func Test_Func_addTablePrefix(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
prefix := ""
array := map[string]string{
"user": "`user`",
"user u": "`user` u",
"user as u": "`user` as u",
"user,user_detail": "`user`,`user_detail`",
"user u, user_detail ut": "`user` u,`user_detail` ut",
"`user`.user_detail": "`user`.`user_detail`",
"`user`.`user_detail`": "`user`.`user_detail`",
"user as u, user_detail as ut": "`user` as u,`user_detail` as ut",
"UserCenter.user as u, UserCenter.user_detail as ut": "`UserCenter`.`user` as u,`UserCenter`.`user_detail` as ut",
// mssql global schema access with double dots.
"UserCenter..user as u, user_detail as ut": "`UserCenter`..`user` as u,`user_detail` as ut",
}
for k, v := range array {
t.Assert(doHandleTableName(k, prefix, "`", "`"), v)
}
})
gtest.C(t, func(t *gtest.T) {
prefix := "gf_"
array := map[string]string{
"user": "`gf_user`",
"user u": "`gf_user` u",
"user as u": "`gf_user` as u",
"user,user_detail": "`gf_user`,`gf_user_detail`",
"user u, user_detail ut": "`gf_user` u,`gf_user_detail` ut",
"`user`.user_detail": "`user`.`gf_user_detail`",
"`user`.`user_detail`": "`user`.`gf_user_detail`",
"user as u, user_detail as ut": "`gf_user` as u,`gf_user_detail` as ut",
"UserCenter.user as u, UserCenter.user_detail as ut": "`UserCenter`.`gf_user` as u,`UserCenter`.`gf_user_detail` as ut",
// mssql global schema access with double dots.
"UserCenter..user as u, user_detail as ut": "`UserCenter`..`gf_user` as u,`gf_user_detail` as ut",
}
for k, v := range array {
t.Assert(doHandleTableName(k, prefix, "`", "`"), v)
}
})
}

View File

@ -0,0 +1,289 @@
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gdb
import (
"fmt"
"github.com/gogf/gf/os/gcmd"
"github.com/gogf/gf/os/gtime"
"github.com/gogf/gf/test/gtest"
"testing"
)
const (
SCHEMA = "test_internal"
)
var (
db DB
configNode ConfigNode
)
func init() {
parser, err := gcmd.Parse(map[string]bool{
"name": true,
"type": true,
}, false)
gtest.Assert(err, nil)
configNode = ConfigNode{
Host: "127.0.0.1",
Port: "3306",
User: "root",
Pass: "12345678",
Name: parser.GetOpt("name", ""),
Type: parser.GetOpt("type", "mysql"),
Role: "master",
Charset: "utf8",
Weight: 1,
MaxIdleConnCount: 10,
MaxOpenConnCount: 10,
MaxConnLifetime: 600,
}
AddConfigNode(DEFAULT_GROUP_NAME, configNode)
// Default db.
if r, err := New(); err != nil {
gtest.Error(err)
} else {
db = r
}
schemaTemplate := "CREATE DATABASE IF NOT EXISTS `%s` CHARACTER SET UTF8"
if _, err := db.Exec(fmt.Sprintf(schemaTemplate, SCHEMA)); err != nil {
gtest.Error(err)
}
db.SetSchema(SCHEMA)
}
func dropTable(table string) {
if _, err := db.Exec(fmt.Sprintf("DROP TABLE IF EXISTS `%s`", table)); err != nil {
gtest.Error(err)
}
}
func Test_Func_FormatSqlWithArgs(t *testing.T) {
// mysql
gtest.C(t, func(t *gtest.T) {
var s string
s = FormatSqlWithArgs("select * from table where id>=? and sex=?", []interface{}{100, 1})
t.Assert(s, "select * from table where id>=100 and sex=1")
})
// 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})
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})
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})
t.Assert(s, "select * from table where id>=100 and sex=1")
})
}
func Test_Func_doQuoteWord(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
array := map[string]string{
"user": "`user`",
"user u": "user u",
"user_detail": "`user_detail`",
"user,user_detail": "user,user_detail",
"user u, user_detail ut": "user u, user_detail ut",
"u.id asc": "u.id asc",
"u.id asc, ut.uid desc": "u.id asc, ut.uid desc",
}
for k, v := range array {
t.Assert(doQuoteWord(k, "`", "`"), v)
}
})
}
func Test_Func_doQuoteString(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
array := map[string]string{
"user": "`user`",
"user u": "`user` u",
"user,user_detail": "`user`,`user_detail`",
"user u, user_detail ut": "`user` u,`user_detail` ut",
"u.id, u.name, u.age": "`u`.`id`,`u`.`name`,`u`.`age`",
"u.id asc": "`u`.`id` asc",
"u.id asc, ut.uid desc": "`u`.`id` asc,`ut`.`uid` desc",
"user.user u, user.user_detail ut": "`user`.`user` u,`user`.`user_detail` ut",
// mssql global schema access with double dots.
"user..user u, user.user_detail ut": "`user`..`user` u,`user`.`user_detail` ut",
}
for k, v := range array {
t.Assert(doQuoteString(k, "`", "`"), v)
}
})
}
func Test_Func_addTablePrefix(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
prefix := ""
array := map[string]string{
"user": "`user`",
"user u": "`user` u",
"user as u": "`user` as u",
"user,user_detail": "`user`,`user_detail`",
"user u, user_detail ut": "`user` u,`user_detail` ut",
"`user`.user_detail": "`user`.`user_detail`",
"`user`.`user_detail`": "`user`.`user_detail`",
"user as u, user_detail as ut": "`user` as u,`user_detail` as ut",
"UserCenter.user as u, UserCenter.user_detail as ut": "`UserCenter`.`user` as u,`UserCenter`.`user_detail` as ut",
// mssql global schema access with double dots.
"UserCenter..user as u, user_detail as ut": "`UserCenter`..`user` as u,`user_detail` as ut",
}
for k, v := range array {
t.Assert(doHandleTableName(k, prefix, "`", "`"), v)
}
})
gtest.C(t, func(t *gtest.T) {
prefix := "gf_"
array := map[string]string{
"user": "`gf_user`",
"user u": "`gf_user` u",
"user as u": "`gf_user` as u",
"user,user_detail": "`gf_user`,`gf_user_detail`",
"user u, user_detail ut": "`gf_user` u,`gf_user_detail` ut",
"`user`.user_detail": "`user`.`gf_user_detail`",
"`user`.`user_detail`": "`user`.`gf_user_detail`",
"user as u, user_detail as ut": "`gf_user` as u,`gf_user_detail` as ut",
"UserCenter.user as u, UserCenter.user_detail as ut": "`UserCenter`.`gf_user` as u,`UserCenter`.`gf_user_detail` as ut",
// mssql global schema access with double dots.
"UserCenter..user as u, user_detail as ut": "`UserCenter`..`gf_user` as u,`gf_user_detail` as ut",
}
for k, v := range array {
t.Assert(doHandleTableName(k, prefix, "`", "`"), v)
}
})
}
func Test_Model_getSoftFieldName(t *testing.T) {
table1 := "soft_deleting_table_" + gtime.TimestampNanoStr()
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;
`, table1)); err != nil {
gtest.Error(err)
}
defer dropTable(table1)
table2 := "soft_deleting_table_" + gtime.TimestampNanoStr()
if _, err := db.Exec(fmt.Sprintf(`
CREATE TABLE %s (
id int(11) NOT NULL,
name varchar(45) DEFAULT NULL,
createat datetime DEFAULT NULL,
updateat datetime DEFAULT NULL,
deleteat datetime DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, table2)); err != nil {
gtest.Error(err)
}
defer dropTable(table2)
gtest.C(t, func(t *gtest.T) {
model := db.Table(table1)
gtest.Assert(model.getSoftFieldNameCreate(table2), "createat")
gtest.Assert(model.getSoftFieldNameUpdate(table2), "updateat")
gtest.Assert(model.getSoftFieldNameDelete(table2), "deleteat")
})
}
func Test_Model_getConditionForSoftDeleting(t *testing.T) {
table1 := "soft_deleting_table_" + gtime.TimestampNanoStr()
if _, err := db.Exec(fmt.Sprintf(`
CREATE TABLE %s (
id1 int(11) NOT NULL,
name1 varchar(45) DEFAULT NULL,
create_at datetime DEFAULT NULL,
update_at datetime DEFAULT NULL,
delete_at datetime DEFAULT NULL,
PRIMARY KEY (id1)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, table1)); err != nil {
gtest.Error(err)
}
defer dropTable(table1)
table2 := "soft_deleting_table_" + gtime.TimestampNanoStr()
if _, err := db.Exec(fmt.Sprintf(`
CREATE TABLE %s (
id2 int(11) NOT NULL,
name2 varchar(45) DEFAULT NULL,
createat datetime DEFAULT NULL,
updateat datetime DEFAULT NULL,
deleteat datetime DEFAULT NULL,
PRIMARY KEY (id2)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, table2)); err != nil {
gtest.Error(err)
}
defer dropTable(table2)
gtest.C(t, func(t *gtest.T) {
model := db.Table(table1)
t.Assert(model.getConditionForSoftDeleting(), "`delete_at` IS NULL")
})
gtest.C(t, func(t *gtest.T) {
model := db.Table(fmt.Sprintf(`%s as t`, table1))
t.Assert(model.getConditionForSoftDeleting(), "`delete_at` IS NULL")
})
gtest.C(t, func(t *gtest.T) {
model := db.Table(fmt.Sprintf(`%s, %s`, table1, table2))
t.Assert(model.getConditionForSoftDeleting(), fmt.Sprintf(
"`%s`.`delete_at` IS NULL AND `%s`.`deleteat` IS NULL",
table1, table2,
))
})
gtest.C(t, func(t *gtest.T) {
model := db.Table(fmt.Sprintf(`%s t1, %s as t2`, table1, table2))
t.Assert(model.getConditionForSoftDeleting(), "`t1`.`delete_at` IS NULL AND `t2`.`deleteat` IS NULL")
})
gtest.C(t, func(t *gtest.T) {
model := db.Table(fmt.Sprintf(`%s as t1, %s as t2`, table1, table2))
t.Assert(model.getConditionForSoftDeleting(), "`t1`.`delete_at` IS NULL AND `t2`.`deleteat` IS NULL")
})
gtest.C(t, func(t *gtest.T) {
model := db.Table(fmt.Sprintf(`%s as t1`, table1)).LeftJoin(table2+" t2", "t2.id2=t1.id1")
t.Assert(model.getConditionForSoftDeleting(), "`t1`.`delete_at` IS NULL AND `t2`.`deleteat` IS NULL")
})
gtest.C(t, func(t *gtest.T) {
model := db.Table(fmt.Sprintf(`%s`, table1)).LeftJoin(table2, "t2.id2=t1.id1")
t.Assert(model.getConditionForSoftDeleting(), fmt.Sprintf(
"`%s`.`delete_at` IS NULL AND `%s`.`deleteat` IS NULL",
table1, table2,
))
})
gtest.C(t, func(t *gtest.T) {
model := db.Table(fmt.Sprintf(`%s`, table1)).LeftJoin(table2, "t2.id2=t1.id1").RightJoin(table2, "t2.id2=t1.id1")
t.Assert(model.getConditionForSoftDeleting(), fmt.Sprintf(
"`%s`.`delete_at` IS NULL AND `%s`.`deleteat` IS NULL AND `%s`.`deleteat` IS NULL",
table1, table2, table2,
))
})
gtest.C(t, func(t *gtest.T) {
model := db.Table(table1+" as t1").LeftJoin(table2+" as t2", "t2.id2=t1.id1").RightJoin(table2+" as t3 ", "t2.id2=t1.id1")
t.Assert(
model.getConditionForSoftDeleting(),
"`t1`.`delete_at` IS NULL AND `t2`.`deleteat` IS NULL AND `t3`.`deleteat` IS NULL",
)
})
}

View File

@ -26,7 +26,7 @@ func Test_Model_Insert(t *testing.T) {
table := createTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
user := db.From(table)
user := db.Table(table)
result, err := user.Filter().Data(g.Map{
"id": 1,
"uid": 1,
@ -338,7 +338,7 @@ func Test_Model_Safe(t *testing.T) {
t.Assert(err, nil)
t.Assert(len(all), 2)
all, err = md2.ForPage(1, 10).All()
all, err = md2.Page(1, 10).All()
t.Assert(err, nil)
t.Assert(len(all), 2)
})
@ -362,7 +362,7 @@ func Test_Model_Safe(t *testing.T) {
t.Assert(all[0]["id"].Int(), 1)
t.Assert(all[1]["id"].Int(), 3)
all, err = md2.ForPage(1, 10).All()
all, err = md2.Page(1, 10).All()
t.Assert(err, nil)
t.Assert(len(all), 2)
@ -378,7 +378,7 @@ func Test_Model_Safe(t *testing.T) {
t.Assert(all[1]["id"].Int(), 5)
t.Assert(all[2]["id"].Int(), 6)
all, err = md3.ForPage(1, 10).All()
all, err = md3.Page(1, 10).All()
t.Assert(err, nil)
t.Assert(len(all), 3)
})
@ -820,7 +820,7 @@ func Test_Model_Structs(t *testing.T) {
}
var users []*User
err := db.Table(table).Where("id<0").Structs(&users)
t.Assert(err, sql.ErrNoRows)
t.Assert(err, nil)
})
}
@ -905,12 +905,14 @@ func Test_Model_Scan(t *testing.T) {
NickName string
CreateTime *gtime.Time
}
user := new(User)
users := new([]*User)
var (
user = new(User)
users = new([]*User)
)
err1 := db.Table(table).Where("id < 0").Scan(user)
err2 := db.Table(table).Where("id < 0").Scan(users)
t.Assert(err1, sql.ErrNoRows)
t.Assert(err2, sql.ErrNoRows)
t.Assert(err2, nil)
})
}
@ -1274,6 +1276,11 @@ func Test_Model_Where_GTime(t *testing.T) {
t.Assert(err, nil)
t.Assert(len(result), 10)
})
gtest.C(t, func(t *gtest.T) {
result, err := db.Table(table).Where("create_time>?", *gtime.NewFromStr("2010-09-01")).All()
t.Assert(err, nil)
t.Assert(len(result), 10)
})
}
func Test_Model_WherePri(t *testing.T) {
@ -1566,7 +1573,7 @@ func Test_Model_Offset(t *testing.T) {
})
}
func Test_Model_ForPage(t *testing.T) {
func Test_Model_Page(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
@ -1576,6 +1583,15 @@ func Test_Model_ForPage(t *testing.T) {
t.Assert(result[0]["id"], 7)
t.Assert(result[1]["id"], 8)
})
gtest.C(t, func(t *gtest.T) {
model := db.Table(table).Safe().Order("id")
all, err := model.Page(3, 3).All()
count, err := model.Count()
t.Assert(err, nil)
t.Assert(len(all), 3)
t.Assert(all[0]["id"], "7")
t.Assert(count, SIZE)
})
}
func Test_Model_Option_Map(t *testing.T) {
@ -1856,8 +1872,8 @@ func Test_Model_FieldsStr(t *testing.T) {
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
t.Assert(db.Table(table).FieldsStr(), "id,passport,password,nickname,create_time")
t.Assert(db.Table(table).FieldsStr("a."), "a.id,a.passport,a.password,a.nickname,a.create_time")
t.Assert(db.Table(table).FieldsStr(), "`id`,`passport`,`password`,`nickname`,`create_time`")
t.Assert(db.Table(table).FieldsStr("a."), "`a`.`id`,`a`.`passport`,`a`.`password`,`a`.`nickname`,`a`.`create_time`")
})
}
@ -1866,8 +1882,8 @@ func Test_Model_FieldsExStr(t *testing.T) {
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
t.Assert(db.Table(table).FieldsExStr("create_time,nickname"), "id,passport,password")
t.Assert(db.Table(table).FieldsExStr("create_time,nickname", "a."), "a.id,a.passport,a.password")
t.Assert(db.Table(table).FieldsExStr("create_time,nickname"), "`id`,`passport`,`password`")
t.Assert(db.Table(table).FieldsExStr("create_time,nickname", "a."), "`a`.`id`,`a`.`passport`,`a`.`password`")
})
}
@ -2123,3 +2139,218 @@ func Test_Model_OmitEmpty_Time(t *testing.T) {
t.Assert(n, 1)
})
}
func Test_Result_Chunk(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
r, err := db.Table(table).Order("id asc").All()
t.Assert(err, nil)
chunks := r.Chunk(3)
t.Assert(len(chunks), 4)
t.Assert(chunks[0][0]["id"].Int(), 1)
t.Assert(chunks[1][0]["id"].Int(), 4)
t.Assert(chunks[2][0]["id"].Int(), 7)
t.Assert(chunks[3][0]["id"].Int(), 10)
})
}
func Test_Model_DryRun(t *testing.T) {
table := createInitTable()
defer dropTable(table)
db.SetDryRun(true)
defer db.SetDryRun(false)
gtest.C(t, func(t *gtest.T) {
one, err := db.Table(table).FindOne(1)
t.Assert(err, nil)
t.Assert(one["id"], 1)
})
gtest.C(t, func(t *gtest.T) {
r, err := db.Table(table).Data("passport", "port_1").WherePri(1).Update()
t.Assert(err, nil)
n, err := r.RowsAffected()
t.Assert(err, nil)
t.Assert(n, 0)
})
}
func Test_Model_Cache(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
one, err := db.Table(table).Cache(time.Second, "test1").FindOne(1)
t.Assert(err, nil)
t.Assert(one["passport"], "user_1")
r, err := db.Table(table).Data("passport", "user_100").WherePri(1).Update()
t.Assert(err, nil)
n, err := r.RowsAffected()
t.Assert(err, nil)
t.Assert(n, 1)
one, err = db.Table(table).Cache(time.Second, "test1").FindOne(1)
t.Assert(err, nil)
t.Assert(one["passport"], "user_1")
time.Sleep(time.Second * 2)
one, err = db.Table(table).Cache(time.Second, "test1").FindOne(1)
t.Assert(err, nil)
t.Assert(one["passport"], "user_100")
})
gtest.C(t, func(t *gtest.T) {
one, err := db.Table(table).Cache(time.Second, "test2").FindOne(2)
t.Assert(err, nil)
t.Assert(one["passport"], "user_2")
r, err := db.Table(table).Data("passport", "user_200").Cache(-1, "test2").WherePri(2).Update()
t.Assert(err, nil)
n, err := r.RowsAffected()
t.Assert(err, nil)
t.Assert(n, 1)
one, err = db.Table(table).Cache(time.Second, "test2").FindOne(2)
t.Assert(err, nil)
t.Assert(one["passport"], "user_200")
})
// transaction.
gtest.C(t, func(t *gtest.T) {
// make cache for id 3
one, err := db.Table(table).Cache(time.Second, "test3").FindOne(3)
t.Assert(err, nil)
t.Assert(one["passport"], "user_3")
r, err := db.Table(table).Data("passport", "user_300").Cache(time.Second, "test3").WherePri(3).Update()
t.Assert(err, nil)
n, err := r.RowsAffected()
t.Assert(err, nil)
t.Assert(n, 1)
err = db.Transaction(func(tx *gdb.TX) error {
one, err := tx.Table(table).Cache(time.Second, "test3").FindOne(3)
t.Assert(err, nil)
t.Assert(one["passport"], "user_300")
return nil
})
t.Assert(err, nil)
one, err = db.Table(table).Cache(time.Second, "test3").FindOne(3)
t.Assert(err, nil)
t.Assert(one["passport"], "user_3")
})
gtest.C(t, func(t *gtest.T) {
// make cache for id 4
one, err := db.Table(table).Cache(time.Second, "test4").FindOne(4)
t.Assert(err, nil)
t.Assert(one["passport"], "user_4")
r, err := db.Table(table).Data("passport", "user_400").Cache(time.Second, "test3").WherePri(4).Update()
t.Assert(err, nil)
n, err := r.RowsAffected()
t.Assert(err, nil)
t.Assert(n, 1)
err = db.Transaction(func(tx *gdb.TX) error {
// Cache feature disabled.
one, err := tx.Table(table).Cache(time.Second, "test4").FindOne(4)
t.Assert(err, nil)
t.Assert(one["passport"], "user_400")
// Update the cache.
r, err := tx.Table(table).Data("passport", "user_4000").
Cache(-1, "test4").WherePri(4).Update()
t.Assert(err, nil)
n, err := r.RowsAffected()
t.Assert(err, nil)
t.Assert(n, 1)
return nil
})
t.Assert(err, nil)
// Read from db.
one, err = db.Table(table).Cache(time.Second, "test4").FindOne(4)
t.Assert(err, nil)
t.Assert(one["passport"], "user_4000")
})
}
func Test_Model_Having(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
all, err := db.Table(table).Where("id > 1").Having("id > 8").All()
t.Assert(err, nil)
t.Assert(len(all), 2)
})
gtest.C(t, func(t *gtest.T) {
all, err := db.Table(table).Where("id > 1").Having("id > ?", 8).All()
t.Assert(err, nil)
t.Assert(len(all), 2)
})
gtest.C(t, func(t *gtest.T) {
all, err := db.Table(table).Where("id > ?", 1).Having("id > ?", 8).All()
t.Assert(err, nil)
t.Assert(len(all), 2)
})
gtest.C(t, func(t *gtest.T) {
all, err := db.Table(table).Where("id > ?", 1).Having("id", 8).All()
t.Assert(err, nil)
t.Assert(len(all), 1)
})
}
func Test_Model_Distinct(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
all, err := db.Table(table, "t").Fields("distinct t.id").Where("id > 1").Having("id > 8").All()
t.Assert(err, nil)
t.Assert(len(all), 2)
})
}
func Test_Model_Min_Max(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
value, err := db.Table(table, "t").Fields("min(t.id)").Where("id > 1").Value()
t.Assert(err, nil)
t.Assert(value.Int(), 2)
})
gtest.C(t, func(t *gtest.T) {
value, err := db.Table(table, "t").Fields("max(t.id)").Where("id > 1").Value()
t.Assert(err, nil)
t.Assert(value.Int(), 10)
})
}
func Test_Model_NullField(t *testing.T) {
table := createTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
type User struct {
Id int
Passport *string
}
data := g.Map{
"id": 1,
"passport": nil,
}
result, err := db.Table(table).Data(data).Insert()
t.Assert(err, nil)
n, _ := result.RowsAffected()
t.Assert(n, 1)
one, err := db.Table(table).FindOne(1)
t.Assert(err, nil)
var user *User
err = one.Struct(&user)
t.Assert(err, nil)
t.Assert(user.Id, data["id"])
t.Assert(user.Passport, data["passport"])
})
}

View File

@ -94,5 +94,87 @@ func Test_Model_Inherit_MapToStruct(t *testing.T) {
t.Assert(user.CreateTime, data["create_time"])
})
}
func Test_Struct_Empty(t *testing.T) {
table := createTable()
defer dropTable(table)
type User struct {
Id int
Passport string
Password string
Nickname string
}
gtest.C(t, func(t *gtest.T) {
one, err := db.Table(table).Where("id=100").One()
t.Assert(err, nil)
user := new(User)
t.AssertNE(one.Struct(user), nil)
})
gtest.C(t, func(t *gtest.T) {
one, err := db.Table(table).Where("id=100").One()
t.Assert(err, nil)
var user *User
t.AssertNE(one.Struct(&user), nil)
})
gtest.C(t, func(t *gtest.T) {
one, err := db.Table(table).Where("id=100").One()
t.Assert(err, nil)
var user *User
t.AssertNE(one.Struct(user), nil)
})
}
func Test_Structs_Empty(t *testing.T) {
table := createTable()
defer dropTable(table)
type User struct {
Id int
Passport string
Password string
Nickname string
}
gtest.C(t, func(t *gtest.T) {
all, err := db.Table(table).Where("id>100").All()
t.Assert(err, nil)
users := make([]User, 0)
t.Assert(all.Structs(&users), nil)
})
gtest.C(t, func(t *gtest.T) {
all, err := db.Table(table).Where("id>100").All()
t.Assert(err, nil)
users := make([]User, 10)
t.AssertNE(all.Structs(&users), nil)
})
gtest.C(t, func(t *gtest.T) {
all, err := db.Table(table).Where("id>100").All()
t.Assert(err, nil)
var users []User
t.Assert(all.Structs(&users), nil)
})
gtest.C(t, func(t *gtest.T) {
all, err := db.Table(table).Where("id>100").All()
t.Assert(err, nil)
users := make([]*User, 0)
t.Assert(all.Structs(&users), nil)
})
gtest.C(t, func(t *gtest.T) {
all, err := db.Table(table).Where("id>100").All()
t.Assert(err, nil)
users := make([]*User, 10)
t.Assert(all.Structs(&users), nil)
})
gtest.C(t, func(t *gtest.T) {
all, err := db.Table(table).Where("id>100").All()
t.Assert(err, nil)
var users []*User
t.Assert(all.Structs(&users), nil)
})
}

View File

@ -0,0 +1,370 @@
// 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/os/gtime"
"testing"
"time"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/test/gtest"
)
func Test_CreateUpdateDeleteTime(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)
gtest.C(t, func(t *gtest.T) {
// Insert
dataInsert := g.Map{
"id": 1,
"name": "name_1",
}
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["name"].String(), "name_1")
t.Assert(oneInsert["delete_at"].String(), "")
t.AssertGE(oneInsert["create_at"].GTime().Timestamp(), gtime.Timestamp()-2)
t.AssertGE(oneInsert["update_at"].GTime().Timestamp(), gtime.Timestamp())
time.Sleep(2 * time.Second)
// Save
dataSave := g.Map{
"id": 1,
"name": "name_10",
}
r, err = db.Table(table).Data(dataSave).Save()
t.Assert(err, nil)
n, _ = r.RowsAffected()
t.Assert(n, 2)
oneSave, err := db.Table(table).FindOne(1)
t.Assert(err, nil)
t.Assert(oneSave["id"].Int(), 1)
t.Assert(oneSave["name"].String(), "name_10")
t.Assert(oneSave["delete_at"].String(), "")
t.Assert(oneSave["create_at"].GTime().Timestamp(), oneInsert["create_at"].GTime().Timestamp())
t.AssertNE(oneSave["update_at"].GTime().Timestamp(), oneInsert["update_at"].GTime().Timestamp())
t.AssertGE(oneSave["update_at"].GTime().Timestamp(), gtime.Now().Timestamp()-2)
time.Sleep(2 * time.Second)
// Update
dataUpdate := g.Map{
"id": 1,
"name": "name_1000",
}
r, err = db.Table(table).Data(dataUpdate).WherePri(1).Update()
t.Assert(err, nil)
n, _ = r.RowsAffected()
t.Assert(n, 1)
oneUpdate, err := db.Table(table).FindOne(1)
t.Assert(err, nil)
t.Assert(oneUpdate["id"].Int(), 1)
t.Assert(oneUpdate["name"].String(), "name_1000")
t.Assert(oneUpdate["delete_at"].String(), "")
t.Assert(oneUpdate["create_at"].GTime().Timestamp(), oneInsert["create_at"].GTime().Timestamp())
t.AssertGE(oneUpdate["update_at"].GTime().Timestamp(), gtime.Now().Timestamp()-2)
// Replace
dataReplace := g.Map{
"id": 1,
"name": "name_100",
}
r, err = db.Table(table).Data(dataReplace).Replace()
t.Assert(err, nil)
n, _ = r.RowsAffected()
t.Assert(n, 2)
oneReplace, err := db.Table(table).FindOne(1)
t.Assert(err, nil)
t.Assert(oneReplace["id"].Int(), 1)
t.Assert(oneReplace["name"].String(), "name_100")
t.Assert(oneReplace["delete_at"].String(), "")
t.AssertGE(oneReplace["create_at"].GTime().Timestamp(), oneInsert["create_at"].GTime().Timestamp())
t.AssertGE(oneReplace["update_at"].GTime().Timestamp(), oneInsert["update_at"].GTime().Timestamp())
time.Sleep(2 * time.Second)
// Delete
r, err = db.Table(table).Delete("id", 1)
t.Assert(err, nil)
n, _ = r.RowsAffected()
t.Assert(n, 1)
// Delete Select
one4, err := db.Table(table).FindOne(1)
t.Assert(err, nil)
t.Assert(len(one4), 0)
one5, err := db.Table(table).Unscoped().FindOne(1)
t.Assert(err, nil)
t.Assert(one5["id"].Int(), 1)
t.AssertGE(one5["delete_at"].GTime().Timestamp(), gtime.Now().Timestamp()-2)
// Delete Count
i, err := db.Table(table).FindCount()
t.Assert(err, nil)
t.Assert(i, 0)
i, err = db.Table(table).Unscoped().FindCount()
t.Assert(err, nil)
t.Assert(i, 1)
// Delete Unscoped
r, err = db.Table(table).Unscoped().Delete("id", 1)
t.Assert(err, nil)
n, _ = r.RowsAffected()
t.Assert(n, 1)
one6, err := db.Table(table).Unscoped().FindOne(1)
t.Assert(err, nil)
t.Assert(len(one6), 0)
i, err = db.Table(table).Unscoped().FindCount()
t.Assert(err, nil)
t.Assert(i, 0)
})
}
func Test_SoftDelete_Join(t *testing.T) {
table1 := "time_test_table1"
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;
`, table1)); err != nil {
gtest.Error(err)
}
defer dropTable(table1)
table2 := "time_test_table2"
if _, err := db.Exec(fmt.Sprintf(`
CREATE TABLE %s (
id int(11) NOT NULL,
name varchar(45) DEFAULT NULL,
createat datetime DEFAULT NULL,
updateat datetime DEFAULT NULL,
deleteat datetime DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, table2)); err != nil {
gtest.Error(err)
}
defer dropTable(table2)
gtest.C(t, func(t *gtest.T) {
//db.SetDebug(true)
dataInsert1 := g.Map{
"id": 1,
"name": "name_1",
}
r, err := db.Table(table1).Data(dataInsert1).Insert()
t.Assert(err, nil)
n, _ := r.RowsAffected()
t.Assert(n, 1)
dataInsert2 := g.Map{
"id": 1,
"name": "name_2",
}
r, err = db.Table(table2).Data(dataInsert2).Insert()
t.Assert(err, nil)
n, _ = r.RowsAffected()
t.Assert(n, 1)
one, err := db.Table(table1, "t1").LeftJoin(table2, "t2", "t2.id=t1.id").Fields("t1.name").FindOne()
t.Assert(err, nil)
t.Assert(one["name"], "name_1")
// Soft deleting.
r, err = db.Table(table1).Delete()
t.Assert(err, nil)
n, _ = r.RowsAffected()
t.Assert(n, 1)
one, err = db.Table(table1, "t1").LeftJoin(table2, "t2", "t2.id=t1.id").Fields("t1.name").FindOne()
t.Assert(err, nil)
t.Assert(one.IsEmpty(), true)
one, err = db.Table(table2, "t2").LeftJoin(table1, "t1", "t2.id=t1.id").Fields("t2.name").FindOne()
t.Assert(err, nil)
t.Assert(one.IsEmpty(), true)
})
}
func Test_CreateUpdateTime_Struct(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)
type Entity struct {
Id uint64 `orm:"id,primary" json:"id"`
Name string `orm:"name" json:"name"`
CreateAt *gtime.Time `orm:"create_at" json:"create_at"`
UpdateAt *gtime.Time `orm:"update_at" json:"update_at"`
DeleteAt *gtime.Time `orm:"delete_at" json:"delete_at"`
}
gtest.C(t, func(t *gtest.T) {
// Insert
dataInsert := &Entity{
Id: 1,
Name: "name_1",
CreateAt: nil,
UpdateAt: nil,
DeleteAt: nil,
}
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["name"].String(), "name_1")
t.Assert(oneInsert["delete_at"].String(), "")
t.AssertGE(oneInsert["create_at"].GTime().Timestamp(), gtime.Timestamp()-2)
t.AssertGE(oneInsert["update_at"].GTime().Timestamp(), gtime.Timestamp())
time.Sleep(2 * time.Second)
// Save
dataSave := &Entity{
Id: 1,
Name: "name_10",
CreateAt: nil,
UpdateAt: nil,
DeleteAt: nil,
}
r, err = db.Table(table).Data(dataSave).Save()
t.Assert(err, nil)
n, _ = r.RowsAffected()
t.Assert(n, 2)
oneSave, err := db.Table(table).FindOne(1)
t.Assert(err, nil)
t.Assert(oneSave["id"].Int(), 1)
t.Assert(oneSave["name"].String(), "name_10")
t.Assert(oneSave["delete_at"].String(), "")
t.Assert(oneSave["create_at"].GTime().Timestamp(), oneInsert["create_at"].GTime().Timestamp())
t.AssertNE(oneSave["update_at"].GTime().Timestamp(), oneInsert["update_at"].GTime().Timestamp())
t.AssertGE(oneSave["update_at"].GTime().Timestamp(), gtime.Now().Timestamp()-2)
time.Sleep(2 * time.Second)
// Update
dataUpdate := &Entity{
Id: 1,
Name: "name_1000",
CreateAt: nil,
UpdateAt: nil,
DeleteAt: nil,
}
r, err = db.Table(table).Data(dataUpdate).WherePri(1).Update()
t.Assert(err, nil)
n, _ = r.RowsAffected()
t.Assert(n, 1)
oneUpdate, err := db.Table(table).FindOne(1)
t.Assert(err, nil)
t.Assert(oneUpdate["id"].Int(), 1)
t.Assert(oneUpdate["name"].String(), "name_1000")
t.Assert(oneUpdate["delete_at"].String(), "")
t.Assert(oneUpdate["create_at"].GTime().Timestamp(), oneInsert["create_at"].GTime().Timestamp())
t.AssertGE(oneUpdate["update_at"].GTime().Timestamp(), gtime.Now().Timestamp()-2)
// Replace
dataReplace := &Entity{
Id: 1,
Name: "name_100",
CreateAt: nil,
UpdateAt: nil,
DeleteAt: nil,
}
r, err = db.Table(table).Data(dataReplace).Replace()
t.Assert(err, nil)
n, _ = r.RowsAffected()
t.Assert(n, 2)
oneReplace, err := db.Table(table).FindOne(1)
t.Assert(err, nil)
t.Assert(oneReplace["id"].Int(), 1)
t.Assert(oneReplace["name"].String(), "name_100")
t.Assert(oneReplace["delete_at"].String(), "")
t.AssertGE(oneReplace["create_at"].GTime().Timestamp(), oneInsert["create_at"].GTime().Timestamp())
t.AssertGE(oneReplace["update_at"].GTime().Timestamp(), oneInsert["update_at"].GTime().Timestamp())
time.Sleep(2 * time.Second)
// Delete
r, err = db.Table(table).Delete("id", 1)
t.Assert(err, nil)
n, _ = r.RowsAffected()
t.Assert(n, 1)
// Delete Select
one4, err := db.Table(table).FindOne(1)
t.Assert(err, nil)
t.Assert(len(one4), 0)
one5, err := db.Table(table).Unscoped().FindOne(1)
t.Assert(err, nil)
t.Assert(one5["id"].Int(), 1)
t.AssertGE(one5["delete_at"].GTime().Timestamp(), gtime.Now().Timestamp()-2)
// Delete Count
i, err := db.Table(table).FindCount()
t.Assert(err, nil)
t.Assert(i, 0)
i, err = db.Table(table).Unscoped().FindCount()
t.Assert(err, nil)
t.Assert(i, 1)
// Delete Unscoped
r, err = db.Table(table).Unscoped().Delete("id", 1)
t.Assert(err, nil)
n, _ = r.RowsAffected()
t.Assert(n, 1)
one6, err := db.Table(table).Unscoped().FindOne(1)
t.Assert(err, nil)
t.Assert(len(one6), 0)
i, err = db.Table(table).Unscoped().FindCount()
t.Assert(err, nil)
t.Assert(i, 0)
})
}

View File

@ -7,7 +7,9 @@
package gdb_test
import (
"errors"
"fmt"
"github.com/gogf/gf/database/gdb"
"testing"
"github.com/gogf/gf/frame/g"
@ -120,7 +122,7 @@ func Test_TX_Insert(t *testing.T) {
if err != nil {
gtest.Error(err)
}
user := tx.From(table)
user := tx.Table(table)
if _, err := user.Data(g.Map{
"id": 1,
"passport": "t1",
@ -300,7 +302,6 @@ func Test_TX_Replace(t *testing.T) {
t.Assert(value.String(), "name_1")
}
})
}
func Test_TX_Save(t *testing.T) {
@ -713,5 +714,53 @@ func Test_TX_Delete(t *testing.T) {
t.AssertNE(n, 0)
}
})
}
func Test_Transaction(t *testing.T) {
table := createInitTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
err := db.Transaction(func(tx *gdb.TX) error {
if _, err := tx.Replace(table, g.Map{
"id": 1,
"passport": "USER_1",
"password": "PASS_1",
"nickname": "NAME_1",
"create_time": gtime.Now().String(),
}); err != nil {
t.Error(err)
}
return errors.New("error")
})
t.AssertNE(err, nil)
if value, err := db.Table(table).Fields("nickname").Where("id", 1).Value(); err != nil {
gtest.Error(err)
} else {
t.Assert(value.String(), "name_1")
}
})
gtest.C(t, func(t *gtest.T) {
err := db.Transaction(func(tx *gdb.TX) error {
if _, err := tx.Replace(table, g.Map{
"id": 1,
"passport": "USER_1",
"password": "PASS_1",
"nickname": "NAME_1",
"create_time": gtime.Now().String(),
}); err != nil {
t.Error(err)
}
return nil
})
t.Assert(err, nil)
if value, err := db.Table(table).Fields("nickname").Where("id", 1).Value(); err != nil {
gtest.Error(err)
} else {
t.Assert(value.String(), "NAME_1")
}
})
}

View File

@ -57,6 +57,7 @@ const (
gDEFAULT_POOL_IDLE_TIMEOUT = 10 * time.Second
gDEFAULT_POOL_CONN_TIMEOUT = 10 * time.Second
gDEFAULT_POOL_MAX_IDLE = 10
gDEFAULT_POOL_MAX_ACTIVE = 100
gDEFAULT_POOL_MAX_LIFE_TIME = 30 * time.Second
)
@ -74,6 +75,10 @@ func New(config Config) *Redis {
if config.MaxIdle == 0 {
config.MaxIdle = gDEFAULT_POOL_MAX_IDLE
}
// This value SHOULD NOT exceed the connection limit of redis server.
if config.MaxActive == 0 {
config.MaxActive = gDEFAULT_POOL_MAX_ACTIVE
}
if config.IdleTimeout == 0 {
config.IdleTimeout = gDEFAULT_POOL_IDLE_TIMEOUT
}
@ -126,6 +131,9 @@ func New(config Config) *Redis {
// NewFromStr creates a redis client object with given configuration string.
// Redis client maintains a connection pool automatically.
// The parameter <str> like:
// 127.0.0.1:6379,0
// 127.0.0.1:6379,0,password
func NewFromStr(str string) (*Redis, error) {
config, err := ConfigFromStr(str)
if err != nil {

View File

@ -42,12 +42,14 @@ func init() {
}
}
// CallerPath returns the function name and the absolute file path along with its line number of the caller.
// CallerPath returns the function name and the absolute file path along with its line
// number of the caller.
func Caller(skip ...int) (function string, path string, line int) {
return CallerWithFilter("", skip...)
}
// CallerPathWithFilter returns the function name and the absolute file path along with its line number of the caller.
// CallerPathWithFilter returns the function name and the absolute file path along with
// its line number of the caller.
//
// The parameter <filter> is used to filter the path of the caller.
func CallerWithFilter(filter string, skip ...int) (function string, path string, line int) {
@ -84,7 +86,10 @@ func CallerWithFilter(filter string, skip ...int) (function string, path string,
return "", "", -1
}
// callerFromIndex returns the caller position and according information exclusive of the debug package.
// callerFromIndex returns the caller position and according information exclusive of the
// debug package.
//
// VERY NOTE THAT, the returned index value should be <index - 1> as the caller's start point.
func callerFromIndex(filters []string) (pc uintptr, file string, line int, index int) {
var filtered, ok bool
for index = 0; index < gMAX_DEPTH; index++ {
@ -102,6 +107,9 @@ func callerFromIndex(filters []string) (pc uintptr, file string, line int, index
if strings.Contains(file, gFILTER_KEY) {
continue
}
if index > 0 {
index--
}
return
}
}

View File

@ -35,25 +35,37 @@ func StackWithFilter(filter string, skip ...int) string {
// StackWithFilters returns a formatted stack trace of the goroutine that calls it.
// It calls runtime.Stack with a large enough buffer to capture the entire trace.
//
// The parameter <filters> is a slice of strings, which are used to filter the path of the caller.
// The parameter <filters> is a slice of strings, which are used to filter the path of the
// caller.
//
// TODO Improve the performance using debug.Stack.
func StackWithFilters(filters []string, skip ...int) string {
number := 0
if len(skip) > 0 {
number = skip[0]
}
name := ""
space := " "
index := 1
buffer := bytes.NewBuffer(nil)
filtered := false
ok := true
pc, file, line, start := callerFromIndex(filters)
var (
name = ""
space = " "
index = 1
buffer = bytes.NewBuffer(nil)
filtered = false
ok = true
pc, file, line, start = callerFromIndex(filters)
)
for i := start + number; i < gMAX_DEPTH; i++ {
if i != start {
pc, file, line, ok = runtime.Caller(i)
}
if ok {
if goRootForFilter != "" && len(file) >= len(goRootForFilter) && file[0:len(goRootForFilter)] == goRootForFilter {
// Filter empty file.
if file == "" {
continue
}
// GOROOT filter.
if goRootForFilter != "" &&
len(file) >= len(goRootForFilter) &&
file[0:len(goRootForFilter)] == goRootForFilter {
continue
}
filtered = false

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