├── .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 | [![Go Doc](https://img.shields.io/badge/godoc-reference-blue.svg)](https://godoc.org/github.com/piaohao/godis) 3 | [![Build Status](https://travis-ci.com/piaohao/godis.svg?branch=dev.master)](https://travis-ci.com/piaohao/godis) 4 | [![Go Report](https://goreportcard.com/badge/github.com/piaohao/godis?123)](https://goreportcard.com/report/github.com/piaohao/godis) 5 | [![codecov](https://codecov.io/gh/piaohao/godis/branch/master/graph/badge.svg)](https://codecov.io/gh/piaohao/godis) 6 | [![License](https://img.shields.io/badge/license-MIT-green.svg)](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 | [![Go Doc](https://img.shields.io/badge/godoc-reference-blue.svg)](https://godoc.org/github.com/piaohao/godis) 3 | [![Build Status](https://travis-ci.com/piaohao/godis.svg?branch=dev.master)](https://travis-ci.com/piaohao/godis) 4 | [![Go Report](https://goreportcard.com/badge/github.com/piaohao/godis?123)](https://goreportcard.com/report/github.com/piaohao/godis) 5 | [![codecov](https://codecov.io/gh/piaohao/godis/branch/master/graph/badge.svg)](https://codecov.io/gh/piaohao/godis) 6 | [![License](https://img.shields.io/badge/license-MIT-green.svg)](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 | --------------------------------------------------------------------------------