├── .gitignore
├── .travis.yml
├── LICENSE
├── README.cn.md
├── README.md
├── client.go
├── cluster.go
├── cluster_test.go
├── command.go
├── connection.go
├── convert_util.go
├── convert_util_test.go
├── crc16.go
├── crc16_test.go
├── errors.go
├── go.mod
├── lock.go
├── lock_test.go
├── pipeline.go
├── pipeline_test.go
├── pool.go
├── pool_test.go
├── protocol.go
├── redis.go
├── redis_advanced_cmd_test.go
├── redis_basic_cmd_test.go
├── redis_bench_test.go
├── redis_cluster_cmd_test.go
├── redis_multikey_cmd_test.go
├── redis_script_cmd_test.go
├── redis_sentinel_cmd_test.go
└── redis_test.go
/.gitignore:
--------------------------------------------------------------------------------
1 | # Binaries for programs and plugins
2 | *.exe
3 | *.exe~
4 | *.dll
5 | *.so
6 | *.dylib
7 |
8 | # Test binary, build with `go test -c`
9 | *.test
10 |
11 | # Output of the go coverage tool, specifically when used with LiteIDE
12 | *.out
13 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: go
2 |
3 | go:
4 | - "1.12.x"
5 |
6 | branches:
7 | only:
8 | - master
9 |
10 | env:
11 | - GO111MODULE=on
12 |
13 | services:
14 | - docker
15 | - redis-server
16 |
17 | addons:
18 | hosts:
19 | - local
20 |
21 | before_install:
22 | - pwd
23 |
24 | install:
25 | - cat /etc/hosts
26 |
27 | script:
28 | - docker run --rm -it -d -p 7000-7005:7000-7005 --net=host areyouok/redis-cluster
29 | - sleep 10
30 | - GOARCH=amd64 go test -v ./... -race -coverprofile=coverage.txt -covermode=atomic
31 |
32 | after_success:
33 | - bash <(curl -s https://codecov.io/bash)
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 piaohao
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.cn.md:
--------------------------------------------------------------------------------
1 | # godis
2 | [](https://godoc.org/github.com/piaohao/godis)
3 | [](https://travis-ci.com/piaohao/godis)
4 | [](https://goreportcard.com/report/github.com/piaohao/godis)
5 | [](https://codecov.io/gh/piaohao/godis)
6 | [](https://github.com/piaohao/godis)
7 |
8 | godis是一个golang实现的redis客户端,参考jedis实现.
9 | godis实现了几乎所有的redis命令,包括单机命令,集群命令,管道命令和事物命令等.
10 | 如果你用过jedis,你就能非常容易地上手godis,因为godis的方法命名几乎全部来自jedis.
11 | 值得一提的是,godis实现了单机和集群模式下的分布式锁,godis的锁比redisson快很多,在i7,8核32g的电脑测试,10万次for循环,8个线程,业务逻辑是简单的count++,redisson需要18-20秒,而godis只需要7秒左右.
12 | godis已经完成了大多数命令的测试用例,比较稳定.
13 | 非常高兴你能提出任何建议,我会积极地迭代这个项目.
14 |
15 | * [github地址: https://github.com/piaohao/godis](https://github.com/piaohao/godis)
16 | * [gitee地址: https://gitee.com/piaohao/godis](https://gitee.com/piaohao/godis)
17 |
18 | # 特色
19 | * cluster集群
20 | * pipeline管道
21 | * transaction事物
22 | * distributed lock分布式锁
23 | * 其他功能在持续开发中
24 |
25 | # 安装
26 | ```
27 | go get -u github.com/piaohao/godis
28 | ```
29 | 或者使用 `go.mod`:
30 | ```
31 | require github.com/piaohao/godis latest
32 | ```
33 | # 文档
34 |
35 | * [ApiDoc](https://godoc.org/github.com/piaohao/godis)
36 |
37 | # 快速开始
38 | 1. 基本例子
39 |
40 | ```go
41 | package main
42 |
43 | import (
44 | "github.com/piaohao/godis"
45 | )
46 |
47 | func main() {
48 | redis := godis.NewRedis(&godis.Option{
49 | Host: "localhost",
50 | Port: 6379,
51 | Db: 0,
52 | })
53 | defer redis.Close()
54 | redis.Set("godis", "1")
55 | arr, _ := redis.Get("godis")
56 | println(arr)
57 | }
58 | ```
59 | 1. 使用连接池
60 | ```go
61 | package main
62 |
63 | import (
64 | "github.com/piaohao/godis"
65 | )
66 |
67 | func main() {
68 | option:=&godis.Option{
69 | Host: "localhost",
70 | Port: 6379,
71 | Db: 0,
72 | }
73 | pool := godis.NewPool(&godis.PoolConfig{}, option)
74 | redis, _ := pool.GetResource()
75 | defer redis.Close()
76 | redis.Set("godis", "1")
77 | arr, _ := redis.Get("godis")
78 | println(arr)
79 | }
80 | ```
81 | 1. 发布订阅
82 | ```go
83 | package main
84 |
85 | import (
86 | "github.com/piaohao/godis"
87 | "time"
88 | )
89 |
90 | func main() {
91 | option:=&godis.Option{
92 | Host: "localhost",
93 | Port: 6379,
94 | Db: 0,
95 | }
96 | pool := godis.NewPool(&godis.PoolConfig{}, option)
97 | go func() {
98 | redis, _ := pool.GetResource()
99 | defer redis.Close()
100 | pubsub := &godis.RedisPubSub{
101 | OnMessage: func(channel, message string) {
102 | println(channel, message)
103 | },
104 | OnSubscribe: func(channel string, subscribedChannels int) {
105 | println(channel, subscribedChannels)
106 | },
107 | OnPong: func(channel string) {
108 | println("recieve pong")
109 | },
110 | }
111 | redis.Subscribe(pubsub, "godis")
112 | }()
113 | time.Sleep(1 * time.Second)
114 | {
115 | redis, _ := pool.GetResource()
116 | defer redis.Close()
117 | redis.Publish("godis", "godis pubsub")
118 | redis.Close()
119 | }
120 | time.Sleep(1 * time.Second)
121 | }
122 | ```
123 | 1. cluster集群
124 | ```go
125 | package main
126 |
127 | import (
128 | "github.com/piaohao/godis"
129 | "time"
130 | )
131 |
132 | func main() {
133 | cluster := godis.NewRedisCluster(&godis.ClusterOption{
134 | Nodes: []string{"localhost:7000", "localhost:7001", "localhost:7002", "localhost:7003", "localhost:7004", "localhost:7005"},
135 | ConnectionTimeout: 0,
136 | SoTimeout: 0,
137 | MaxAttempts: 0,
138 | Password: "",
139 | PoolConfig: &godis.PoolConfig{},
140 | })
141 | cluster.Set("cluster", "godis cluster")
142 | reply, _ := cluster.Get("cluster")
143 | println(reply)
144 | }
145 | ```
146 | 1. pipeline管道
147 | ```go
148 | package main
149 |
150 | import (
151 | "github.com/piaohao/godis"
152 | "time"
153 | )
154 |
155 | func main() {
156 | option:=&godis.Option{
157 | Host: "localhost",
158 | Port: 6379,
159 | Db: 0,
160 | }
161 | pool := godis.NewPool(&godis.PoolConfig{}, option)
162 | redis, _ := pool.GetResource()
163 | defer redis.Close()
164 | p := redis.Pipelined()
165 | infoResp, _ := p.Info()
166 | timeResp, _ := p.Time()
167 | p.Sync()
168 | timeList, _ := timeResp.Get()
169 | println(timeList)
170 | info, _ := infoResp.Get()
171 | println(info)
172 | }
173 | ```
174 | 1. transaction事物
175 | ```go
176 | package main
177 |
178 | import (
179 | "github.com/piaohao/godis"
180 | "time"
181 | )
182 |
183 | func main() {
184 | option:=&godis.Option{
185 | Host: "localhost",
186 | Port: 6379,
187 | Db: 0,
188 | }
189 | pool := godis.NewPool(nil, option)
190 | redis, _ := pool.GetResource()
191 | defer redis.Close()
192 | p, _ := redis.Multi()
193 | infoResp, _ := p.Info()
194 | timeResp, _ := p.Time()
195 | p.Exec()
196 | timeList, _ := timeResp.Get()
197 | println(timeList)
198 | info, _ := infoResp.Get()
199 | println(info)
200 | }
201 | ```
202 | 1. distribute lock分布式锁
203 | * single redis
204 | ```go
205 | package main
206 |
207 | import (
208 | "github.com/piaohao/godis"
209 | "time"
210 | )
211 |
212 | func main() {
213 | locker := godis.NewLocker(&godis.Option{
214 | Host: "localhost",
215 | Port: 6379,
216 | Db: 0,
217 | }, &godis.LockOption{
218 | Timeout: 5*time.Second,
219 | })
220 | lock, err := locker.TryLock("lock")
221 | if err == nil && lock!=nil {
222 | //do something
223 | locker.UnLock(lock)
224 | }
225 |
226 | }
227 | ```
228 | * redis cluster
229 | ```go
230 | package main
231 |
232 | import (
233 | "github.com/piaohao/godis"
234 | "time"
235 | )
236 |
237 | func main() {
238 | locker := godis.NewClusterLocker(&godis.ClusterOption{
239 | Nodes: []string{"localhost:7000", "localhost:7001", "localhost:7002", "localhost:7003", "localhost:7004", "localhost:7005"},
240 | ConnectionTimeout: 0,
241 | SoTimeout: 0,
242 | MaxAttempts: 0,
243 | Password: "",
244 | PoolConfig: &godis.PoolConfig{},
245 | },&godis.LockOption{
246 | Timeout: 5*time.Second,
247 | })
248 | lock, err := locker.TryLock("lock")
249 | if err == nil && lock!=nil {
250 | //do something
251 | locker.UnLock(lock)
252 | }
253 | }
254 | ```
255 | # 证书
256 |
257 | `godis` 使用的是 [MIT License](LICENSE), 永远100%免费和开源.
258 |
259 | # 鸣谢
260 | * [jedis,java非常出名的redis客户端](https://github.com/xetorthio/jedis)
261 | * [gf,功能非常强大的go web框架](https://github.com/gogf/gf)
262 | * [go-commons-pool,参照apache common-pool实现的go连接池](https://github.com/jolestar/go-commons-pool)
263 |
264 | # 联系
265 |
266 | piao.hao@qq.com
267 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # godis
2 | [](https://godoc.org/github.com/piaohao/godis)
3 | [](https://travis-ci.com/piaohao/godis)
4 | [](https://goreportcard.com/report/github.com/piaohao/godis)
5 | [](https://codecov.io/gh/piaohao/godis)
6 | [](https://github.com/piaohao/godis)
7 |
8 | redis client implement by golang, refers to jedis.
9 | this library implements most of redis command, include normal redis command, cluster command, sentinel command, pipeline command and transaction command.
10 | if you've ever used jedis, then you can use godis easily, godis almost has the same method of jedis.
11 | especially, godis implements distributed lock in single mode and cluster mode, godis's lock is much more faster than redisson, on my computer(i7,8core,32g), run 100,000 loop, use 8 threads, the business code is just count++, redisson need 18s-20s, while godis just need 7 second.
12 | godis has done many test case to make sure it's stable.
13 |
14 | I am glad you made any suggestions, and I will actively iterate over the project.
15 |
16 | * [https://github.com/piaohao/godis](https://github.com/piaohao/godis)
17 | * [https://gitee.com/piaohao/godis](https://gitee.com/piaohao/godis)
18 |
19 | # Features
20 | * cluster
21 | * pipeline
22 | * transaction
23 | * distributed lock
24 | * other feature under development
25 |
26 | # Installation
27 | ```
28 | go get -u github.com/piaohao/godis
29 | ```
30 | or use `go.mod`:
31 | ```
32 | require github.com/piaohao/godis latest
33 | ```
34 | # Documentation
35 |
36 | * [ApiDoc](https://godoc.org/github.com/piaohao/godis)
37 |
38 | # Quick Start
39 | 1. basic example
40 |
41 | ```go
42 | package main
43 |
44 | import (
45 | "github.com/piaohao/godis"
46 | )
47 |
48 | func main() {
49 | redis := godis.NewRedis(&godis.Option{
50 | Host: "localhost",
51 | Port: 6379,
52 | Db: 0,
53 | })
54 | defer redis.Close()
55 | redis.Set("godis", "1")
56 | arr, _ := redis.Get("godis")
57 | println(arr)
58 | }
59 | ```
60 | 1. use pool
61 | ```go
62 | package main
63 |
64 | import (
65 | "github.com/piaohao/godis"
66 | )
67 |
68 | func main() {
69 | option:=&godis.Option{
70 | Host: "localhost",
71 | Port: 6379,
72 | Db: 0,
73 | }
74 | pool := godis.NewPool(&godis.PoolConfig{}, option)
75 | redis, _ := pool.GetResource()
76 | defer redis.Close()
77 | redis.Set("godis", "1")
78 | arr, _ := redis.Get("godis")
79 | println(arr)
80 | }
81 | ```
82 | 1. pubsub
83 | ```go
84 | package main
85 |
86 | import (
87 | "github.com/piaohao/godis"
88 | "time"
89 | )
90 |
91 | func main() {
92 | option:=&godis.Option{
93 | Host: "localhost",
94 | Port: 6379,
95 | Db: 0,
96 | }
97 | pool := godis.NewPool(&godis.PoolConfig{}, option)
98 | go func() {
99 | redis, _ := pool.GetResource()
100 | defer redis.Close()
101 | pubsub := &godis.RedisPubSub{
102 | OnMessage: func(channel, message string) {
103 | println(channel, message)
104 | },
105 | OnSubscribe: func(channel string, subscribedChannels int) {
106 | println(channel, subscribedChannels)
107 | },
108 | OnPong: func(channel string) {
109 | println("recieve pong")
110 | },
111 | }
112 | redis.Subscribe(pubsub, "godis")
113 | }()
114 | time.Sleep(1 * time.Second)
115 | {
116 | redis, _ := pool.GetResource()
117 | defer redis.Close()
118 | redis.Publish("godis", "godis pubsub")
119 | redis.Close()
120 | }
121 | time.Sleep(1 * time.Second)
122 | }
123 | ```
124 | 1. cluster
125 | ```go
126 | package main
127 |
128 | import (
129 | "github.com/piaohao/godis"
130 | "time"
131 | )
132 |
133 | func main() {
134 | cluster := godis.NewRedisCluster(&godis.ClusterOption{
135 | Nodes: []string{"localhost:7000", "localhost:7001", "localhost:7002", "localhost:7003", "localhost:7004", "localhost:7005"},
136 | ConnectionTimeout: 0,
137 | SoTimeout: 0,
138 | MaxAttempts: 0,
139 | Password: "",
140 | PoolConfig: &godis.PoolConfig{},
141 | })
142 | cluster.Set("cluster", "godis cluster")
143 | reply, _ := cluster.Get("cluster")
144 | println(reply)
145 | }
146 | ```
147 | 1. pipeline
148 | ```go
149 | package main
150 |
151 | import (
152 | "github.com/piaohao/godis"
153 | "time"
154 | )
155 |
156 | func main() {
157 | option:=&godis.Option{
158 | Host: "localhost",
159 | Port: 6379,
160 | Db: 0,
161 | }
162 | pool := godis.NewPool(&godis.PoolConfig{}, option)
163 | redis, _ := pool.GetResource()
164 | defer redis.Close()
165 | p := redis.Pipelined()
166 | infoResp, _ := p.Info()
167 | timeResp, _ := p.Time()
168 | p.Sync()
169 | timeList, _ := timeResp.Get()
170 | println(timeList)
171 | info, _ := infoResp.Get()
172 | println(info)
173 | }
174 | ```
175 | 1. transaction
176 | ```go
177 | package main
178 |
179 | import (
180 | "github.com/piaohao/godis"
181 | "time"
182 | )
183 |
184 | func main() {
185 | option:=&godis.Option{
186 | Host: "localhost",
187 | Port: 6379,
188 | Db: 0,
189 | }
190 | pool := godis.NewPool(nil, option)
191 | redis, _ := pool.GetResource()
192 | defer redis.Close()
193 | p, _ := redis.Multi()
194 | infoResp, _ := p.Info()
195 | timeResp, _ := p.Time()
196 | p.Exec()
197 | timeList, _ := timeResp.Get()
198 | println(timeList)
199 | info, _ := infoResp.Get()
200 | println(info)
201 | }
202 | ```
203 | 1. distribute lock
204 | * single redis
205 | ```go
206 | package main
207 |
208 | import (
209 | "github.com/piaohao/godis"
210 | "time"
211 | )
212 |
213 | func main() {
214 | locker := godis.NewLocker(&godis.Option{
215 | Host: "localhost",
216 | Port: 6379,
217 | Db: 0,
218 | }, &godis.LockOption{
219 | Timeout: 5*time.Second,
220 | })
221 | lock, err := locker.TryLock("lock")
222 | if err == nil && lock!=nil {
223 | //do something
224 | locker.UnLock(lock)
225 | }
226 |
227 | }
228 | ```
229 | * redis cluster
230 | ```go
231 | package main
232 |
233 | import (
234 | "github.com/piaohao/godis"
235 | "time"
236 | )
237 |
238 | func main() {
239 | locker := godis.NewClusterLocker(&godis.ClusterOption{
240 | Nodes: []string{"localhost:7000", "localhost:7001", "localhost:7002", "localhost:7003", "localhost:7004", "localhost:7005"},
241 | ConnectionTimeout: 0,
242 | SoTimeout: 0,
243 | MaxAttempts: 0,
244 | Password: "",
245 | PoolConfig: &godis.PoolConfig{},
246 | },&godis.LockOption{
247 | Timeout: 5*time.Second,
248 | })
249 | lock, err := locker.TryLock("lock")
250 | if err == nil && lock!=nil {
251 | //do something
252 | locker.UnLock(lock)
253 | }
254 | }
255 | ```
256 | # License
257 |
258 | `godis` is licensed under the [MIT License](LICENSE), 100% free and open-source, forever.
259 |
260 | # Thanks
261 | * [[ jedis ] jedis is a popular redis client write by java](https://github.com/xetorthio/jedis)
262 | * [[ gf ] gf is a amazing web framework write by golang](https://github.com/gogf/gf)
263 | * [[ go-commons-pool ] refers to apache comnmon-pool](https://github.com/jolestar/go-commons-pool)
264 |
265 | # Contact
266 |
267 | piao.hao@qq.com
268 |
--------------------------------------------------------------------------------
/command.go:
--------------------------------------------------------------------------------
1 | package godis
2 |
3 | import (
4 | "fmt"
5 | "strconv"
6 | "strings"
7 | )
8 |
9 | //ZAddParams ...
10 | type ZAddParams struct {
11 | params map[string]string
12 | }
13 |
14 | //NewZAddParams constructor
15 | func NewZAddParams() *ZAddParams {
16 | return &ZAddParams{params: make(map[string]string)}
17 | }
18 |
19 | //XX set XX parameter, Only update elements that already exist. Never add elements.
20 | func (p *ZAddParams) XX() *ZAddParams {
21 | p.params["XX"] = "XX"
22 | return p
23 | }
24 |
25 | //NX set NX parameter, Don't update already existing elements. Always add new elements.
26 | func (p *ZAddParams) NX() *ZAddParams {
27 | p.params["NX"] = "NX"
28 | return p
29 | }
30 |
31 | //CH set CH parameter, Modify the return value from the number of new elements added, to the total number of elements changed
32 | func (p *ZAddParams) CH() *ZAddParams {
33 | p.params["CH"] = "CH"
34 | return p
35 | }
36 |
37 | //getByteParams get all params
38 | func (p *ZAddParams) getByteParams(key []byte, args ...[]byte) [][]byte {
39 | arr := make([][]byte, 0)
40 | arr = append(arr, key)
41 | if p.Contains("XX") {
42 | arr = append(arr, []byte("XX"))
43 | }
44 | if p.Contains("NX") {
45 | arr = append(arr, []byte("NX"))
46 | }
47 | if p.Contains("CH") {
48 | arr = append(arr, []byte("CH"))
49 | }
50 | for _, a := range args {
51 | arr = append(arr, a)
52 | }
53 | return arr
54 | }
55 |
56 | //Contains return params map contains the key
57 | func (p *ZAddParams) Contains(key string) bool {
58 | _, ok := p.params[key]
59 | return ok
60 | }
61 |
62 | //BitPosParams bitpos params
63 | type BitPosParams struct {
64 | params [][]byte
65 | }
66 |
67 | //SortParams sort params
68 | type SortParams struct {
69 | params []string
70 | }
71 |
72 | //NewSortParams create new sort params instance
73 | func NewSortParams() *SortParams {
74 | return &SortParams{params: make([]string, 0)}
75 | }
76 |
77 | func (p *SortParams) getParams() [][]byte {
78 | return StrArrToByteArrArr(p.params)
79 | }
80 |
81 | //By set by param with pattern
82 | func (p *SortParams) By(pattern string) *SortParams {
83 | p.params = append(p.params, keywordBy.name)
84 | p.params = append(p.params, pattern)
85 | return p
86 | }
87 |
88 | //NoSort set by param with nosort
89 | func (p *SortParams) NoSort() *SortParams {
90 | p.params = append(p.params, keywordBy.name)
91 | p.params = append(p.params, keywordNosort.name)
92 | return p
93 | }
94 |
95 | //Desc set desc param,then sort elements in descending order
96 | func (p *SortParams) Desc() *SortParams {
97 | p.params = append(p.params, keywordDesc.name)
98 | return p
99 | }
100 |
101 | //Asc set asc param,then sort elements in ascending order
102 | func (p *SortParams) Asc() *SortParams {
103 | p.params = append(p.params, keywordAsc.name)
104 | return p
105 | }
106 |
107 | //Limit limit the sort result,[x,y)
108 | func (p *SortParams) Limit(start, count int) *SortParams {
109 | p.params = append(p.params, keywordLimit.name)
110 | p.params = append(p.params, strconv.Itoa(start))
111 | p.params = append(p.params, strconv.Itoa(count))
112 | return p
113 | }
114 |
115 | //Alpha sort elements in alpha order
116 | func (p *SortParams) Alpha() *SortParams {
117 | p.params = append(p.params, keywordAlpha.name)
118 | return p
119 | }
120 |
121 | //Get set get param with patterns
122 | func (p *SortParams) Get(patterns ...string) *SortParams {
123 | for _, pattern := range patterns {
124 | p.params = append(p.params, keywordGet.name)
125 | p.params = append(p.params, pattern)
126 | }
127 | return p
128 | }
129 |
130 | //ScanParams scan,hscan,sscan,zscan params
131 | type ScanParams struct {
132 | //params map[*keyword][]byte
133 | params map[string]string
134 | }
135 |
136 | //NewScanParams create scan params instance
137 | func NewScanParams() *ScanParams {
138 | return &ScanParams{params: make(map[string]string)}
139 | }
140 |
141 | //Match scan match pattern
142 | func (s *ScanParams) Match(pattern string) *ScanParams {
143 | s.params[keywordMatch.name] = pattern
144 | return s
145 | }
146 |
147 | //Count scan result count
148 | func (s *ScanParams) Count(count int) *ScanParams {
149 | s.params[keywordCount.name] = strconv.Itoa(count)
150 | return s
151 | }
152 |
153 | //getParams get all scan params
154 | func (s ScanParams) getParams() [][]byte {
155 | arr := make([][]byte, 0)
156 | for k, v := range s.params {
157 | arr = append(arr, []byte(k))
158 | arr = append(arr, []byte(v))
159 | }
160 | return arr
161 | }
162 |
163 | //GetMatch get the match param value
164 | func (s ScanParams) GetMatch() string {
165 | if v, ok := s.params[keywordMatch.name]; ok {
166 | return v
167 | }
168 | return ""
169 | }
170 |
171 | //ListOption list option
172 | type ListOption struct {
173 | name string // name ...
174 | }
175 |
176 | //getRaw get the option name byte array
177 | func (l *ListOption) getRaw() []byte {
178 | return []byte(l.name)
179 | }
180 |
181 | //NewListOption create new list option instance
182 | func newListOption(name string) *ListOption {
183 | return &ListOption{name}
184 | }
185 |
186 | var (
187 | //ListOptionBefore insert an new element before designated element
188 | ListOptionBefore = newListOption("BEFORE")
189 | //ListOptionAfter insert an new element after designated element
190 | ListOptionAfter = newListOption("AFTER")
191 | )
192 |
193 | //GeoUnit geo unit,m|mi|km|ft
194 | type GeoUnit struct {
195 | name string // name of geo unit
196 | }
197 |
198 | //getRaw get the name byte array
199 | func (g *GeoUnit) getRaw() []byte {
200 | return []byte(g.name)
201 | }
202 |
203 | //NewGeoUnit create a new geounit instance
204 | func newGeoUnit(name string) *GeoUnit {
205 | return &GeoUnit{name}
206 | }
207 |
208 | var (
209 | //GeoUnitMi calculate distance use mi unit
210 | GeoUnitMi = newGeoUnit("mi")
211 | //GeoUnitM calculate distance use m unit
212 | GeoUnitM = newGeoUnit("m")
213 | //GeoUnitKm calculate distance use km unit
214 | GeoUnitKm = newGeoUnit("km")
215 | //GeoUnitFt calculate distance use ft unit
216 | GeoUnitFt = newGeoUnit("ft")
217 | )
218 |
219 | //GeoRadiusParams geo radius param
220 | type GeoRadiusParams struct {
221 | params map[string]string
222 | }
223 |
224 | //NewGeoRadiusParam create a new geo radius param instance
225 | func NewGeoRadiusParam() *GeoRadiusParams {
226 | return &GeoRadiusParams{params: make(map[string]string)}
227 | }
228 |
229 | //WithCoord fill the geo result with coordinate
230 | func (p *GeoRadiusParams) WithCoord() *GeoRadiusParams {
231 | p.params["withcoord"] = "withcoord"
232 | return p
233 | }
234 |
235 | //WithDist fill the geo result with distance
236 | func (p *GeoRadiusParams) WithDist() *GeoRadiusParams {
237 | p.params["withdist"] = "withdist"
238 | return p
239 | }
240 |
241 | //SortAscending sort th geo result in ascending order
242 | func (p *GeoRadiusParams) SortAscending() *GeoRadiusParams {
243 | p.params["asc"] = "asc"
244 | return p
245 | }
246 |
247 | //SortDescending sort the geo result in descending order
248 | func (p *GeoRadiusParams) SortDescending() *GeoRadiusParams {
249 | p.params["desc"] = "desc"
250 | return p
251 | }
252 |
253 | //Count fill the geo result with count
254 | func (p *GeoRadiusParams) Count(count int) *GeoRadiusParams {
255 | if count > 0 {
256 | p.params["count"] = strconv.Itoa(count)
257 | }
258 | return p
259 | }
260 |
261 | //getParams get geo param byte array
262 | func (p *GeoRadiusParams) getParams(args [][]byte) [][]byte {
263 | arr := make([][]byte, 0)
264 | for _, a := range args {
265 | arr = append(arr, a)
266 | }
267 |
268 | if p.Contains("withcoord") {
269 | arr = append(arr, []byte("withcoord"))
270 | }
271 | if p.Contains("withdist") {
272 | arr = append(arr, []byte("withdist"))
273 | }
274 |
275 | if p.Contains("count") {
276 | arr = append(arr, []byte("count"))
277 | count, _ := strconv.Atoi(p.params["count"])
278 | arr = append(arr, IntToByteArr(count))
279 | }
280 |
281 | if p.Contains("asc") {
282 | arr = append(arr, []byte("asc"))
283 | } else if p.Contains("desc") {
284 | arr = append(arr, []byte("desc"))
285 | }
286 |
287 | return arr
288 | }
289 |
290 | //Contains test geo param contains the key
291 | func (p *GeoRadiusParams) Contains(key string) bool {
292 | _, ok := p.params[key]
293 | return ok
294 | }
295 |
296 | //Tuple zset tuple
297 | type Tuple struct {
298 | element string
299 | score float64
300 | }
301 |
302 | //GeoRadiusResponse geo radius response
303 | type GeoRadiusResponse struct {
304 | member string
305 | distance float64
306 | coordinate GeoCoordinate
307 | }
308 |
309 | func newGeoRadiusResponse(member string) *GeoRadiusResponse {
310 | return &GeoRadiusResponse{member: member}
311 | }
312 |
313 | //GeoCoordinate geo coordinate struct
314 | type GeoCoordinate struct {
315 | longitude float64
316 | latitude float64
317 | }
318 |
319 | //ScanResult scan result struct
320 | type ScanResult struct {
321 | Cursor string
322 | Results []string
323 | }
324 |
325 | //ZParams zset operation params
326 | type ZParams struct {
327 | params []string
328 | }
329 |
330 | //getParams get params byte array
331 | func (g *ZParams) getParams() [][]byte {
332 | return StrArrToByteArrArr(g.params)
333 | }
334 |
335 | //WeightsByDouble Set weights.
336 | func (g *ZParams) WeightsByDouble(weights ...float64) *ZParams {
337 | g.params = append(g.params, keywordWeights.name)
338 | for _, w := range weights {
339 | g.params = append(g.params, Float64ToStr(w))
340 | }
341 | return g
342 | }
343 |
344 | //Aggregate Set Aggregate.
345 | func (g *ZParams) Aggregate(aggregate *Aggregate) *ZParams {
346 | g.params = append(g.params, keywordAggregate.name)
347 | g.params = append(g.params, aggregate.name)
348 | return g
349 | }
350 |
351 | //newZParams create a new zparams instance
352 | func newZParams() *ZParams {
353 | return &ZParams{params: make([]string, 0)}
354 | }
355 |
356 | //Aggregate aggregate,sum|min|max
357 | type Aggregate struct {
358 | name string // name of Aggregate
359 | }
360 |
361 | //getRaw get the name byte array
362 | func (g *Aggregate) getRaw() []byte {
363 | return []byte(g.name)
364 | }
365 |
366 | //newAggregate create a new geounit instance
367 | func newAggregate(name string) *Aggregate {
368 | return &Aggregate{name}
369 | }
370 |
371 | var (
372 | //AggregateSum aggregate result with sum operation
373 | AggregateSum = newAggregate("SUM")
374 | //AggregateMin aggregate result with min operation
375 | AggregateMin = newAggregate("MIN")
376 | //AggregateMax aggregate result with max operation
377 | AggregateMax = newAggregate("MAX")
378 | )
379 |
380 | //RedisPubSub redis pubsub struct
381 | type RedisPubSub struct {
382 | subscribedChannels int
383 | redis *Redis
384 | OnMessage func(channel, message string) //receive message
385 | OnPMessage func(pattern string, channel, message string) //receive pattern message
386 | OnSubscribe func(channel string, subscribedChannels int) //listen subscribe event
387 | OnUnSubscribe func(channel string, subscribedChannels int) //listen unsubscribe event
388 | OnPUnSubscribe func(pattern string, subscribedChannels int) //listen pattern unsubscribe event
389 | OnPSubscribe func(pattern string, subscribedChannels int) //listen pattern subscribe event
390 | OnPong func(channel string) //listen heart beat event
391 | }
392 |
393 | //Subscribe subscribe some channels
394 | func (r *RedisPubSub) Subscribe(channels ...string) error {
395 | r.redis.mu.RLock()
396 | defer r.redis.mu.RUnlock()
397 | if r.redis.client == nil {
398 | return newConnectError("redisPubSub is not subscribed to a Redis instance")
399 | }
400 | err := r.redis.client.subscribe(channels...)
401 | if err != nil {
402 | return err
403 | }
404 | err = r.redis.client.flush()
405 | if err != nil {
406 | return err
407 | }
408 | return nil
409 | }
410 |
411 | //UnSubscribe unsubscribe some channels
412 | func (r *RedisPubSub) UnSubscribe(channels ...string) error {
413 | r.redis.mu.RLock()
414 | defer r.redis.mu.RUnlock()
415 | if r.redis.client == nil {
416 | return newConnectError("redisPubSub is not subscribed to a Redis instance")
417 | }
418 | err := r.redis.client.unsubscribe(channels...)
419 | if err != nil {
420 | return err
421 | }
422 | err = r.redis.client.flush()
423 | if err != nil {
424 | return err
425 | }
426 | return nil
427 | }
428 |
429 | //PSubscribe subscribe some pattern channels
430 | func (r *RedisPubSub) PSubscribe(channels ...string) error {
431 | r.redis.mu.RLock()
432 | defer r.redis.mu.RUnlock()
433 | if r.redis.client == nil {
434 | return newConnectError("redisPubSub is not subscribed to a Redis instance")
435 | }
436 | err := r.redis.client.psubscribe(channels...)
437 | if err != nil {
438 | return err
439 | }
440 | err = r.redis.client.flush()
441 | if err != nil {
442 | return err
443 | }
444 | return nil
445 | }
446 |
447 | //PUnSubscribe unsubscribe some pattern channels
448 | func (r *RedisPubSub) PUnSubscribe(channels ...string) error {
449 | r.redis.mu.RLock()
450 | defer r.redis.mu.RUnlock()
451 | if r.redis.client == nil {
452 | return newConnectError("redisPubSub is not subscribed to a Redis instance")
453 | }
454 | err := r.redis.client.punsubscribe(channels...)
455 | if err != nil {
456 | return err
457 | }
458 | err = r.redis.client.flush()
459 | if err != nil {
460 | return err
461 | }
462 | return nil
463 | }
464 |
465 | func (r *RedisPubSub) proceed(redis *Redis, channels ...string) error {
466 | r.redis = redis
467 | err := r.redis.client.subscribe(channels...)
468 | if err != nil {
469 | return err
470 | }
471 | err = r.redis.client.flush()
472 | if err != nil {
473 | return err
474 | }
475 | return r.process(redis)
476 | }
477 |
478 | func (r *RedisPubSub) isSubscribed() bool {
479 | return r.subscribedChannels > 0
480 | }
481 |
482 | func (r *RedisPubSub) proceedWithPatterns(redis *Redis, patterns ...string) error {
483 | r.redis = redis
484 | err := r.redis.client.psubscribe(patterns...)
485 | if err != nil {
486 | return err
487 | }
488 | err = r.redis.client.flush()
489 | if err != nil {
490 | return err
491 | }
492 | return r.process(redis)
493 | }
494 |
495 | func (r *RedisPubSub) process(redis *Redis) error {
496 | for {
497 | reply, err := redis.client.connection.getRawObjectMultiBulkReply()
498 | if err != nil {
499 | return err
500 | }
501 | respUpper := strings.ToUpper(string(reply[0].([]byte)))
502 | switch respUpper {
503 | case keywordSubscribe.name:
504 | r.processSubscribe(reply)
505 | case keywordUnsubscribe.name:
506 | r.processUnSubscribe(reply)
507 | case keywordMessage.name:
508 | r.processMessage(reply)
509 | case keywordPMessage.name:
510 | r.processPMessage(reply)
511 | case keywordPSubscribe.name:
512 | r.processPSubscribe(reply)
513 | case cmdPUnSubscribe.name:
514 | r.processPUnSubscribe(reply)
515 | case keywordPong.name:
516 | r.processPong(reply)
517 | default:
518 | return fmt.Errorf("unknown message type: %v", reply)
519 | }
520 | if !r.isSubscribed() {
521 | break
522 | }
523 | }
524 | redis.mu.Lock()
525 | defer redis.mu.Unlock()
526 | // Reset pipeline count because subscribe() calls would have increased it but nothing decremented it.
527 | redis.client.resetPipelinedCount()
528 | // Invalidate instance since this thread is no longer listening
529 | r.redis.client = nil
530 | return nil
531 | }
532 |
533 | func (r *RedisPubSub) processSubscribe(reply []interface{}) {
534 | r.subscribedChannels = int(reply[2].(int64))
535 | bChannel := reply[1].([]byte)
536 | strChannel := ""
537 | if bChannel != nil {
538 | strChannel = string(bChannel)
539 | }
540 | r.OnSubscribe(strChannel, r.subscribedChannels)
541 | }
542 |
543 | func (r *RedisPubSub) processUnSubscribe(reply []interface{}) {
544 | r.subscribedChannels = int(reply[2].(int64))
545 | bChannel := reply[1].([]byte)
546 | strChannel := ""
547 | if bChannel != nil {
548 | strChannel = string(bChannel)
549 | }
550 | r.OnUnSubscribe(strChannel, r.subscribedChannels)
551 | }
552 |
553 | func (r *RedisPubSub) processMessage(reply []interface{}) {
554 | bChannel := reply[1].([]byte)
555 | bMsg := reply[2].([]byte)
556 | strChannel := ""
557 | if bChannel != nil {
558 | strChannel = string(bChannel)
559 | }
560 | strMsg := ""
561 | if bChannel != nil {
562 | strMsg = string(bMsg)
563 | }
564 | r.OnMessage(strChannel, strMsg)
565 | }
566 |
567 | func (r *RedisPubSub) processPMessage(reply []interface{}) {
568 | bPattern := reply[1].([]byte)
569 | bChannel := reply[2].([]byte)
570 | bMsg := reply[3].([]byte)
571 | strPattern := ""
572 | if bPattern != nil {
573 | strPattern = string(bPattern)
574 | }
575 | strChannel := ""
576 | if bChannel != nil {
577 | strChannel = string(bChannel)
578 | }
579 | strMsg := ""
580 | if bChannel != nil {
581 | strMsg = string(bMsg)
582 | }
583 | r.OnPMessage(strPattern, strChannel, strMsg)
584 | }
585 |
586 | func (r *RedisPubSub) processPSubscribe(reply []interface{}) {
587 | r.subscribedChannels = int(reply[2].(int64))
588 | bPattern := reply[1].([]byte)
589 | strPattern := ""
590 | if bPattern != nil {
591 | strPattern = string(bPattern)
592 | }
593 | r.OnPSubscribe(strPattern, r.subscribedChannels)
594 | }
595 |
596 | func (r *RedisPubSub) processPUnSubscribe(reply []interface{}) {
597 | r.subscribedChannels = int(reply[2].(int64))
598 | bPattern := reply[1].([]byte)
599 | strPattern := ""
600 | if bPattern != nil {
601 | strPattern = string(bPattern)
602 | }
603 | r.OnPUnSubscribe(strPattern, r.subscribedChannels)
604 | }
605 |
606 | func (r *RedisPubSub) processPong(reply []interface{}) {
607 | bPattern := reply[1].([]byte)
608 | strPattern := ""
609 | if bPattern != nil {
610 | strPattern = string(bPattern)
611 | }
612 | r.OnPong(strPattern)
613 | }
614 |
615 | //BitOP bit operation struct
616 | type BitOP struct {
617 | name string //name if bit operation
618 | }
619 |
620 | //getRaw get the name byte array
621 | func (g *BitOP) getRaw() []byte {
622 | return []byte(g.name)
623 | }
624 |
625 | //NewBitOP
626 | func newBitOP(name string) *BitOP {
627 | return &BitOP{name}
628 | }
629 |
630 | var (
631 | //BitOpAnd 'and' bit operation,&
632 | BitOpAnd = newBitOP("AND")
633 | //BitOpOr 'or' bit operation,|
634 | BitOpOr = newBitOP("OR")
635 | //BitOpXor 'xor' bit operation,X xor Y -> (X || Y) && !(X && Y)
636 | BitOpXor = newBitOP("XOR")
637 | //BitOpNot 'not' bit operation,^
638 | BitOpNot = newBitOP("NOT")
639 | )
640 |
641 | //SlowLog redis slow log struct
642 | type SlowLog struct {
643 | id int64
644 | timeStamp int64
645 | executionTime int64
646 | args []string
647 | }
648 |
649 | //DebugParams debug params
650 | type DebugParams struct {
651 | command []string
652 | }
653 |
654 | //NewDebugParamsSegfault create debug prams with segfault
655 | func NewDebugParamsSegfault() *DebugParams {
656 | return &DebugParams{command: []string{"SEGFAULT"}}
657 | }
658 |
659 | //NewDebugParamsObject create debug paramas with key
660 | func NewDebugParamsObject(key string) *DebugParams {
661 | return &DebugParams{command: []string{"OBJECT", key}}
662 | }
663 |
664 | //NewDebugParamsReload create debug params with reload
665 | func NewDebugParamsReload() *DebugParams {
666 | return &DebugParams{command: []string{"RELOAD"}}
667 | }
668 |
669 | //Reset reset struct
670 | type Reset struct {
671 | name string //name of reset
672 | }
673 |
674 | //getRaw get the name byte array
675 | func (g *Reset) getRaw() []byte {
676 | return []byte(g.name)
677 | }
678 |
679 | func newReset(name string) *Reset {
680 | return &Reset{name}
681 | }
682 |
683 | var (
684 | //ResetSoft soft reset
685 | ResetSoft = newReset("SOFT")
686 | //ResetHard hard reset
687 | ResetHard = newReset("HARD")
688 | )
689 |
--------------------------------------------------------------------------------
/connection.go:
--------------------------------------------------------------------------------
1 | package godis
2 |
3 | import (
4 | "bufio"
5 | "fmt"
6 | "net"
7 | "time"
8 | )
9 |
10 | type connection struct {
11 | host string
12 | port int
13 | connectionTimeout time.Duration
14 | soTimeout time.Duration
15 |
16 | socket net.Conn
17 | protocol *protocol
18 | broken bool
19 | pipelinedCommands int
20 | }
21 |
22 | func newConnection(host string, port int, connectionTimeout, soTimeout time.Duration) *connection {
23 | if host == "" {
24 | host = defaultHost
25 | }
26 | if port == 0 {
27 | port = defaultPort
28 | }
29 | if connectionTimeout == 0 {
30 | connectionTimeout = defaultTimeout
31 | }
32 | if soTimeout == 0 {
33 | soTimeout = defaultTimeout
34 | }
35 | return &connection{
36 | host: host,
37 | port: port,
38 | connectionTimeout: connectionTimeout,
39 | soTimeout: soTimeout,
40 | broken: false,
41 | }
42 | }
43 |
44 | func (c *connection) setTimeoutInfinite() error {
45 | if !c.isConnected() {
46 | err := c.connect()
47 | if err != nil {
48 | return err
49 | }
50 | }
51 | err := c.socket.SetDeadline(time.Time{})
52 | if err != nil {
53 | c.broken = true
54 | return newConnectError(err.Error())
55 | }
56 | return nil
57 | }
58 |
59 | func (c *connection) rollbackTimeout() error {
60 | if c.socket == nil {
61 | c.broken = true
62 | return newConnectError("socket is closed")
63 | }
64 | err := c.socket.SetDeadline(time.Now().Add(c.connectionTimeout))
65 | if err != nil {
66 | c.broken = true
67 | return newConnectError(err.Error())
68 | }
69 | return nil
70 | }
71 |
72 | func (c *connection) resetPipelinedCount() {
73 | c.pipelinedCommands = 0
74 | }
75 |
76 | func (c *connection) sendCommand(cmd protocolCommand, args ...[]byte) error {
77 | err := c.connect()
78 | if err != nil {
79 | return err
80 | }
81 | if err := c.protocol.sendCommand(cmd.getRaw(), args...); err != nil {
82 | return err
83 | }
84 | c.pipelinedCommands++
85 | return nil
86 | }
87 |
88 | func (c *connection) sendCommandByStr(cmd string, args ...[]byte) error {
89 | err := c.connect()
90 | if err != nil {
91 | return err
92 | }
93 | if err := c.protocol.sendCommand([]byte(cmd), args...); err != nil {
94 | return err
95 | }
96 | c.pipelinedCommands++
97 | return nil
98 | }
99 |
100 | func (c *connection) readProtocolWithCheckingBroken() (interface{}, error) {
101 | if c.broken {
102 | return nil, newConnectError("attempting to read from a broken connection")
103 | }
104 | read, err := c.protocol.read()
105 | if err == nil {
106 | return read, nil
107 | }
108 | switch err.(type) {
109 | case *ConnectError:
110 | c.broken = true
111 | }
112 | return nil, err
113 | }
114 |
115 | func (c *connection) getStatusCodeReply() (string, error) {
116 | reply, err := c.getOne()
117 | if err != nil {
118 | return "", err
119 | }
120 | if reply == nil {
121 | return "", nil
122 | }
123 | switch t := reply.(type) {
124 | case string:
125 | return t, nil
126 | case []byte:
127 | return string(t), nil
128 | default:
129 | return "", newDataError(fmt.Sprintf("data error:%v", reply))
130 | }
131 | }
132 |
133 | func (c *connection) getBulkReply() (string, error) {
134 | result, err := c.getBinaryBulkReply()
135 | if err != nil {
136 | return "", err
137 | }
138 | return string(result), nil
139 | }
140 |
141 | func (c *connection) getBinaryBulkReply() ([]byte, error) {
142 | reply, err := c.getOne()
143 | if err != nil {
144 | return nil, err
145 | }
146 | if reply == nil {
147 | return []byte{}, nil
148 | }
149 | switch reply.(type) {
150 | case []byte:
151 | return reply.([]byte), nil
152 | case []interface{}:
153 | arr := make([]byte, 0)
154 | for _, i := range reply.([]interface{}) {
155 | arr = append(arr, i.(byte))
156 | }
157 | return arr, nil
158 | }
159 | return reply.([]byte), nil
160 | }
161 |
162 | func (c *connection) getIntegerReply() (int64, error) {
163 | reply, err := c.getOne()
164 | if err != nil {
165 | return 0, err
166 | }
167 | if reply == nil {
168 | return 0, nil
169 | }
170 | switch reply.(type) {
171 | case int64:
172 | return reply.(int64), nil
173 | }
174 | return -1, nil
175 | }
176 |
177 | func (c *connection) getMultiBulkReply() ([]string, error) {
178 | reply, err := c.getBinaryMultiBulkReply()
179 | if err != nil {
180 | return nil, err
181 | }
182 | resp := make([]string, 0)
183 | for _, r := range reply {
184 | resp = append(resp, string(r))
185 | }
186 | return resp, nil
187 | }
188 |
189 | func (c *connection) getBinaryMultiBulkReply() ([][]byte, error) {
190 | reply, err := c.getOne()
191 | if err != nil {
192 | return nil, err
193 | }
194 | if reply == nil {
195 | return [][]byte{}, nil
196 | }
197 | resp := reply.([]interface{})
198 | arr := make([][]byte, 0)
199 | for _, res := range resp {
200 | arr = append(arr, res.([]byte))
201 | }
202 | return arr, nil
203 | }
204 |
205 | func (c *connection) getUnflushedObjectMultiBulkReply() ([]interface{}, error) {
206 | reply, err := c.readProtocolWithCheckingBroken()
207 | if err != nil {
208 | return nil, err
209 | }
210 | if reply == nil {
211 | return []interface{}{}, nil
212 | }
213 | return reply.([]interface{}), nil
214 | }
215 |
216 | func (c *connection) getRawObjectMultiBulkReply() ([]interface{}, error) {
217 | return c.getUnflushedObjectMultiBulkReply()
218 | }
219 |
220 | func (c *connection) getObjectMultiBulkReply() ([]interface{}, error) {
221 | if err := c.flush(); err != nil {
222 | return nil, err
223 | }
224 | c.pipelinedCommands--
225 | return c.getRawObjectMultiBulkReply()
226 | }
227 |
228 | func (c *connection) getIntegerMultiBulkReply() ([]int64, error) {
229 | reply, err := c.getOne()
230 | if err != nil {
231 | return nil, err
232 | }
233 | if reply == nil {
234 | return []int64{}, nil
235 | }
236 | switch reply.(type) {
237 | case []interface{}:
238 | arr := make([]int64, 0)
239 | for _, item := range reply.([]interface{}) {
240 | arr = append(arr, item.(int64))
241 | }
242 | return arr, nil
243 | default:
244 | return reply.([]int64), nil
245 | }
246 | }
247 |
248 | func (c *connection) getOne() (interface{}, error) {
249 | if err := c.flush(); err != nil {
250 | return "", err
251 | }
252 | c.pipelinedCommands--
253 | return c.readProtocolWithCheckingBroken()
254 | }
255 |
256 | func (c *connection) getAll(expect ...int) (interface{}, error) {
257 | num := 0
258 | if len(expect) > 0 {
259 | num = expect[0]
260 | }
261 | if err := c.flush(); err != nil {
262 | return nil, err
263 | }
264 | all := make([]interface{}, 0)
265 | for c.pipelinedCommands > num {
266 | obj, err := c.readProtocolWithCheckingBroken()
267 | if err != nil {
268 | all = append(all, err)
269 | } else {
270 | all = append(all, obj)
271 | }
272 | c.pipelinedCommands--
273 | }
274 | return all, nil
275 | }
276 |
277 | func (c *connection) flush() error {
278 | err := c.protocol.os.flush()
279 | if err != nil {
280 | c.broken = true
281 | return newConnectError(err.Error())
282 | }
283 | return nil
284 | }
285 |
286 | func (c *connection) connect() error {
287 | if c.isConnected() {
288 | return nil
289 | }
290 | conn, err := net.DialTimeout("tcp", fmt.Sprint(c.host, ":", c.port), c.connectionTimeout)
291 | if err != nil {
292 | return newConnectError(err.Error())
293 | }
294 | err = conn.SetDeadline(time.Now().Add(c.soTimeout))
295 | if err != nil {
296 | return newConnectError(err.Error())
297 | }
298 | c.socket = conn
299 | os := newRedisOutputStream(bufio.NewWriter(c.socket), c)
300 | is := newRedisInputStream(bufio.NewReader(c.socket), c)
301 | c.protocol = newProtocol(os, is)
302 | return nil
303 | }
304 |
305 | func (c *connection) isConnected() bool {
306 | if c.socket == nil {
307 | return false
308 | }
309 | return true
310 | }
311 |
312 | func (c *connection) close() error {
313 | if c.socket == nil {
314 | return nil
315 | }
316 | err := c.socket.Close()
317 | c.socket = nil
318 | return err
319 | }
320 |
--------------------------------------------------------------------------------
/convert_util.go:
--------------------------------------------------------------------------------
1 | package godis
2 |
3 | import (
4 | "fmt"
5 | "math"
6 | "strconv"
7 | )
8 |
9 | //BoolToByteArr convert bool to byte array
10 | func BoolToByteArr(a bool) []byte {
11 | if a {
12 | return bytesTrue
13 | }
14 | return bytesFalse
15 | }
16 |
17 | //IntToByteArr convert int to byte array
18 | func IntToByteArr(a int) []byte {
19 | buf := make([]byte, 0)
20 | return strconv.AppendInt(buf, int64(a), 10)
21 | }
22 |
23 | //Int64ToByteArr convert int64 to byte array
24 | func Int64ToByteArr(a int64) []byte {
25 | buf := make([]byte, 0)
26 | return strconv.AppendInt(buf, a, 10)
27 | }
28 |
29 | //Float64ToStr convert float64 to string
30 | func Float64ToStr(a float64) string {
31 | if math.IsInf(a, 1) {
32 | return "+inf"
33 | } else if math.IsInf(a, -1) {
34 | return "-inf"
35 | } else {
36 | return strconv.FormatFloat(a, 'f', -1, 64)
37 | }
38 | }
39 |
40 | //Float64ToByteArr convert float64 to byte array
41 | func Float64ToByteArr(a float64) []byte {
42 | var incrBytes []byte
43 | if math.IsInf(a, 1) {
44 | incrBytes = []byte("+inf")
45 | } else if math.IsInf(a, -1) {
46 | incrBytes = []byte("-inf")
47 | } else {
48 | incrBytes = []byte(strconv.FormatFloat(a, 'f', -1, 64))
49 | }
50 | return incrBytes
51 | }
52 |
53 | //ByteArrToFloat64 convert byte array to float64
54 | func ByteArrToFloat64(bytes []byte) float64 {
55 | f, _ := strconv.ParseFloat(string(bytes), 64)
56 | return f
57 | }
58 |
59 | //StrStrArrToByteArrArr convert string and string array to byte array
60 | func StrStrArrToByteArrArr(str string, arr []string) [][]byte {
61 | params := make([][]byte, 0)
62 | params = append(params, []byte(str))
63 | for _, v := range arr {
64 | params = append(params, []byte(v))
65 | }
66 | return params
67 | }
68 |
69 | //StrStrArrToStrArr convert string and string array to string array
70 | func StrStrArrToStrArr(str string, arr []string) []string {
71 | params := make([]string, 0)
72 | params = append(params, str)
73 | for _, v := range arr {
74 | params = append(params, v)
75 | }
76 | return params
77 | }
78 |
79 | //StrArrToByteArrArr convert string array to byte array list
80 | func StrArrToByteArrArr(arr []string) [][]byte {
81 | newArr := make([][]byte, 0)
82 | for _, a := range arr {
83 | newArr = append(newArr, []byte(a))
84 | }
85 | return newArr
86 | }
87 |
88 | //StrToFloat64Reply convert string reply to float64 reply
89 | func StrToFloat64Reply(reply string, err error) (float64, error) {
90 | if err != nil {
91 | return 0, err
92 | }
93 | f, e := strconv.ParseFloat(reply, 64)
94 | if e != nil {
95 | return 0, e
96 | }
97 | return f, nil
98 | }
99 |
100 | //StrArrToMapReply convert string array reply to map reply
101 | func StrArrToMapReply(reply []string, err error) (map[string]string, error) {
102 | if err != nil {
103 | return nil, err
104 | }
105 | newMap := make(map[string]string, len(reply)/2)
106 | for i := 0; i < len(reply); i += 2 {
107 | newMap[reply[i]] = reply[i+1]
108 | }
109 | return newMap, nil
110 | }
111 |
112 | //Int64ToBoolReply convert int64 reply to bool reply
113 | func Int64ToBoolReply(reply int64, err error) (bool, error) {
114 | if err != nil {
115 | return false, err
116 | }
117 | return reply == 1, nil
118 | }
119 |
120 | //ByteArrToStrReply convert byte array reply to string reply
121 | func ByteArrToStrReply(reply []byte, err error) (string, error) {
122 | if err != nil {
123 | return "", err
124 | }
125 | return string(reply), nil
126 | }
127 |
128 | //StrArrToTupleReply convert string array reply to tuple array reply
129 | func StrArrToTupleReply(reply []string, err error) ([]Tuple, error) {
130 | if len(reply) == 0 {
131 | return []Tuple{}, nil
132 | }
133 | newArr := make([]Tuple, 0)
134 | for i := 0; i < len(reply); i += 2 {
135 | f, err := strconv.ParseFloat(reply[i+1], 64)
136 | if err != nil {
137 | return nil, err
138 | }
139 | newArr = append(newArr, Tuple{element: reply[i], score: f})
140 | }
141 | return newArr, err
142 | }
143 |
144 | //ObjArrToScanResultReply convert object array reply to scanresult reply
145 | func ObjArrToScanResultReply(reply []interface{}, err error) (*ScanResult, error) {
146 | if err != nil || len(reply) == 0 {
147 | return nil, err
148 | }
149 | nexCursor := string(reply[0].([]byte))
150 | result := make([]string, 0)
151 | for _, r := range reply[1].([]interface{}) {
152 | result = append(result, string(r.([]byte)))
153 | }
154 | return &ScanResult{Cursor: nexCursor, Results: result}, err
155 | }
156 |
157 | //ObjArrToGeoCoordinateReply convert object array reply to GeoCoordinate reply
158 | func ObjArrToGeoCoordinateReply(reply []interface{}, err error) ([]*GeoCoordinate, error) {
159 | if err != nil || len(reply) == 0 {
160 | return nil, err
161 | }
162 | arr := make([]*GeoCoordinate, 0)
163 | for _, r := range reply {
164 | if r == nil {
165 | arr = append(arr, nil)
166 | } else {
167 | rArr := r.([]interface{})
168 | lng, err := strconv.ParseFloat(string(rArr[0].([]byte)), 64)
169 | if err != nil {
170 | return nil, err
171 | }
172 | lat, err := strconv.ParseFloat(string(rArr[1].([]byte)), 64)
173 | if err != nil {
174 | return nil, err
175 | }
176 | arr = append(arr, &GeoCoordinate{
177 | longitude: lng,
178 | latitude: lat,
179 | })
180 | }
181 | }
182 | return arr, err
183 | }
184 |
185 | //ObjArrToGeoRadiusResponseReply convert object array reply to GeoRadiusResponse reply
186 | func ObjArrToGeoRadiusResponseReply(reply []interface{}, err error) ([]GeoRadiusResponse, error) {
187 | if err != nil || len(reply) == 0 {
188 | return nil, err
189 | }
190 | arr := make([]GeoRadiusResponse, 0)
191 | switch reply[0].(type) {
192 | case []interface{}:
193 | var resp GeoRadiusResponse
194 | for _, r := range reply {
195 | informations := r.([]interface{})
196 | resp = *newGeoRadiusResponse(string(informations[0].([]byte)))
197 | size := len(informations)
198 | for idx := 1; idx < size; idx++ {
199 | info := informations[idx]
200 | switch info.(type) {
201 | case []interface{}:
202 | coord := info.([]interface{})
203 | resp.coordinate = GeoCoordinate{
204 | longitude: ByteArrToFloat64(coord[0].([]byte)),
205 | latitude: ByteArrToFloat64(coord[1].([]byte)),
206 | }
207 | default:
208 | resp.distance = ByteArrToFloat64(info.([]byte))
209 | }
210 | }
211 | arr = append(arr, resp)
212 | }
213 | default:
214 | for _, r := range reply {
215 | arr = append(arr, *newGeoRadiusResponse(string(r.([]byte))))
216 | }
217 | }
218 | return arr, err
219 | }
220 |
221 | //ObjArrToMapArrayReply convert object array reply to map array reply
222 | func ObjArrToMapArrayReply(reply []interface{}, err error) ([]map[string]string, error) {
223 | if err != nil || len(reply) == 0 {
224 | return nil, err
225 | }
226 | masters := make([]map[string]string, 0)
227 | for _, re := range reply {
228 | m := make(map[string]string)
229 | arr := re.([][]byte)
230 | for i := 0; i < len(arr); i += 2 {
231 | m[string(arr[i])] = string(arr[i+1])
232 | }
233 | masters = append(masters, m)
234 | }
235 | return masters, nil
236 | }
237 |
238 | //ObjToEvalResult resolve response data when use script command
239 | func ObjToEvalResult(reply interface{}, err error) (interface{}, error) {
240 | if err != nil {
241 | return nil, err
242 | }
243 | switch reply.(type) {
244 | case []byte:
245 | return string(reply.([]byte)), nil
246 | case []interface{}:
247 | list := reply.([]interface{})
248 | result := make([]interface{}, 0)
249 | for _, l := range list {
250 | evalResult, err := ObjToEvalResult(l, nil)
251 | if err != nil {
252 | return nil, err
253 | }
254 | result = append(result, evalResult)
255 | }
256 | return result, nil
257 | }
258 | return reply, err
259 | }
260 |
261 | //
262 |
263 | //ToStrReply convert object reply to string reply
264 | func ToStrReply(reply interface{}, err error) (string, error) {
265 | if err != nil {
266 | return "", err
267 | }
268 | switch reply.(type) {
269 | case []byte:
270 | return string(reply.([]byte)), nil
271 | }
272 | return reply.(string), nil
273 | }
274 |
275 | //ToInt64Reply convert object reply to int64 reply
276 | func ToInt64Reply(reply interface{}, err error) (int64, error) {
277 | if err != nil {
278 | return 0, err
279 | }
280 | return reply.(int64), nil
281 | }
282 |
283 | //ToInt64ArrReply convert object reply to int64 array reply
284 | func ToInt64ArrReply(reply interface{}, err error) ([]int64, error) {
285 | if err != nil {
286 | return nil, err
287 | }
288 | return reply.([]int64), nil
289 | }
290 |
291 | //ToBoolReply convert object reply to bool reply
292 | func ToBoolReply(reply interface{}, err error) (bool, error) {
293 | if err != nil {
294 | return false, err
295 | }
296 | return reply.(bool), nil
297 | }
298 |
299 | //ToFloat64Reply convert object reply to float64 reply
300 | func ToFloat64Reply(reply interface{}, err error) (float64, error) {
301 | if err != nil {
302 | return 0, err
303 | }
304 | return reply.(float64), nil
305 | }
306 |
307 | //ToBoolArrReply convert object reply to bool array reply
308 | func ToBoolArrReply(reply interface{}, err error) ([]bool, error) {
309 | if err != nil {
310 | return nil, err
311 | }
312 | return reply.([]bool), nil
313 | }
314 |
315 | //ToStrArrReply convert object reply to string array reply
316 | func ToStrArrReply(reply interface{}, err error) ([]string, error) {
317 | if err != nil {
318 | return nil, err
319 | }
320 | return reply.([]string), nil
321 | }
322 |
323 | //ToScanResultReply convert object reply to scanresult reply
324 | func ToScanResultReply(reply interface{}, err error) (*ScanResult, error) {
325 | if err != nil {
326 | return nil, err
327 | }
328 | return reply.(*ScanResult), nil
329 | }
330 |
331 | //ToMapReply convert object reply to map reply
332 | func ToMapReply(reply interface{}, err error) (map[string]string, error) {
333 | if err != nil {
334 | return nil, err
335 | }
336 | return reply.(map[string]string), nil
337 | }
338 |
339 | //ToTupleArrReply convert object reply to tuple array reply
340 | func ToTupleArrReply(reply interface{}, err error) ([]Tuple, error) {
341 | if err != nil {
342 | return nil, err
343 | }
344 | return reply.([]Tuple), nil
345 | }
346 |
347 | //ToGeoCoordArrReply convert object reply to geocoordinate array reply
348 | func ToGeoCoordArrReply(reply interface{}, err error) ([]*GeoCoordinate, error) {
349 | if err != nil {
350 | return nil, err
351 | }
352 | return reply.([]*GeoCoordinate), nil
353 | }
354 |
355 | //ToGeoRespArrReply convert object reply to GeoRadiusResponse array reply
356 | func ToGeoRespArrReply(reply interface{}, err error) ([]GeoRadiusResponse, error) {
357 | if err != nil {
358 | return nil, err
359 | }
360 | return reply.([]GeoRadiusResponse), nil
361 | }
362 |
363 | //
364 |
365 | //Builder convert pipeline|transaction response data
366 | type Builder interface {
367 | build(data interface{}) (interface{}, error)
368 | }
369 |
370 | var (
371 | //StrBuilder convert interface to string
372 | StrBuilder = newStrBuilder()
373 | //Int64Builder convert interface to int64
374 | Int64Builder = newInt64Builder()
375 | //StrArrBuilder convert interface to string array
376 | StrArrBuilder = newStringArrayBuilder()
377 | )
378 |
379 | type strBuilder struct {
380 | }
381 |
382 | func newStrBuilder() *strBuilder {
383 | return &strBuilder{}
384 | }
385 |
386 | func (b *strBuilder) build(data interface{}) (interface{}, error) {
387 | if data == nil {
388 | return "", nil
389 | }
390 | switch data.(type) {
391 | case []byte:
392 | return string(data.([]byte)), nil
393 | case error:
394 | return "", data.(error)
395 | }
396 | return "", fmt.Errorf("unexpected type:%T", data)
397 | }
398 |
399 | type int64Builder struct {
400 | }
401 |
402 | func newInt64Builder() *int64Builder {
403 | return &int64Builder{}
404 | }
405 |
406 | func (b *int64Builder) build(data interface{}) (interface{}, error) {
407 | if data == nil {
408 | return 0, nil
409 | }
410 | switch data.(type) {
411 | case int64:
412 | return data.(int64), nil
413 | }
414 | return 0, fmt.Errorf("unexpected type:%T", data)
415 | }
416 |
417 | type strArrBuilder struct {
418 | }
419 |
420 | func newStringArrayBuilder() *strArrBuilder {
421 | return &strArrBuilder{}
422 | }
423 |
424 | func (b *strArrBuilder) build(data interface{}) (interface{}, error) {
425 | if data == nil {
426 | return []string{}, nil
427 | }
428 | switch data.(type) {
429 | case []interface{}:
430 | arr := make([]string, 0)
431 | for _, b := range data.([]interface{}) {
432 | if b == nil {
433 | arr = append(arr, "")
434 | } else {
435 | arr = append(arr, string(b.([]byte)))
436 | }
437 | }
438 | return arr, nil
439 | }
440 | return nil, fmt.Errorf("unexpected type:%T", data)
441 | }
442 |
--------------------------------------------------------------------------------
/convert_util_test.go:
--------------------------------------------------------------------------------
1 | package godis
2 |
3 | import (
4 | "fmt"
5 | "github.com/stretchr/testify/assert"
6 | "math"
7 | "testing"
8 | )
9 |
10 | func TestBoolToByteArray(t *testing.T) {
11 | arr := BoolToByteArr(true)
12 | assert.Equal(t, []byte{0x31}, arr)
13 |
14 | arr = BoolToByteArr(false)
15 | assert.Equal(t, []byte{0x30}, arr)
16 | }
17 |
18 | func TestByteArrToStringReply(t *testing.T) {
19 | arr := []byte("good")
20 | s, e := ByteArrToStrReply(arr, nil)
21 | assert.Nil(t, e)
22 | assert.Equal(t, "good", s)
23 |
24 | s, e = ByteArrToStrReply(arr, newClusterMaxAttemptsError("exceed max attempts"))
25 | assert.NotNil(t, e, e.Error())
26 | assert.Equal(t, "", s)
27 | }
28 |
29 | func TestByteArrayToFloat64(t *testing.T) {
30 | arr := []byte("1.1")
31 | f := ByteArrToFloat64(arr)
32 | assert.Equal(t, 1.1, f)
33 | }
34 |
35 | func TestFloat64ToByteArray(t *testing.T) {
36 | arr := []byte("1.1")
37 | arr1 := Float64ToByteArr(1.1)
38 | assert.Equal(t, arr1, arr)
39 |
40 | arr1 = Float64ToByteArr(math.Inf(1))
41 | assert.Equal(t, []byte("+inf"), arr1)
42 |
43 | arr1 = Float64ToByteArr(math.Inf(-1))
44 | assert.Equal(t, []byte("-inf"), arr1)
45 | }
46 |
47 | func TestInt64ToBoolReply(t *testing.T) {
48 | b, e := Int64ToBoolReply(1, nil)
49 | assert.Nil(t, e)
50 | assert.Equal(t, true, b)
51 |
52 | b, e = Int64ToBoolReply(0, nil)
53 | assert.Nil(t, e)
54 | assert.Equal(t, false, b)
55 |
56 | b, e = Int64ToBoolReply(10, nil)
57 | assert.Nil(t, e)
58 | assert.Equal(t, false, b)
59 |
60 | b, e = Int64ToBoolReply(0, newRedirectError("redirect too many times"))
61 | assert.NotNil(t, e, e.Error())
62 | assert.Equal(t, false, b)
63 | }
64 |
65 | func TestInt64ToByteArray(t *testing.T) {
66 | }
67 |
68 | func TestIntToByteArray(t *testing.T) {
69 | }
70 |
71 | func TestObjectArrToGeoCoordinateReply(t *testing.T) {
72 | arr, e := ObjArrToGeoCoordinateReply(nil, newMovedDataError("move error", "localhost", 7000, 1000))
73 | assert.NotNil(t, e, e.Error())
74 | assert.Nil(t, arr)
75 |
76 | arr0 := make([]interface{}, 0)
77 | for i := 0; i < 3; i++ {
78 | arr0 = append(arr0, nil)
79 | }
80 | arr, e = ObjArrToGeoCoordinateReply(arr0, nil)
81 | assert.Nil(t, e)
82 | assert.Len(t, arr, 3)
83 |
84 | arr0 = make([]interface{}, 0)
85 | item := make([]interface{}, 0)
86 | item = append(item, []byte("1a"))
87 | item = append(item, []byte("2"))
88 | arr0 = append(arr0, item)
89 | arr, e = ObjArrToGeoCoordinateReply(arr0, nil)
90 | assert.NotNil(t, e)
91 | assert.Nil(t, arr)
92 |
93 | arr0 = make([]interface{}, 0)
94 | item = make([]interface{}, 0)
95 | item = append(item, []byte("1"))
96 | item = append(item, []byte("2b"))
97 | arr0 = append(arr0, item)
98 | arr, e = ObjArrToGeoCoordinateReply(arr0, nil)
99 | assert.NotNil(t, e)
100 | assert.Nil(t, arr)
101 | }
102 |
103 | func TestObjectArrToGeoRadiusResponseReply(t *testing.T) {
104 | arr, e := ObjArrToGeoRadiusResponseReply(nil, newAskDataError("move error", "localhost", 7000, 1000))
105 | assert.NotNil(t, e, e.Error())
106 | assert.Nil(t, arr)
107 |
108 | arr0 := make([]interface{}, 0)
109 | arr0 = append(arr0, []byte("a"))
110 | arr0 = append(arr0, []byte("b"))
111 | arr, e = ObjArrToGeoRadiusResponseReply(arr0, nil)
112 | assert.Nil(t, e)
113 | assert.Len(t, arr, 2)
114 | }
115 |
116 | func TestObjectArrToMapArrayReply(t *testing.T) {
117 | objs := make([]interface{}, 0)
118 | for i := 0; i < 4; i++ {
119 | objs = append(objs, [][]byte{[]byte(fmt.Sprintf("good%d", i)), []byte(fmt.Sprintf("good%d", i+1))})
120 | }
121 | arr, e := ObjArrToMapArrayReply(objs, nil)
122 | assert.Nil(t, e)
123 | assert.Len(t, arr, 4)
124 | }
125 |
126 | func TestObjectArrToScanResultReply(t *testing.T) {
127 | result, e := ObjArrToScanResultReply(nil, newNoReachableClusterNodeError("no reachable server"))
128 | assert.NotNil(t, e, e.Error())
129 | assert.Nil(t, result)
130 | }
131 |
132 | func TestObjectToEvalResult(t *testing.T) {
133 | arr, e := ObjToEvalResult(nil, newClusterError("error"))
134 | assert.NotNil(t, e, e.Error())
135 | assert.Nil(t, arr)
136 |
137 | arr0 := make([]interface{}, 0)
138 | arr0 = append(arr0, []byte("a"))
139 | arr0 = append(arr0, []byte("b"))
140 | arr, e = ObjToEvalResult(arr0, nil)
141 | assert.Nil(t, e)
142 | assert.Len(t, arr, 2)
143 |
144 | arr, e = ObjToEvalResult("a", nil)
145 | assert.Nil(t, e)
146 | assert.Equal(t, "a", arr)
147 | }
148 |
149 | func TestStringArrToTupleReply(t *testing.T) {
150 | arr := []string{"a", "1", "b", "2"}
151 | tuples, e := StrArrToTupleReply(arr, nil)
152 | assert.Nil(t, e)
153 | assert.Len(t, tuples, 2)
154 |
155 | arr = []string{}
156 | tuples, e = StrArrToTupleReply(arr, nil)
157 | assert.Nil(t, e)
158 | assert.Len(t, tuples, 0)
159 |
160 | arr = []string{"a", "1a", "b", "2"}
161 | tuples, e = StrArrToTupleReply(arr, nil)
162 | assert.NotNil(t, e) //convert failed
163 | assert.Nil(t, tuples)
164 | }
165 |
166 | func TestStringArrayToByteArray(t *testing.T) {
167 | }
168 |
169 | func TestStringArrayToMapReply(t *testing.T) {
170 | m, e := StrArrToMapReply(nil, newRedisError("internal error"))
171 | assert.NotNil(t, e, e.Error())
172 | assert.Nil(t, m)
173 | }
174 |
175 | func TestStringStringArrayToByteArray(t *testing.T) {
176 | }
177 |
178 | func TestStringStringArrayToStringArray(t *testing.T) {
179 | }
180 |
181 | func TestStringToFloat64Reply(t *testing.T) {
182 | f, e := StrToFloat64Reply("1.1", nil)
183 | assert.Nil(t, e)
184 | assert.Equal(t, 1.1, f)
185 |
186 | f, e = StrToFloat64Reply("1.1a", nil)
187 | assert.NotNil(t, e) //not a float
188 | assert.Equal(t, float64(0), f)
189 |
190 | f, e = StrToFloat64Reply("1.1", newDataError("error data format"))
191 | assert.NotNil(t, e, e.Error())
192 | assert.Equal(t, float64(0), f)
193 | }
194 |
195 | func TestToBoolArrayReply(t *testing.T) {
196 | b, e := ToBoolArrReply(nil, newBusyError("is busy now"))
197 | assert.NotNil(t, e, e.Error())
198 | assert.Nil(t, b)
199 | }
200 |
201 | func TestToBoolReply(t *testing.T) {
202 | b, e := ToBoolReply(nil, newBusyError("is busy now"))
203 | assert.NotNil(t, e, e.Error())
204 | assert.Equal(t, false, b)
205 | }
206 |
207 | func TestToFloat64Reply(t *testing.T) {
208 | b, e := ToFloat64Reply(nil, newClusterMaxAttemptsError("internal error"))
209 | assert.NotNil(t, e, e.Error())
210 | assert.Equal(t, float64(0), b)
211 | }
212 |
213 | func TestToGeoArrayReply(t *testing.T) {
214 | b, e := ToGeoCoordArrReply(nil, newClusterOperationError("is busy now"))
215 | assert.NotNil(t, e, e.Error())
216 | assert.Nil(t, b)
217 | }
218 |
219 | func TestToGeoRespArrayReply(t *testing.T) {
220 | b, e := ToGeoRespArrReply(nil, newNoScriptError("is busy now"))
221 | assert.NotNil(t, e, e.Error())
222 | assert.Nil(t, b)
223 | }
224 |
225 | func TestToInt64ArrayReply(t *testing.T) {
226 | var obj interface{}
227 | obj = []int64{1, 2, 3}
228 | arr, e := ToInt64ArrReply(obj, nil)
229 | assert.Nil(t, e)
230 | assert.Equal(t, []int64{1, 2, 3}, arr)
231 | }
232 |
233 | func TestToInt64Reply(t *testing.T) {
234 | _, e := ToGeoCoordArrReply(nil, newBusyError("is busy now"))
235 | assert.NotNil(t, e, e.Error())
236 | }
237 |
238 | func TestToMapReply(t *testing.T) {
239 | _, e := ToMapReply(nil, newBusyError("is busy now"))
240 | assert.NotNil(t, e, e.Error())
241 | }
242 |
243 | func TestToScanResultReply(t *testing.T) {
244 | _, e := ToScanResultReply(nil, newBusyError("is busy now"))
245 | assert.NotNil(t, e, e.Error())
246 | }
247 |
248 | func TestToStringArrayReply(t *testing.T) {
249 | _, e := ToStrArrReply(nil, newBusyError("is busy now"))
250 | assert.NotNil(t, e, e.Error())
251 | }
252 |
253 | func TestToStringReply(t *testing.T) {
254 | _, e := ToStrReply(nil, newBusyError("is busy now"))
255 | assert.NotNil(t, e, e.Error())
256 | }
257 |
258 | func TestToTupleArrayReply(t *testing.T) {
259 | _, e := ToTupleArrReply(nil, newBusyError("is busy now"))
260 | assert.NotNil(t, e, e.Error())
261 | }
262 |
263 | func Test_int64Builder_build(t *testing.T) {
264 | b := newInt64Builder()
265 | r, e := b.build(nil)
266 | assert.Nil(t, e)
267 | assert.Equal(t, 0, r)
268 |
269 | r, e = b.build("a")
270 | assert.NotNil(t, e)
271 | assert.Equal(t, 0, r)
272 | }
273 |
274 | func Test_stringArrayBuilder_build(t *testing.T) {
275 | b := newStringArrayBuilder()
276 | r, e := b.build(nil)
277 | assert.Nil(t, e)
278 | assert.Empty(t, r)
279 |
280 | r, e = b.build(1)
281 | assert.NotNil(t, e)
282 | assert.Equal(t, nil, r)
283 | }
284 |
285 | func Test_stringBuilder_build(t *testing.T) {
286 | b := newStrBuilder()
287 | r, e := b.build(nil)
288 | assert.Nil(t, e)
289 | assert.Equal(t, "", r)
290 |
291 | r, e = b.build(1)
292 | assert.NotNil(t, e)
293 | assert.Equal(t, "", r)
294 | }
295 |
--------------------------------------------------------------------------------
/crc16.go:
--------------------------------------------------------------------------------
1 | package godis
2 |
3 | var lookupTable = []uint16{0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5,
4 | 0x60C6, 0x70E7, 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, 0x1231,
5 | 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B, 0xA35A,
6 | 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4,
7 | 0x5485, 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, 0x3653, 0x2672,
8 | 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4, 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF,
9 | 0xE7FE, 0xD79D, 0xC7BC, 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
10 | 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, 0x5AF5, 0x4AD4, 0x7AB7,
11 | 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58,
12 | 0xBB3B, 0xAB1A, 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE,
13 | 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4,
14 | 0x3E13, 0x2E32, 0x1E51, 0x0E70, 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59,
15 | 0x8F78, 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, 0x1080, 0x00A1,
16 | 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067, 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D,
17 | 0xD31C, 0xE37F, 0xF35E, 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256,
18 | 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, 0x34E2, 0x24C3, 0x14A0,
19 | 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E,
20 | 0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, 0xD94C,
21 | 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827,
22 | 0x18C0, 0x08E1, 0x3882, 0x28A3, 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB,
23 | 0xBB9A, 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, 0xFD2E, 0xED0F,
24 | 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2,
25 | 0x2C83, 0x1CE0, 0x0CC1, 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8,
26 | 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0}
27 |
28 | // CRC16 Implementation according to CCITT standard Polynomial : 1021 (x^16 + x^12 + x^5 + 1) See Appendix A. CRC16 reference implementation in ANSIC
30 | type crc16 struct {
31 | tagUtil *redisClusterHashTagUtil
32 | }
33 |
34 | //new construct
35 | func newCRC16() *crc16 {
36 | return &crc16{tagUtil: newRedisClusterHashTagUtil()}
37 | }
38 |
39 | func (c *crc16) getStringSlot(key string) uint16 {
40 | key = c.tagUtil.getHashTag(key)
41 | // optimization with modulo operator with power of 2
42 | // equivalent to getCRC16(key) % 16384
43 | return c.getStringCRC16(key) & (16384 - 1)
44 | }
45 |
46 | func (c *crc16) getByteSlot(key []byte) uint16 {
47 | s := -1
48 | e := -1
49 | sFound := false
50 | for i := 0; i < len(key); i++ {
51 | if key[i] == '{' && !sFound {
52 | s = i
53 | sFound = true
54 | }
55 | if key[i] == '}' && sFound {
56 | e = i
57 | break
58 | }
59 | }
60 | if s > -1 && e > -1 && e != s+1 {
61 | return c.getCRC16(key, s+1, e) & (16384 - 1)
62 | }
63 | return c.getBytesCRC16(key) & (16384 - 1)
64 | }
65 |
66 | func (c *crc16) getCRC16(bytes []byte, s, e int) uint16 {
67 | var crc uint16 = 0x0000
68 | for i := s; i < e; i++ {
69 | crc = (crc << uint16(8)) ^ lookupTable[((crc>>uint16(8))^uint16(bytes[i]))&0x00FF]
70 | }
71 | return crc
72 | }
73 |
74 | func (c *crc16) getBytesCRC16(bytes []byte) uint16 {
75 | return c.getCRC16(bytes, 0, len(bytes))
76 | }
77 |
78 | func (c *crc16) getStringCRC16(key string) uint16 {
79 | bytesKey := []byte(key)
80 | return c.getCRC16(bytesKey, 0, len(bytesKey))
81 | }
82 |
--------------------------------------------------------------------------------
/crc16_test.go:
--------------------------------------------------------------------------------
1 | package godis
2 |
3 | import (
4 | "reflect"
5 | "testing"
6 | )
7 |
8 | func Test_crc16_getByteSlot(t *testing.T) {
9 | type args struct {
10 | key []byte
11 | }
12 | tests := []struct {
13 | name string
14 | args args
15 | want uint16
16 | }{
17 | {
18 | name: "crc16",
19 | args: args{key: []byte("godis")},
20 | want: 3033,
21 | },
22 | {
23 | name: "crc16",
24 | args: args{key: []byte("test{godis}")},
25 | want: 3033,
26 | },
27 | {
28 | name: "crc16",
29 | args: args{key: []byte("test2{godis}")},
30 | want: 3033,
31 | },
32 | }
33 | for _, tt := range tests {
34 | t.Run(tt.name, func(t *testing.T) {
35 | c := &crc16{
36 | tagUtil: newRedisClusterHashTagUtil(),
37 | }
38 | if got := c.getByteSlot(tt.args.key); got != tt.want {
39 | t.Errorf("getByteSlot() = %v, want %v", got, tt.want)
40 | }
41 | })
42 | }
43 | }
44 |
45 | func Test_crc16_getBytesCRC16(t *testing.T) {
46 | type args struct {
47 | bytes []byte
48 | }
49 | tests := []struct {
50 | name string
51 | args args
52 | want uint16
53 | }{
54 | {
55 | name: "crc16",
56 | args: args{bytes: []byte("godis")},
57 | want: 19417,
58 | },
59 | }
60 | for _, tt := range tests {
61 | t.Run(tt.name, func(t *testing.T) {
62 | c := &crc16{
63 | tagUtil: newRedisClusterHashTagUtil(),
64 | }
65 | if got := c.getBytesCRC16(tt.args.bytes); got != tt.want {
66 | t.Errorf("getBytesCRC16() = %v, want %v", got, tt.want)
67 | }
68 | })
69 | }
70 | }
71 |
72 | func Test_crc16_getCRC16(t *testing.T) {
73 | type args struct {
74 | bytes []byte
75 | s int
76 | e int
77 | }
78 | tests := []struct {
79 | name string
80 | args args
81 | want uint16
82 | }{
83 | {
84 | name: "crc16",
85 | args: args{bytes: []byte("godis"), s: 0, e: 5},
86 | want: 19417,
87 | },
88 | }
89 | for _, tt := range tests {
90 | t.Run(tt.name, func(t *testing.T) {
91 | c := &crc16{
92 | tagUtil: newRedisClusterHashTagUtil(),
93 | }
94 | if got := c.getCRC16(tt.args.bytes, tt.args.s, tt.args.e); got != tt.want {
95 | t.Errorf("getCRC16() = %v, want %v", got, tt.want)
96 | }
97 | })
98 | }
99 | }
100 |
101 | func Test_crc16_getStringCRC16(t *testing.T) {
102 | type args struct {
103 | key string
104 | }
105 | tests := []struct {
106 | name string
107 | args args
108 | want uint16
109 | }{
110 | {
111 | name: "crc16",
112 | args: args{key: "godis"},
113 | want: 19417,
114 | },
115 | }
116 | for _, tt := range tests {
117 | t.Run(tt.name, func(t *testing.T) {
118 | c := &crc16{
119 | tagUtil: newRedisClusterHashTagUtil(),
120 | }
121 | if got := c.getStringCRC16(tt.args.key); got != tt.want {
122 | t.Errorf("getStringCRC16() = %v, want %v", got, tt.want)
123 | }
124 | })
125 | }
126 | }
127 |
128 | func Test_crc16_getStringSlot(t *testing.T) {
129 | type args struct {
130 | key string
131 | }
132 | tests := []struct {
133 | name string
134 | args args
135 | want uint16
136 | }{
137 | {
138 | name: "crc16",
139 | args: args{key: "godis"},
140 | want: 3033,
141 | },
142 | }
143 | for _, tt := range tests {
144 | t.Run(tt.name, func(t *testing.T) {
145 | c := &crc16{
146 | tagUtil: newRedisClusterHashTagUtil(),
147 | }
148 | if got := c.getStringSlot(tt.args.key); got != tt.want {
149 | t.Errorf("getStringSlot() = %v, want %v", got, tt.want)
150 | }
151 | })
152 | }
153 | }
154 |
155 | func Test_newCRC16(t *testing.T) {
156 | tests := []struct {
157 | name string
158 | want *crc16
159 | }{
160 | {
161 | name: "crc16",
162 | want: newCRC16(),
163 | },
164 | }
165 | for _, tt := range tests {
166 | t.Run(tt.name, func(t *testing.T) {
167 | if got := newCRC16(); !reflect.DeepEqual(got, tt.want) {
168 | t.Errorf("newCRC16() = %v, want %v", got, tt.want)
169 | }
170 | })
171 | }
172 | }
173 |
--------------------------------------------------------------------------------
/errors.go:
--------------------------------------------------------------------------------
1 | package godis
2 |
3 | //RedisError basic redis error
4 | type RedisError struct {
5 | Message string
6 | }
7 |
8 | func newRedisError(message string) *RedisError {
9 | return &RedisError{Message: message}
10 | }
11 |
12 | func (e *RedisError) Error() string {
13 | return e.Message
14 | }
15 |
16 | //RedirectError cluster operation redirect error
17 | type RedirectError struct {
18 | Message string
19 | }
20 |
21 | func newRedirectError(message string) *RedirectError {
22 | return &RedirectError{Message: message}
23 | }
24 |
25 | func (e *RedirectError) Error() string {
26 | return e.Message
27 | }
28 |
29 | //ClusterMaxAttemptsError cluster operation exceed max attempts errror
30 | type ClusterMaxAttemptsError struct {
31 | Message string
32 | }
33 |
34 | func newClusterMaxAttemptsError(message string) *ClusterMaxAttemptsError {
35 | return &ClusterMaxAttemptsError{Message: message}
36 | }
37 |
38 | func (e *ClusterMaxAttemptsError) Error() string {
39 | return e.Message
40 | }
41 |
42 | //NoReachableClusterNodeError have no reachable cluster node error
43 | type NoReachableClusterNodeError struct {
44 | Message string
45 | }
46 |
47 | func newNoReachableClusterNodeError(message string) *NoReachableClusterNodeError {
48 | return &NoReachableClusterNodeError{Message: message}
49 | }
50 |
51 | func (e *NoReachableClusterNodeError) Error() string {
52 | return e.Message
53 | }
54 |
55 | //MovedDataError cluster move data error
56 | type MovedDataError struct {
57 | Message string
58 | Host string
59 | Port int
60 | Slot int
61 | }
62 |
63 | func newMovedDataError(message string, host string, port int, slot int) *MovedDataError {
64 | return &MovedDataError{Message: message, Host: host, Port: port, Slot: slot}
65 | }
66 |
67 | func (e *MovedDataError) Error() string {
68 | return e.Message
69 | }
70 |
71 | //AskDataError ask data error
72 | type AskDataError struct {
73 | Message string
74 | Host string
75 | Port int
76 | Slot int
77 | }
78 |
79 | func newAskDataError(message string, host string, port int, slot int) *AskDataError {
80 | return &AskDataError{Message: message, Host: host, Port: port, Slot: slot}
81 | }
82 |
83 | func (e *AskDataError) Error() string {
84 | return e.Message
85 | }
86 |
87 | //ClusterError cluster basic error
88 | type ClusterError struct {
89 | Message string
90 | }
91 |
92 | func newClusterError(message string) *ClusterError {
93 | return &ClusterError{Message: message}
94 | }
95 |
96 | func (e *ClusterError) Error() string {
97 | return e.Message
98 | }
99 |
100 | //BusyError operation is busy error
101 | type BusyError struct {
102 | Message string
103 | }
104 |
105 | func newBusyError(message string) *BusyError {
106 | return &BusyError{Message: message}
107 | }
108 |
109 | func (e *BusyError) Error() string {
110 | return e.Message
111 | }
112 |
113 | //NoScriptError has no script error
114 | type NoScriptError struct {
115 | Message string
116 | }
117 |
118 | func newNoScriptError(message string) *NoScriptError {
119 | return &NoScriptError{Message: message}
120 | }
121 |
122 | func (e *NoScriptError) Error() string {
123 | return e.Message
124 | }
125 |
126 | //DataError data error
127 | type DataError struct {
128 | Message string
129 | }
130 |
131 | func newDataError(message string) *DataError {
132 | return &DataError{Message: message}
133 | }
134 |
135 | func (e *DataError) Error() string {
136 | return e.Message
137 | }
138 |
139 | //ConnectError redis connection error,such as io timeout
140 | type ConnectError struct {
141 | Message string
142 | }
143 |
144 | func newConnectError(message string) *ConnectError {
145 | return &ConnectError{Message: message}
146 | }
147 |
148 | func (e *ConnectError) Error() string {
149 | return e.Message
150 | }
151 |
152 | //ClusterOperationError cluster operation error
153 | type ClusterOperationError struct {
154 | Message string
155 | }
156 |
157 | func newClusterOperationError(message string) *ClusterOperationError {
158 | return &ClusterOperationError{Message: message}
159 | }
160 |
161 | func (e *ClusterOperationError) Error() string {
162 | return e.Message
163 | }
164 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/piaohao/godis
2 |
3 | require (
4 | github.com/fortytw2/leaktest v1.3.0 // indirect
5 | github.com/jolestar/go-commons-pool v2.0.0+incompatible
6 | github.com/stretchr/testify v1.3.0
7 | )
8 |
--------------------------------------------------------------------------------
/lock.go:
--------------------------------------------------------------------------------
1 | package godis
2 |
3 | import (
4 | "errors"
5 | "strconv"
6 | "time"
7 | )
8 |
9 | //ErrLockTimeOut when get lock exceed the timeout,then return error
10 | var ErrLockTimeOut = errors.New("get lock timeout")
11 |
12 | //Lock different keys with different lock
13 | type Lock struct {
14 | name string
15 | }
16 |
17 | //Locker the lock client
18 | type Locker struct {
19 | timeout time.Duration
20 | ch chan bool
21 | pool *Pool
22 | }
23 |
24 | //NewLocker create new locker
25 | func NewLocker(option *Option, lockOption *LockOption) *Locker {
26 | if lockOption == nil {
27 | lockOption = &LockOption{}
28 | }
29 | if lockOption.Timeout.Nanoseconds() == 0 {
30 | lockOption.Timeout = 5 * time.Second
31 | }
32 | pool := NewPool(&PoolConfig{MaxTotal: 500}, option)
33 | return &Locker{
34 | timeout: lockOption.Timeout,
35 | ch: make(chan bool, 1),
36 | pool: pool,
37 | }
38 | }
39 |
40 | //LockOption locker options
41 | type LockOption struct {
42 | Timeout time.Duration //lock wait timeout
43 | }
44 |
45 | //TryLock acquire a lock,when it returns a non nil locker,get lock success,
46 | // otherwise, it returns an error,get lock failed
47 | func (l *Locker) TryLock(key string) (*Lock, error) {
48 | deadline := time.Now().Add(l.timeout)
49 | value := strconv.FormatInt(deadline.UnixNano(), 10)
50 | for {
51 | redis, err := l.pool.GetResource()
52 | if err != nil {
53 | return nil, err
54 | }
55 | if time.Now().After(deadline) {
56 | return nil, ErrLockTimeOut
57 | }
58 | status, err := redis.SetWithParamsAndTime(key, value, "nx", "px", l.timeout.Nanoseconds()/1e6)
59 | redis.Close()
60 | if err == nil && status == keywordOk.name {
61 | if len(l.ch) > 0 {
62 | <-l.ch
63 | }
64 | return &Lock{name: key}, nil
65 | }
66 | select {
67 | case <-l.ch:
68 | continue
69 | case <-time.After(l.timeout):
70 | return nil, ErrLockTimeOut
71 | }
72 | }
73 | }
74 |
75 | //UnLock when your business end,then release the locker
76 | func (l *Locker) UnLock(lock *Lock) error {
77 | redis, err := l.pool.GetResource()
78 | if err != nil {
79 | return err
80 | }
81 | defer redis.Close()
82 | if len(l.ch) == 0 {
83 | l.ch <- true
84 | }
85 | c, err := redis.Del(lock.name)
86 | if err != nil {
87 | return err
88 | }
89 | if c == 0 {
90 | return nil
91 | }
92 | return nil
93 | }
94 |
95 | //ClusterLocker cluster lock client
96 | type ClusterLocker struct {
97 | timeout time.Duration
98 | ch chan bool
99 | redisCluster *RedisCluster
100 | }
101 |
102 | //NewClusterLocker create new cluster locker
103 | func NewClusterLocker(option *ClusterOption, lockOption *LockOption) *ClusterLocker {
104 | if lockOption == nil {
105 | lockOption = &LockOption{}
106 | }
107 | if lockOption.Timeout.Nanoseconds() == 0 {
108 | lockOption.Timeout = 5 * time.Second
109 | }
110 | return &ClusterLocker{
111 | timeout: lockOption.Timeout,
112 | ch: make(chan bool, 1),
113 | redisCluster: NewRedisCluster(option),
114 | }
115 | }
116 |
117 | //TryLock acquire a lock,when it returns a non nil locker,get lock success,
118 | // otherwise, it returns an error,get lock failed
119 | func (l *ClusterLocker) TryLock(key string) (*Lock, error) {
120 | deadline := time.Now().Add(l.timeout)
121 | value := strconv.FormatInt(deadline.UnixNano(), 10)
122 | for {
123 | if time.Now().After(deadline) {
124 | return nil, ErrLockTimeOut
125 | }
126 | if len(l.ch) == 0 {
127 | status, err := l.redisCluster.SetWithParamsAndTime(key, value, "nx", "px", l.timeout.Nanoseconds()/1e6)
128 | //get lock success
129 | if err == nil && status == keywordOk.name {
130 | if len(l.ch) > 0 {
131 | <-l.ch
132 | }
133 | return &Lock{name: key}, nil
134 | }
135 | }
136 | select {
137 | case <-l.ch:
138 | continue
139 | case <-time.After(l.timeout):
140 | return nil, ErrLockTimeOut
141 | }
142 | }
143 | }
144 |
145 | //UnLock when your business end,then release the locker
146 | func (l *ClusterLocker) UnLock(lock *Lock) error {
147 | if len(l.ch) == 0 {
148 | l.ch <- true
149 | }
150 | c, err := l.redisCluster.Del(lock.name)
151 | if c == 0 {
152 | return nil
153 | }
154 | return err
155 | }
156 |
--------------------------------------------------------------------------------
/lock_test.go:
--------------------------------------------------------------------------------
1 | package godis
2 |
3 | import (
4 | "fmt"
5 | "github.com/stretchr/testify/assert"
6 | "math/rand"
7 | "sync"
8 | "sync/atomic"
9 | "testing"
10 | "time"
11 | )
12 |
13 | func TestRedisCluster_Lock(t *testing.T) {
14 | count := 0
15 | var group sync.WaitGroup
16 | locker := NewClusterLocker(clusterOption, nil)
17 | ch := make(chan bool, 4)
18 | total := 10000
19 | timeoutCount := int32(0)
20 | for i := 0; i < total; i++ {
21 | group.Add(1)
22 | ch <- true
23 | go func() {
24 | defer group.Done()
25 | lock, err := locker.TryLock("lock")
26 | if err == nil {
27 | if lock != nil {
28 | count++
29 | locker.UnLock(lock)
30 | } else {
31 | atomic.AddInt32(&timeoutCount, 1)
32 | }
33 | } else {
34 | atomic.AddInt32(&timeoutCount, 1)
35 | }
36 | <-ch
37 | }()
38 | }
39 | group.Wait()
40 | realCount := count + int(timeoutCount)
41 | assert.Equal(t, total, realCount, "want %d,but %d", total, realCount)
42 | }
43 |
44 | func TestRedis_Lock(t *testing.T) {
45 | locker := NewLocker(option, &LockOption{Timeout: 1 * time.Second})
46 | count := 0
47 | var group sync.WaitGroup
48 | ch := make(chan bool, 8)
49 | total := 10000
50 | timeoutCount := int32(0)
51 | for i := 0; i < total; i++ {
52 | group.Add(1)
53 | ch <- true
54 | go func() {
55 | defer group.Done()
56 | lock, err := locker.TryLock("lock")
57 | if err == nil {
58 | if lock != nil {
59 | count++
60 | locker.UnLock(lock)
61 | } else {
62 | atomic.AddInt32(&timeoutCount, 1)
63 | }
64 | } else {
65 | fmt.Printf("%v\n", err)
66 | atomic.AddInt32(&timeoutCount, 1)
67 | }
68 | <-ch
69 | }()
70 | }
71 | group.Wait()
72 | realCount := count + int(timeoutCount)
73 | assert.Equal(t, total, realCount, "want %d,but %d", total, realCount)
74 | }
75 |
76 | func TestRedis_Lock_Exception(t *testing.T) {
77 | locker := NewLocker(option, &LockOption{Timeout: 1 * time.Second})
78 | count := 0
79 | var group sync.WaitGroup
80 | ch := make(chan bool, 8)
81 | total := 20
82 | timeoutCount := int32(0)
83 | for i := 0; i < total; i++ {
84 | group.Add(1)
85 | ch <- true
86 | go func() {
87 | defer group.Done()
88 | lock, err := locker.TryLock("lock")
89 | if err == nil {
90 | if lock != nil {
91 | count++
92 | //simulate business exception
93 | rand.NewSource(time.Now().UnixNano())
94 | if rand.Intn(10) > 4 {
95 | <-ch
96 | return
97 | }
98 | locker.UnLock(lock)
99 | } else {
100 | atomic.AddInt32(&timeoutCount, 1)
101 | }
102 | } else {
103 | fmt.Printf("%v\n", err)
104 | atomic.AddInt32(&timeoutCount, 1)
105 | }
106 | <-ch
107 | }()
108 | }
109 | group.Wait()
110 | realCount := count + int(timeoutCount)
111 | assert.Equal(t, total, realCount, "want %d,but %d", total, realCount)
112 | }
113 |
114 | func _BenchmarkRedisLock(b *testing.B) {
115 | locker := NewLocker(option, &LockOption{Timeout: 3 * time.Second})
116 | count := 0
117 | for i := 0; i < 100; i++ {
118 | lock, err := locker.TryLock("lock")
119 | if err != nil {
120 | fmt.Printf("%v\n", err)
121 | continue
122 | }
123 | if lock != nil {
124 | count++
125 | fmt.Printf("%d\n", count)
126 | locker.UnLock(lock)
127 | }
128 | }
129 | b.Log(count)
130 | }
131 |
132 | //ignore this case,cause race data
133 | func _TestRedisNoLock(t *testing.T) {
134 | count := 0
135 | var group sync.WaitGroup
136 | ch := make(chan bool, 8)
137 | for i := 0; i < 1000; i++ {
138 | group.Add(1)
139 | go func() {
140 | defer group.Done()
141 | ch <- true
142 | count++
143 | <-ch
144 | }()
145 | }
146 | group.Wait()
147 | t.Log(count)
148 | }
149 |
150 | func TestCluster_Set(t *testing.T) {
151 | cluster := NewRedisCluster(clusterOption)
152 | count := int32(0)
153 | var group sync.WaitGroup
154 | ch := make(chan bool, 8)
155 | for i := 0; i < 100000; i++ {
156 | group.Add(1)
157 | ch <- true
158 | go func() {
159 | defer group.Done()
160 | atomic.AddInt32(&count, 1)
161 | reply, err := cluster.SetWithParamsAndTime("lock", "1", "nx", "px", 5*time.Second.Nanoseconds()/1e6)
162 | if err != nil {
163 | fmt.Printf("err:%v\n", err)
164 | } else {
165 | if reply == "OK" {
166 | fmt.Printf("reply:%s\n", reply)
167 | }
168 | }
169 | <-ch
170 | }()
171 | }
172 | group.Wait()
173 | t.Log(count)
174 | }
175 |
--------------------------------------------------------------------------------
/pipeline.go:
--------------------------------------------------------------------------------
1 | package godis
2 |
3 | import "sync"
4 |
5 | //Response pipeline and transaction response,include replies from redis
6 | type Response struct {
7 | response interface{} //store replies
8 | exception *DataError
9 |
10 | building bool //whether response is building
11 | built bool //whether response is build done
12 | isSet bool //whether response is set with data
13 |
14 | builder Builder //response data convert rule
15 | data interface{} //real data
16 | dependency *Response //response cycle dependency
17 | }
18 |
19 | func newResponse() *Response {
20 | return &Response{
21 | building: false,
22 | built: false,
23 | isSet: false,
24 | }
25 | }
26 |
27 | func (r *Response) set(data interface{}) {
28 | r.data = data
29 | r.isSet = true
30 | }
31 |
32 | //Get get real content of response
33 | func (r *Response) Get() (interface{}, error) {
34 | if r.dependency != nil && r.dependency.isSet && !r.dependency.built {
35 | err := r.dependency.build()
36 | if err != nil {
37 | return nil, err
38 | }
39 | }
40 | if !r.isSet {
41 | return nil, newDataError("please close pipeline or multi block before calling this method")
42 | }
43 | if !r.built {
44 | err := r.build()
45 | if err != nil {
46 | return nil, err
47 | }
48 | }
49 | if r.exception != nil {
50 | return nil, r.exception
51 | }
52 | return r.response, nil
53 | }
54 |
55 | func (r *Response) setDependency(dependency *Response) {
56 | r.dependency = dependency
57 | }
58 |
59 | func (r *Response) build() error {
60 | if r.building {
61 | return nil
62 | }
63 | r.building = true
64 | defer func() {
65 | r.building = false
66 | r.built = true
67 | }()
68 | if r.data != nil {
69 | switch r.data.(type) {
70 | case *DataError:
71 | r.exception = r.data.(*DataError)
72 | return nil
73 | }
74 | result, err := r.builder.build(r.data)
75 | if err != nil {
76 | return err
77 | }
78 | r.response = result
79 | }
80 | r.data = nil
81 | return nil
82 | }
83 |
84 | //Transaction redis transaction struct
85 | type Transaction struct {
86 | *multiKeyPipelineBase
87 | inTransaction bool
88 | }
89 |
90 | func newTransaction(c *client) *Transaction {
91 | base := newMultiKeyPipelineBase(c)
92 | base.getClient = func(key string) *client {
93 | return c
94 | }
95 | return &Transaction{multiKeyPipelineBase: base}
96 | }
97 |
98 | //Clear clear
99 | func (t *Transaction) Clear() (string, error) {
100 | if t.inTransaction {
101 | return t.Discard()
102 | }
103 | return "", nil
104 | }
105 |
106 | //Exec execute transaction
107 | func (t *Transaction) Exec() ([]interface{}, error) {
108 | err := t.client.exec()
109 | if err != nil {
110 | return nil, err
111 | }
112 | _, err = t.client.getAll(1)
113 | if err != nil {
114 | return nil, err
115 | }
116 | t.inTransaction = false
117 | reply, err := t.client.getObjectMultiBulkReply()
118 | if err != nil {
119 | return nil, err
120 | }
121 | result := make([]interface{}, 0)
122 | for _, r := range reply {
123 | result = append(result, t.generateResponse(r))
124 | }
125 | return result, nil
126 | }
127 |
128 | //ExecGetResponse ...
129 | func (t *Transaction) ExecGetResponse() ([]*Response, error) {
130 | err := t.client.exec()
131 | if err != nil {
132 | return nil, err
133 | }
134 | _, err = t.client.getAll(1)
135 | if err != nil {
136 | return nil, err
137 | }
138 | t.inTransaction = false
139 | reply, err := t.client.getObjectMultiBulkReply()
140 | if err != nil {
141 | return nil, err
142 | }
143 | result := make([]*Response, 0)
144 | for _, r := range reply {
145 | result = append(result, t.generateResponse(r))
146 | }
147 | return result, nil
148 | }
149 |
150 | //Discard see redis command
151 | func (t *Transaction) Discard() (string, error) {
152 | err := t.client.discard()
153 | if err != nil {
154 | return "", err
155 | }
156 | _, err = t.client.getAll(1)
157 | if err != nil {
158 | return "", err
159 | }
160 | t.inTransaction = false
161 | t.clean()
162 | return t.client.getStatusCodeReply()
163 | }
164 |
165 | func (t *Transaction) clean() {
166 | t.pipelinedResponses = make([]*Response, 0)
167 | }
168 |
169 | //Pipeline redis pipeline struct
170 | type Pipeline struct {
171 | *multiKeyPipelineBase
172 | }
173 |
174 | func newPipeline(c *client) *Pipeline {
175 | base := newMultiKeyPipelineBase(c)
176 | base.getClient = func(key string) *client {
177 | return c
178 | }
179 | return &Pipeline{multiKeyPipelineBase: base}
180 | }
181 |
182 | //Sync see redis command
183 | func (p *Pipeline) Sync() error {
184 | if len(p.pipelinedResponses) == 0 {
185 | return nil
186 | }
187 | all, err := p.client.connection.getAll()
188 | if err != nil {
189 | return err
190 | }
191 | for _, a := range all.([]interface{}) {
192 | p.generateResponse(a)
193 | }
194 | return nil
195 | }
196 |
197 | type queue struct {
198 | pipelinedResponses []*Response
199 | mu sync.Mutex
200 | }
201 |
202 | func newQueue() *queue {
203 | return &queue{pipelinedResponses: make([]*Response, 0)}
204 | }
205 |
206 | func (q *queue) clean() {
207 | q.mu.Lock()
208 | defer q.mu.Unlock()
209 | q.pipelinedResponses = make([]*Response, 0)
210 | }
211 |
212 | func (q *queue) generateResponse(data interface{}) *Response {
213 | q.mu.Lock()
214 | defer q.mu.Unlock()
215 | size := len(q.pipelinedResponses)
216 | if size == 0 {
217 | return nil
218 | }
219 | r := q.pipelinedResponses[0]
220 | r.set(data)
221 | if size == 1 {
222 | q.pipelinedResponses = make([]*Response, 0)
223 | } else {
224 | q.pipelinedResponses = q.pipelinedResponses[1:]
225 | }
226 | return r
227 | }
228 |
229 | func (q *queue) getResponse(builder Builder) *Response {
230 | q.mu.Lock()
231 | defer q.mu.Unlock()
232 | response := newResponse()
233 | response.builder = builder
234 | q.pipelinedResponses = append(q.pipelinedResponses, response)
235 | return response
236 | }
237 |
238 | func (q *queue) hasPipelinedResponse() bool {
239 | return q.getPipelinedResponseLength() > 0
240 | }
241 |
242 | func (q *queue) getPipelinedResponseLength() int {
243 | q.mu.Lock()
244 | defer q.mu.Unlock()
245 | return len(q.pipelinedResponses)
246 | }
247 |
248 | type multiKeyPipelineBase struct {
249 | *queue
250 | client *client
251 |
252 | getClient func(key string) *client
253 | }
254 |
255 | func newMultiKeyPipelineBase(client *client) *multiKeyPipelineBase {
256 | return &multiKeyPipelineBase{queue: newQueue(), client: client}
257 | }
258 |
259 | //
260 |
261 | //BgRewriteAof see redis command
262 | func (p *multiKeyPipelineBase) BgRewriteAof() (*Response, error) {
263 | err := p.client.bgrewriteaof()
264 | if err != nil {
265 | return nil, err
266 | }
267 | return p.getResponse(StrBuilder), nil
268 | }
269 |
270 | //BgSave see redis command
271 | func (p *multiKeyPipelineBase) BgSave() (*Response, error) {
272 | err := p.client.bgsave()
273 | if err != nil {
274 | return nil, err
275 | }
276 | return p.getResponse(StrBuilder), nil
277 | }
278 |
279 | //ConfigGet see redis command
280 | func (p *multiKeyPipelineBase) ConfigGet(pattern string) (*Response, error) {
281 | err := p.client.configGet(pattern)
282 | if err != nil {
283 | return nil, err
284 | }
285 | return p.getResponse(StrArrBuilder), nil
286 | }
287 |
288 | //ConfigSet see redis command
289 | func (p *multiKeyPipelineBase) ConfigSet(parameter, value string) (*Response, error) {
290 | err := p.client.configSet(parameter, value)
291 | if err != nil {
292 | return nil, err
293 | }
294 | return p.getResponse(StrBuilder), nil
295 | }
296 |
297 | //ConfigResetStat see redis command
298 | func (p *multiKeyPipelineBase) ConfigResetStat() (*Response, error) {
299 | err := p.client.configResetStat()
300 | if err != nil {
301 | return nil, err
302 | }
303 | return p.getResponse(StrBuilder), nil
304 | }
305 |
306 | //Save see redis command
307 | func (p *multiKeyPipelineBase) Save() (*Response, error) {
308 | err := p.client.save()
309 | if err != nil {
310 | return nil, err
311 | }
312 | return p.getResponse(StrBuilder), nil
313 | }
314 |
315 | //LastSave see redis command
316 | func (p *multiKeyPipelineBase) LastSave() (*Response, error) {
317 | err := p.client.lastsave()
318 | if err != nil {
319 | return nil, err
320 | }
321 | return p.getResponse(Int64Builder), nil
322 | }
323 |
324 | //FlushDB see redis command
325 | func (p *multiKeyPipelineBase) FlushDB() (*Response, error) {
326 | err := p.client.flushDB()
327 | if err != nil {
328 | return nil, err
329 | }
330 | return p.getResponse(StrBuilder), nil
331 | }
332 |
333 | //FlushAll see redis command
334 | func (p *multiKeyPipelineBase) FlushAll() (*Response, error) {
335 | err := p.client.flushAll()
336 | if err != nil {
337 | return nil, err
338 | }
339 | return p.getResponse(StrBuilder), nil
340 | }
341 |
342 | //Info see redis command
343 | func (p *multiKeyPipelineBase) Info() (*Response, error) {
344 | err := p.client.info()
345 | if err != nil {
346 | return nil, err
347 | }
348 | return p.getResponse(StrBuilder), nil
349 | }
350 |
351 | //Time see redis command
352 | func (p *multiKeyPipelineBase) Time() (*Response, error) {
353 | err := p.client.time()
354 | if err != nil {
355 | return nil, err
356 | }
357 | return p.getResponse(StrArrBuilder), nil
358 | }
359 |
360 | //DbSize see redis command
361 | func (p *multiKeyPipelineBase) DbSize() (*Response, error) {
362 | err := p.client.dbSize()
363 | if err != nil {
364 | return nil, err
365 | }
366 | return p.getResponse(Int64Builder), nil
367 | }
368 |
369 | //Shutdown see redis command
370 | func (p *multiKeyPipelineBase) Shutdown() (*Response, error) {
371 | err := p.client.shutdown()
372 | if err != nil {
373 | return nil, err
374 | }
375 | return p.getResponse(StrBuilder), nil
376 | }
377 |
378 | //Ping see redis command
379 | func (p *multiKeyPipelineBase) Ping() (*Response, error) {
380 | err := p.client.ping()
381 | if err != nil {
382 | return nil, err
383 | }
384 | return p.getResponse(StrBuilder), nil
385 | }
386 |
387 | //Select see redis command
388 | func (p *multiKeyPipelineBase) Select(index int) (*Response, error) {
389 | err := p.client.selectDb(index)
390 | if err != nil {
391 | return nil, err
392 | }
393 | return p.getResponse(StrBuilder), nil
394 | }
395 |
396 | //
397 |
398 | //
399 |
400 | //Del see redis command
401 | func (p *multiKeyPipelineBase) Del(keys ...string) (*Response, error) {
402 | err := p.client.del(keys...)
403 | if err != nil {
404 | return nil, err
405 | }
406 | return p.getResponse(Int64Builder), nil
407 | }
408 |
409 | //Exists see redis command
410 | func (p *multiKeyPipelineBase) Exists(keys ...string) (*Response, error) {
411 | err := p.client.exists(keys...)
412 | if err != nil {
413 | return nil, err
414 | }
415 | return p.getResponse(Int64Builder), nil
416 | }
417 |
418 | //BLPopTimeout see redis command
419 | func (p *multiKeyPipelineBase) BLPopTimeout(timeout int, keys ...string) (*Response, error) {
420 | err := p.client.blpopTimout(timeout, keys...)
421 | if err != nil {
422 | return nil, err
423 | }
424 | return p.getResponse(StrArrBuilder), nil
425 | }
426 |
427 | //BRPopTimeout see redis command
428 | func (p *multiKeyPipelineBase) BRPopTimeout(timeout int, keys ...string) (*Response, error) {
429 | err := p.client.brpopTimout(timeout, keys...)
430 | if err != nil {
431 | return nil, err
432 | }
433 | return p.getResponse(StrArrBuilder), nil
434 | }
435 |
436 | //BLPop see redis command
437 | func (p *multiKeyPipelineBase) BLPop(args ...string) (*Response, error) {
438 | err := p.client.blpop(args)
439 | if err != nil {
440 | return nil, err
441 | }
442 | return p.getResponse(StrArrBuilder), nil
443 | }
444 |
445 | //BRPop see redis command
446 | func (p *multiKeyPipelineBase) BRPop(args ...string) (*Response, error) {
447 | err := p.client.brpop(args)
448 | if err != nil {
449 | return nil, err
450 | }
451 | return p.getResponse(StrArrBuilder), nil
452 | }
453 |
454 | //Keys see redis command
455 | func (p *multiKeyPipelineBase) Keys(pattern string) (*Response, error) {
456 | err := p.client.keys(pattern)
457 | if err != nil {
458 | return nil, err
459 | }
460 | return p.getResponse(StrArrBuilder), nil
461 | }
462 |
463 | //MGet see redis command
464 | func (p *multiKeyPipelineBase) MGet(keys ...string) (*Response, error) {
465 | err := p.client.mget(keys...)
466 | if err != nil {
467 | return nil, err
468 | }
469 | return p.getResponse(StrArrBuilder), nil
470 | }
471 |
472 | //MSet see redis command
473 | func (p *multiKeyPipelineBase) MSet(kvs ...string) (*Response, error) {
474 | err := p.client.mset(kvs...)
475 | if err != nil {
476 | return nil, err
477 | }
478 | return p.getResponse(StrBuilder), nil
479 | }
480 |
481 | //MSetNx see redis command
482 | func (p *multiKeyPipelineBase) MSetNx(kvs ...string) (*Response, error) {
483 | err := p.client.msetnx(kvs...)
484 | if err != nil {
485 | return nil, err
486 | }
487 | return p.getResponse(Int64Builder), nil
488 | }
489 |
490 | //Rename see redis command
491 | func (p *multiKeyPipelineBase) Rename(oldkey, newkey string) (*Response, error) {
492 | err := p.client.rename(oldkey, newkey)
493 | if err != nil {
494 | return nil, err
495 | }
496 | return p.getResponse(StrBuilder), nil
497 | }
498 |
499 | //RenameNx see redis command
500 | func (p *multiKeyPipelineBase) RenameNx(oldkey, newkey string) (*Response, error) {
501 | err := p.client.renamenx(oldkey, newkey)
502 | if err != nil {
503 | return nil, err
504 | }
505 | return p.getResponse(Int64Builder), nil
506 | }
507 |
508 | //RPopLPush see redis command
509 | func (p *multiKeyPipelineBase) RPopLPush(srcKey, destKey string) (*Response, error) {
510 | err := p.client.rpopLpush(srcKey, destKey)
511 | if err != nil {
512 | return nil, err
513 | }
514 | return p.getResponse(StrBuilder), nil
515 | }
516 |
517 | //SDiff see redis command
518 | func (p *multiKeyPipelineBase) SDiff(keys ...string) (*Response, error) {
519 | err := p.client.sDiff(keys...)
520 | if err != nil {
521 | return nil, err
522 | }
523 | return p.getResponse(StrArrBuilder), nil
524 | }
525 |
526 | //SDiffStore see redis command
527 | func (p *multiKeyPipelineBase) SDiffStore(destKey string, keys ...string) (*Response, error) {
528 | err := p.client.sDiffStore(destKey, keys...)
529 | if err != nil {
530 | return nil, err
531 | }
532 | return p.getResponse(Int64Builder), nil
533 | }
534 |
535 | //SInter see redis command
536 | func (p *multiKeyPipelineBase) SInter(keys ...string) (*Response, error) {
537 | err := p.client.sInter(keys...)
538 | if err != nil {
539 | return nil, err
540 | }
541 | return p.getResponse(StrArrBuilder), nil
542 | }
543 |
544 | //SInterStore see redis command
545 | func (p *multiKeyPipelineBase) SInterStore(destKey string, keys ...string) (*Response, error) {
546 | err := p.client.sInterStore(destKey, keys...)
547 | if err != nil {
548 | return nil, err
549 | }
550 | return p.getResponse(Int64Builder), nil
551 | }
552 |
553 | //SMove see redis command
554 | func (p *multiKeyPipelineBase) SMove(srcKey, destKey, member string) (*Response, error) {
555 | err := p.client.smove(srcKey, destKey, member)
556 | if err != nil {
557 | return nil, err
558 | }
559 | return p.getResponse(Int64Builder), nil
560 | }
561 |
562 | //SortMulti see redis command
563 | func (p *multiKeyPipelineBase) SortStore(key string, destKey string, params ...*SortParams) (*Response, error) {
564 | err := p.client.sortMulti(key, destKey, params...)
565 | if err != nil {
566 | return nil, err
567 | }
568 | return p.getResponse(Int64Builder), nil
569 | }
570 |
571 | //SUnion see redis command
572 | func (p *multiKeyPipelineBase) SUnion(keys ...string) (*Response, error) {
573 | err := p.client.sUnion(keys...)
574 | if err != nil {
575 | return nil, err
576 | }
577 | return p.getResponse(StrArrBuilder), nil
578 | }
579 |
580 | //SUnionStore see redis command
581 | func (p *multiKeyPipelineBase) SUnionStore(destKey string, keys ...string) (*Response, error) {
582 | err := p.client.sUnionStore(destKey, keys...)
583 | if err != nil {
584 | return nil, err
585 | }
586 | return p.getResponse(Int64Builder), nil
587 | }
588 |
589 | //Watch see redis command
590 | func (p *multiKeyPipelineBase) Watch(keys ...string) (*Response, error) {
591 | err := p.client.watch(keys...)
592 | if err != nil {
593 | return nil, err
594 | }
595 | return p.getResponse(StrBuilder), nil
596 | }
597 |
598 | //ZInterStore see redis command
599 | func (p *multiKeyPipelineBase) ZInterStore(destKey string, sets ...string) (*Response, error) {
600 | err := p.client.zinterstore(destKey, sets...)
601 | if err != nil {
602 | return nil, err
603 | }
604 | return p.getResponse(Int64Builder), nil
605 | }
606 |
607 | //ZInterStoreWithParams see redis command
608 | func (p *multiKeyPipelineBase) ZInterStoreWithParams(destKey string, params *ZParams, sets ...string) (*Response, error) {
609 | err := p.client.zinterstoreWithParams(destKey, params, sets...)
610 | if err != nil {
611 | return nil, err
612 | }
613 | return p.getResponse(Int64Builder), nil
614 | }
615 |
616 | //ZUnionStore see redis command
617 | func (p *multiKeyPipelineBase) ZUnionStore(destKey string, sets ...string) (*Response, error) {
618 | err := p.client.zunionstore(destKey, sets...)
619 | if err != nil {
620 | return nil, err
621 | }
622 | return p.getResponse(Int64Builder), nil
623 | }
624 |
625 | //ZUnionStoreWithParams see redis command
626 | func (p *multiKeyPipelineBase) ZUnionStoreWithParams(destKey string, params *ZParams, sets ...string) (*Response, error) {
627 | err := p.client.zunionstoreWithParams(destKey, params, sets...)
628 | if err != nil {
629 | return nil, err
630 | }
631 | return p.getResponse(Int64Builder), nil
632 | }
633 |
634 | //BRPopLPush see redis command
635 | func (p *multiKeyPipelineBase) BRPopLPush(source, destination string, timeout int) (*Response, error) {
636 | err := p.client.brpoplpush(source, destination, timeout)
637 | if err != nil {
638 | return nil, err
639 | }
640 | return p.getResponse(StrArrBuilder), nil
641 | }
642 |
643 | //Publish see redis command
644 | func (p *multiKeyPipelineBase) Publish(channel, message string) (*Response, error) {
645 | err := p.client.publish(channel, message)
646 | if err != nil {
647 | return nil, err
648 | }
649 | return p.getResponse(Int64Builder), nil
650 | }
651 |
652 | //RandomKey see redis command
653 | func (p *multiKeyPipelineBase) RandomKey() (*Response, error) {
654 | err := p.client.randomKey()
655 | if err != nil {
656 | return nil, err
657 | }
658 | return p.getResponse(StrBuilder), nil
659 | }
660 |
661 | //BitOp see redis command
662 | func (p *multiKeyPipelineBase) BitOp(op BitOP, destKey string, srcKeys ...string) (*Response, error) {
663 | err := p.client.bitop(op, destKey, srcKeys...)
664 | if err != nil {
665 | return nil, err
666 | }
667 | return p.getResponse(Int64Builder), nil
668 | }
669 |
670 | //PfMerge see redis command
671 | func (p *multiKeyPipelineBase) PfMerge(destKey string, srcKeys ...string) (*Response, error) {
672 | err := p.client.pfmerge(destKey, srcKeys...)
673 | if err != nil {
674 | return nil, err
675 | }
676 | return p.getResponse(StrBuilder), nil
677 | }
678 |
679 | //PfCount see redis command
680 | func (p *multiKeyPipelineBase) PfCount(keys ...string) (*Response, error) {
681 | err := p.client.pfcount(keys...)
682 | if err != nil {
683 | return nil, err
684 | }
685 | return p.getResponse(Int64Builder), nil
686 | }
687 |
688 | //
689 |
690 | //
691 |
692 | //ClusterNodes see redis command
693 | func (p *multiKeyPipelineBase) ClusterNodes() (*Response, error) {
694 | err := p.client.clusterNodes()
695 | if err != nil {
696 | return nil, err
697 | }
698 | return p.getResponse(StrBuilder), nil
699 | }
700 |
701 | //ClusterMeet see redis command
702 | func (p *multiKeyPipelineBase) ClusterMeet(ip string, port int) (*Response, error) {
703 | err := p.client.clusterMeet(ip, port)
704 | if err != nil {
705 | return nil, err
706 | }
707 | return p.getResponse(StrBuilder), nil
708 | }
709 |
710 | //ClusterAddSlots see redis command
711 | func (p *multiKeyPipelineBase) ClusterAddSlots(slots ...int) (*Response, error) {
712 | err := p.client.clusterAddSlots(slots...)
713 | if err != nil {
714 | return nil, err
715 | }
716 | return p.getResponse(StrBuilder), nil
717 | }
718 |
719 | //ClusterDelSlots see redis command
720 | func (p *multiKeyPipelineBase) ClusterDelSlots(slots ...int) (*Response, error) {
721 | err := p.client.clusterDelSlots(slots...)
722 | if err != nil {
723 | return nil, err
724 | }
725 | return p.getResponse(StrBuilder), nil
726 | }
727 |
728 | //ClusterInfo see redis command
729 | func (p *multiKeyPipelineBase) ClusterInfo() (*Response, error) {
730 | err := p.client.clusterInfo()
731 | if err != nil {
732 | return nil, err
733 | }
734 | return p.getResponse(StrBuilder), nil
735 | }
736 |
737 | //ClusterGetKeysInSlot see redis command
738 | func (p *multiKeyPipelineBase) ClusterGetKeysInSlot(slot int, count int) (*Response, error) {
739 | err := p.client.clusterGetKeysInSlot(slot, count)
740 | if err != nil {
741 | return nil, err
742 | }
743 | return p.getResponse(StrArrBuilder), nil
744 | }
745 |
746 | //ClusterSetSlotNode see redis command
747 | func (p *multiKeyPipelineBase) ClusterSetSlotNode(slot int, nodeID string) (*Response, error) {
748 | err := p.client.clusterSetSlotNode(slot, nodeID)
749 | if err != nil {
750 | return nil, err
751 | }
752 | return p.getResponse(StrBuilder), nil
753 | }
754 |
755 | //ClusterSetSlotMigrating see redis command
756 | func (p *multiKeyPipelineBase) ClusterSetSlotMigrating(slot int, nodeID string) (*Response, error) {
757 | err := p.client.clusterSetSlotMigrating(slot, nodeID)
758 | if err != nil {
759 | return nil, err
760 | }
761 | return p.getResponse(StrBuilder), nil
762 | }
763 |
764 | //ClusterSetSlotImporting see redis command
765 | func (p *multiKeyPipelineBase) ClusterSetSlotImporting(slot int, nodeID string) (*Response, error) {
766 | err := p.client.clusterSetSlotImporting(slot, nodeID)
767 | if err != nil {
768 | return nil, err
769 | }
770 | return p.getResponse(StrBuilder), nil
771 | }
772 |
773 | //
774 |
775 | //
776 |
777 | //Eval see redis command
778 | func (p *multiKeyPipelineBase) Eval(script string, keyCount int, params ...string) (*Response, error) {
779 | err := p.getClient(script).eval(script, keyCount, params...)
780 | if err != nil {
781 | return nil, err
782 | }
783 | return p.getResponse(StrBuilder), nil
784 | }
785 |
786 | //EvalSha see redis command
787 | func (p *multiKeyPipelineBase) EvalSha(sha1 string, keyCount int, params ...string) (*Response, error) {
788 | err := p.getClient(sha1).evalsha(sha1, keyCount, params...)
789 | if err != nil {
790 | return nil, err
791 | }
792 | return p.getResponse(StrBuilder), nil
793 | }
794 |
795 | //
796 |
--------------------------------------------------------------------------------
/pool.go:
--------------------------------------------------------------------------------
1 | package godis
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "github.com/jolestar/go-commons-pool"
7 | "time"
8 | )
9 |
10 | var (
11 | //ErrClosed when pool is closed,continue operate pool will return this error
12 | ErrClosed = errors.New("pool is closed")
13 | )
14 |
15 | //Pool redis pool
16 | type Pool struct {
17 | internalPool *pool.ObjectPool
18 | ctx context.Context
19 | }
20 |
21 | //PoolConfig redis pool config, see go-commons-pool ObjectPoolConfig
22 | type PoolConfig struct {
23 | MaxTotal int //The cap on the number of objects that can be allocated
24 | MaxIdle int //The cap on the number of "idle" instances in the pool
25 | MinIdle int //The minimum number of idle objects to maintain in the pool
26 |
27 | LIFO bool //Whether the pool has LIFO (last in, first out) behaviour
28 | TestOnBorrow bool //Whether objects borrowed from the pool will be validated before being returned from the ObjectPool.BorrowObject() method
29 | TestWhileIdle bool //Whether objects sitting idle in the pool will be validated by the idle object evictor (if any - see TimeBetweenEvictionRuns )
30 | TestOnReturn bool //Whether objects borrowed from the pool will be validated when they are returned to the pool via the ObjectPool.ReturnObject() method
31 | TestOnCreate bool //Whether objects created for the pool will be validated before being returned from the ObjectPool.BorrowObject() method.
32 | BlockWhenExhausted bool //Whether to block when the ObjectPool.BorrowObject() method is invoked when the pool is exhausted
33 |
34 | MinEvictableIdleTime time.Duration //The minimum amount of time an object may sit idle in the pool
35 | SoftMinEvictableIdleTime time.Duration //if MinEvictableIdleTime is positive, then SoftMinEvictableIdleTime is ignored
36 | TimeBetweenEvictionRuns time.Duration //The amount of time sleep between runs of the idle object evictor goroutine.
37 | EvictionPolicyName string //The name of the EvictionPolicy implementation
38 | NumTestsPerEvictionRun int //The maximum number of objects to examine during each run
39 | }
40 |
41 | //NewPool create new pool
42 | func NewPool(config *PoolConfig, option *Option) *Pool {
43 | poolConfig := pool.NewDefaultPoolConfig()
44 | if config != nil && config.MaxTotal != 0 {
45 | poolConfig.MaxTotal = config.MaxTotal
46 | }
47 | if config != nil && config.MaxIdle != 0 {
48 | poolConfig.MaxIdle = config.MaxIdle
49 | }
50 | if config != nil && config.MinIdle != 0 {
51 | poolConfig.MinIdle = config.MinIdle
52 | }
53 | if config != nil && config.MinEvictableIdleTime != 0 {
54 | poolConfig.MinEvictableIdleTime = config.MinEvictableIdleTime
55 | }
56 | if config != nil && config.TestOnBorrow != false {
57 | poolConfig.TestOnBorrow = config.TestOnBorrow
58 | }
59 | ctx := context.Background()
60 | internalPool := pool.NewObjectPool(ctx, newFactory(option), poolConfig)
61 | internalPool.PreparePool(ctx)
62 | return &Pool{
63 | ctx: ctx,
64 | internalPool: internalPool,
65 | }
66 | }
67 |
68 | //GetResource get redis instance from pool
69 | func (p *Pool) GetResource() (*Redis, error) {
70 | obj, err := p.internalPool.BorrowObject(p.ctx)
71 | if err != nil {
72 | return nil, newConnectError(err.Error())
73 | }
74 | redis := obj.(*Redis)
75 | redis.setDataSource(p)
76 | return redis, nil
77 | }
78 |
79 | func (p *Pool) returnBrokenResourceObject(resource *Redis) error {
80 | if resource != nil {
81 | return p.internalPool.InvalidateObject(p.ctx, resource)
82 | }
83 | return nil
84 | }
85 |
86 | func (p *Pool) returnResourceObject(resource *Redis) error {
87 | if resource == nil {
88 | return nil
89 | }
90 | return p.internalPool.ReturnObject(p.ctx, resource)
91 | }
92 |
93 | //Destroy destroy pool
94 | func (p *Pool) Destroy() {
95 | p.internalPool.Close(p.ctx)
96 | }
97 |
98 | //Factory redis pool factory
99 | type factory struct {
100 | option *Option
101 | }
102 |
103 | //NewFactory create new redis pool factory
104 | func newFactory(option *Option) *factory {
105 | return &factory{option: option}
106 | }
107 |
108 | //MakeObject make new object from pool
109 | func (f factory) MakeObject(ctx context.Context) (*pool.PooledObject, error) {
110 | redis := NewRedis(f.option)
111 | defer func() {
112 | if e := recover(); e != nil {
113 | redis.Close()
114 | }
115 | }()
116 | err := redis.Connect()
117 | if err != nil {
118 | return nil, err
119 | }
120 | return pool.NewPooledObject(redis), nil
121 | }
122 |
123 | //DestroyObject destroy object of pool
124 | func (f factory) DestroyObject(ctx context.Context, object *pool.PooledObject) error {
125 | redis := object.Object.(*Redis)
126 | _, err := redis.Quit()
127 | if err != nil {
128 | return err
129 | }
130 | return nil
131 | }
132 |
133 | //ValidateObject validate object is available
134 | func (f factory) ValidateObject(ctx context.Context, object *pool.PooledObject) bool {
135 | redis := object.Object.(*Redis)
136 | if redis.client.host() != f.option.Host {
137 | return false
138 | }
139 | if redis.client.port() != f.option.Port {
140 | return false
141 | }
142 | reply, err := redis.Ping()
143 | if err != nil {
144 | return false
145 | }
146 | return reply == "PONG"
147 | }
148 |
149 | //ActivateObject active object
150 | func (f factory) ActivateObject(ctx context.Context, object *pool.PooledObject) error {
151 | redis := object.Object.(*Redis)
152 | if redis.client.Db == f.option.Db {
153 | return nil
154 | }
155 | _, err := redis.Select(f.option.Db)
156 | if err != nil {
157 | return err
158 | }
159 | return nil
160 | }
161 |
162 | //PassivateObject passivate object
163 | func (f factory) PassivateObject(ctx context.Context, object *pool.PooledObject) error {
164 | //todo how to passivate redis object
165 | return nil
166 | }
167 |
--------------------------------------------------------------------------------
/pool_test.go:
--------------------------------------------------------------------------------
1 | package godis
2 |
3 | import (
4 | "github.com/stretchr/testify/assert"
5 | "testing"
6 | "time"
7 | )
8 |
9 | func TestPool_Basic(t *testing.T) {
10 | pool := NewPool(&PoolConfig{
11 | MaxTotal: 4,
12 | MaxIdle: 2,
13 | MinIdle: 2,
14 | TestOnBorrow: true,
15 | }, &Option{
16 | Host: "localhost",
17 | Port: 6379,
18 | ConnectionTimeout: 2 * time.Second,
19 | SoTimeout: 2 * time.Second,
20 | Password: "",
21 | Db: 0,
22 | })
23 | redis, e := pool.GetResource()
24 | assert.Nil(t, e)
25 |
26 | s, e := redis.Echo("godis")
27 | assert.Nil(t, e)
28 | assert.Equal(t, "godis", s)
29 |
30 | e = pool.returnResourceObject(redis)
31 | assert.Nil(t, e)
32 | s, e = redis.Echo("godis")
33 | assert.Nil(t, e)
34 | assert.Equal(t, "godis", s)
35 |
36 | redis1, e := pool.GetResource()
37 | assert.Nil(t, e)
38 | e = pool.returnBrokenResourceObject(redis1)
39 | assert.Nil(t, e)
40 | s, e = redis1.Echo("godis")
41 | assert.NotNil(t, e)
42 | assert.Equal(t, "", s)
43 |
44 | pool.internalPool.Clear(nil)
45 |
46 | redis3, e := pool.GetResource()
47 | assert.Nil(t, e)
48 | pool.Destroy()
49 | s, e = redis3.Echo("godis")
50 | assert.Nil(t, e)
51 | assert.Equal(t, "godis", s)
52 |
53 | _, e = pool.GetResource()
54 | assert.NotNil(t, e)
55 | }
56 |
57 | func TestPool_Basic2(t *testing.T) {
58 | pool := NewPool(&PoolConfig{
59 | MaxTotal: 4,
60 | MaxIdle: 2,
61 | MinIdle: 2,
62 | MinEvictableIdleTime: 10,
63 | TestOnBorrow: true,
64 | }, &Option{
65 | Host: "localhost",
66 | Port: 6379,
67 | ConnectionTimeout: 2 * time.Second,
68 | SoTimeout: 2 * time.Second,
69 | Password: "123456",
70 | Db: 0,
71 | })
72 | _, e := pool.GetResource()
73 | assert.NotNil(t, e) //auth error
74 | }
75 |
76 | func TestPool_Basic3(t *testing.T) {
77 | pool := NewPool(&PoolConfig{
78 | MaxTotal: 30,
79 | MaxIdle: 20,
80 | MinIdle: 10,
81 | MinEvictableIdleTime: 10,
82 | TestOnBorrow: true,
83 | }, &Option{
84 | Host: "localhost",
85 | Port: 6380,
86 | ConnectionTimeout: 2 * time.Second,
87 | SoTimeout: 2 * time.Second,
88 | Db: 0,
89 | })
90 | _, e := pool.GetResource()
91 | assert.NotNil(t, e) //auth error
92 | }
93 |
--------------------------------------------------------------------------------
/protocol.go:
--------------------------------------------------------------------------------
1 | package godis
2 |
3 | import (
4 | "bufio"
5 | "fmt"
6 | "math"
7 | "strconv"
8 | "strings"
9 | "time"
10 | )
11 |
12 | const (
13 | askPrefix = "ASK "
14 | movedPrefix = "MOVED "
15 | clusterDownPrefix = "CLUSTERDOWN "
16 | busyPrefix = "BUSY "
17 | noscriptPrefix = "NOSCRIPT "
18 |
19 | defaultHost = "localhost"
20 | defaultPort = 6379
21 | defaultSentinelPort = 26379
22 | defaultTimeout = 5 * time.Second
23 | defaultDatabase = 2 * time.Second
24 |
25 | dollarByte = '$'
26 | asteriskByte = '*'
27 | plusByte = '+'
28 | minusByte = '-'
29 | colonByte = ':'
30 |
31 | sentinelMasters = "masters"
32 | sentinelGetMasterAddrByName = "get-master-addr-by-name"
33 | sentinelReset = "reset"
34 | sentinelSlaves = "slaves"
35 | sentinelFailOver = "failover"
36 | sentinelMonitor = "monitor"
37 | sentinelRemove = "remove"
38 | sentinelSet = "set"
39 |
40 | clusterNodes = "nodes"
41 | clusterMeet = "meet"
42 | clusterReset = "reset"
43 | clusterAddSlots = "addslots"
44 | clusterDelSlots = "delslots"
45 | clusterInfo = "info"
46 | clusterGetKeysInSlot = "getkeysinslot"
47 | clusterSetSlot = "setslot"
48 | clusterSetSlotNode = "node"
49 | clusterSetSlotMigrating = "migrating"
50 | clusterSetSlotImporting = "importing"
51 | clusterSetSlotStable = "stable"
52 | clusterForget = "forget"
53 | clusterFlushSlot = "flushslots"
54 | clusterKeySlot = "keyslot"
55 | clusterCountKeyInSlot = "countkeysinslot"
56 | clusterSaveConfig = "saveconfig"
57 | clusterReplicate = "replicate"
58 | clusterSlaves = "slaves"
59 | clusterFailOver = "failover"
60 | clusterSlots = "slots"
61 | pubSubChannels = "channels"
62 | pubSubNumSub = "numsub"
63 | pubSubNumPat = "numpat"
64 | )
65 |
66 | var (
67 | bytesTrue = IntToByteArr(1)
68 | bytesFalse = IntToByteArr(0)
69 | bytesTilde = []byte("~")
70 |
71 | positiveInfinityBytes = []byte("+inf")
72 | negativeInfinityBytes = []byte("-inf")
73 | )
74 |
75 | var (
76 | sizeTable = []int{9, 99, 999, 9999, 99999, 999999, 9999999, 99999999,
77 | 999999999, math.MaxInt32}
78 |
79 | digitTens = []byte{'0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '1',
80 | '1', '1', '1', '1', '1', '1', '1', '1', '1', '2', '2', '2', '2', '2', '2', '2', '2', '2',
81 | '2', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '4', '4', '4', '4', '4', '4', '4',
82 | '4', '4', '4', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '6', '6', '6', '6', '6',
83 | '6', '6', '6', '6', '6', '7', '7', '7', '7', '7', '7', '7', '7', '7', '7', '8', '8', '8',
84 | '8', '8', '8', '8', '8', '8', '8', '9', '9', '9', '9', '9', '9', '9', '9', '9', '9'}
85 |
86 | digitOnes = []byte{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0',
87 | '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8',
88 | '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6',
89 | '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4',
90 | '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2',
91 | '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}
92 |
93 | digits = []byte{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a',
94 | 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
95 | 't', 'u', 'v', 'w', 'x', 'y', 'z'}
96 | )
97 |
98 | // send message to redis
99 | type redisOutputStream struct {
100 | *bufio.Writer
101 | buf []byte
102 | count int
103 | c *connection
104 | }
105 |
106 | func newRedisOutputStream(bw *bufio.Writer, c *connection) *redisOutputStream {
107 | return &redisOutputStream{
108 | Writer: bw,
109 | buf: make([]byte, 8192),
110 | c: c,
111 | }
112 | }
113 |
114 | func (r *redisOutputStream) writeIntCrLf(b int) error {
115 | if b < 0 {
116 | if err := r.writeByte('-'); err != nil {
117 | return err
118 | }
119 | b = -b
120 | }
121 | size := 0
122 | for b > sizeTable[size] {
123 | size++
124 | }
125 | size++
126 | if size >= len(r.buf)-r.count {
127 | if err := r.flushBuffer(); err != nil {
128 | return err
129 | }
130 | }
131 | q, p := 0, 0
132 | charPos := r.count + size
133 | for b >= 65536 {
134 | q = b / 100
135 | p = b - ((q << 6) + (q << 5) + (q << 2))
136 | b = q
137 | charPos--
138 | r.buf[charPos] = digitOnes[p]
139 | charPos--
140 | r.buf[charPos] = digitTens[p]
141 | }
142 | for {
143 | q = (b * 52429) >> (16 + 3)
144 | p = b - ((q << 3) + (q << 1))
145 | charPos--
146 | r.buf[charPos] = digits[p]
147 | b = q
148 | if b == 0 {
149 | break
150 | }
151 | }
152 | r.count += size
153 | return r.writeCrLf()
154 | }
155 |
156 | func (r *redisOutputStream) writeCrLf() error {
157 | if 2 >= len(r.buf)-r.count {
158 | if err := r.flushBuffer(); err != nil {
159 | return err
160 | }
161 | }
162 | r.buf[r.count] = '\r'
163 | r.count++
164 | r.buf[r.count] = '\n'
165 | r.count++
166 | return nil
167 | }
168 |
169 | func (r *redisOutputStream) flushBuffer() error {
170 | if r.count <= 0 {
171 | return nil
172 | }
173 | if err := r.c.socket.SetDeadline(time.Now().Add(r.c.soTimeout)); err != nil {
174 | return newConnectError(err.Error())
175 | }
176 | _, err := r.Write(r.buf[0:r.count])
177 | if err != nil {
178 | return err
179 | }
180 | r.count = 0
181 | return nil
182 | }
183 |
184 | func (r *redisOutputStream) writeByte(b byte) error {
185 | if r.count == len(r.buf) {
186 | return r.flushBuffer()
187 | }
188 | r.buf[r.count] = b
189 | r.count++
190 | return nil
191 | }
192 |
193 | func (r *redisOutputStream) write(b []byte) error {
194 | return r.writeWithPos(b, 0, len(b))
195 | }
196 |
197 | func (r *redisOutputStream) writeWithPos(b []byte, off, size int) error {
198 | if size >= len(r.buf) {
199 | err := r.flushBuffer()
200 | if err != nil {
201 | return err
202 | }
203 | _, err = r.Write(b[off:size])
204 | return err
205 | }
206 |
207 | if size >= len(r.buf)-r.count {
208 | err := r.flushBuffer()
209 | if err != nil {
210 | return err
211 | }
212 | }
213 | for i := off; i < size; i++ {
214 | r.buf[r.count] = b[i]
215 | r.count++
216 | }
217 | return nil
218 | }
219 |
220 | func (r *redisOutputStream) flush() error {
221 | if err := r.flushBuffer(); err != nil {
222 | return newConnectError(err.Error())
223 | }
224 | if err := r.Flush(); err != nil {
225 | return err
226 | }
227 | return nil
228 | }
229 |
230 | // receive message from redis
231 | type redisInputStream struct {
232 | *bufio.Reader
233 | buf []byte
234 | count int
235 | limit int
236 | c *connection
237 | }
238 |
239 | func newRedisInputStream(br *bufio.Reader, c *connection) *redisInputStream {
240 | return &redisInputStream{
241 | Reader: br,
242 | buf: make([]byte, 8192),
243 | c: c,
244 | }
245 | }
246 |
247 | func (r *redisInputStream) readByte() (byte, error) {
248 | err := r.ensureFill()
249 | if err != nil {
250 | return 0, err
251 | }
252 | ret := r.buf[r.count]
253 | r.count++
254 | return ret, nil
255 | }
256 |
257 | func (r *redisInputStream) ensureFill() error {
258 | if r.count < r.limit {
259 | return nil
260 | }
261 | var err error
262 | r.limit, err = r.Read(r.buf)
263 | if err != nil {
264 | return newConnectError(err.Error())
265 | }
266 | err = r.c.socket.SetDeadline(time.Now().Add(r.c.soTimeout))
267 | if err != nil {
268 | return newConnectError(err.Error())
269 | }
270 | r.count = 0
271 | if r.limit == -1 {
272 | return newConnectError("Unexpected end of stream")
273 | }
274 | return nil
275 | }
276 |
277 | func (r *redisInputStream) readLine() (string, error) {
278 | buf := ""
279 | for {
280 | err := r.ensureFill()
281 | if err != nil {
282 | return "", err
283 | }
284 | b := r.buf[r.count]
285 | r.count++
286 | if b == '\r' {
287 | err := r.ensureFill()
288 | if err != nil {
289 | return "", err
290 | }
291 | c := r.buf[r.count]
292 | r.count++
293 | if c == '\n' {
294 | break
295 | }
296 | buf += string(b)
297 | buf += string(c)
298 | } else {
299 | buf += string(b)
300 | }
301 | }
302 | if buf == "" {
303 | return "", newConnectError("It seems like server has closed the connection.")
304 | }
305 | return buf, nil
306 | }
307 |
308 | func (r *redisInputStream) readLineBytes() ([]byte, error) {
309 | err := r.ensureFill()
310 | if err != nil {
311 | return nil, err
312 | }
313 | pos := r.count
314 | buf := r.buf
315 | for {
316 | if pos == r.limit {
317 | return r.readLineBytesSlowly()
318 | }
319 | p := buf[pos]
320 | pos++
321 | if p == '\r' {
322 | if pos == r.limit {
323 | return r.readLineBytesSlowly()
324 | }
325 | p := buf[pos]
326 | pos++
327 | if p == '\n' {
328 | break
329 | }
330 | }
331 | }
332 | N := pos - r.count - 2
333 | line := make([]byte, N)
334 | j := 0
335 | for i := r.count; i <= N; i++ {
336 | line[j] = buf[i]
337 | j++
338 | }
339 | r.count = pos
340 | return line, nil
341 | }
342 |
343 | func (r *redisInputStream) readLineBytesSlowly() ([]byte, error) {
344 | buf := make([]byte, 0)
345 | for {
346 | err := r.ensureFill()
347 | if err != nil {
348 | return nil, err
349 | }
350 | b := r.buf[r.count]
351 | r.count++
352 | if b == 'r' {
353 | err := r.ensureFill()
354 | if err != nil {
355 | return nil, err
356 | }
357 | c := r.buf[r.count]
358 | r.count++
359 | if c == '\n' {
360 | break
361 | }
362 | buf = append(buf, b)
363 | buf = append(buf, c)
364 | } else {
365 | buf = append(buf, b)
366 | }
367 | }
368 | return buf, nil
369 | }
370 |
371 | func (r *redisInputStream) readIntCrLf() (int64, error) {
372 | err := r.ensureFill()
373 | if err != nil {
374 | return 0, err
375 | }
376 | buf := r.buf
377 | isNeg := false
378 | if buf[r.count] == '-' {
379 | isNeg = true
380 | }
381 | if isNeg {
382 | r.count++
383 | }
384 | value := int64(0)
385 | for {
386 | err := r.ensureFill()
387 | if err != nil {
388 | return 0, err
389 | }
390 | b := buf[r.count]
391 | r.count++
392 | if b == '\r' {
393 | err := r.ensureFill()
394 | if err != nil {
395 | return 0, err
396 | }
397 | c := buf[r.count]
398 | r.count++
399 | if c != '\n' {
400 | return 0, newConnectError("Unexpected character!")
401 | }
402 | break
403 | } else {
404 | value = value*10 + int64(b) - int64('0')
405 | }
406 | }
407 | if isNeg {
408 | return -value, nil
409 | }
410 | return value, nil
411 | }
412 |
413 | type protocol struct {
414 | os *redisOutputStream
415 | is *redisInputStream
416 | }
417 |
418 | func newProtocol(os *redisOutputStream, is *redisInputStream) *protocol {
419 | return &protocol{
420 | os: os,
421 | is: is,
422 | }
423 | }
424 |
425 | func (p *protocol) sendCommand(command []byte, args ...[]byte) error {
426 | if err := p.os.writeByte(asteriskByte); err != nil {
427 | return err
428 | }
429 | if err := p.os.writeIntCrLf(len(args) + 1); err != nil {
430 | return err
431 | }
432 | if err := p.os.writeByte(dollarByte); err != nil {
433 | return err
434 | }
435 | if err := p.os.writeIntCrLf(len(command)); err != nil {
436 | return err
437 | }
438 | if err := p.os.write(command); err != nil {
439 | return err
440 | }
441 | if err := p.os.writeCrLf(); err != nil {
442 | return err
443 | }
444 | for _, arg := range args {
445 | if err := p.os.writeByte(dollarByte); err != nil {
446 | return err
447 | }
448 | if err := p.os.writeIntCrLf(len(arg)); err != nil {
449 | return err
450 | }
451 | if err := p.os.write(arg); err != nil {
452 | return err
453 | }
454 | if err := p.os.writeCrLf(); err != nil {
455 | return err
456 | }
457 | }
458 | return nil
459 | }
460 |
461 | func (p *protocol) read() (interface{}, error) {
462 | return p.process()
463 | }
464 |
465 | func (p *protocol) process() (interface{}, error) {
466 | b, err := p.is.readByte()
467 | if err != nil {
468 | return nil, newConnectError(err.Error())
469 | }
470 | switch b {
471 | case plusByte:
472 | return p.processStatusCodeReply()
473 | case dollarByte:
474 | return p.processBulkReply()
475 | case asteriskByte:
476 | return p.processMultiBulkReply()
477 | case colonByte:
478 | return p.processInteger()
479 | case minusByte:
480 | return p.processError()
481 | default:
482 | return nil, newConnectError(fmt.Sprintf("Unknown reply: %b", b))
483 | }
484 | }
485 |
486 | func (p *protocol) processStatusCodeReply() ([]byte, error) {
487 | return p.is.readLineBytes()
488 | }
489 |
490 | func (p *protocol) processBulkReply() ([]byte, error) {
491 | l, err := p.is.readIntCrLf()
492 | if err != nil {
493 | return nil, newConnectError(err.Error())
494 | }
495 | if l == -1 {
496 | return nil, nil
497 | }
498 | line := make([]byte, 0)
499 | for {
500 | err := p.is.ensureFill()
501 | if err != nil {
502 | return nil, err
503 | }
504 | b := p.is.buf[p.is.count]
505 | p.is.count++
506 | if b == '\r' {
507 | err := p.is.ensureFill()
508 | if err != nil {
509 | return nil, err
510 | }
511 | c := p.is.buf[p.is.count]
512 | p.is.count++
513 | if c != '\n' {
514 | return nil, newConnectError("Unexpected character!")
515 | }
516 | break
517 | } else {
518 | line = append(line, b)
519 | }
520 | }
521 | return line, nil
522 | }
523 |
524 | func (p *protocol) processMultiBulkReply() ([]interface{}, error) {
525 | l, err := p.is.readIntCrLf()
526 | if err != nil {
527 | return nil, newConnectError(err.Error())
528 | }
529 | if l == -1 {
530 | return nil, nil
531 | }
532 | ret := make([]interface{}, 0)
533 | for i := 0; i < int(l); i++ {
534 | if obj, err := p.process(); err != nil {
535 | ret = append(ret, newDataError(err.Error()))
536 | } else {
537 | ret = append(ret, obj)
538 | }
539 | }
540 | return ret, nil
541 | }
542 |
543 | func (p *protocol) processInteger() (int64, error) {
544 | return p.is.readIntCrLf()
545 | }
546 |
547 | func (p *protocol) processError() (interface{}, error) {
548 | msg, err := p.is.readLine()
549 | if err != nil {
550 | return nil, newConnectError(err.Error())
551 | }
552 | if strings.HasPrefix(msg, movedPrefix) {
553 | host, port, slot := p.parseTargetHostAndSlot(msg)
554 | return nil, newMovedDataError(msg, host, port, slot)
555 | } else if strings.HasPrefix(msg, askPrefix) {
556 | host, port, slot := p.parseTargetHostAndSlot(msg)
557 | return nil, newAskDataError(msg, host, port, slot)
558 | } else if strings.HasPrefix(msg, clusterDownPrefix) {
559 | return nil, newClusterError(msg)
560 | } else if strings.HasPrefix(msg, busyPrefix) {
561 | return nil, newBusyError(msg)
562 | } else if strings.HasPrefix(msg, noscriptPrefix) {
563 | return nil, newNoScriptError(msg)
564 | }
565 | return nil, newDataError(msg)
566 | }
567 |
568 | func (p *protocol) parseTargetHostAndSlot(clusterRedirectResponse string) (string, int, int) {
569 | arr := strings.Split(clusterRedirectResponse, " ")
570 | host, port := p.extractParts(arr[2])
571 | slot, _ := strconv.Atoi(arr[1])
572 | po, _ := strconv.Atoi(port)
573 | return host, po, slot
574 | }
575 |
576 | func (p *protocol) extractParts(from string) (string, string) {
577 | idx := strings.LastIndex(from, ":")
578 | host := from
579 | if idx != -1 {
580 | host = from[0:idx]
581 | }
582 | port := ""
583 | if idx != -1 {
584 | port = from[idx+1:]
585 | }
586 | return host, port
587 | }
588 |
589 | // redis protocol command
590 | type protocolCommand struct {
591 | name string // name of command
592 | }
593 |
594 | // getRaw get name byte array
595 | func (p protocolCommand) getRaw() []byte {
596 | return []byte(p.name)
597 | }
598 |
599 | func newProtocolCommand(name string) protocolCommand {
600 | return protocolCommand{name}
601 | }
602 |
603 | var (
604 | cmdPing = newProtocolCommand("PING")
605 | cmdSet = newProtocolCommand("SET")
606 | cmdGet = newProtocolCommand("GET")
607 | cmdQuit = newProtocolCommand("QUIT")
608 | cmdExists = newProtocolCommand("EXISTS")
609 | cmdDel = newProtocolCommand("DEL")
610 | cmdUnlink = newProtocolCommand("UNLINK")
611 | cmdType = newProtocolCommand("TYPE")
612 | cmdFlushDB = newProtocolCommand("FLUSHDB")
613 | cmdKeys = newProtocolCommand("KEYS")
614 | cmdRandomKey = newProtocolCommand("RANDOMKEY")
615 | cmdRename = newProtocolCommand("RENAME")
616 | cmdRenameNx = newProtocolCommand("RENAMENX")
617 | cmdRenameX = newProtocolCommand("RENAMEX")
618 | cmdDbSize = newProtocolCommand("DBSIZE")
619 | cmdExpire = newProtocolCommand("EXPIRE")
620 | cmdExpireAt = newProtocolCommand("EXPIREAT")
621 | cmdTTL = newProtocolCommand("TTL")
622 | cmdSelect = newProtocolCommand("SELECT")
623 | cmdMove = newProtocolCommand("MOVE")
624 | cmdFlushAll = newProtocolCommand("FLUSHALL")
625 | cmdGetSet = newProtocolCommand("GETSET")
626 | cmdMGet = newProtocolCommand("MGET")
627 | cmdSetNx = newProtocolCommand("SETNX")
628 | cmdSetEx = newProtocolCommand("SETEX")
629 | cmdMSet = newProtocolCommand("MSET")
630 | cmdMSetNx = newProtocolCommand("MSETNX")
631 | cmdDecrBy = newProtocolCommand("DECRBY")
632 | cmdDecr = newProtocolCommand("DECR")
633 | cmdIncrBy = newProtocolCommand("INCRBY")
634 | cmdIncr = newProtocolCommand("INCR")
635 | cmdAppend = newProtocolCommand("APPEND")
636 | cmdSubstr = newProtocolCommand("SUBSTR")
637 | cmdHSet = newProtocolCommand("HSET")
638 | cmdHGet = newProtocolCommand("HGET")
639 | cmdHSetNx = newProtocolCommand("HSETNX")
640 | cmdHMSet = newProtocolCommand("HMSET")
641 | cmdHMGet = newProtocolCommand("HMGET")
642 | cmdHIncrBy = newProtocolCommand("HINCRBY")
643 | cmdHExists = newProtocolCommand("HEXISTS")
644 | cmdHDel = newProtocolCommand("HDEL")
645 | cmdHLen = newProtocolCommand("HLEN")
646 | cmdHKeys = newProtocolCommand("HKEYS")
647 | cmdHVals = newProtocolCommand("HVALS")
648 | cmdHGetAll = newProtocolCommand("HGETALL")
649 | cmdRPush = newProtocolCommand("RPUSH")
650 | cmdLPush = newProtocolCommand("LPUSH")
651 | cmdLLen = newProtocolCommand("LLEN")
652 | cmdLRange = newProtocolCommand("LRANGE")
653 | cmdLtrim = newProtocolCommand("LTRIM")
654 | cmdLIndex = newProtocolCommand("LINDEX")
655 | cmdLSet = newProtocolCommand("LSET")
656 | cmdLRem = newProtocolCommand("LREM")
657 | cmdLPop = newProtocolCommand("LPOP")
658 | cmdRPop = newProtocolCommand("RPOP")
659 | cmdRPopLPush = newProtocolCommand("RPOPLPUSH")
660 | cmdSAdd = newProtocolCommand("SADD")
661 | cmdSMembers = newProtocolCommand("SMEMBERS")
662 | cmdSRem = newProtocolCommand("SREM")
663 | cmdSPop = newProtocolCommand("SPOP")
664 | cmdSMove = newProtocolCommand("SMOVE")
665 | cmdSCard = newProtocolCommand("SCARD")
666 | cmdSIsMember = newProtocolCommand("SISMEMBER")
667 | cmdSInter = newProtocolCommand("SINTER")
668 | cmdSInterStore = newProtocolCommand("SINTERSTORE")
669 | cmdSUnion = newProtocolCommand("SUNION")
670 | cmdSUnionStore = newProtocolCommand("SUNIONSTORE")
671 | cmdSDiff = newProtocolCommand("SDIFF")
672 | cmdSDiffStore = newProtocolCommand("SDIFFSTORE")
673 | cmdSRandMember = newProtocolCommand("SRANDMEMBER")
674 | cmdZAdd = newProtocolCommand("ZADD")
675 | cmdZRange = newProtocolCommand("ZRANGE")
676 | cmdZRem = newProtocolCommand("ZREM")
677 | cmdZIncrBy = newProtocolCommand("ZINCRBY")
678 | cmdZRank = newProtocolCommand("ZRANK")
679 | cmdZRevRank = newProtocolCommand("ZREVRANK")
680 | cmdZRevRange = newProtocolCommand("ZREVRANGE")
681 | cmdZCard = newProtocolCommand("ZCARD")
682 | cmdZScore = newProtocolCommand("ZSCORE")
683 | cmdMulti = newProtocolCommand("MULTI")
684 | cmdDiscard = newProtocolCommand("DISCARD")
685 | cmdExec = newProtocolCommand("EXEC")
686 | cmdWatch = newProtocolCommand("WATCH")
687 | cmdUnwatch = newProtocolCommand("UNWATCH")
688 | cmdSort = newProtocolCommand("SORT")
689 | cmdBLPop = newProtocolCommand("BLPOP")
690 | cmdBRPop = newProtocolCommand("BRPOP")
691 | cmdAuth = newProtocolCommand("AUTH")
692 | cmdSubscribe = newProtocolCommand("SUBSCRIBE")
693 | cmdPublish = newProtocolCommand("PUBLISH")
694 | cmdUnSubscribe = newProtocolCommand("UNSUBSCRIBE")
695 | cmdPSubscribe = newProtocolCommand("PSUBSCRIBE")
696 | cmdPUnSubscribe = newProtocolCommand("PUNSUBSCRIBE")
697 | cmdPubSub = newProtocolCommand("PUBSUB")
698 | cmdZCount = newProtocolCommand("ZCOUNT")
699 | cmdZRangeByScore = newProtocolCommand("ZRANGEBYSCORE")
700 | cmdZRevRangeByScore = newProtocolCommand("ZREVRANGEBYSCORE")
701 | cmdZRemRangeByRank = newProtocolCommand("ZREMRANGEBYRANK")
702 | cmdZRemRangeByScore = newProtocolCommand("ZREMRANGEBYSCORE")
703 | cmdZUnionStore = newProtocolCommand("ZUNIONSTORE")
704 | cmdZInterStore = newProtocolCommand("ZINTERSTORE")
705 | cmdZLexCount = newProtocolCommand("ZLEXCOUNT")
706 | cmdZRangeByLex = newProtocolCommand("ZRANGEBYLEX")
707 | cmdZRevRangeByLex = newProtocolCommand("ZREVRANGEBYLEX")
708 | cmdZRemRangeByLex = newProtocolCommand("ZREMRANGEBYLEX")
709 | cmdSave = newProtocolCommand("SAVE")
710 | cmdBgSave = newProtocolCommand("BGSAVE")
711 | cmdBgRewriteAof = newProtocolCommand("BGREWRITEAOF")
712 | cmdLastSave = newProtocolCommand("LASTSAVE")
713 | cmdShutdown = newProtocolCommand("SHUTDOWN")
714 | cmdInfo = newProtocolCommand("INFO")
715 | cmdMonitor = newProtocolCommand("MONITOR")
716 | cmdSlaveOf = newProtocolCommand("SLAVEOF")
717 | cmdConfig = newProtocolCommand("CONFIG")
718 | cmdStrLen = newProtocolCommand("STRLEN")
719 | cmdSync = newProtocolCommand("SYNC")
720 | cmdLPushX = newProtocolCommand("LPUSHX")
721 | cmdPersist = newProtocolCommand("PERSIST")
722 | cmdRPushX = newProtocolCommand("RPUSHX")
723 | cmdEcho = newProtocolCommand("ECHO")
724 | cmdLInsert = newProtocolCommand("LINSERT")
725 | cmdDebug = newProtocolCommand("DEBUG")
726 | cmdBRPopLPush = newProtocolCommand("BRPOPLPUSH")
727 | cmdSetBit = newProtocolCommand("SETBIT")
728 | cmdGetBit = newProtocolCommand("GETBIT")
729 | cmdBitPos = newProtocolCommand("BITPOS")
730 | cmdSetRange = newProtocolCommand("SETRANGE")
731 | cmdGetRange = newProtocolCommand("GETRANGE")
732 | cmdEval = newProtocolCommand("EVAL")
733 | cmdEvalSha = newProtocolCommand("EVALSHA")
734 | cmdScript = newProtocolCommand("SCRIPT")
735 | cmdSlowLog = newProtocolCommand("SLOWLOG")
736 | cmdObject = newProtocolCommand("OBJECT")
737 | cmdBitCount = newProtocolCommand("BITCOUNT")
738 | cmdBitOp = newProtocolCommand("BITOP")
739 | cmdSentinel = newProtocolCommand("SENTINEL")
740 | cmdDump = newProtocolCommand("DUMP")
741 | cmdRestore = newProtocolCommand("RESTORE")
742 | cmdPExpire = newProtocolCommand("PEXPIRE")
743 | cmdPExpireAt = newProtocolCommand("PEXPIREAT")
744 | cmdPTTL = newProtocolCommand("PTTL")
745 | cmdIncrByFloat = newProtocolCommand("INCRBYFLOAT")
746 | cmdPSetEx = newProtocolCommand("PSETEX")
747 | cmdClient = newProtocolCommand("CLIENT")
748 | cmdTime = newProtocolCommand("TIME")
749 | cmdMigrate = newProtocolCommand("MIGRATE")
750 | cmdHIncrByFloat = newProtocolCommand("HINCRBYFLOAT")
751 | cmdScan = newProtocolCommand("SCAN")
752 | cmdHScan = newProtocolCommand("HSCAN")
753 | cmdSScan = newProtocolCommand("SSCAN")
754 | cmdZScan = newProtocolCommand("ZSCAN")
755 | cmdWait = newProtocolCommand("WAIT")
756 | cmdCluster = newProtocolCommand("CLUSTER")
757 | cmdAsking = newProtocolCommand("ASKING")
758 | cmdPfAdd = newProtocolCommand("PFADD")
759 | cmdPfCount = newProtocolCommand("PFCOUNT")
760 | cmdPfMerge = newProtocolCommand("PFMERGE")
761 | cmdReadonly = newProtocolCommand("READONLY")
762 | cmdGeoAdd = newProtocolCommand("GEOADD")
763 | cmdGeoDist = newProtocolCommand("GEODIST")
764 | cmdGeoHash = newProtocolCommand("GEOHASH")
765 | cmdGeoPos = newProtocolCommand("GEOPOS")
766 | cmdGeoRadius = newProtocolCommand("GEORADIUS")
767 | cmdGeoRadiusRo = newProtocolCommand("GEORADIUS_RO")
768 | cmdGeoRadiusByMember = newProtocolCommand("GEORADIUSBYMEMBER")
769 | cmdGeoRadiusByMemberRo = newProtocolCommand("GEORADIUSBYMEMBER_RO")
770 | cmdModule = newProtocolCommand("MODULE")
771 | cmdBitField = newProtocolCommand("BITFIELD")
772 | cmdHStrLen = newProtocolCommand("HSTRLEN")
773 | cmdTouch = newProtocolCommand("TOUCH")
774 | cmdSwapDB = newProtocolCommand("SWAPDB")
775 | cmdMemory = newProtocolCommand("MEMORY")
776 | cmdXAdd = newProtocolCommand("XADD")
777 | cmdXLen = newProtocolCommand("XLEN")
778 | cmdXDel = newProtocolCommand("XDEL")
779 | cmdXTrim = newProtocolCommand("XTRIM")
780 | cmdXRange = newProtocolCommand("XRANGE")
781 | cmdXRevRange = newProtocolCommand("XREVRANGE")
782 | cmdXRead = newProtocolCommand("XREAD")
783 | cmdXAck = newProtocolCommand("XACK")
784 | cmdXGroup = newProtocolCommand("XGROUP")
785 | cmdXReadGroup = newProtocolCommand("XREADGROUP")
786 | cmdXPending = newProtocolCommand("XPENDING")
787 | cmdXClaim = newProtocolCommand("XCLAIM")
788 | )
789 |
790 | // redis keyword
791 | type keyword struct {
792 | name string // name of keyword
793 | }
794 |
795 | // getRaw byte array of name
796 | func (k *keyword) getRaw() []byte {
797 | return []byte(k.name)
798 | }
799 |
800 | func newKeyword(name string) *keyword {
801 | return &keyword{name}
802 | }
803 |
804 | var (
805 | keywordAggregate = newKeyword("AGGREGATE")
806 | keywordAlpha = newKeyword("ALPHA")
807 | keywordAsc = newKeyword("ASC")
808 | keywordBy = newKeyword("BY")
809 | keywordDesc = newKeyword("DESC")
810 | keywordGet = newKeyword("GET")
811 | keywordLimit = newKeyword("LIMIT")
812 | keywordMessage = newKeyword("MESSAGE")
813 | keywordNo = newKeyword("NO")
814 | keywordNosort = newKeyword("NOSORT")
815 | keywordPMessage = newKeyword("PMESSAGE")
816 | keywordPSubscribe = newKeyword("PSUBSCRIBE")
817 | keywordPUnSubscribe = newKeyword("PUNSUBSCRIBE")
818 | keywordOk = newKeyword("OK")
819 | keywordOne = newKeyword("ONE")
820 | keywordQueued = newKeyword("QUEUED")
821 | keywordSet = newKeyword("SET")
822 | keywordStore = newKeyword("STORE")
823 | keywordSubscribe = newKeyword("SUBSCRIBE")
824 | keywordUnsubscribe = newKeyword("UNSUBSCRIBE")
825 | keywordWeights = newKeyword("WEIGHTS")
826 | keywordWithScores = newKeyword("WITHSCORES")
827 | keywordResetStat = newKeyword("RESETSTAT")
828 | keywordRewrite = newKeyword("REWRITE")
829 | keywordReset = newKeyword("RESET")
830 | keywordFlush = newKeyword("FLUSH")
831 | keywordExists = newKeyword("EXISTS")
832 | keywordLoad = newKeyword("LOAD")
833 | keywordKill = newKeyword("KILL")
834 | keywordLen = newKeyword("LEN")
835 | keywordRefCount = newKeyword("REFCOUNT")
836 | keywordEncoding = newKeyword("ENCODING")
837 | keywordIdleTime = newKeyword("IDLETIME")
838 | keywordGetName = newKeyword("GETNAME")
839 | keywordSetName = newKeyword("SETNAME")
840 | keywordList = newKeyword("LIST")
841 | keywordMatch = newKeyword("MATCH")
842 | keywordCount = newKeyword("COUNT")
843 | keywordPing = newKeyword("PING")
844 | keywordPong = newKeyword("PONG")
845 | keywordUnload = newKeyword("UNLOAD")
846 | keywordReplace = newKeyword("REPLACE")
847 | keywordKeys = newKeyword("KEYS")
848 | keywordPause = newKeyword("PAUSE")
849 | keywordDoctor = newKeyword("DOCTOR")
850 | keywordBlock = newKeyword("BLOCK")
851 | keywordNoAck = newKeyword("NOACK")
852 | keywordStreams = newKeyword("STREAMS")
853 | keywordKey = newKeyword("KEY")
854 | keywordCreate = newKeyword("CREATE")
855 | keywordMkStream = newKeyword("MKSTREAM")
856 | keywordSetID = newKeyword("SETID")
857 | keywordDestroy = newKeyword("DESTROY")
858 | keywordDelConsumer = newKeyword("DELCONSUMER")
859 | keywordMaxLen = newKeyword("MAXLEN")
860 | keywordGroup = newKeyword("GROUP")
861 | keywordIdle = newKeyword("IDLE")
862 | keywordTime = newKeyword("TIME")
863 | keywordRetryCount = newKeyword("RETRYCOUNT")
864 | keywordForce = newKeyword("FORCE")
865 | )
866 |
--------------------------------------------------------------------------------
/redis_advanced_cmd_test.go:
--------------------------------------------------------------------------------
1 | package godis
2 |
3 | import (
4 | "github.com/stretchr/testify/assert"
5 | "testing"
6 | "time"
7 | )
8 |
9 | func TestRedis_ConfigGet(t *testing.T) {
10 | redis := NewRedis(option)
11 | defer redis.Close()
12 | reply, err := redis.ConfigGet("timeout")
13 | assert.Nil(t, err, "err is nil")
14 | assert.Equal(t, []string{"timeout", "0"}, reply)
15 |
16 | redisBroken := NewRedis(option)
17 | defer redisBroken.Close()
18 | redisBroken.client.connection.host = "localhost1"
19 | redisBroken.Close()
20 | _, err = redisBroken.ConfigGet("timeout")
21 | assert.NotNil(t, err)
22 | }
23 |
24 | func TestRedis_ConfigSet(t *testing.T) {
25 | redis := NewRedis(option)
26 | defer redis.Close()
27 | reply, err := redis.ConfigSet("timeout", "30")
28 | assert.Nil(t, err)
29 | assert.Equal(t, "OK", reply)
30 | reply1, err := redis.ConfigGet("timeout")
31 | assert.Nil(t, err)
32 | assert.Equal(t, []string{"timeout", "30"}, reply1)
33 | reply, err = redis.ConfigSet("timeout", "0")
34 | assert.Nil(t, err)
35 | assert.Equal(t, "OK", reply)
36 | reply1, err = redis.ConfigGet("timeout")
37 | assert.Nil(t, err)
38 | assert.Equal(t, []string{"timeout", "0"}, reply1)
39 |
40 | redisBroken := NewRedis(option)
41 | defer redisBroken.Close()
42 | redisBroken.client.connection.host = "localhost1"
43 | redisBroken.Close()
44 | _, err = redisBroken.ConfigSet("timeout", "30")
45 | assert.NotNil(t, err)
46 | }
47 |
48 | func TestRedis_SlowlogGet(t *testing.T) {
49 | redis := NewRedis(option)
50 | defer redis.Close()
51 | flushAll()
52 | arr, err := redis.SlowLogGet()
53 | assert.Nil(t, err)
54 | t.Log(arr)
55 | //assert.NotEmpty(t, arr)
56 |
57 | redis.SlowLogGet(1)
58 |
59 | redisBroken := NewRedis(option)
60 | defer redisBroken.Close()
61 | redisBroken.client.connection.host = "localhost1"
62 | redisBroken.Close()
63 | _, err = redisBroken.SlowLogGet()
64 | assert.NotNil(t, err)
65 | }
66 |
67 | func TestRedis_SlowlogLen(t *testing.T) {
68 | redis := NewRedis(option)
69 | defer redis.Close()
70 | l, err := redis.SlowLogLen()
71 | assert.Nil(t, err)
72 | assert.True(t, l >= 0)
73 |
74 | redisBroken := NewRedis(option)
75 | defer redisBroken.Close()
76 | redisBroken.client.connection.host = "localhost1"
77 | redisBroken.Close()
78 | _, err = redisBroken.SlowLogLen()
79 | assert.NotNil(t, err)
80 | }
81 |
82 | func TestRedis_SlowlogReset(t *testing.T) {
83 | redis := NewRedis(option)
84 | defer redis.Close()
85 | str, err := redis.SlowLogReset()
86 | assert.Nil(t, err)
87 | assert.Equal(t, "OK", str)
88 |
89 | redisBroken := NewRedis(option)
90 | defer redisBroken.Close()
91 | redisBroken.client.connection.host = "localhost1"
92 | redisBroken.Close()
93 | _, err = redisBroken.SlowLogReset()
94 | assert.NotNil(t, err)
95 | }
96 |
97 | func TestRedis_ObjectEncoding(t *testing.T) {
98 | flushAll()
99 | redis := NewRedis(option)
100 | defer redis.Close()
101 | redis.Set("godis", "good")
102 | encode, err := redis.ObjectEncoding("godis")
103 | assert.Nil(t, err)
104 | assert.Equal(t, "embstr", encode)
105 | redis.Set("godis", "12")
106 | encode, err = redis.ObjectEncoding("godis")
107 | assert.Nil(t, err)
108 | assert.Equal(t, "int", encode)
109 |
110 | redisBroken := NewRedis(option)
111 | defer redisBroken.Close()
112 | redisBroken.client.connection.host = "localhost1"
113 | redisBroken.Close()
114 | _, err = redisBroken.ObjectEncoding("godis")
115 | assert.NotNil(t, err)
116 | }
117 |
118 | func TestRedis_ObjectIdletime(t *testing.T) {
119 | flushAll()
120 | redis := NewRedis(option)
121 | defer redis.Close()
122 | redis.Set("godis", "good")
123 | time.Sleep(1000 * time.Millisecond)
124 | idle, err := redis.ObjectIdleTime("godis")
125 | assert.Nil(t, err)
126 | assert.True(t, idle > 0)
127 |
128 | redisBroken := NewRedis(option)
129 | defer redisBroken.Close()
130 | redisBroken.client.connection.host = "localhost1"
131 | redisBroken.Close()
132 | _, err = redisBroken.ObjectIdleTime("godis")
133 | assert.NotNil(t, err)
134 | }
135 |
136 | func TestRedis_ObjectRefcount(t *testing.T) {
137 | flushAll()
138 | redis := NewRedis(option)
139 | defer redis.Close()
140 | redis.Set("godis", "good")
141 | count, err := redis.ObjectRefCount("godis")
142 | assert.Nil(t, err)
143 | assert.Equal(t, int64(1), count)
144 |
145 | redisBroken := NewRedis(option)
146 | defer redisBroken.Close()
147 | redisBroken.client.connection.host = "localhost1"
148 | redisBroken.Close()
149 | _, err = redisBroken.ObjectRefCount("godis")
150 | assert.NotNil(t, err)
151 | }
152 |
--------------------------------------------------------------------------------
/redis_basic_cmd_test.go:
--------------------------------------------------------------------------------
1 | package godis
2 |
3 | import (
4 | "github.com/stretchr/testify/assert"
5 | "testing"
6 | "time"
7 | )
8 |
9 | func TestRedis_Auth(t *testing.T) {
10 | redis := NewRedis(option)
11 | ok, err := redis.Auth("")
12 | redis.Close()
13 | assert.NotNil(t, err)
14 | assert.Equal(t, "", ok)
15 | redis = NewRedis(option)
16 | ok, err = redis.Auth("123456")
17 | redis.Close()
18 | assert.NotNil(t, err)
19 | assert.Equal(t, "", ok)
20 |
21 | redisBroken := NewRedis(option1)
22 | defer redisBroken.Close()
23 | m, _ := redisBroken.Multi()
24 | _, err = redisBroken.Auth("123456")
25 | assert.NotNil(t, err)
26 | m.Discard()
27 | redisBroken.client.connection.host = "localhost1"
28 | redisBroken.Close()
29 | _, err = redisBroken.Auth("123456")
30 | assert.NotNil(t, err)
31 | }
32 |
33 | func TestRedis_Ping(t *testing.T) {
34 | redis := NewRedis(option)
35 | defer redis.Close()
36 | ret, err := redis.Ping()
37 | assert.Nil(t, err)
38 | assert.Equal(t, "PONG", ret)
39 |
40 | redisBroken := NewRedis(option1)
41 | defer redisBroken.Close()
42 | m, _ := redisBroken.Multi()
43 | _, err = redisBroken.Ping()
44 | assert.NotNil(t, err)
45 | m.Discard()
46 | redisBroken.client.connection.host = "localhost1"
47 | redisBroken.Close()
48 | _, err = redisBroken.Ping()
49 | assert.NotNil(t, err)
50 | }
51 |
52 | func TestRedis_Quit(t *testing.T) {
53 | redis := NewRedis(option)
54 | defer redis.Close()
55 | ret, err := redis.Quit()
56 | assert.Nil(t, err)
57 | assert.Equal(t, "OK", ret)
58 |
59 | redisBroken := NewRedis(option1)
60 | defer redisBroken.Close()
61 | m, _ := redisBroken.Multi()
62 | _, err = redisBroken.Quit()
63 | assert.NotNil(t, err)
64 | m.Discard()
65 | redisBroken.client.connection.host = "localhost1"
66 | redisBroken.Close()
67 | _, err = redisBroken.Quit()
68 | assert.NotNil(t, err)
69 | }
70 |
71 | func TestRedis_FlushDB(t *testing.T) {
72 | redis := NewRedis(option)
73 | redis.Set("godis", "good")
74 | redis.Close()
75 | redis = NewRedis(option)
76 | reply, err := redis.Get("godis")
77 | assert.Nil(t, err)
78 | assert.Equal(t, "good", reply)
79 | redis = NewRedis(option)
80 | redis.Select(2)
81 | redis.Set("godis", "good")
82 | reply, err = redis.FlushDB()
83 | assert.Nil(t, err)
84 | assert.Equal(t, "OK", reply)
85 | reply, err = redis.Get("godis")
86 | redis.Close()
87 | assert.Nil(t, err)
88 | assert.Equal(t, "", reply)
89 | redis = NewRedis(option)
90 | redis.Select(0)
91 | reply, err = redis.Get("godis")
92 | redis.Close()
93 | assert.Nil(t, err)
94 | assert.Equal(t, "good", reply)
95 |
96 | redisBroken := NewRedis(option1)
97 | defer redisBroken.Close()
98 | m, _ := redisBroken.Multi()
99 | _, err = redisBroken.FlushDB()
100 | assert.NotNil(t, err)
101 | m.Discard()
102 | redisBroken.client.connection.host = "localhost1"
103 | redisBroken.Close()
104 | _, err = redisBroken.FlushDB()
105 | assert.NotNil(t, err)
106 | }
107 |
108 | func TestRedis_FlushAll(t *testing.T) {
109 | redis := NewRedis(option)
110 | redis.Set("godis", "good")
111 | redis.Close()
112 | redis = NewRedis(option)
113 | reply, err := redis.Get("godis")
114 | redis.Close()
115 | assert.Nil(t, err)
116 | assert.Equal(t, "good", reply)
117 | redis = NewRedis(option)
118 | redis.Select(2)
119 | redis.Set("godis", "good")
120 | reply, err = redis.FlushAll()
121 | assert.Nil(t, err)
122 | assert.Equal(t, "OK", reply)
123 | reply, err = redis.Get("godis")
124 | redis.Close()
125 | assert.Nil(t, err)
126 | assert.Equal(t, "", reply)
127 | redis = NewRedis(option)
128 | redis.Select(0)
129 | reply, err = redis.Get("godis")
130 | redis.Close()
131 | assert.Nil(t, err)
132 | assert.Equal(t, "", reply)
133 |
134 | redisBroken := NewRedis(option1)
135 | defer redisBroken.Close()
136 | m, _ := redisBroken.Multi()
137 | _, err = redisBroken.FlushAll()
138 | assert.NotNil(t, err)
139 | m.Discard()
140 | redisBroken.client.connection.host = "localhost1"
141 | redisBroken.Close()
142 | _, err = redisBroken.FlushAll()
143 | assert.NotNil(t, err)
144 | }
145 |
146 | func TestRedis_DbSize(t *testing.T) {
147 | flushAll()
148 | redis := NewRedis(option)
149 | redis.Set("godis", "good")
150 | redis.Close()
151 | redis = NewRedis(option)
152 | redis.Set("godis1", "good")
153 | redis.Close()
154 | redis = NewRedis(option)
155 | ret, err := redis.DbSize()
156 | redis.Close()
157 | assert.Nil(t, err)
158 | assert.Equal(t, int64(2), ret)
159 |
160 | redisBroken := NewRedis(option1)
161 | defer redisBroken.Close()
162 | m, _ := redisBroken.Multi()
163 | _, err = redisBroken.DbSize()
164 | assert.NotNil(t, err)
165 | m.Discard()
166 | redisBroken.client.connection.host = "localhost1"
167 | redisBroken.Close()
168 | _, err = redisBroken.DbSize()
169 | assert.NotNil(t, err)
170 | }
171 |
172 | func TestRedis_Select(t *testing.T) {
173 | redis := NewRedis(option)
174 | ret, err := redis.Select(15)
175 | redis = NewRedis(option)
176 | assert.Nil(t, err)
177 | assert.Equal(t, "OK", ret)
178 | redis = NewRedis(option)
179 | ret, err = redis.Select(16)
180 | redis.Close()
181 | assert.NotNil(t, err)
182 | assert.Equal(t, "", ret)
183 |
184 | redisBroken := NewRedis(option1)
185 | defer redisBroken.Close()
186 | m, _ := redisBroken.Multi()
187 | _, err = redisBroken.Select(15)
188 | assert.NotNil(t, err)
189 | m.Discard()
190 | redisBroken.client.connection.host = "localhost1"
191 | redisBroken.Close()
192 | _, err = redisBroken.Select(15)
193 | assert.NotNil(t, err)
194 | }
195 |
196 | func TestRedis_Save(t *testing.T) {
197 | flushAll()
198 | redis := NewRedis(option)
199 | redis.Set("godis", "good")
200 | redis.Close()
201 | redis = NewRedis(option)
202 | ret, err := redis.Save()
203 | redis.Close()
204 | assert.Nil(t, err)
205 | assert.Equal(t, "OK", ret)
206 |
207 | redisBroken := NewRedis(option1)
208 | defer redisBroken.Close()
209 | m, _ := redisBroken.Multi()
210 | _, err = redisBroken.Save()
211 | assert.Nil(t, err)
212 | m.Discard()
213 | redisBroken.client.connection.host = "localhost1"
214 | redisBroken.Close()
215 | _, err = redisBroken.Save()
216 | assert.NotNil(t, err)
217 | }
218 |
219 | func TestRedis_Bgsave(t *testing.T) {
220 | flushAll()
221 | redis := NewRedis(option)
222 | redis.Set("godis", "good")
223 | redis.Close()
224 | redis = NewRedis(option)
225 | _, err := redis.BgSave()
226 | redis.Close()
227 | assert.Nil(t, err)
228 |
229 | redisBroken := NewRedis(option1)
230 | defer redisBroken.Close()
231 | m, _ := redisBroken.Multi()
232 | _, err = redisBroken.BgSave()
233 | assert.Nil(t, err)
234 | m.Discard()
235 | redisBroken.client.connection.host = "localhost1"
236 | redisBroken.Close()
237 | _, err = redisBroken.BgSave()
238 | assert.NotNil(t, err)
239 | }
240 |
241 | func TestRedis_Bgrewriteaof(t *testing.T) {
242 | flushAll()
243 | redis := NewRedis(option)
244 | redis.Set("godis", "good")
245 | redis.Close()
246 | redis = NewRedis(option)
247 | _, err := redis.BgRewriteAof()
248 | redis.Close()
249 | assert.Nil(t, err)
250 |
251 | redisBroken := NewRedis(option1)
252 | defer redisBroken.Close()
253 | m, _ := redisBroken.Multi()
254 | _, err = redisBroken.BgRewriteAof()
255 | assert.Nil(t, err)
256 | m.Discard()
257 | redisBroken.client.connection.host = "localhost1"
258 | redisBroken.Close()
259 | _, err = redisBroken.BgRewriteAof()
260 | assert.NotNil(t, err)
261 | }
262 |
263 | func TestRedis_Lastsave(t *testing.T) {
264 | redis := NewRedis(option)
265 | defer redis.Close()
266 | _, err := redis.LastSave()
267 | assert.Nil(t, err)
268 |
269 | redisBroken := NewRedis(option1)
270 | defer redisBroken.Close()
271 | m, _ := redisBroken.Multi()
272 | _, err = redisBroken.LastSave()
273 | assert.Nil(t, err)
274 | m.Discard()
275 | redisBroken.client.connection.host = "localhost1"
276 | redisBroken.Close()
277 | _, err = redisBroken.LastSave()
278 | assert.NotNil(t, err)
279 | }
280 |
281 | // ignore this case,cause it will shutdown redis
282 | func TestRedis_Shutdown(t *testing.T) {
283 | redis := NewRedis(&Option{
284 | Host: "localhost",
285 | Port: 8888,
286 | })
287 | defer redis.Close()
288 | _, err := redis.Shutdown()
289 | assert.NotNil(t, err)
290 |
291 | time.Sleep(time.Second)
292 | redis1 := NewRedis(option)
293 | defer redis1.Close()
294 | s, err := redis1.Set("godis", "good")
295 | assert.Nil(t, err)
296 | assert.Equal(t, "OK", s)
297 |
298 | redisBroken := NewRedis(option1)
299 | defer redisBroken.Close()
300 | m, _ := redisBroken.Multi()
301 | _, err = redisBroken.Shutdown()
302 | assert.Nil(t, err)
303 | m.Discard()
304 | redisBroken.client.connection.host = "localhost1"
305 | redisBroken.Close()
306 | _, err = redisBroken.Shutdown()
307 | assert.NotNil(t, err)
308 | }
309 |
310 | func TestRedis_Info(t *testing.T) {
311 | redis := NewRedis(option)
312 | _, err := redis.Info()
313 | redis.Close()
314 | assert.Nil(t, err)
315 | redis = NewRedis(option)
316 | _, err = redis.Info("stats")
317 | redis.Close()
318 | assert.Nil(t, err)
319 | redis = NewRedis(option)
320 | _, err = redis.Info("clients", "memory")
321 | redis.Close()
322 | assert.NotNil(t, err)
323 |
324 | redisBroken := NewRedis(option1)
325 | defer redisBroken.Close()
326 | m, _ := redisBroken.Multi()
327 | _, err = redisBroken.Info()
328 | assert.Nil(t, err)
329 | m.Discard()
330 | redisBroken.client.connection.host = "localhost1"
331 | redisBroken.Close()
332 | _, err = redisBroken.Info()
333 | assert.NotNil(t, err)
334 | }
335 |
336 | func TestRedis_Slaveof(t *testing.T) {
337 | redis := NewRedis(option)
338 | defer redis.Close()
339 | _, err := redis.SlaveOf("localhost", 6379)
340 | assert.Nil(t, err)
341 |
342 | redisBroken := NewRedis(option1)
343 | defer redisBroken.Close()
344 | m, _ := redisBroken.Multi()
345 | _, err = redisBroken.SlaveOf("localhost", 6379)
346 | assert.Nil(t, err)
347 | m.Discard()
348 | redisBroken.client.connection.host = "localhost1"
349 | redisBroken.Close()
350 | _, err = redisBroken.SlaveOf("localhost", 6379)
351 | assert.NotNil(t, err)
352 | }
353 |
354 | func TestRedis_SlaveofNoOne(t *testing.T) {
355 | redis := NewRedis(option)
356 | defer redis.Close()
357 | _, err := redis.SlaveOfNoOne()
358 | assert.Nil(t, err)
359 |
360 | redisBroken := NewRedis(option1)
361 | defer redisBroken.Close()
362 | m, _ := redisBroken.Multi()
363 | _, err = redisBroken.SlaveOfNoOne()
364 | assert.Nil(t, err)
365 | m.Discard()
366 | redisBroken.client.connection.host = "localhost1"
367 | redisBroken.Close()
368 | _, err = redisBroken.SlaveOfNoOne()
369 | assert.NotNil(t, err)
370 | }
371 |
372 | func TestRedis_Debug(t *testing.T) {
373 | flushAll()
374 | redis := NewRedis(option)
375 | defer redis.Close()
376 | redis.Set("godis", "good")
377 | _, err := redis.Debug(*NewDebugParamsObject("godis"))
378 | assert.Nil(t, err)
379 |
380 | //_, err = redis.Debug(*NewDebugParamsSegfault())
381 | //assert.NotNil(t, err) //EOF error
382 | //
383 | //get, err := redis.Get("godis")
384 | //assert.NotNil(t, err)
385 | //assert.Equal(t, "", get)
386 | //
387 | //time.Sleep(1 * time.Second)
388 | redis1 := NewRedis(option)
389 | defer redis1.Close()
390 | //get, err := redis1.Get("godis")
391 | //assert.Nil(t, err)
392 | //assert.Equal(t, "", get)
393 |
394 | _, err = redis1.Debug(*NewDebugParamsReload())
395 | assert.Nil(t, err) //EOF error
396 |
397 | get, err := redis1.Get("godis")
398 | assert.Nil(t, err)
399 | assert.Equal(t, "good", get)
400 |
401 | redisBroken := NewRedis(option1)
402 | defer redisBroken.Close()
403 | m, _ := redisBroken.Multi()
404 | _, err = redisBroken.Debug(*NewDebugParamsObject("godis"))
405 | assert.Nil(t, err)
406 | m.Discard()
407 | redisBroken.client.connection.host = "localhost1"
408 | redisBroken.Close()
409 | _, err = redisBroken.Debug(*NewDebugParamsObject("godis"))
410 | assert.NotNil(t, err)
411 | }
412 |
413 | func TestRedis_ConfigResetStat(t *testing.T) {
414 | redis := NewRedis(option)
415 | defer redis.Close()
416 | _, err := redis.ConfigResetStat()
417 | assert.Nil(t, err)
418 |
419 | redisBroken := NewRedis(option1)
420 | defer redisBroken.Close()
421 | m, _ := redisBroken.Multi()
422 | _, err = redisBroken.ConfigResetStat()
423 | assert.Nil(t, err)
424 | m.Discard()
425 | redisBroken.client.connection.host = "localhost1"
426 | redisBroken.Close()
427 | _, err = redisBroken.ConfigResetStat()
428 | assert.NotNil(t, err)
429 | }
430 |
431 | func TestRedis_WaitReplicas(t *testing.T) {
432 | redis := NewRedis(option)
433 | defer redis.Close()
434 | ret, err := redis.WaitReplicas(1, 1)
435 | assert.Nil(t, err)
436 | assert.Equal(t, int64(0), ret)
437 |
438 | redisBroken := NewRedis(option1)
439 | defer redisBroken.Close()
440 | m, _ := redisBroken.Multi()
441 | _, err = redisBroken.WaitReplicas(1, 1)
442 | assert.NotNil(t, err)
443 | m.Discard()
444 | redisBroken.client.connection.host = "localhost1"
445 | redisBroken.Close()
446 | _, err = redisBroken.WaitReplicas(1, 1)
447 | assert.NotNil(t, err)
448 | }
449 |
--------------------------------------------------------------------------------
/redis_bench_test.go:
--------------------------------------------------------------------------------
1 | package godis
2 |
3 | import "testing"
4 |
5 | func BenchmarkSet(b *testing.B) {
6 | b.ResetTimer()
7 | pool := NewPool(nil, option)
8 | for i := 0; i < b.N; i++ {
9 | redis, _ := pool.GetResource()
10 | redis.Set("godis", "good")
11 | redis.Close()
12 | }
13 | }
14 |
15 | func BenchmarkGet(b *testing.B) {
16 | b.ResetTimer()
17 | pool := NewPool(nil, option)
18 | for i := 0; i < b.N; i++ {
19 | redis, _ := pool.GetResource()
20 | redis.Get("godis")
21 | redis.Close()
22 | }
23 | }
24 |
25 | func BenchmarkIncr(b *testing.B) {
26 | flushAll()
27 | b.ResetTimer()
28 | pool := NewPool(nil, option)
29 | i := 0
30 | for ; i < b.N; i++ {
31 | redis, _ := pool.GetResource()
32 | redis.Incr("godis")
33 | redis.Close()
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/redis_cluster_cmd_test.go:
--------------------------------------------------------------------------------
1 | package godis
2 |
3 | import (
4 | "github.com/stretchr/testify/assert"
5 | "strings"
6 | "testing"
7 | )
8 |
9 | var option1 = &Option{
10 | Host: "localhost",
11 | Port: 7000,
12 | }
13 |
14 | func TestRedis_ClusterAddSlots(t *testing.T) {
15 | redis := NewRedis(option1)
16 | defer redis.Close()
17 | slots, err := redis.ClusterAddSlots(10000)
18 | assert.NotNil(t, err)
19 | assert.Equal(t, "", slots)
20 |
21 | redisBroken := NewRedis(option1)
22 | defer redisBroken.Close()
23 | m, _ := redisBroken.Multi()
24 | _, err = redisBroken.ClusterAddSlots(10000)
25 | assert.NotNil(t, err)
26 | m.Discard()
27 | redisBroken.client.connection.host = "localhost1"
28 | redisBroken.Close()
29 | _, err = redisBroken.ClusterAddSlots(10000)
30 | assert.NotNil(t, err)
31 | }
32 |
33 | func TestRedis_ClusterCountKeysInSlot(t *testing.T) {
34 | redis := NewRedis(option1)
35 | defer redis.Close()
36 | slots, err := redis.ClusterCountKeysInSlot(10000)
37 | assert.Nil(t, err)
38 | assert.Equal(t, int64(0), slots)
39 |
40 | redisBroken := NewRedis(option1)
41 | defer redisBroken.Close()
42 | m, _ := redisBroken.Multi()
43 | _, err = redisBroken.ClusterCountKeysInSlot(10000)
44 | assert.NotNil(t, err)
45 | m.Discard()
46 | redisBroken.client.connection.host = "localhost1"
47 | redisBroken.Close()
48 | _, err = redisBroken.ClusterCountKeysInSlot(10000)
49 | assert.NotNil(t, err)
50 | }
51 |
52 | func TestRedis_ClusterDelSlots(t *testing.T) {
53 | redis := NewRedis(option)
54 | defer redis.Close()
55 | slots, err := redis.ClusterDelSlots(10000)
56 | assert.NotNil(t, err)
57 | assert.Equal(t, "", slots)
58 |
59 | redisBroken := NewRedis(option1)
60 | defer redisBroken.Close()
61 | m, _ := redisBroken.Multi()
62 | _, err = redisBroken.ClusterDelSlots(10000)
63 | assert.NotNil(t, err)
64 | m.Discard()
65 | redisBroken.client.connection.host = "localhost1"
66 | redisBroken.Close()
67 | _, err = redisBroken.ClusterDelSlots(10000)
68 | assert.NotNil(t, err)
69 | }
70 |
71 | func TestRedis_ClusterFailover(t *testing.T) {
72 | redis := NewRedis(option1)
73 | defer redis.Close()
74 | slots, err := redis.ClusterFailOver()
75 | assert.NotNil(t, err)
76 | assert.Equal(t, "", slots)
77 |
78 | redisBroken := NewRedis(option1)
79 | defer redisBroken.Close()
80 | m, _ := redisBroken.Multi()
81 | _, err = redisBroken.ClusterFailOver()
82 | assert.NotNil(t, err)
83 | m.Discard()
84 | redisBroken.client.connection.host = "localhost1"
85 | redisBroken.Close()
86 | _, err = redisBroken.ClusterFailOver()
87 | assert.NotNil(t, err)
88 | }
89 |
90 | func TestRedis_ClusterFlushSlots(t *testing.T) {
91 | redis := NewRedis(option)
92 | defer redis.Close()
93 | slots, err := redis.ClusterFlushSlots()
94 | assert.NotNil(t, err)
95 | assert.Equal(t, "", slots)
96 |
97 | redisBroken := NewRedis(option1)
98 | defer redisBroken.Close()
99 | m, _ := redisBroken.Multi()
100 | _, err = redisBroken.ClusterFlushSlots()
101 | assert.NotNil(t, err)
102 | m.Discard()
103 | redisBroken.client.connection.host = "localhost1"
104 | redisBroken.Close()
105 | _, err = redisBroken.ClusterFlushSlots()
106 | assert.NotNil(t, err)
107 | }
108 |
109 | func TestRedis_ClusterForget(t *testing.T) {
110 | redis := NewRedis(option1)
111 | defer redis.Close()
112 | _, err := redis.ClusterForget("1")
113 | assert.NotNil(t, err)
114 |
115 | redisBroken := NewRedis(option1)
116 | defer redisBroken.Close()
117 | m, _ := redisBroken.Multi()
118 | _, err = redisBroken.ClusterForget("1")
119 | assert.NotNil(t, err)
120 | m.Discard()
121 | redisBroken.client.connection.host = "localhost1"
122 | redisBroken.Close()
123 | _, err = redisBroken.ClusterForget("1")
124 | assert.NotNil(t, err)
125 | }
126 |
127 | func TestRedis_ClusterGetKeysInSlot(t *testing.T) {
128 | redis := NewRedis(option)
129 | defer redis.Close()
130 | slots, err := redis.ClusterGetKeysInSlot(1, 1)
131 | assert.NotNil(t, err)
132 | assert.Empty(t, slots)
133 |
134 | redisBroken := NewRedis(option1)
135 | defer redisBroken.Close()
136 | m, _ := redisBroken.Multi()
137 | _, err = redisBroken.ClusterGetKeysInSlot(1, 1)
138 | assert.NotNil(t, err)
139 | m.Discard()
140 | redisBroken.client.connection.host = "localhost1"
141 | redisBroken.Close()
142 | _, err = redisBroken.ClusterGetKeysInSlot(1, 1)
143 | assert.NotNil(t, err)
144 | }
145 |
146 | func TestRedis_ClusterInfo(t *testing.T) {
147 | redis := NewRedis(option1)
148 | defer redis.Close()
149 | s, err := redis.ClusterInfo()
150 | assert.Nil(t, err)
151 | assert.NotEmpty(t, s)
152 |
153 | redisBroken := NewRedis(option1)
154 | defer redisBroken.Close()
155 | m, _ := redisBroken.Multi()
156 | _, err = redisBroken.ClusterInfo()
157 | assert.NotNil(t, err)
158 | m.Discard()
159 | redisBroken.client.connection.host = "localhost1"
160 | redisBroken.Close()
161 | _, err = redisBroken.ClusterInfo()
162 | assert.NotNil(t, err)
163 | }
164 |
165 | func TestRedis_ClusterKeySlot(t *testing.T) {
166 | redis := NewRedis(option)
167 | defer redis.Close()
168 | slots, err := redis.ClusterKeySlot("godis")
169 | assert.NotNil(t, err)
170 | assert.Equal(t, int64(0), slots)
171 |
172 | redisBroken := NewRedis(option1)
173 | defer redisBroken.Close()
174 | m, _ := redisBroken.Multi()
175 | _, err = redisBroken.ClusterKeySlot("godis")
176 | assert.NotNil(t, err)
177 | m.Discard()
178 | redisBroken.client.connection.host = "localhost1"
179 | redisBroken.Close()
180 | _, err = redisBroken.ClusterKeySlot("godis")
181 | assert.NotNil(t, err)
182 | }
183 |
184 | func TestRedis_ClusterMeet(t *testing.T) {
185 | redis := NewRedis(option)
186 | defer redis.Close()
187 | slots, err := redis.ClusterMeet("localhost", 8000)
188 | assert.NotNil(t, err)
189 | assert.Equal(t, "", slots)
190 |
191 | redisBroken := NewRedis(option1)
192 | defer redisBroken.Close()
193 | m, _ := redisBroken.Multi()
194 | _, err = redisBroken.ClusterMeet("localhost", 8000)
195 | assert.NotNil(t, err)
196 | m.Discard()
197 | redisBroken.client.connection.host = "localhost1"
198 | redisBroken.Close()
199 | _, err = redisBroken.ClusterMeet("localhost", 8000)
200 | assert.NotNil(t, err)
201 | }
202 |
203 | func TestRedis_ClusterNodes(t *testing.T) {
204 | redis := NewRedis(option1)
205 | defer redis.Close()
206 | s, err := redis.ClusterNodes()
207 | assert.Nil(t, err)
208 | assert.NotEmpty(t, s)
209 | //t.Log(s)
210 |
211 | nodeID := s[:strings.Index(s, " ")]
212 | redis.ClusterSlaves(nodeID)
213 | //assert.Nil(t, err)
214 | //assert.NotEmpty(t, slaves)
215 |
216 | redisBroken := NewRedis(option1)
217 | defer redisBroken.Close()
218 | m, _ := redisBroken.Multi()
219 | _, err = redisBroken.ClusterNodes()
220 | assert.NotNil(t, err)
221 | m.Discard()
222 | redisBroken.client.connection.host = "localhost1"
223 | redisBroken.Close()
224 | _, err = redisBroken.ClusterNodes()
225 | assert.NotNil(t, err)
226 | }
227 |
228 | func TestRedis_ClusterReplicate(t *testing.T) {
229 | redis := NewRedis(option)
230 | defer redis.Close()
231 | slots, err := redis.ClusterReplicate("godis")
232 | assert.NotNil(t, err)
233 | assert.Equal(t, "", slots)
234 |
235 | redisBroken := NewRedis(option1)
236 | defer redisBroken.Close()
237 | m, _ := redisBroken.Multi()
238 | _, err = redisBroken.ClusterReplicate("godis")
239 | assert.NotNil(t, err)
240 | m.Discard()
241 | redisBroken.client.connection.host = "localhost1"
242 | redisBroken.Close()
243 | _, err = redisBroken.ClusterReplicate("godis")
244 | assert.NotNil(t, err)
245 | }
246 |
247 | func TestRedis_ClusterReset(t *testing.T) {
248 | redis := NewRedis(option)
249 | defer redis.Close()
250 | slots, err := redis.ClusterReset(*ResetSoft)
251 | assert.NotNil(t, err)
252 | assert.Equal(t, "", slots)
253 |
254 | redisBroken := NewRedis(option1)
255 | defer redisBroken.Close()
256 | m, _ := redisBroken.Multi()
257 | _, err = redisBroken.ClusterReset(*ResetSoft)
258 | assert.NotNil(t, err)
259 | m.Discard()
260 | redisBroken.client.connection.host = "localhost1"
261 | redisBroken.Close()
262 | _, err = redisBroken.ClusterReset(*ResetSoft)
263 | assert.NotNil(t, err)
264 | }
265 |
266 | func TestRedis_ClusterSaveConfig(t *testing.T) {
267 | redis := NewRedis(option)
268 | defer redis.Close()
269 | slots, err := redis.ClusterSaveConfig()
270 | assert.NotNil(t, err)
271 | assert.Equal(t, "", slots)
272 |
273 | redisBroken := NewRedis(option1)
274 | defer redisBroken.Close()
275 | m, _ := redisBroken.Multi()
276 | _, err = redisBroken.ClusterSaveConfig()
277 | assert.NotNil(t, err)
278 | m.Discard()
279 | redisBroken.client.connection.host = "localhost1"
280 | redisBroken.Close()
281 | _, err = redisBroken.ClusterSaveConfig()
282 | assert.NotNil(t, err)
283 | }
284 |
285 | func TestRedis_ClusterSetSlotImporting(t *testing.T) {
286 | redis := NewRedis(option)
287 | defer redis.Close()
288 | slots, err := redis.ClusterSetSlotImporting(1, "godis")
289 | assert.NotNil(t, err)
290 | assert.Equal(t, "", slots)
291 |
292 | redisBroken := NewRedis(option1)
293 | defer redisBroken.Close()
294 | m, _ := redisBroken.Multi()
295 | _, err = redisBroken.ClusterSetSlotImporting(1, "godis")
296 | assert.NotNil(t, err)
297 | m.Discard()
298 | redisBroken.client.connection.host = "localhost1"
299 | redisBroken.Close()
300 | _, err = redisBroken.ClusterSetSlotImporting(1, "godis")
301 | assert.NotNil(t, err)
302 | }
303 |
304 | func TestRedis_ClusterSetSlotMigrating(t *testing.T) {
305 | redis := NewRedis(option)
306 | defer redis.Close()
307 | slots, err := redis.ClusterSetSlotMigrating(1, "godis")
308 | assert.NotNil(t, err)
309 | assert.Equal(t, "", slots)
310 |
311 | redisBroken := NewRedis(option1)
312 | defer redisBroken.Close()
313 | m, _ := redisBroken.Multi()
314 | _, err = redisBroken.ClusterSetSlotMigrating(1, "godis")
315 | assert.NotNil(t, err)
316 | m.Discard()
317 | redisBroken.client.connection.host = "localhost1"
318 | redisBroken.Close()
319 | _, err = redisBroken.ClusterSetSlotMigrating(1, "godis")
320 | assert.NotNil(t, err)
321 | }
322 |
323 | func TestRedis_ClusterSetSlotNode(t *testing.T) {
324 | redis := NewRedis(option)
325 | defer redis.Close()
326 | slots, err := redis.ClusterSetSlotNode(1, "godis")
327 | assert.NotNil(t, err)
328 | assert.Equal(t, "", slots)
329 |
330 | redisBroken := NewRedis(option1)
331 | defer redisBroken.Close()
332 | m, _ := redisBroken.Multi()
333 | _, err = redisBroken.ClusterSetSlotNode(1, "godis")
334 | assert.NotNil(t, err)
335 | m.Discard()
336 | redisBroken.client.connection.host = "localhost1"
337 | redisBroken.Close()
338 | _, err = redisBroken.ClusterSetSlotNode(1, "godis")
339 | assert.NotNil(t, err)
340 | }
341 |
342 | func TestRedis_ClusterSetSlotStable(t *testing.T) {
343 | redis := NewRedis(option)
344 | defer redis.Close()
345 | slots, err := redis.ClusterSetSlotStable(1)
346 | assert.NotNil(t, err)
347 | assert.Equal(t, "", slots)
348 |
349 | redisBroken := NewRedis(option1)
350 | defer redisBroken.Close()
351 | m, _ := redisBroken.Multi()
352 | _, err = redisBroken.ClusterSetSlotStable(1)
353 | assert.NotNil(t, err)
354 | m.Discard()
355 | redisBroken.client.connection.host = "localhost1"
356 | redisBroken.Close()
357 | _, err = redisBroken.ClusterSetSlotStable(1)
358 | assert.NotNil(t, err)
359 | }
360 |
361 | func TestRedis_ClusterSlots(t *testing.T) {
362 | redis := NewRedis(option1)
363 | defer redis.Close()
364 | s, err := redis.ClusterSlots()
365 | assert.Nil(t, err)
366 | assert.NotEmpty(t, s)
367 |
368 | redisBroken := NewRedis(option1)
369 | defer redisBroken.Close()
370 | m, _ := redisBroken.Multi()
371 | _, err = redisBroken.ClusterSlots()
372 | assert.NotNil(t, err)
373 | m.Discard()
374 | redisBroken.client.connection.host = "localhost1"
375 | redisBroken.Close()
376 | _, err = redisBroken.ClusterSlots()
377 | assert.NotNil(t, err)
378 | }
379 |
--------------------------------------------------------------------------------
/redis_multikey_cmd_test.go:
--------------------------------------------------------------------------------
1 | package godis
2 |
3 | import (
4 | "fmt"
5 | "github.com/stretchr/testify/assert"
6 | "testing"
7 | "time"
8 | )
9 |
10 | func TestRedis_Keys(t *testing.T) {
11 | initDb()
12 | redis := NewRedis(option)
13 | defer redis.Close()
14 | redis.Set("godis1", "good")
15 | arr, e := redis.Keys("*")
16 | assert.Nil(t, e)
17 | assert.ElementsMatch(t, []string{"godis", "godis1"}, arr)
18 |
19 | b, e := redis.Exists("godis")
20 | assert.Nil(t, e)
21 | assert.Equal(t, int64(1), b)
22 |
23 | redisBroken := NewRedis(option1)
24 | defer redisBroken.Close()
25 | m, _ := redisBroken.Multi()
26 | _, e = redisBroken.Keys("*")
27 | assert.NotNil(t, e)
28 | m.Discard()
29 | redisBroken.client.connection.host = "localhost1"
30 | redisBroken.Close()
31 | _, e = redisBroken.Keys("*")
32 | assert.NotNil(t, e)
33 | }
34 |
35 | func TestRedis_Del(t *testing.T) {
36 | initDb()
37 | redis := NewRedis(option)
38 | defer redis.Close()
39 | c, e := redis.Del("godis")
40 | assert.Nil(t, e)
41 | assert.Equal(t, int64(1), c)
42 |
43 | redisBroken := NewRedis(option1)
44 | defer redisBroken.Close()
45 | m, _ := redisBroken.Multi()
46 | _, e = redisBroken.Del("godis")
47 | assert.NotNil(t, e)
48 | m.Discard()
49 | redisBroken.client.connection.host = "localhost1"
50 | redisBroken.Close()
51 | _, e = redisBroken.Del("godis")
52 | assert.NotNil(t, e)
53 | }
54 |
55 | func TestRedis_Blpop(t *testing.T) {
56 | flushAll()
57 | redis := NewRedis(option)
58 | defer redis.Close()
59 | redis.LPush("command", "update system...")
60 | redis.LPush("request", "visit page")
61 |
62 | arr, e := redis.BLPop("job", "command", "request", "0")
63 | assert.Nil(t, e)
64 | assert.ElementsMatch(t, []string{"command", "update system..."}, arr)
65 |
66 | redisBroken := NewRedis(option1)
67 | defer redisBroken.Close()
68 | m, _ := redisBroken.Multi()
69 | _, e = redisBroken.BLPop("job", "command", "request", "0")
70 | assert.NotNil(t, e)
71 | m.Discard()
72 | redisBroken.client.connection.host = "localhost1"
73 | redisBroken.Close()
74 | _, e = redisBroken.BLPop("job", "command", "request", "0")
75 | assert.NotNil(t, e)
76 | }
77 |
78 | func TestRedis_BlpopTimout(t *testing.T) {
79 | flushAll()
80 | redis := NewRedis(option)
81 | defer redis.Close()
82 | go func() {
83 | redis := NewRedis(option)
84 | defer redis.Close()
85 | arr, e := redis.BLPopTimeout(5, "command", "update system...")
86 | assert.Nil(t, e)
87 | assert.ElementsMatch(t, []string{"command", "update system..."}, arr)
88 | }()
89 | time.Sleep(1 * time.Second)
90 | redis.LPush("command", "update system...")
91 | redis.LPush("request", "visit page")
92 | time.Sleep(1 * time.Second)
93 |
94 | redisBroken := NewRedis(option1)
95 | defer redisBroken.Close()
96 | m, _ := redisBroken.Multi()
97 | _, e := redisBroken.BLPopTimeout(5, "command", "update system...")
98 | assert.NotNil(t, e)
99 | m.Discard()
100 | redisBroken.client.connection.host = "localhost1"
101 | redisBroken.Close()
102 | _, e = redisBroken.BLPopTimeout(5, "command", "update system...")
103 | assert.NotNil(t, e)
104 | }
105 |
106 | func TestRedis_Brpop(t *testing.T) {
107 | flushAll()
108 | redis := NewRedis(option)
109 | defer redis.Close()
110 | redis.LPush("command", "update system...")
111 | redis.LPush("request", "visit page")
112 |
113 | arr, e := redis.BRPop("job", "command", "request", "0")
114 | assert.Nil(t, e)
115 | assert.ElementsMatch(t, []string{"command", "update system..."}, arr)
116 |
117 | redisBroken := NewRedis(option1)
118 | defer redisBroken.Close()
119 | m, _ := redisBroken.Multi()
120 | _, e = redisBroken.BRPop("job", "command", "request", "0")
121 | assert.NotNil(t, e)
122 | m.Discard()
123 | redisBroken.client.connection.host = "localhost1"
124 | redisBroken.Close()
125 | _, e = redisBroken.BRPop("job", "command", "request", "0")
126 | assert.NotNil(t, e)
127 | }
128 |
129 | func TestRedis_BrpopTimout(t *testing.T) {
130 | flushAll()
131 | redis := NewRedis(option)
132 | defer redis.Close()
133 | go func() {
134 | redis := NewRedis(option)
135 | defer redis.Close()
136 | arr, e := redis.BRPopTimeout(5, "command", "update system...")
137 | assert.Nil(t, e)
138 | assert.ElementsMatch(t, []string{"command", "update system..."}, arr)
139 | }()
140 | time.Sleep(1 * time.Second)
141 | redis.LPush("command", "update system...")
142 | redis.LPush("request", "visit page")
143 | time.Sleep(1 * time.Second)
144 |
145 | redisBroken := NewRedis(option1)
146 | defer redisBroken.Close()
147 | m, _ := redisBroken.Multi()
148 | _, e := redisBroken.BRPopTimeout(5, "command", "update system...")
149 | assert.NotNil(t, e)
150 | m.Discard()
151 | redisBroken.client.connection.host = "localhost1"
152 | redisBroken.Close()
153 | _, e = redisBroken.BRPopTimeout(5, "command", "update system...")
154 | assert.NotNil(t, e)
155 | }
156 |
157 | func TestRedis_Mset(t *testing.T) {
158 | initDb()
159 | redis := NewRedis(option)
160 | defer redis.Close()
161 |
162 | s, e := redis.MSet("godis1", "good", "godis2", "good")
163 | assert.Nil(t, e)
164 | assert.Equal(t, "OK", s)
165 |
166 | c, e := redis.MSetNx("godis1", "good1")
167 | assert.Nil(t, e)
168 | assert.Equal(t, int64(0), c)
169 |
170 | arr, e := redis.MGet("godis", "godis1", "godis2")
171 | assert.Nil(t, e)
172 | assert.ElementsMatch(t, []string{"good", "good", "good"}, arr)
173 |
174 | redisBroken := NewRedis(option1)
175 | defer redisBroken.Close()
176 | m, _ := redisBroken.Multi()
177 | _, e = redisBroken.MSet("godis1", "good", "godis2", "good")
178 | assert.NotNil(t, e)
179 | m.Discard()
180 | redisBroken.client.connection.host = "localhost1"
181 | redisBroken.Close()
182 | _, e = redisBroken.MSet("godis1", "good", "godis2", "good")
183 | assert.NotNil(t, e)
184 | }
185 |
186 | func TestRedis_Rename(t *testing.T) {
187 | initDb()
188 | redis := NewRedis(option)
189 | defer redis.Close()
190 | s, e := redis.Rename("godis", "godis1")
191 | assert.Nil(t, e)
192 | assert.Equal(t, "OK", s)
193 |
194 | redis.Set("godis", "good")
195 | c, e := redis.RenameNx("godis", "godis1")
196 | assert.Nil(t, e)
197 | assert.Equal(t, int64(0), c)
198 |
199 | redisBroken := NewRedis(option1)
200 | defer redisBroken.Close()
201 | m, _ := redisBroken.Multi()
202 | _, e = redisBroken.Rename("godis", "godis1")
203 | assert.NotNil(t, e)
204 | _, e = redisBroken.RenameNx("godis", "godis1")
205 | assert.NotNil(t, e)
206 | m.Discard()
207 | redisBroken.client.connection.host = "localhost1"
208 | redisBroken.Close()
209 | _, e = redisBroken.Rename("godis", "godis1")
210 | assert.NotNil(t, e)
211 | _, e = redisBroken.RenameNx("godis", "godis1")
212 | assert.NotNil(t, e)
213 | }
214 |
215 | func TestRedis_Brpoplpush(t *testing.T) {
216 | flushAll()
217 | redis := NewRedis(option)
218 | defer redis.Close()
219 | go func() {
220 | redis := NewRedis(option)
221 | defer redis.Close()
222 | arr, e := redis.BRPopLPush("command", "update system...", 5)
223 | assert.Nil(t, e)
224 | assert.Equal(t, "update system...", arr)
225 | }()
226 | time.Sleep(1 * time.Second)
227 | redis.LPush("command", "update system...")
228 | redis.LPush("request", "visit page")
229 | time.Sleep(1 * time.Second)
230 |
231 | redisBroken := NewRedis(option1)
232 | defer redisBroken.Close()
233 | m, _ := redisBroken.Multi()
234 | _, e := redisBroken.BRPopLPush("command", "update system...", 5)
235 | assert.NotNil(t, e)
236 | m.Discard()
237 | redisBroken.client.connection.host = "localhost1"
238 | redisBroken.Close()
239 | _, e = redisBroken.BRPopLPush("command", "update system...", 5)
240 | assert.NotNil(t, e)
241 | }
242 |
243 | func TestRedis_Sdiff(t *testing.T) {
244 | flushAll()
245 | redis := NewRedis(option)
246 | defer redis.Close()
247 | redis.SAdd("godis1", "1", "2", "3")
248 | redis.SAdd("godis2", "2", "3", "4")
249 |
250 | arr, e := redis.SDiff("godis1", "godis2")
251 | assert.Nil(t, e)
252 | assert.ElementsMatch(t, []string{"1"}, arr)
253 |
254 | c, e := redis.SDiffStore("godis3", "godis1", "godis2")
255 | assert.Nil(t, e)
256 | assert.Equal(t, int64(1), c)
257 |
258 | arr, e = redis.SMembers("godis3")
259 | assert.Nil(t, e)
260 | assert.ElementsMatch(t, []string{"1"}, arr)
261 |
262 | arr, e = redis.SInter("godis1", "godis2")
263 | assert.Nil(t, e)
264 | assert.ElementsMatch(t, []string{"2", "3"}, arr)
265 |
266 | c, e = redis.SInterStore("godis4", "godis1", "godis2")
267 | assert.Nil(t, e)
268 | assert.Equal(t, int64(2), c)
269 |
270 | arr, e = redis.SUnion("godis1", "godis2")
271 | assert.Nil(t, e)
272 | assert.ElementsMatch(t, []string{"1", "2", "3", "4"}, arr)
273 |
274 | c, e = redis.SUnionStore("godis5", "godis1", "godis2")
275 | assert.Nil(t, e)
276 | assert.Equal(t, int64(4), c)
277 |
278 | redisBroken := NewRedis(option1)
279 | defer redisBroken.Close()
280 | m, _ := redisBroken.Multi()
281 | _, e = redisBroken.SDiff("godis1", "godis2")
282 | assert.NotNil(t, e)
283 | _, e = redisBroken.SDiffStore("godis3", "godis1", "godis2")
284 | assert.NotNil(t, e)
285 | _, e = redisBroken.SMembers("godis3")
286 | assert.NotNil(t, e)
287 | _, e = redisBroken.SInter("godis1", "godis2")
288 | assert.NotNil(t, e)
289 | _, e = redisBroken.SInterStore("godis4", "godis1", "godis2")
290 | assert.NotNil(t, e)
291 | _, e = redisBroken.SUnion("godis1", "godis2")
292 | assert.NotNil(t, e)
293 | _, e = redisBroken.SUnionStore("godis5", "godis1", "godis2")
294 | assert.NotNil(t, e)
295 | m.Discard()
296 | redisBroken.client.connection.host = "localhost1"
297 | redisBroken.Close()
298 | _, e = redisBroken.SDiff("godis1", "godis2")
299 | assert.NotNil(t, e)
300 | _, e = redisBroken.SDiffStore("godis3", "godis1", "godis2")
301 | assert.NotNil(t, e)
302 | _, e = redisBroken.SMembers("godis3")
303 | assert.NotNil(t, e)
304 | _, e = redisBroken.SInter("godis1", "godis2")
305 | assert.NotNil(t, e)
306 | _, e = redisBroken.SInterStore("godis4", "godis1", "godis2")
307 | assert.NotNil(t, e)
308 | _, e = redisBroken.SUnion("godis1", "godis2")
309 | assert.NotNil(t, e)
310 | _, e = redisBroken.SUnionStore("godis5", "godis1", "godis2")
311 | assert.NotNil(t, e)
312 | }
313 |
314 | func TestRedis_Smove(t *testing.T) {
315 | flushAll()
316 | redis := NewRedis(option)
317 | defer redis.Close()
318 | redis.SAdd("godis", "1", "2")
319 | redis.SAdd("godis1", "3", "4")
320 |
321 | s, e := redis.SMove("godis", "godis1", "2")
322 | assert.Nil(t, e)
323 | assert.Equal(t, int64(1), s)
324 |
325 | arr, _ := redis.SMembers("godis")
326 | assert.ElementsMatch(t, []string{"1"}, arr)
327 |
328 | arr, _ = redis.SMembers("godis1")
329 | assert.ElementsMatch(t, []string{"2", "3", "4"}, arr)
330 |
331 | redisBroken := NewRedis(option1)
332 | defer redisBroken.Close()
333 | m, _ := redisBroken.Multi()
334 | _, e = redisBroken.SMove("godis", "godis1", "2")
335 | assert.NotNil(t, e)
336 | m.Discard()
337 | redisBroken.client.connection.host = "localhost1"
338 | redisBroken.Close()
339 | _, e = redisBroken.SMove("godis", "godis1", "2")
340 | assert.NotNil(t, e)
341 |
342 | }
343 |
344 | func TestRedis_Sort(t *testing.T) {
345 | flushAll()
346 | redis := NewRedis(option)
347 | defer redis.Close()
348 | redis.LPush("godis", "3", "2", "1", "4", "6", "5")
349 | p := NewSortParams().Desc()
350 | arr, e := redis.Sort("godis", p)
351 | assert.Nil(t, e)
352 | assert.Equal(t, []string{"6", "5", "4", "3", "2", "1"}, arr)
353 |
354 | p = NewSortParams().Desc().Limit(0, 1)
355 | arr, e = redis.Sort("godis", p)
356 | assert.Nil(t, e)
357 | assert.Equal(t, []string{"6"}, arr)
358 |
359 | p = NewSortParams().Asc()
360 | arr, e = redis.Sort("godis", p)
361 | assert.Nil(t, e)
362 | assert.Equal(t, []string{"1", "2", "3", "4", "5", "6"}, arr)
363 |
364 | p = NewSortParams().Alpha()
365 | arr, e = redis.Sort("godis", p)
366 | assert.Nil(t, e)
367 | assert.Equal(t, []string{"1", "2", "3", "4", "5", "6"}, arr)
368 |
369 | p = NewSortParams().By("*").Get("*")
370 | arr, e = redis.Sort("godis", p)
371 | assert.Nil(t, e)
372 | assert.Equal(t, []string{"", "", "", "", "", ""}, arr)
373 |
374 | c, e := redis.SortStore("godis", "godis1", p)
375 | assert.Nil(t, e)
376 | assert.Equal(t, int64(6), c)
377 |
378 | p = NewSortParams().NoSort()
379 | arr, e = redis.Sort("godis", p)
380 | assert.Nil(t, e)
381 | assert.Equal(t, []string{"3", "2", "1", "4", "6", "5"}, arr)
382 |
383 | redisBroken := NewRedis(option1)
384 | defer redisBroken.Close()
385 | m, _ := redisBroken.Multi()
386 | _, e = redisBroken.Sort("godis", p)
387 | assert.NotNil(t, e)
388 | m.Discard()
389 | redisBroken.client.connection.host = "localhost1"
390 | redisBroken.Close()
391 | _, e = redisBroken.Sort("godis", p)
392 | assert.NotNil(t, e)
393 | }
394 |
395 | func TestRedis_Watch(t *testing.T) {
396 | flushAll()
397 | redis := NewRedis(option)
398 | defer redis.Close()
399 | s, e := redis.Watch("godis")
400 | assert.Nil(t, e)
401 | assert.Equal(t, "OK", s)
402 |
403 | s, e = redis.Unwatch()
404 | assert.Nil(t, e)
405 | assert.Equal(t, "OK", s)
406 |
407 | redisBroken := NewRedis(option1)
408 | defer redisBroken.Close()
409 | m, _ := redisBroken.Multi()
410 | _, e = redisBroken.Watch("godis")
411 | assert.NotNil(t, e)
412 | _, e = redisBroken.Unwatch()
413 | assert.NotNil(t, e)
414 | m.Discard()
415 | redisBroken.client.connection.host = "localhost1"
416 | redisBroken.Close()
417 | _, e = redisBroken.Watch("godis")
418 | assert.NotNil(t, e)
419 | _, e = redisBroken.Unwatch()
420 | assert.NotNil(t, e)
421 | }
422 |
423 | func TestRedis_Zinterstore(t *testing.T) {
424 | flushAll()
425 | redis := NewRedis(option)
426 | defer redis.Close()
427 | c, err := redis.ZAddByMap("godis1", map[string]float64{"a": 1, "b": 2, "c": 3})
428 | assert.Nil(t, err)
429 | assert.Equal(t, int64(3), c)
430 |
431 | c, err = redis.ZAddByMap("godis2", map[string]float64{"a": 1, "b": 2})
432 | assert.Nil(t, err)
433 | assert.Equal(t, int64(2), c)
434 |
435 | c, err = redis.ZInterStore("godis3", "godis1", "godis2")
436 | assert.Nil(t, err)
437 | assert.Equal(t, int64(2), c)
438 |
439 | param := newZParams().Aggregate(AggregateSum)
440 | c, err = redis.ZInterStoreWithParams("godis3", param, "godis1", "godis2")
441 | assert.Nil(t, err)
442 | assert.Equal(t, int64(2), c)
443 |
444 | c, err = redis.ZUnionStore("godis3", "godis1", "godis2")
445 | assert.Nil(t, err)
446 | assert.Equal(t, int64(3), c)
447 |
448 | param = newZParams().Aggregate(AggregateMax)
449 | c, err = redis.ZUnionStoreWithParams("godis3", param, "godis1", "godis2")
450 | assert.Nil(t, err)
451 | assert.Equal(t, int64(3), c)
452 |
453 | redisBroken := NewRedis(option1)
454 | defer redisBroken.Close()
455 | m, _ := redisBroken.Multi()
456 | _, err = redisBroken.ZInterStore("godis3", "godis1", "godis2")
457 | assert.NotNil(t, err)
458 | _, err = redisBroken.ZInterStoreWithParams("godis3", param, "godis1", "godis2")
459 | assert.NotNil(t, err)
460 | _, err = redisBroken.ZUnionStore("godis3", "godis1", "godis2")
461 | assert.NotNil(t, err)
462 | _, err = redisBroken.ZUnionStoreWithParams("godis3", param, "godis1", "godis2")
463 | assert.NotNil(t, err)
464 | m.Discard()
465 | redisBroken.client.connection.host = "localhost1"
466 | redisBroken.Close()
467 | _, err = redisBroken.ZInterStore("godis3", "godis1", "godis2")
468 | assert.NotNil(t, err)
469 | _, err = redisBroken.ZInterStoreWithParams("godis3", param, "godis1", "godis2")
470 | assert.NotNil(t, err)
471 | _, err = redisBroken.ZUnionStore("godis3", "godis1", "godis2")
472 | assert.NotNil(t, err)
473 | _, err = redisBroken.ZUnionStoreWithParams("godis3", param, "godis1", "godis2")
474 | assert.NotNil(t, err)
475 | }
476 |
477 | func TestRedis_Subscribe(t *testing.T) {
478 | flushAll()
479 | redis := NewRedis(option)
480 | defer redis.Close()
481 | pubsub := &RedisPubSub{
482 | OnMessage: func(channel, message string) {
483 | t.Logf("receive message ,channel:%s,message:%s", channel, message)
484 | },
485 | OnSubscribe: func(channel string, subscribedChannels int) {
486 | t.Logf("receive subscribe command ,channel:%s,subscribedChannels:%d", channel, subscribedChannels)
487 | },
488 | OnUnSubscribe: func(channel string, subscribedChannels int) {
489 | t.Logf("receive unsubscribe command ,channel:%s,subscribedChannels:%d", channel, subscribedChannels)
490 | },
491 | OnPMessage: func(pattern string, channel, message string) {
492 | t.Logf("receive pmessage ,pattern:%s,channel:%s,message:%s", pattern, channel, message)
493 | },
494 | OnPSubscribe: func(pattern string, subscribedChannels int) {
495 | t.Logf("receive psubscribe command ,pattern:%s,subscribedChannels:%d", pattern, subscribedChannels)
496 | },
497 | OnPUnSubscribe: func(pattern string, subscribedChannels int) {
498 | t.Logf("receive punsubscribe command ,pattern:%s,subscribedChannels:%d", pattern, subscribedChannels)
499 | },
500 | OnPong: func(channel string) {
501 | t.Logf("receive pong ,channel:%s", channel)
502 | },
503 | }
504 | go func() {
505 | r := NewRedis(option)
506 | defer r.Close()
507 | r.Subscribe(pubsub, "godis")
508 | }()
509 | //sleep mills, ensure message can publish to subscribers
510 | time.Sleep(500 * time.Millisecond)
511 | redis.Publish("godis", "publish a message to godis channel")
512 | //sleep mills, ensure message can publish to subscribers
513 | time.Sleep(500 * time.Millisecond)
514 |
515 | pubsub2 := &RedisPubSub{
516 | OnMessage: func(channel, message string) {
517 | t.Logf("receive message ,channel:%s,message:%s", channel, message)
518 | },
519 | OnSubscribe: func(channel string, subscribedChannels int) {
520 | t.Logf("receive subscribe command ,channel:%s,subscribedChannels:%d", channel, subscribedChannels)
521 | },
522 | OnUnSubscribe: func(channel string, subscribedChannels int) {
523 | t.Logf("receive unsubscribe command ,channel:%s,subscribedChannels:%d", channel, subscribedChannels)
524 | },
525 | OnPMessage: func(pattern string, channel, message string) {
526 | t.Logf("receive pmessage ,pattern:%s,channel:%s,message:%s", pattern, channel, message)
527 | },
528 | OnPSubscribe: func(pattern string, subscribedChannels int) {
529 | t.Logf("receive psubscribe command ,pattern:%s,subscribedChannels:%d", pattern, subscribedChannels)
530 | },
531 | OnPUnSubscribe: func(pattern string, subscribedChannels int) {
532 | t.Logf("receive punsubscribe command ,pattern:%s,subscribedChannels:%d", pattern, subscribedChannels)
533 | },
534 | OnPong: func(channel string) {
535 | t.Logf("receive pong ,channel:%s", channel)
536 | },
537 | }
538 | redis1 := NewRedis(option)
539 | defer redis1.Close()
540 | pubsub2.redis = redis1
541 | go func() {
542 | pubsub2.Subscribe("godis1")
543 | pubsub2.process(redis1)
544 | }()
545 | //sleep mills, ensure message can publish to subscribers
546 | time.Sleep(500 * time.Millisecond)
547 | redis.Publish("godis1", "publish a message to godis channel")
548 | //sleep mills, ensure message can publish to subscribers
549 | time.Sleep(500 * time.Millisecond)
550 | pubsub2.UnSubscribe("godis1")
551 | time.Sleep(500 * time.Millisecond)
552 |
553 | redisBroken := NewRedis(option1)
554 | defer redisBroken.Close()
555 | m, _ := redisBroken.Multi()
556 | _, e := redisBroken.Publish("godis1", "publish a message to godis channel")
557 | assert.NotNil(t, e)
558 | pubsub2.UnSubscribe("godis1")
559 | m.Discard()
560 | redisBroken.client.connection.host = "localhost1"
561 | redisBroken.Close()
562 | _, e = redisBroken.Publish("godis1", "publish a message to godis channel")
563 | assert.NotNil(t, e)
564 | pubsub2.UnSubscribe("godis1")
565 | }
566 |
567 | func TestRedis_Psubscribe(t *testing.T) {
568 | flushAll()
569 | redis := NewRedis(option)
570 | defer redis.Close()
571 | pubsub := &RedisPubSub{
572 | OnMessage: func(channel, message string) {
573 | t.Logf("receive message ,channel:%s,message:%s", channel, message)
574 | },
575 | OnSubscribe: func(channel string, subscribedChannels int) {
576 | t.Logf("receive subscribe command ,channel:%s,subscribedChannels:%d", channel, subscribedChannels)
577 | },
578 | OnUnSubscribe: func(channel string, subscribedChannels int) {
579 | t.Logf("receive unsubscribe command ,channel:%s,subscribedChannels:%d", channel, subscribedChannels)
580 | },
581 | OnPMessage: func(pattern string, channel, message string) {
582 | t.Logf("receive pmessage ,pattern:%s,channel:%s,message:%s", pattern, channel, message)
583 | },
584 | OnPSubscribe: func(pattern string, subscribedChannels int) {
585 | t.Logf("receive psubscribe command ,pattern:%s,subscribedChannels:%d", pattern, subscribedChannels)
586 | },
587 | OnPUnSubscribe: func(pattern string, subscribedChannels int) {
588 | t.Logf("receive punsubscribe command ,pattern:%s,subscribedChannels:%d", pattern, subscribedChannels)
589 | },
590 | OnPong: func(channel string) {
591 | t.Logf("receive pong ,channel:%s", channel)
592 | },
593 | }
594 | go func() {
595 | r := NewRedis(option)
596 | defer r.Close()
597 | r.PSubscribe(pubsub, "godis")
598 | }()
599 | //sleep mills, ensure message can publish to subscribers
600 | time.Sleep(500 * time.Millisecond)
601 | redis.Publish("godis", "publish a message to godis channel")
602 | //sleep mills, ensure message can publish to subscribers
603 | time.Sleep(500 * time.Millisecond)
604 |
605 | pubsub2 := &RedisPubSub{
606 | OnMessage: func(channel, message string) {
607 | t.Logf("receive message ,channel:%s,message:%s", channel, message)
608 | },
609 | OnSubscribe: func(channel string, subscribedChannels int) {
610 | t.Logf("receive subscribe command ,channel:%s,subscribedChannels:%d", channel, subscribedChannels)
611 | },
612 | OnUnSubscribe: func(channel string, subscribedChannels int) {
613 | t.Logf("receive unsubscribe command ,channel:%s,subscribedChannels:%d", channel, subscribedChannels)
614 | },
615 | OnPMessage: func(pattern string, channel, message string) {
616 | t.Logf("receive pmessage ,pattern:%s,channel:%s,message:%s", pattern, channel, message)
617 | },
618 | OnPSubscribe: func(pattern string, subscribedChannels int) {
619 | t.Logf("receive psubscribe command ,pattern:%s,subscribedChannels:%d", pattern, subscribedChannels)
620 | },
621 | OnPUnSubscribe: func(pattern string, subscribedChannels int) {
622 | t.Logf("receive punsubscribe command ,pattern:%s,subscribedChannels:%d", pattern, subscribedChannels)
623 | },
624 | OnPong: func(channel string) {
625 | t.Logf("receive pong ,channel:%s", channel)
626 | },
627 | }
628 | redis1 := NewRedis(option)
629 | defer redis1.Close()
630 | pubsub2.redis = redis1
631 | pubsub2.PSubscribe("godis1")
632 | pubsub2.PUnSubscribe("godis1")
633 | time.Sleep(500 * time.Millisecond)
634 |
635 | pubsub3 := &RedisPubSub{
636 | OnMessage: func(channel, message string) {
637 | t.Logf("receive message ,channel:%s,message:%s", channel, message)
638 | },
639 | OnSubscribe: func(channel string, subscribedChannels int) {
640 | t.Logf("receive subscribe command ,channel:%s,subscribedChannels:%d", channel, subscribedChannels)
641 | },
642 | OnUnSubscribe: func(channel string, subscribedChannels int) {
643 | t.Logf("receive unsubscribe command ,channel:%s,subscribedChannels:%d", channel, subscribedChannels)
644 | },
645 | OnPMessage: func(pattern string, channel, message string) {
646 | t.Logf("receive pmessage ,pattern:%s,channel:%s,message:%s", pattern, channel, message)
647 | },
648 | OnPSubscribe: func(pattern string, subscribedChannels int) {
649 | t.Logf("receive psubscribe command ,pattern:%s,subscribedChannels:%d", pattern, subscribedChannels)
650 | },
651 | OnPUnSubscribe: func(pattern string, subscribedChannels int) {
652 | t.Logf("receive punsubscribe command ,pattern:%s,subscribedChannels:%d", pattern, subscribedChannels)
653 | },
654 | OnPong: func(channel string) {
655 | t.Logf("receive pong ,channel:%s", channel)
656 | },
657 | }
658 | redis2 := NewRedis(option)
659 | defer redis2.Close()
660 | pubsub3.redis = redis2
661 | go func() {
662 | //pubsub2.PSubscribe("godis1")
663 | pubsub3.proceedWithPatterns(redis2, "godis2")
664 | }()
665 | //sleep mills, ensure message can publish to subscribers
666 | time.Sleep(500 * time.Millisecond)
667 | redis.Publish("godis2", "publish a message to godis channel")
668 | //sleep mills, ensure message can publish to subscribers
669 | time.Sleep(500 * time.Millisecond)
670 | e := pubsub3.PUnSubscribe("godis2")
671 | assert.Nil(t, e)
672 | time.Sleep(500 * time.Millisecond)
673 |
674 | redisBroken := NewRedis(option1)
675 | defer redisBroken.Close()
676 | m, _ := redisBroken.Multi()
677 | _, e = redisBroken.Publish("godis1", "publish a message to godis channel")
678 | assert.NotNil(t, e)
679 | pubsub2.PUnSubscribe("godis1")
680 | m.Discard()
681 | redisBroken.client.connection.host = "localhost1"
682 | redisBroken.Close()
683 | _, e = redisBroken.Publish("godis1", "publish a message to godis channel")
684 | assert.NotNil(t, e)
685 | pubsub2.PUnSubscribe("godis1")
686 | }
687 |
688 | func TestRedis_RandomKey(t *testing.T) {
689 | initDb()
690 | redis := NewRedis(option)
691 | defer redis.Close()
692 | s, e := redis.RandomKey()
693 | assert.Nil(t, e)
694 | assert.Equal(t, "godis", s)
695 |
696 | redisBroken := NewRedis(option1)
697 | defer redisBroken.Close()
698 | m, _ := redisBroken.Multi()
699 | _, e = redisBroken.RandomKey()
700 | assert.NotNil(t, e)
701 | m.Discard()
702 | redisBroken.client.connection.host = "localhost1"
703 | redisBroken.Close()
704 | _, e = redisBroken.RandomKey()
705 | assert.NotNil(t, e)
706 | }
707 |
708 | func TestRedis_Bitop(t *testing.T) {
709 | flushAll()
710 | redis := NewRedis(option)
711 | defer redis.Close()
712 | b, e := redis.SetBit("bit-1", 0, "1")
713 | assert.Nil(t, e)
714 | assert.Equal(t, false, b)
715 |
716 | b, e = redis.SetBit("bit-1", 3, "1")
717 | assert.Nil(t, e)
718 | assert.Equal(t, false, b)
719 |
720 | b, e = redis.SetBit("bit-2", 0, "1")
721 | assert.Nil(t, e)
722 | assert.Equal(t, false, b)
723 |
724 | b, e = redis.SetBit("bit-2", 1, "1")
725 | assert.Nil(t, e)
726 | assert.Equal(t, false, b)
727 |
728 | b, e = redis.SetBit("bit-2", 3, "1")
729 | assert.Nil(t, e)
730 | assert.Equal(t, false, b)
731 |
732 | i, e := redis.BitOp(*BitOpAnd, "and-result", "bit-1", "bit-2")
733 | assert.Nil(t, e)
734 | assert.Equal(t, int64(1), i)
735 |
736 | b, e = redis.GetBit("and-result", 0)
737 | assert.Nil(t, e)
738 | assert.Equal(t, true, b)
739 |
740 | redisBroken := NewRedis(option1)
741 | defer redisBroken.Close()
742 | m, _ := redisBroken.Multi()
743 | _, e = redisBroken.SetBit("bit-1", 0, "1")
744 | assert.Nil(t, e)
745 | _, e = redisBroken.BitOp(*BitOpAnd, "and-result", "bit-1", "bit-2")
746 | assert.NotNil(t, e)
747 | m.Discard()
748 | redisBroken.client.connection.host = "localhost1"
749 | redisBroken.Close()
750 | _, e = redisBroken.SetBit("bit-1", 0, "1")
751 | assert.NotNil(t, e)
752 | _, e = redisBroken.BitOp(*BitOpAnd, "and-result", "bit-1", "bit-2")
753 | assert.NotNil(t, e)
754 | }
755 |
756 | func TestRedis_Scan(t *testing.T) {
757 | flushAll()
758 | redis := NewRedis(option)
759 | defer redis.Close()
760 | for i := 0; i < 1000; i++ {
761 | redis.Set(fmt.Sprintf("godis%d", i), fmt.Sprintf("godis%d", i))
762 | }
763 | c, err := redis.Keys("godis*")
764 | assert.Nil(t, err)
765 | assert.Len(t, c, 1000)
766 |
767 | params := NewScanParams().Match("godis*").Count(10)
768 | cursor := "0"
769 | total := 0
770 | for {
771 | result, err := redis.Scan(cursor, params)
772 | assert.Nil(t, err)
773 | total += len(result.Results)
774 | cursor = result.Cursor
775 | if result.Cursor == "0" {
776 | break
777 | }
778 | }
779 | assert.Equal(t, 1000, total)
780 |
781 | redisBroken := NewRedis(option1)
782 | defer redisBroken.Close()
783 | m, _ := redisBroken.Multi()
784 | _, e := redisBroken.Scan(cursor, params)
785 | assert.NotNil(t, e)
786 | m.Discard()
787 | redisBroken.client.connection.host = "localhost1"
788 | redisBroken.Close()
789 | _, e = redisBroken.Scan(cursor, params)
790 | assert.NotNil(t, e)
791 | }
792 |
--------------------------------------------------------------------------------
/redis_script_cmd_test.go:
--------------------------------------------------------------------------------
1 | package godis
2 |
3 | import (
4 | "github.com/stretchr/testify/assert"
5 | "testing"
6 | )
7 |
8 | func TestRedis_Eval(t *testing.T) {
9 | initDb()
10 | redis := NewRedis(option)
11 | defer redis.Close()
12 | s, err := redis.Eval(`return redis.call("get",KEYS[1])`, 1, "godis")
13 | assert.Nil(t, err)
14 | assert.Equal(t, "good", s)
15 |
16 | s, err = redis.Eval(`return redis.call("set",KEYS[1],ARGV[1])`, 1, "eval", "godis")
17 | assert.Nil(t, err)
18 | assert.Equal(t, "OK", s)
19 |
20 | s, err = redis.Eval(`return redis.call("get",KEYS[1])`, 1, "eval")
21 | assert.Nil(t, err)
22 | assert.Equal(t, "godis", s)
23 |
24 | redisBroken := NewRedis(option)
25 | defer redisBroken.Close()
26 | redisBroken.client.connection.host = "localhost1"
27 | redisBroken.Close()
28 | _, err = redisBroken.Eval(`return redis.call("get",KEYS[1])`, 1, "godis")
29 | assert.NotNil(t, err)
30 | }
31 |
32 | func TestRedis_EvalByKeyArgs(t *testing.T) {
33 | initDb()
34 | redis := NewRedis(option)
35 | defer redis.Close()
36 | s, err := redis.EvalByKeyArgs(`return redis.call("get",KEYS[1])`, []string{"godis"}, []string{})
37 | assert.Nil(t, err)
38 | assert.Equal(t, "good", s)
39 |
40 | s, err = redis.EvalByKeyArgs(`return redis.call("set",KEYS[1],ARGV[1])`, []string{"eval"}, []string{"godis"})
41 | assert.Nil(t, err)
42 | assert.Equal(t, "OK", s)
43 |
44 | s, err = redis.EvalByKeyArgs(`return redis.call("get",KEYS[1])`, []string{"eval"}, []string{})
45 | assert.Nil(t, err)
46 | assert.Equal(t, "godis", s)
47 | TestRedis_Set(t)
48 |
49 | redisBroken := NewRedis(option)
50 | defer redisBroken.Close()
51 | redisBroken.client.connection.host = "localhost1"
52 | redisBroken.Close()
53 | _, err = redisBroken.EvalByKeyArgs(`return redis.call("get",KEYS[1])`, []string{"godis"}, []string{})
54 | assert.NotNil(t, err)
55 | }
56 |
57 | func TestRedis_ScriptLoad(t *testing.T) {
58 | initDb()
59 | redis := NewRedis(option)
60 | defer redis.Close()
61 | sha, err := redis.ScriptLoad(`return redis.call("get",KEYS[1])`)
62 | assert.Nil(t, err)
63 |
64 | bools, err := redis.ScriptExists(sha)
65 | assert.Nil(t, err)
66 | assert.Equal(t, []bool{true}, bools)
67 |
68 | s, err := redis.EvalSha(sha, 1, "godis")
69 | assert.Nil(t, err)
70 | assert.Equal(t, "good", s)
71 |
72 | redisBroken := NewRedis(option)
73 | defer redisBroken.Close()
74 | redisBroken.client.connection.host = "localhost1"
75 | redisBroken.Close()
76 | _, err = redisBroken.ScriptLoad(`return redis.call("get",KEYS[1])`)
77 | assert.NotNil(t, err)
78 | _, err = redisBroken.ScriptExists(sha)
79 | assert.NotNil(t, err)
80 | }
81 |
--------------------------------------------------------------------------------
/redis_sentinel_cmd_test.go:
--------------------------------------------------------------------------------
1 | package godis
2 |
3 | import (
4 | "github.com/stretchr/testify/assert"
5 | "testing"
6 | )
7 |
8 | func TestRedis_SentinelFailover(t *testing.T) {
9 | redis := NewRedis(option)
10 | defer redis.Close()
11 | s, err := redis.SentinelFailOver("a")
12 | assert.NotNil(t, err)
13 | assert.Equal(t, "", s)
14 |
15 | redisBroken := NewRedis(option)
16 | defer redisBroken.Close()
17 | redisBroken.client.connection.host = "localhost1"
18 | redisBroken.Close()
19 | _, err = redisBroken.SentinelFailOver("a")
20 | assert.NotNil(t, err)
21 | }
22 |
23 | func TestRedis_SentinelGetMasterAddrByName(t *testing.T) {
24 | redis := NewRedis(option)
25 | defer redis.Close()
26 | _, err := redis.SentinelGetMasterAddrByName("a")
27 | assert.NotNil(t, err)
28 |
29 | redisBroken := NewRedis(option)
30 | defer redisBroken.Close()
31 | redisBroken.client.connection.host = "localhost1"
32 | redisBroken.Close()
33 | _, err = redisBroken.SentinelGetMasterAddrByName("a")
34 | assert.NotNil(t, err)
35 | }
36 |
37 | func TestRedis_SentinelMasters(t *testing.T) {
38 | redis := NewRedis(option)
39 | defer redis.Close()
40 | _, err := redis.SentinelMasters()
41 | assert.NotNil(t, err)
42 |
43 | redisBroken := NewRedis(option)
44 | defer redisBroken.Close()
45 | redisBroken.client.connection.host = "localhost1"
46 | redisBroken.Close()
47 | _, err = redisBroken.SentinelMasters()
48 | assert.NotNil(t, err)
49 | }
50 |
51 | func TestRedis_SentinelMonitor(t *testing.T) {
52 | redis := NewRedis(option)
53 | defer redis.Close()
54 | _, err := redis.SentinelMonitor("a", "", 0, 0)
55 | assert.NotNil(t, err)
56 |
57 | redisBroken := NewRedis(option)
58 | defer redisBroken.Close()
59 | redisBroken.client.connection.host = "localhost1"
60 | redisBroken.Close()
61 | _, err = redisBroken.SentinelMonitor("a", "", 0, 0)
62 | assert.NotNil(t, err)
63 | }
64 |
65 | func TestRedis_SentinelRemove(t *testing.T) {
66 | redis := NewRedis(option)
67 | defer redis.Close()
68 | _, err := redis.SentinelRemove("a")
69 | assert.NotNil(t, err)
70 |
71 | redisBroken := NewRedis(option)
72 | defer redisBroken.Close()
73 | redisBroken.client.connection.host = "localhost1"
74 | redisBroken.Close()
75 | _, err = redisBroken.SentinelRemove("a")
76 | assert.NotNil(t, err)
77 | }
78 |
79 | func TestRedis_SentinelReset(t *testing.T) {
80 | redis := NewRedis(option)
81 | defer redis.Close()
82 | _, err := redis.SentinelReset("a")
83 | assert.NotNil(t, err)
84 |
85 | redisBroken := NewRedis(option)
86 | defer redisBroken.Close()
87 | redisBroken.client.connection.host = "localhost1"
88 | redisBroken.Close()
89 | _, err = redisBroken.SentinelReset("a")
90 | assert.NotNil(t, err)
91 | }
92 |
93 | func TestRedis_SentinelSet(t *testing.T) {
94 | redis := NewRedis(option)
95 | defer redis.Close()
96 | _, err := redis.SentinelSet("a", nil)
97 | assert.NotNil(t, err)
98 |
99 | redisBroken := NewRedis(option)
100 | defer redisBroken.Close()
101 | redisBroken.client.connection.host = "localhost1"
102 | redisBroken.Close()
103 | _, err = redisBroken.SentinelSet("a", nil)
104 | assert.NotNil(t, err)
105 | }
106 |
107 | func TestRedis_SentinelSlaves(t *testing.T) {
108 | redis := NewRedis(option)
109 | defer redis.Close()
110 | _, err := redis.SentinelSlaves("a")
111 | assert.NotNil(t, err)
112 |
113 | redisBroken := NewRedis(option)
114 | defer redisBroken.Close()
115 | redisBroken.client.connection.host = "localhost1"
116 | redisBroken.Close()
117 | _, err = redisBroken.SentinelSlaves("a")
118 | assert.NotNil(t, err)
119 | }
120 |
--------------------------------------------------------------------------------