├── .gitignore ├── .travis.yml ├── AUTHORS ├── CONTRIBUTORS ├── LICENSE ├── README.md └── redis ├── commands.go ├── commands_test.go ├── pubsub_test.go ├── redis.go ├── redis_test.go ├── selector.go ├── selector_test.go └── types.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *DS_Store* 3 | *.rdb 4 | *.aof 5 | *.test 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | services: 2 | - redis 3 | 4 | language: go 5 | 6 | go: 7 | - 1.4.2 8 | - 1.4.3 9 | - 1.5 10 | - 1.5.1 11 | 12 | script: 13 | - go test -v ./redis 14 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # This is the official list of go-redis authors for copyright purposes. 2 | # This file is distinct from the CONTRIBUTORS file. 3 | # 4 | # Names should be added to this file as 5 | # Name or Organization 6 | # 7 | # The email address is not required for organizations. 8 | # 9 | # Please keep the list sorted. 10 | 11 | Alexandre Fiori 12 | Gleicon Moraes 13 | Lucas Fontes 14 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | # This is the official list of go-redis contributors for copyright purposes. 2 | # 3 | # Names should be added to this file as 4 | # Name or Organization 5 | # 6 | # Use the following command to generate the list: 7 | # 8 | # git shortlog -se | awk '{print $2 " " $3 " " $4}' 9 | # 10 | # The email address is not required for organizations. 11 | # 12 | # Please keep the list sorted. 13 | 14 | Bruno Celeste 15 | Juliano Martinez 16 | Thiago Ribeiro 17 | Thijs Cadier 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | go-redis 2 | ======== 3 | 4 | go-redis is a [Redis](http://redis.io) client library for the 5 | [Go programming language](http://golang.org). It's built on the skeleton of 6 | [gomemcache](http://github.com/bradfitz/gomemcache). 7 | 8 | It is safe to use by multiple goroutines, and scales well by automatically 9 | making new connections to redis on demand. Idle connections stay in the 10 | connection pool until time out. 11 | 12 | Licensed under the Apache License, Version 2.0. 13 | 14 | 15 | ## Status 16 | 17 | The library is stable and has been extensively tested on 18 | [freegeoip.net](http://freegeoip.net) as the underlying quota mechanism 19 | on Redis. It has served dozens of billions of queries that used this library 20 | to manage usage. 21 | 22 | It is incomplete, though. Me, [@gleicon](https://github.com/gleicon), 23 | [@lxfontes](https://github.com/lxfontes) and others have only implemented the 24 | commands we needed for our applications so far, and continue doing so with no 25 | rush or schedule. 26 | See [commands.go](https://github.com/fiorix/go-redis/blob/master/redis/commands.go) 27 | for a list of supported commands - they're in alphabetical order. Contributors 28 | are welcome. 29 | 30 | We've written other Redis client libraries before, also very stable and used 31 | in large deployments by major companies. 32 | 33 | [![Build Status](https://secure.travis-ci.org/fiorix/go-redis.png)](http://travis-ci.org/fiorix/go-redis) 34 | 35 | 36 | ## Installing 37 | 38 | Make sure Go is installed, and both $GOROOT and $GOPATH are set, then 39 | run: 40 | 41 | $ go get github.com/fiorix/go-redis/redis 42 | 43 | 44 | ## Usage 45 | 46 | Hello world: 47 | 48 | import "github.com/fiorix/go-redis/redis" 49 | 50 | func main() { 51 | rc := redis.New("10.0.0.1:6379", "10.0.0.2:6379", "10.0.0.3:6379") 52 | rc.Set("foo", "bar") 53 | 54 | v, err := rc.Get("foo") 55 | ... 56 | } 57 | 58 | When connected to multiple servers, commands such as PING, INFO and 59 | similar are only executed on the first server. GET, SET and others are 60 | distributed by their key. 61 | 62 | New connections are created on demand, and stay available in the connection 63 | pool until they time out. The library scales very well under high load. 64 | 65 | 66 | ### Unix socket, dbid and password support 67 | 68 | The client supports ip:port or unix socket for connecting to redis. 69 | 70 | rc := redis.New("/tmp/redis.sock db=5 passwd=foobared") 71 | 72 | Database ID and password can only be set by ``New()`` and can't be 73 | changed later. If that is required, make a new connection. 74 | 75 | 76 | ## Credits 77 | 78 | Thanks to (in no particular order): 79 | 80 | - [gomemcache](https://github.com/bradfitz/gomemcache): for the skeleton of 81 | this client library. 82 | -------------------------------------------------------------------------------- /redis/commands.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013-2015 go-redis authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package redis 16 | 17 | // WORK IN PROGRESS 18 | // 19 | // Redis commands 20 | // 21 | // Some commands take an integer timeout, in seconds. It's not a time.Duration 22 | // because redis only supports seconds resolution for timeouts. 23 | // 24 | // Redis allows clients to block indefinitely by setting timeout to 0, but 25 | // it does not work here. All functions below use the timeout not only to 26 | // block the operation in redis, but also as a socket read timeout (+delta) 27 | // to free up system resources. 28 | // 29 | // The default TCP read timeout is 200ms. If a timeout is required to 30 | // be "indefinitely", then set it to something like 86400. 31 | // 32 | // See redis.DefaultTimeout for details. 33 | // 34 | // 🍺 35 | 36 | import ( 37 | "errors" 38 | "strings" 39 | "time" 40 | ) 41 | 42 | // Append implements http://redis.io/commands/append. 43 | func (c *Client) Append(key, value string) (int, error) { 44 | v, err := c.execWithKey(true, "append", key, value) 45 | if err != nil { 46 | return 0, err 47 | } 48 | return iface2int(v) 49 | } 50 | 51 | // BgRewriteAOF implements http://redis.io/commands/bgrewriteaof. 52 | // Cannot be sharded. 53 | func (c *Client) BgRewriteAOF() (string, error) { 54 | v, err := c.execOnFirst(false, "BGREWRITEAOF") 55 | if err != nil { 56 | return "", err 57 | } 58 | return iface2str(v) 59 | } 60 | 61 | // BgSave implements http://redis.io/commands/bgsave. 62 | // Cannot be sharded. 63 | func (c *Client) BgSave() (string, error) { 64 | v, err := c.execOnFirst(false, "BGSAVE") 65 | if err != nil { 66 | return "", err 67 | } 68 | return iface2str(v) 69 | } 70 | 71 | // Ping implements http://redis.io/commands/ping. 72 | // Cannot be sharded. 73 | func (c *Client) Ping() error { 74 | v, err := c.execOnFirst(false, "PING") 75 | if err != nil { 76 | return err 77 | } 78 | s, err := iface2str(v) 79 | if err != nil { 80 | return err 81 | } else if s != "PONG" { 82 | return ErrServerError 83 | } 84 | return nil 85 | } 86 | 87 | // BitCount implements http://redis.io/commands/bitcount. 88 | // Start and end are ignored if start is a negative number. 89 | func (c *Client) BitCount(key string, start, end int) (int, error) { 90 | var ( 91 | v interface{} 92 | err error 93 | ) 94 | if start > -1 { 95 | v, err = c.execWithKey(true, "BITCOUNT", key, start, end) 96 | } else { 97 | v, err = c.execWithKey(true, "BITCOUNT", key) 98 | } 99 | if err != nil { 100 | return 0, err 101 | } 102 | return iface2int(v) 103 | } 104 | 105 | // BitOp implements http://redis.io/commands/bitop. 106 | // Cannot be sharded. 107 | func (c *Client) BitOp(operation, destkey, key string, keys ...string) (int, error) { 108 | a := append([]string{"BITOP", operation, destkey, key}, keys...) 109 | v, err := c.execOnFirst(true, vstr2iface(a)...) 110 | if err != nil { 111 | return 0, err 112 | } 113 | return iface2int(v) 114 | } 115 | 116 | // blbrPop implements both BLPop and BRPop. 117 | func (c *Client) blbrPop(cmd string, timeout int, keys ...string) (k, v string, err error) { 118 | var r interface{} 119 | r, err = c.execWithKeyTimeout( 120 | true, 121 | timeout, 122 | cmd, 123 | keys[0], 124 | append(vstr2iface(keys[1:]), timeout)..., 125 | ) 126 | if err != nil { 127 | return "", "", err 128 | } 129 | if r == nil { 130 | return "", "", ErrTimedOut 131 | } 132 | switch r.(type) { 133 | case []interface{}: 134 | items := r.([]interface{}) 135 | if len(items) != 2 { 136 | return "", "", ErrServerError 137 | } 138 | // TODO: test types 139 | k = items[0].(string) 140 | v = items[1].(string) 141 | return k, v, nil 142 | } 143 | return "", "", ErrServerError 144 | } 145 | 146 | // BLPop implements http://redis.io/commands/blpop. 147 | // Cannot be sharded. 148 | // 149 | // If timeout is 0, DefaultTimeout is used. 150 | func (c *Client) BLPop(timeout int, keys ...string) (k, v string, err error) { 151 | return c.blbrPop("BLPOP", timeout, keys...) 152 | } 153 | 154 | // BRPop implements http://redis.io/commands/brpop. 155 | // Cannot be sharded. 156 | // 157 | // If timeout is 0, DefaultTimeout is used. 158 | func (c *Client) BRPop(timeout int, keys ...string) (k, v string, err error) { 159 | return c.blbrPop("BRPOP", timeout, keys...) 160 | } 161 | 162 | // BRPopLPush implements http://redis.io/commands/brpoplpush. 163 | // Cannot be sharded. 164 | // 165 | // If timeout is 0, DefaultTimeout is used. 166 | func (c *Client) BRPopLPush(src, dst string, timeout int) (string, error) { 167 | v, err := c.execWithKeyTimeout(true, timeout, "BRPOPLPUSH", src, dst, timeout) 168 | if err != nil { 169 | return "", err 170 | } 171 | if v == nil { 172 | return "", ErrTimedOut 173 | } 174 | return iface2str(v) 175 | } 176 | 177 | // ClientKill implements http://redis.io/commands/client-kill. 178 | // Cannot be sharded. 179 | func (c *Client) ClientKill(addr string) error { 180 | v, err := c.execOnFirst(false, "CLIENT KILL", addr) 181 | if err != nil { 182 | return err 183 | } 184 | switch v.(type) { 185 | case string: 186 | return nil 187 | } 188 | return ErrServerError 189 | } 190 | 191 | // ClientList implements http://redis.io/commands/client-list. 192 | // Cannot be sharded. 193 | func (c *Client) ClientList() ([]string, error) { 194 | v, err := c.execOnFirst(false, "CLIENT LIST") 195 | if err != nil { 196 | return nil, err 197 | } 198 | switch v.(type) { 199 | case string: 200 | return strings.Split(v.(string), "\n"), nil 201 | } 202 | return nil, ErrServerError 203 | } 204 | 205 | // ClientSetName implements http://redis.io/commands/client-setname. 206 | // Cannot be sharded. 207 | // 208 | // This driver creates connections on demand, thus naming them is pointless. 209 | func (c *Client) ClientSetName(name string) error { 210 | v, err := c.execOnFirst(false, "CLIENT SETNAME", name) 211 | if err != nil { 212 | return err 213 | } 214 | switch v.(type) { 215 | case string: 216 | return nil 217 | } 218 | return ErrServerError 219 | } 220 | 221 | // ConfigGet implements http://redis.io/commands/config-get. 222 | // Cannot be sharded. 223 | func (c *Client) ConfigGet(name string) (map[string]string, error) { 224 | v, err := c.execOnFirst(false, "CONFIG GET", name) 225 | if err != nil { 226 | return nil, err 227 | } 228 | return iface2strmap(v), nil 229 | } 230 | 231 | // ConfigSet implements http://redis.io/commands/config-set. 232 | // Cannot be sharded. 233 | func (c *Client) ConfigSet(name, value string) error { 234 | v, err := c.execOnFirst(false, "CONFIG SET", name, value) 235 | if err != nil { 236 | return err 237 | } 238 | switch v.(type) { 239 | case string: 240 | return nil 241 | } 242 | return ErrServerError 243 | } 244 | 245 | // ConfigResetStat implements http://redis.io/commands/config-resetstat. 246 | // Cannot be sharded. 247 | func (c *Client) ConfigResetStat() error { 248 | v, err := c.execOnFirst(false, "CONFIG RESETSTAT") 249 | if err != nil { 250 | return err 251 | } 252 | switch v.(type) { 253 | case string: 254 | return nil 255 | } 256 | return ErrServerError 257 | } 258 | 259 | // DBSize implements http://redis.io/commands/dbsize. 260 | // Cannot be sharded. 261 | func (c *Client) DBSize() (int, error) { 262 | v, err := c.execOnFirst(false, "DBSIZE") 263 | if err != nil { 264 | return 0, err 265 | } 266 | return iface2int(v) 267 | } 268 | 269 | // DebugSegfault implements http://redis.io/commands/debug-segfault. 270 | // Cannot be sharded. 271 | func (c *Client) DebugSegfault() error { 272 | v, err := c.execOnFirst(false, "DEBUG SEGFAULT") 273 | if err != nil { 274 | return err 275 | } 276 | switch v.(type) { 277 | case string: 278 | return nil 279 | } 280 | return ErrServerError 281 | } 282 | 283 | // Decr implements http://redis.io/commands/decr. 284 | func (c *Client) Decr(key string) (int, error) { 285 | v, err := c.execWithKey(true, "DECR", key) 286 | if err != nil { 287 | return 0, err 288 | } 289 | return iface2int(v) 290 | } 291 | 292 | // DecrBy implements http://redis.io/commands/decrby. 293 | func (c *Client) DecrBy(key string, decrement int) (int, error) { 294 | v, err := c.execWithKey(true, "DECRBY", key, decrement) 295 | if err != nil { 296 | return 0, err 297 | } 298 | return iface2int(v) 299 | } 300 | 301 | // Del implements http://redis.io/commands/del. 302 | // Del issues a plain DEL command to redis if the client is connected to 303 | // a single server. On sharded connections, it issues one DEL command per 304 | // key, in the server selected for each given key. 305 | func (c *Client) Del(keys ...string) (n int, err error) { 306 | if c.selector.Sharding() { 307 | n, err = c.delMulti(keys...) 308 | } else { 309 | n, err = c.delPlain(keys...) 310 | } 311 | return n, err 312 | } 313 | 314 | func (c *Client) delMulti(keys ...string) (int, error) { 315 | deleted := 0 316 | for _, key := range keys { 317 | count, err := c.delPlain(key) 318 | if err != nil { 319 | return 0, err 320 | } 321 | deleted += count 322 | } 323 | return deleted, nil 324 | } 325 | 326 | func (c *Client) delPlain(keys ...string) (int, error) { 327 | if len(keys) > 0 { 328 | v, err := c.execWithKey(true, "DEL", keys[0], vstr2iface(keys[1:])...) 329 | if err != nil { 330 | return 0, err 331 | } 332 | return iface2int(v) 333 | } 334 | return 0, nil 335 | } 336 | 337 | // http://redis.io/commands/discard 338 | // TODO: Discard 339 | 340 | // Dump implements http://redis.io/commands/dump. 341 | func (c *Client) Dump(key string) (string, error) { 342 | v, err := c.execWithKey(true, "DUMP", key) 343 | if err != nil { 344 | return "", err 345 | } 346 | return iface2str(v) 347 | } 348 | 349 | // Echo implements http://redis.io/commands/echo. 350 | func (c *Client) Echo(message string) (string, error) { 351 | v, err := c.execWithKey(true, "ECHO", message) 352 | if err != nil { 353 | return "", err 354 | } 355 | return iface2str(v) 356 | } 357 | 358 | // Eval implemenets http://redis.io/commands/eval. 359 | // Cannot be sharded. 360 | func (c *Client) Eval(script string, numkeys int, keys, args []string) (interface{}, error) { 361 | a := []interface{}{ 362 | "EVAL", 363 | script, // escape? 364 | numkeys, 365 | strings.Join(keys, " "), 366 | strings.Join(args, " "), 367 | } 368 | v, err := c.execOnFirst(true, a...) 369 | if err != nil { 370 | return nil, err 371 | } 372 | return v, nil 373 | } 374 | 375 | // EvalSha implements http://redis.io/commands/evalsha. 376 | // Cannot be sharded. 377 | func (c *Client) EvalSha(sha1 string, numkeys int, keys, args []string) (interface{}, error) { 378 | a := []interface{}{ 379 | "EVALSHA", 380 | sha1, 381 | numkeys, 382 | strings.Join(keys, " "), 383 | strings.Join(args, " "), 384 | } 385 | v, err := c.execOnFirst(true, a...) 386 | if err != nil { 387 | return nil, err 388 | } 389 | return v, nil 390 | } 391 | 392 | // http://redis.io/commands/exec 393 | // TODO: Exec 394 | 395 | // Exists implements http://redis.io/commands/exists. 396 | func (c *Client) Exists(key string) (bool, error) { 397 | v, err := c.execWithKey(true, "EXISTS", key) 398 | if err != nil { 399 | return false, err 400 | } 401 | return iface2bool(v) 402 | } 403 | 404 | // Expire implements http://redis.io/commands/expire. 405 | // Expire returns true if a timeout was set for the given key, 406 | // or false when key does not exist or the timeout could not be set. 407 | func (c *Client) Expire(key string, seconds int) (bool, error) { 408 | v, err := c.execWithKey(true, "EXPIRE", key, seconds) 409 | if err != nil { 410 | return false, err 411 | } 412 | return iface2bool(v) 413 | } 414 | 415 | // ExpireAt implements http://redis.io/commands/expireat. 416 | // ExpireAt behaves like Expire. 417 | func (c *Client) ExpireAt(key string, timestamp int) (bool, error) { 418 | v, err := c.execWithKey(true, "EXPIREAT", key, timestamp) 419 | if err != nil { 420 | return false, err 421 | } 422 | return iface2bool(v) 423 | } 424 | 425 | // FlushAll implements http://redis.io/commands/flushall. 426 | // Cannot be sharded. 427 | func (c *Client) FlushAll() error { 428 | _, err := c.execOnFirst(false, "FLUSHALL") 429 | return err 430 | } 431 | 432 | // FlushDB implements http://redis.io/commands/flushall. 433 | // Cannot be sharded. 434 | func (c *Client) FlushDB() error { 435 | _, err := c.execOnFirst(false, "FLUSHDB") 436 | return err 437 | } 438 | 439 | // Get implements http://redis.io/commands/get. 440 | func (c *Client) Get(key string) (string, error) { 441 | v, err := c.execWithKey(true, "GET", key) 442 | if err != nil { 443 | return "", err 444 | } 445 | return iface2str(v) 446 | } 447 | 448 | // GetBit implements http://redis.io/commands/getbit. 449 | func (c *Client) GetBit(key string, offset int) (int, error) { 450 | v, err := c.execWithKey(true, "GETBIT", key, offset) 451 | if err != nil { 452 | return 0, err 453 | } 454 | return iface2int(v) 455 | } 456 | 457 | // GetRange implements http://redis.io/commands/getrange. 458 | func (c *Client) GetRange(key string, start, end int) (string, error) { 459 | v, err := c.execWithKey(true, "GETRANGE", key, start, end) 460 | if err != nil { 461 | return "", err 462 | } 463 | switch v.(type) { 464 | case string: 465 | return v.(string), nil 466 | } 467 | return "", ErrServerError 468 | } 469 | 470 | // GetSet implements http://redis.io/commands/getset. 471 | func (c *Client) GetSet(key, value string) (string, error) { 472 | v, err := c.execWithKey(true, "GETSET", key, value) 473 | if err != nil { 474 | return "", err 475 | } 476 | return iface2str(v) 477 | } 478 | 479 | // Incr implements http://redis.io/commands/incr. 480 | func (c *Client) Incr(key string) (int, error) { 481 | v, err := c.execWithKey(true, "INCR", key) 482 | if err != nil { 483 | return 0, err 484 | } 485 | return iface2int(v) 486 | } 487 | 488 | // IncrBy implements http://redis.io/commands/incrby. 489 | func (c *Client) IncrBy(key string, increment int) (int, error) { 490 | v, err := c.execWithKey(true, "INCRBY", key, increment) 491 | if err != nil { 492 | return 0, err 493 | } 494 | return iface2int(v) 495 | } 496 | 497 | // Keys implement http://redis.io/commands/keys. 498 | // Cannot be sharded. 499 | func (c *Client) Keys(pattern string) ([]string, error) { 500 | keys := []string{} 501 | v, err := c.execOnFirst(true, "KEYS", pattern) 502 | if err != nil { 503 | return keys, err 504 | } 505 | return iface2vstr(v), nil 506 | } 507 | 508 | // Scan implements http://redis.io/commands/scan. 509 | func (c *Client) Scan(cursor string, options ...interface{}) (string, []string, error) { 510 | return c.scanCommandList("SCAN", "", cursor, options...) 511 | } 512 | 513 | // SScan implements http://redis.io/commands/sscan. 514 | func (c *Client) SScan(set string, cursor string, options ...interface{}) (string, []string, error) { 515 | return c.scanCommandList("SSCAN", set, cursor, options...) 516 | } 517 | 518 | // ZScan implements http://redis.io/commands/zscan. 519 | func (c *Client) ZScan(zset string, cursor string, options ...interface{}) (string, map[string]string, error) { 520 | return c.scanCommandMap("ZSCAN", zset, cursor, options...) 521 | } 522 | 523 | // HScan implements http://redis.io/commands/hscan. 524 | func (c *Client) HScan(hash string, cursor string, options ...interface{}) (string, map[string]string, error) { 525 | return c.scanCommandMap("HSCAN", hash, cursor, options...) 526 | } 527 | 528 | // SCAN and SSCAN 529 | func (c *Client) scanCommandList(cmd string, key string, cursor string, options ...interface{}) (string, []string, error) { 530 | empty := []string{} 531 | resp := []interface{}{} 532 | newCursor := "0" 533 | 534 | var v interface{} 535 | var err error 536 | 537 | if len(key) > 0 { // SSCAN 538 | x := []interface{}{cursor} 539 | v, err = c.execWithKey(true, cmd, key, append(x, options...)...) 540 | } else { // SCAN 541 | x := []interface{}{cmd, cursor} 542 | v, err = c.execOnFirst(true, append(x, options...)...) 543 | } 544 | 545 | if err != nil { 546 | return newCursor, empty, err 547 | } 548 | 549 | switch v.(type) { 550 | case []interface{}: 551 | resp = v.([]interface{}) 552 | } 553 | 554 | // New cursor to call 555 | switch resp[0].(type) { 556 | case string: 557 | newCursor = resp[0].(string) 558 | } 559 | 560 | switch resp[1].(type) { 561 | case []interface{}: 562 | return newCursor, iface2vstr(resp[1]), nil 563 | } 564 | 565 | return newCursor, empty, nil 566 | } 567 | 568 | // ZSCAN and HSCAN 569 | func (c *Client) scanCommandMap(cmd string, key string, cursor string, options ...interface{}) (string, map[string]string, error) { 570 | empty := map[string]string{} 571 | resp := []interface{}{} 572 | newCursor := "0" 573 | 574 | x := []interface{}{cursor} 575 | v, err := c.execWithKey(true, cmd, key, append(x, options...)...) 576 | 577 | if err != nil { 578 | return newCursor, empty, err 579 | } 580 | 581 | switch v.(type) { 582 | case []interface{}: 583 | resp = v.([]interface{}) 584 | } 585 | 586 | // New cursor to call 587 | switch resp[0].(type) { 588 | case string: 589 | newCursor = resp[0].(string) 590 | } 591 | 592 | switch resp[1].(type) { 593 | case []interface{}: 594 | return newCursor, iface2strmap(resp[1]), nil 595 | } 596 | 597 | return newCursor, empty, nil 598 | } 599 | 600 | // LPush implements http://redis.io/commands/lpush. 601 | func (c *Client) LPush(key string, values ...string) (int, error) { 602 | v, err := c.execWithKey(true, "LPUSH", key, vstr2iface(values)...) 603 | if err != nil { 604 | return 0, err 605 | } 606 | return iface2int(v) 607 | } 608 | 609 | // LIndex implements http://redis.io/commands/lindex. 610 | func (c *Client) LIndex(key string, index int) (string, error) { 611 | v, err := c.execWithKey(true, "LINDEX", key, index) 612 | if err != nil { 613 | return "", err 614 | } 615 | return iface2str(v) 616 | } 617 | 618 | // LPop implements http://redis.io/commands/lpop. 619 | func (c *Client) LPop(key string) (string, error) { 620 | v, err := c.execWithKey(true, "LPOP", key) 621 | if err != nil { 622 | return "", err 623 | } 624 | return iface2str(v) 625 | } 626 | 627 | // RPop implements http://redis.io/commands/rpop. 628 | func (c *Client) RPop(key string) (string, error) { 629 | v, err := c.execWithKey(true, "RPOP", key) 630 | if err != nil { 631 | return "", err 632 | } 633 | return iface2str(v) 634 | } 635 | 636 | // LLen implements http://redis.io/commands/llen. 637 | func (c *Client) LLen(key string) (int, error) { 638 | v, err := c.execWithKey(true, "LLEN", key) 639 | if err != nil { 640 | return 0, err 641 | } 642 | return iface2int(v) 643 | } 644 | 645 | // LTrim implements http://redis.io/commands/ltrim. 646 | func (c *Client) LTrim(key string, begin, end int) (err error) { 647 | _, err = c.execWithKey(true, "LTRIM", key, begin, end) 648 | return err 649 | } 650 | 651 | // LRange implements http://redis.io/commands/lrange. 652 | func (c *Client) LRange(key string, begin, end int) ([]string, error) { 653 | v, err := c.execWithKey(true, "LRANGE", key, begin, end) 654 | if err != nil { 655 | return []string{}, err 656 | } 657 | return iface2vstr(v), nil 658 | } 659 | 660 | // LRem implements http://redis.io/commands/lrem. 661 | func (c *Client) LRem(key string, count int, value string) (int, error) { 662 | v, err := c.execWithKey(true, "LREM", key, count, value) 663 | if err != nil { 664 | return 0, err 665 | } 666 | return iface2int(v) 667 | } 668 | 669 | // HGet implements http://redis.io/commands/hget. 670 | func (c *Client) HGet(key, member string) (string, error) { 671 | v, err := c.execWithKey(true, "HGET", key, member) 672 | if err != nil { 673 | return "", err 674 | } 675 | return iface2str(v) 676 | } 677 | 678 | // HGetAll implements http://redis.io/commands/hgetall. 679 | func (c *Client) HGetAll(key string) (map[string]string, error) { 680 | v, err := c.execWithKey(true, "HGETALL", key) 681 | if err != nil { 682 | return nil, err 683 | } 684 | return iface2strmap(v), nil 685 | } 686 | 687 | // HIncrBy implements http://redis.io/commands/hincrby. 688 | func (c *Client) HIncrBy(key string, field string, increment int) (int, error) { 689 | v, err := c.execWithKey(true, "HINCRBY", key, field, increment) 690 | if err != nil { 691 | return 0, err 692 | } 693 | return iface2int(v) 694 | } 695 | 696 | // HMGet implements http://redis.io/commands/hmget. 697 | func (c *Client) HMGet(key string, field ...string) ([]string, error) { 698 | v, err := c.execWithKey(true, "HMGET", key, vstr2iface(field)...) 699 | if err != nil { 700 | return nil, err 701 | } 702 | return iface2vstr(v), nil 703 | } 704 | 705 | // HMSet implements http://redis.io/commands/hmset. 706 | func (c *Client) HMSet(key string, items map[string]string) (err error) { 707 | tmp := make([]interface{}, (len(items) * 2)) 708 | idx := 0 709 | for k, v := range items { 710 | n := idx * 2 711 | tmp[n] = k 712 | tmp[n+1] = v 713 | idx++ 714 | } 715 | _, err = c.execWithKey(true, "HMSET", key, tmp...) 716 | return 717 | } 718 | 719 | // HSet implements http://redis.io/commands/hset. 720 | func (c *Client) HSet(key, field, value string) (err error) { 721 | _, err = c.execWithKey(true, "HSET", key, field, value) 722 | return 723 | } 724 | 725 | // HDel implements http://redis.io/commands/hdel. 726 | func (c *Client) HDel(key, field string) (err error) { 727 | _, err = c.execWithKey(true, "HDEL", key, field) 728 | return 729 | } 730 | 731 | // ZIncrBy implements http://redis.io/commands/zincrby. 732 | func (c *Client) ZIncrBy(key string, increment int, member string) (string, error) { 733 | v, err := c.execWithKey(true, "ZINCRBY", key, increment, member) 734 | if err != nil { 735 | return "", err 736 | } 737 | return iface2str(v) 738 | } 739 | 740 | // MGet implements http://redis.io/commands/mget. 741 | // Cannot be sharded. 742 | // 743 | // TODO: support sharded connections. 744 | func (c *Client) MGet(keys ...string) ([]string, error) { 745 | tmp := make([]interface{}, len(keys)+1) 746 | tmp[0] = "MGET" 747 | for n, k := range keys { 748 | tmp[n+1] = k 749 | } 750 | v, err := c.execOnFirst(true, tmp...) 751 | if err != nil { 752 | return nil, err 753 | } 754 | switch v.(type) { 755 | case []interface{}: 756 | items := v.([]interface{}) 757 | resp := make([]string, len(items)) 758 | for n, item := range items { 759 | switch item.(type) { 760 | case string: 761 | resp[n] = item.(string) 762 | } 763 | } 764 | return resp, nil 765 | } 766 | return nil, ErrServerError 767 | } 768 | 769 | // MSet implements http://redis.io/commands/mset. 770 | // Cannot be sharded. 771 | // 772 | // TODO: support sharded connections. 773 | func (c *Client) MSet(items map[string]string) error { 774 | tmp := make([]interface{}, (len(items)*2)+1) 775 | tmp[0] = "MSET" 776 | idx := 0 777 | for k, v := range items { 778 | n := idx * 2 779 | tmp[n+1] = k 780 | tmp[n+2] = v 781 | idx++ 782 | } 783 | _, err := c.execOnFirst(true, tmp...) 784 | if err != nil { 785 | return err 786 | } 787 | return nil 788 | } 789 | 790 | // PFAdd implements http://redis.io/commands/pfadd. 791 | func (c *Client) PFAdd(key string, vs ...interface{}) (int, error) { 792 | v, err := c.execWithKey(true, "PFADD", key, vs...) 793 | 794 | if err != nil { 795 | return 0, err 796 | } 797 | return iface2int(v) 798 | } 799 | 800 | // PFCount implements http://redis.io/commands/pfcount. 801 | func (c *Client) PFCount(keys ...string) (int, error) { 802 | v, err := c.execWithKeys(true, "PFCOUNT", keys) 803 | if err != nil { 804 | return 0, err 805 | } 806 | sum := 0 807 | 808 | if len(v) == 0 { 809 | return 0, nil 810 | } 811 | 812 | for _, value := range v { 813 | a, err := iface2int(value) 814 | if err != nil { 815 | return 0, err 816 | } 817 | sum += a 818 | } 819 | return iface2int(sum) 820 | } 821 | 822 | // PFMerge implements http://redis.io/commands/pfmerge. 823 | func (c *Client) PFMerge(keys ...string) (err error) { 824 | _, err = c.execWithKeys(true, "PFMERGE", keys) 825 | return 826 | } 827 | 828 | // Publish implements http://redis.io/commands/publish. 829 | func (c *Client) Publish(channel, message string) error { 830 | _, err := c.execWithKey(true, "PUBLISH", channel, message) 831 | return err 832 | } 833 | 834 | // RPush implements http://redis.io/commands/rpush. 835 | func (c *Client) RPush(key string, values ...string) (int, error) { 836 | v, err := c.execWithKey(true, "RPUSH", key, vstr2iface(values)...) 837 | if err != nil { 838 | return 0, err 839 | } 840 | return iface2int(v) 841 | } 842 | 843 | // SAdd implements http://redis.io/commands/sadd. 844 | func (c *Client) SAdd(key string, vs ...interface{}) (int, error) { 845 | v, err := c.execWithKey(true, "SADD", key, vs...) 846 | 847 | if err != nil { 848 | return 0, err 849 | } 850 | return iface2int(v) 851 | } 852 | 853 | // SRem implements http://redis.io/commands/srem. 854 | func (c *Client) SRem(key string, vs ...interface{}) (int, error) { 855 | v, err := c.execWithKey(true, "SREM", key, vs...) 856 | 857 | if err != nil { 858 | return 0, err 859 | } 860 | return iface2int(v) 861 | } 862 | 863 | // ScriptLoad implements http://redis.io/commands/script-load. 864 | // Cannot be sharded. 865 | func (c *Client) ScriptLoad(script string) (string, error) { 866 | v, err := c.execOnFirst(true, "SCRIPT", "LOAD", script) 867 | if err != nil { 868 | return "", err 869 | } 870 | return iface2str(v) 871 | } 872 | 873 | // Set implements http://redis.io/commands/set. 874 | func (c *Client) Set(key, value string) (err error) { 875 | _, err = c.execWithKey(true, "SET", key, value) 876 | return 877 | } 878 | 879 | // SetNx implements http://redis.io/commands/setnx. 880 | func (c *Client) SetNx(key, value string) (int, error) { 881 | v, err := c.execWithKey(true, "SETNX", key, value) 882 | if err != nil { 883 | return 0, err 884 | } 885 | return iface2int(v) 886 | } 887 | 888 | // SetBit implements http://redis.io/commands/setbit. 889 | func (c *Client) SetBit(key string, offset, value int) (int, error) { 890 | v, err := c.execWithKey(true, "SETBIT", key, offset, value) 891 | if err != nil { 892 | return 0, err 893 | } 894 | return iface2int(v) 895 | } 896 | 897 | // SetEx implements http://redis.io/commands/setex. 898 | func (c *Client) SetEx(key string, seconds int, value string) (err error) { 899 | _, err = c.execWithKey(true, "SETEX", key, seconds, value) 900 | return 901 | } 902 | 903 | // SMembers implements http://redis.io/commands/smembers. 904 | func (c *Client) SMembers(key string) ([]string, error) { 905 | v, err := c.execWithKey(true, "SMEMBERS", key) 906 | if err != nil { 907 | return []string{}, err 908 | } 909 | return iface2vstr(v), nil 910 | } 911 | 912 | // SMove implements http://redis.io/commands/smove. 913 | func (c *Client) SMove(source string, destination string, member string) (int, error) { 914 | v, err := c.execWithKey(true, "SMOVE", source, destination, member) 915 | if err != nil { 916 | return 0, err 917 | } 918 | return iface2int(v) 919 | } 920 | 921 | // SRandMember implements http://redis.io/commands/srandmember. 922 | func (c *Client) SRandMember(key string, count int) ([]string, error) { 923 | v, err := c.execWithKey(true, "SRANDMEMBER", key, count) 924 | if err != nil { 925 | return []string{}, err 926 | } 927 | return iface2vstr(v), nil 928 | } 929 | 930 | // SIsMember implements http://redis.io/commands/sismember. 931 | func (c *Client) SIsMember(key string, vs ...interface{}) (int, error) { 932 | v, err := c.execWithKey(true, "SISMEMBER", key, vs...) 933 | if err != nil { 934 | return 0, err 935 | } 936 | return iface2int(v) 937 | } 938 | 939 | // SCard implements http://redis.io/commands/scard. 940 | func (c *Client) SCard(key string) (int, error) { 941 | v, err := c.execWithKey(true, "SCARD", key) 942 | if err != nil { 943 | return 0, err 944 | } 945 | return iface2int(v) 946 | } 947 | 948 | // A PubSubMessage carries redis pub/sub messages for clients 949 | // that are subscribed to a topic. See Subscribe for details. 950 | type PubSubMessage struct { 951 | Error error 952 | Value string 953 | Channel string 954 | } 955 | 956 | // Subscribe implements http://redis.io/commands/subscribe. 957 | func (c *Client) Subscribe(channel string, m chan<- PubSubMessage, stop <-chan bool) error { 958 | srv, err := c.selector.PickServer("") 959 | if err != nil { 960 | return err 961 | } 962 | cn, err := c.getConn(srv) 963 | if err != nil { 964 | return err 965 | } 966 | // we cannot return this connection to the pool 967 | // because it'll be in subscribe context. 968 | //defer cn.condRelease(&err) 969 | _, err = c.execute(cn.rw, "SUBSCRIBE", channel) 970 | if err != nil { 971 | return err 972 | } 973 | if err = cn.nc.SetDeadline(time.Time{}); err != nil { 974 | cn.nc.Close() 975 | return err 976 | } 977 | watcher := make(chan struct{}) 978 | go func() { 979 | select { 980 | case <-stop: 981 | cn.nc.Close() 982 | case <-watcher: 983 | } 984 | }() 985 | go func() { 986 | defer cn.nc.Close() 987 | for { 988 | raw, err := c.parseResponse(cn.rw.Reader) 989 | if err != nil { 990 | m <- PubSubMessage{ 991 | Error: err, 992 | } 993 | close(watcher) 994 | return 995 | } 996 | switch raw.(type) { 997 | case []interface{}: 998 | ret := raw.([]interface{}) 999 | m <- PubSubMessage{ 1000 | Value: ret[2].(string), 1001 | Channel: ret[1].(string), 1002 | Error: nil, 1003 | } 1004 | default: 1005 | m <- PubSubMessage{ 1006 | Error: ErrServerError, 1007 | } 1008 | close(watcher) 1009 | return 1010 | } 1011 | } 1012 | }() 1013 | return err 1014 | } 1015 | 1016 | // TTL implements http://redis.io/commands/ttl. 1017 | func (c *Client) TTL(key string) (int, error) { 1018 | v, err := c.execWithKey(true, "TTL", key) 1019 | if err != nil { 1020 | return 0, err 1021 | } 1022 | return iface2int(v) 1023 | } 1024 | 1025 | // ZAdd implements http://redis.io/commands/zadd. 1026 | func (c *Client) ZAdd(key string, vs ...interface{}) (int, error) { 1027 | if len(vs)%2 != 0 { 1028 | return 0, errors.New("Incomplete parameter sequence") 1029 | } 1030 | v, err := c.execWithKey(true, "ZADD", key, vs...) 1031 | if err != nil { 1032 | return 0, err 1033 | } 1034 | return iface2int(v) 1035 | } 1036 | 1037 | // ZCard implements http://redis.io/commands/zcard. 1038 | func (c *Client) ZCard(key string) (int, error) { 1039 | v, err := c.execWithKey(true, "ZCARD", key) 1040 | if err != nil { 1041 | return 0, err 1042 | } 1043 | return iface2int(v) 1044 | } 1045 | 1046 | // ZCount implements http://redis.io/commands/zcount. 1047 | func (c *Client) ZCount(key string, min int, max int) (int, error) { 1048 | v, err := c.execWithKey(true, "ZCOUNT", key, min, max) 1049 | 1050 | if err != nil { 1051 | return 0, err 1052 | } 1053 | return iface2int(v) 1054 | } 1055 | 1056 | // ZRange implements http://redis.io/commands/zrange. 1057 | func (c *Client) ZRange(key string, start int, stop int, withscores bool) ([]string, error) { 1058 | var v interface{} 1059 | var err error 1060 | if withscores == true { 1061 | v, err = c.execWithKey(true, "ZRANGE", key, start, stop, "WITHSCORES") 1062 | } else { 1063 | v, err = c.execWithKey(true, "ZRANGE", key, start, stop) 1064 | } 1065 | if err != nil { 1066 | return nil, err 1067 | } 1068 | return iface2vstr(v), nil 1069 | } 1070 | 1071 | // ZRevRange implements http://redis.io/commands/zrevrange. 1072 | func (c *Client) ZRevRange(key string, start int, stop int, withscores bool) ([]string, error) { 1073 | var v interface{} 1074 | var err error 1075 | if withscores == true { 1076 | v, err = c.execWithKey(true, "ZREVRANGE", key, start, stop, "WITHSCORES") 1077 | } else { 1078 | v, err = c.execWithKey(true, "ZREVRANGE", key, start, stop) 1079 | } 1080 | if err != nil { 1081 | return nil, err 1082 | } 1083 | return iface2vstr(v), nil 1084 | } 1085 | 1086 | // ZRangeByScore implements http://redis.io/commands/ZRANGEBYSCORE 1087 | func (c *Client) ZRangeByScore(key string, min int, max int, withscores bool, limit bool, offset int, count int) ([]string, error) { 1088 | var v interface{} 1089 | var err error 1090 | 1091 | if withscores == true { 1092 | if limit { 1093 | v, err = c.execWithKey(true, "ZRANGEBYSCORE", key, min, max, "WITHSCORES", "LIMIT", offset, count) 1094 | } else { 1095 | v, err = c.execWithKey(true, "ZRANGEBYSCORE", key, min, max, "WITHSCORES") 1096 | } 1097 | } else { 1098 | if limit { 1099 | v, err = c.execWithKey(true, "ZRANGEBYSCORE", key, min, max, "LIMIT", offset, count) 1100 | } else { 1101 | v, err = c.execWithKey(true, "ZRANGEBYSCORE", key, min, max) 1102 | } 1103 | } 1104 | 1105 | if err != nil { 1106 | return nil, err 1107 | } 1108 | return iface2vstr(v), nil 1109 | } 1110 | 1111 | // ZRevRangeByScore implements http://redis.io/commands/ZREVRANGEBYSCORE 1112 | func (c *Client) ZRevRangeByScore(key string, max int, min int, withscores bool, limit bool, offset int, count int) ([]string, error) { 1113 | var v interface{} 1114 | var err error 1115 | 1116 | if withscores == true { 1117 | if limit { 1118 | v, err = c.execWithKey(true, "ZREVRANGEBYSCORE", key, max, min, "WITHSCORES", "LIMIT", offset, count) 1119 | } else { 1120 | v, err = c.execWithKey(true, "ZREVRANGEBYSCORE", key, max, min, "WITHSCORES") 1121 | } 1122 | } else { 1123 | if limit { 1124 | v, err = c.execWithKey(true, "ZREVRANGEBYSCORE", key, max, min, "LIMIT", offset, count) 1125 | } else { 1126 | v, err = c.execWithKey(true, "ZREVRANGEBYSCORE", key, max, min) 1127 | } 1128 | } 1129 | 1130 | if err != nil { 1131 | return nil, err 1132 | } 1133 | return iface2vstr(v), nil 1134 | } 1135 | 1136 | // ZScore implements http://redis.io/commands/zscore. 1137 | func (c *Client) ZScore(key string, member string) (string, error) { 1138 | v, err := c.execWithKey(true, "ZSCORE", key, member) 1139 | if err != nil { 1140 | return "", err 1141 | } 1142 | return iface2str(v) 1143 | } 1144 | 1145 | // ZRem implements http://redis.io/commands/zrem. 1146 | func (c *Client) ZRem(key string, vs ...interface{}) (int, error) { 1147 | v, err := c.execWithKey(true, "ZREM", key, vs...) 1148 | 1149 | if err != nil { 1150 | return 0, err 1151 | } 1152 | return iface2int(v) 1153 | } 1154 | 1155 | // ZRemRangeByScore implements http://redis.io/commands/zremrangebyscore. 1156 | func (c *Client) ZRemRangeByScore(key string, start interface{}, stop interface{}) (int, error) { 1157 | v, err := c.execWithKey(true, "ZREMRANGEBYSCORE", key, start, stop) 1158 | 1159 | if err != nil { 1160 | return 0, err 1161 | } 1162 | return iface2int(v) 1163 | } 1164 | 1165 | // GetMulti is a batch version of Get. The returned map from keys to 1166 | // items may have fewer elements than the input slice, due to memcache 1167 | // cache misses. Each key must be at most 250 bytes in length. 1168 | // If no error is returned, the returned map will also be non-nil. 1169 | /* 1170 | func (c *Client) GetMulti(keys []string) (map[string]*Item, error) { 1171 | var lk sync.Mutex 1172 | m := make(map[string]*Item) 1173 | addItemToMap := func(it *Item) { 1174 | lk.Lock() 1175 | defer lk.Unlock() 1176 | m[it.Key] = it 1177 | } 1178 | 1179 | keyMap := make(map[net.Addr][]string) 1180 | for _, key := range keys { 1181 | if !legalKey(key) { 1182 | return nil, ErrMalformedKey 1183 | } 1184 | addr, err := c.selector.PickServer(key) 1185 | if err != nil { 1186 | return nil, err 1187 | } 1188 | keyMap[addr] = append(keyMap[addr], key) 1189 | } 1190 | 1191 | ch := make(chan error, buffered) 1192 | for addr, keys := range keyMap { 1193 | go func(addr net.Addr, keys []string) { 1194 | //ch <- c.getFromAddr(addr, keys, addItemToMap) 1195 | }(addr, keys) 1196 | } 1197 | 1198 | var err error 1199 | for _ = range keyMap { 1200 | if ge := <-ch; ge != nil { 1201 | err = ge 1202 | } 1203 | } 1204 | return m, err 1205 | } 1206 | */ 1207 | -------------------------------------------------------------------------------- /redis/commands_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013-2015 go-redis authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package redis 16 | 17 | import ( 18 | "strconv" 19 | "strings" 20 | "testing" 21 | "time" 22 | ) 23 | 24 | const errUnexpected = "Unexpected response from redis-server: %#v" 25 | 26 | func TestPublish(t *testing.T) { 27 | k := randomString(16) 28 | v := randomString(16) 29 | if err := rc.Publish(k, v); err != nil { 30 | t.Fatal(err) 31 | } 32 | } 33 | 34 | // TestAppend appends " World" to "Hello" and expects the lenght to be 11. 35 | func TestAppend(t *testing.T) { 36 | defer rc.Del("foobar") 37 | if _, err := rc.Append("foobar", "Hello"); err != nil { 38 | t.Fatal(err) 39 | } 40 | if n, err := rc.Append("foobar", " World"); err != nil { 41 | t.Fatal(err) 42 | } else if n != 11 { 43 | t.Fatalf(errUnexpected, n) 44 | } 45 | } 46 | 47 | // TestBgRewriteAOF starts an Append Only File rewrite process. 48 | func dontTestBgRewriteAOF(t *testing.T) { 49 | if status, err := rc.BgRewriteAOF(); err != nil { 50 | t.Fatal(err) 51 | } else if status != "Background append only file rewriting started" { 52 | t.Fatalf(errUnexpected, status) 53 | } 54 | } 55 | 56 | // TestBgSave saves the DB in background. 57 | func dontTestBgSave(t *testing.T) { 58 | if status, err := rc.BgSave(); err != nil { 59 | t.Fatal(err) 60 | } else if status != "Background saving started" { 61 | t.Fatalf(errUnexpected, status) 62 | } 63 | } 64 | 65 | // TestBitCount reproduces the example from http://redis.io/commands/bitcount. 66 | func TestBitCount(t *testing.T) { 67 | defer rc.Del("mykey") 68 | if err := rc.Set("mykey", "foobar"); err != nil { 69 | t.Fatal(err) 70 | } 71 | if n, err := rc.BitCount("mykey", -1, -1); err != nil { 72 | t.Fatal(err) 73 | } else if n != 26 { 74 | t.Fatalf(errUnexpected, n) 75 | } 76 | } 77 | 78 | // TestBitOp reproduces the example from http://redis.io/commands/bitop. 79 | func TestBitOp(t *testing.T) { 80 | defer rc.Del("key1", "key2", "dest") 81 | if err := rc.Set("key1", "foobar"); err != nil { 82 | t.Fatal(err) 83 | } 84 | if err := rc.Set("key2", "abcdef"); err != nil { 85 | t.Fatal(err) 86 | } 87 | if _, err := rc.BitOp("and", "dest", "key1", "key2"); err != nil { 88 | t.Fatal(err) 89 | } 90 | } 91 | 92 | // TestRPush and LIndex 93 | func TestRPush(t *testing.T) { 94 | rc.Del("list1") 95 | defer rc.Del("list1") 96 | rc.RPush("list1", "a", "b", "c") 97 | if v, err := rc.LIndex("list1", 1); err != nil { 98 | t.Fatal(err) 99 | } else if v != "b" { 100 | t.Fatalf(errUnexpected, "v="+v) 101 | } 102 | } 103 | 104 | // Test RPop 105 | func TestRPop(t *testing.T) { 106 | rc.Del("list1") 107 | defer rc.Del("list1") 108 | rc.RPush("list1", "a", "b", "c") 109 | if v, err := rc.RPop("list1"); err != nil { 110 | t.Fatal(err) 111 | } else if v != "c" { 112 | t.Fatalf(errUnexpected, "v="+v) 113 | } 114 | } 115 | 116 | // Test LPop 117 | func TestLPop(t *testing.T) { 118 | rc.Del("list1") 119 | defer rc.Del("list1") 120 | rc.RPush("list1", "a", "b", "c") 121 | if v, err := rc.LPop("list1"); err != nil { 122 | t.Fatal(err) 123 | } else if v != "a" { 124 | t.Fatalf(errUnexpected, "v="+v) 125 | } 126 | } 127 | 128 | func TestLLen(t *testing.T) { 129 | rc.Del("list1") 130 | defer rc.Del("list1") 131 | rc.RPush("list1", "a", "b", "c") 132 | if v, err := rc.LLen("list1"); err != nil { 133 | t.Fatal(err) 134 | } else if v != 3 { 135 | t.Fatalf(errUnexpected, "v="+string(v)) 136 | } 137 | } 138 | 139 | func TestLTrim(t *testing.T) { 140 | rc.Del("list1") 141 | defer rc.Del("list1") 142 | rc.RPush("list1", "a", "b", "c", "d") 143 | 144 | if err := rc.LTrim("list1", 2, 3); err != nil { 145 | t.Fatal(err) 146 | } 147 | 148 | if v, err := rc.LLen("list1"); err != nil { 149 | t.Fatal(err) 150 | } else if v != 2 { 151 | t.Fatalf(errUnexpected, "len list1 ="+string(v)) 152 | } 153 | } 154 | 155 | func TestLRange(t *testing.T) { 156 | rc.Del("list1") 157 | defer rc.Del("list1") 158 | rc.RPush("list1", "a", "b", "c", "d") 159 | 160 | if v, err := rc.LRange("list1", 0, 1); err != nil { 161 | t.Fatal(err) 162 | } else if v[0] != "a" { 163 | t.Fatalf(errUnexpected, "LRange list1") 164 | } 165 | } 166 | 167 | func TestLRem(t *testing.T) { 168 | rc.Del("list1") 169 | defer rc.Del("list1") 170 | rc.RPush("list1", "a", "b", "c", "d", "c") 171 | if v, err := rc.LRem("list1", 2, "c"); err != nil { 172 | t.Fatal(err) 173 | } else if v != 2 { 174 | t.Fatalf(errUnexpected, "LRem test1 return value") 175 | } 176 | 177 | if v, err := rc.LLen("list1"); err != nil { 178 | t.Fatal(err) 179 | } else if v != 3 { 180 | t.Fatalf(errUnexpected, "LRem test1 length") 181 | } 182 | } 183 | 184 | // TestBLPop reproduces the example from http://redis.io/commands/blpop. 185 | func TestBLPop(t *testing.T) { 186 | rc.Del("list1", "list2") 187 | defer rc.Del("list1", "list2") 188 | rc.RPush("list1", "a", "b", "c") 189 | if k, v, err := rc.BLPop(0, "list1", "list2"); err != nil { 190 | t.Fatal(err) 191 | } else if k != "list1" || v != "a" { 192 | t.Fatalf(errUnexpected, "k="+k+" v="+v) 193 | } 194 | } 195 | 196 | // TestBRPop reproduces the example from http://redis.io/commands/brpop. 197 | func TestBRPop(t *testing.T) { 198 | rc.Del("list1", "list2") 199 | defer rc.Del("list1", "list2") 200 | rc.RPush("list1", "a", "b", "c") 201 | if k, v, err := rc.BRPop(0, "list1", "list2"); err != nil { 202 | t.Fatal(err) 203 | } else if k != "list1" || v != "c" { 204 | t.Fatalf(errUnexpected, "k="+k+" v="+v) 205 | } 206 | } 207 | 208 | // TestBRPopTimeout is the same as TestBRPop, but expects a time out. 209 | // TestBRPopTimeout also tests BLPop (because both share the same code). 210 | func TestBRPopTimeout(t *testing.T) { 211 | rc.Del("list1", "list2") 212 | defer rc.Del("list1", "list2") 213 | if k, v, err := rc.BRPop(1, "list1", "list2"); err != ErrTimedOut { 214 | if err != nil { 215 | t.Fatal(err) 216 | } else { 217 | t.Fatalf(errUnexpected, "k="+k+" v="+v) 218 | } 219 | } 220 | } 221 | 222 | // TestBRPopTimeout2 is the same as TestBRPop, but expects a value. 223 | func TestBRPopTimeout2(t *testing.T) { 224 | rc.Del("list1", "list2") 225 | defer rc.Del("list1", "list2") 226 | go func() { 227 | time.Sleep(100 * time.Millisecond) 228 | rc.LPush("list1", "a", "b", "c") 229 | }() 230 | if k, v, err := rc.BRPop(1, "list1", "list2"); err != nil { 231 | t.Fatal(err) 232 | } else if k != "list1" || v != "a" { 233 | t.Fatalf(errUnexpected, "k="+k+" v="+v) 234 | } 235 | } 236 | 237 | // TestBRPopLPush takes last item of a list and inserts into another. 238 | func TestBRPopLPush(t *testing.T) { 239 | rc.Del("list1", "list2") 240 | defer rc.Del("list1", "list2") 241 | rc.RPush("list1", "a", "b", "c") 242 | if v, err := rc.BRPopLPush("list1", "list2", 0); err != nil { 243 | t.Fatal(err) 244 | } else if v != "c" { 245 | t.Fatalf(errUnexpected, "v="+v) 246 | } 247 | } 248 | 249 | // TestBRPopLPushTimeout is the same as TestBRPopLPush, but expects a time out. 250 | func TestBRPopLPushTimeout(t *testing.T) { 251 | rc.Del("list1", "list2") 252 | defer rc.Del("list1", "list2") 253 | if v, err := rc.BRPopLPush("list1", "list2", 1); err != ErrTimedOut { 254 | if err != nil { 255 | t.Fatal(err) 256 | } else { 257 | t.Fatalf(errUnexpected, "v="+v) 258 | } 259 | } 260 | } 261 | 262 | // TestClientListKill kills the first connection returned by CLIENT LIST. 263 | func TestClientListKill(t *testing.T) { 264 | conInfo := make(map[string]string) 265 | 266 | if clients, err := rc.ClientList(); err != nil { 267 | t.Fatal(err) 268 | } else if len(clients) < 1 { 269 | t.Fatalf(errUnexpected, clients) 270 | } else { 271 | parts := strings.Split(clients[0], " ") 272 | for _, part := range parts { 273 | kv := strings.Split(part, "=") 274 | conInfo[kv[0]] = kv[1] 275 | } 276 | } 277 | defer rc.ClientList() // send any cmd to enforce socket shutdown 278 | if err := rc.ClientKill(conInfo["addr"]); err != nil { 279 | t.Fatal(err) 280 | } 281 | } 282 | 283 | // TestClientSetName name the current connection, and looks it up in the list. 284 | func TestClientSetName(t *testing.T) { 285 | if err := rc.ClientSetName("bozo"); err != nil { 286 | t.Fatal(err) 287 | } 288 | if clients, err := rc.ClientList(); err != nil { 289 | t.Fatal(err) 290 | } else if len(clients) < 1 { 291 | t.Fatalf(errUnexpected, clients) 292 | } else { 293 | found := false 294 | for _, info := range clients { 295 | if strings.Contains(info, " name=bozo ") { 296 | found = true 297 | break 298 | } 299 | } 300 | if !found { 301 | t.Error("Could not find client after SetName") 302 | } 303 | } 304 | } 305 | 306 | // TestConfigGet tests the server port number. 307 | func TestConfigGet(t *testing.T) { 308 | if items, err := rc.ConfigGet("*"); err != nil { 309 | t.Fatal(err) 310 | } else if _, ok := items["dbfilename"]; !ok { 311 | t.Fatalf(errUnexpected, items) 312 | } 313 | } 314 | 315 | // TestConfigSet sets redis dir to /tmp, and back to the default. 316 | func TestConfigSet(t *testing.T) { 317 | items, err := rc.ConfigGet("dir") 318 | if err != nil { 319 | t.Fatal(err) 320 | } 321 | if err = rc.ConfigSet("dir", "/tmp"); err != nil { 322 | t.Fatal(err) 323 | } 324 | if err := rc.ConfigSet("dir", items["dir"]); err != nil { 325 | t.Fatal(err) 326 | } 327 | } 328 | 329 | // TestConfigResetStat resets redis statistics. 330 | func TestConfigResetStat(t *testing.T) { 331 | if err := rc.ConfigResetStat(); err != nil { 332 | t.Fatal(err) 333 | } 334 | } 335 | 336 | // TestDBSize checks the current database size, adds a key, and checks again. 337 | func TestDBSize(t *testing.T) { 338 | size, err := rc.DBSize() 339 | if err != nil { 340 | t.Fatalf(errUnexpected, err) 341 | } 342 | rc.Set("test-db-size", "zzz") 343 | defer rc.Del("test-db-size") 344 | if newsize, err := rc.DBSize(); err != nil { 345 | t.Fatalf(errUnexpected, err) 346 | } else if newsize != size+1 { 347 | t.Fatalf(errUnexpected, newsize) 348 | } 349 | } 350 | 351 | // TestDebugSegfault crashes redis and breaks everything else. 352 | func dontTestDebugSegfault(t *testing.T) { 353 | if err := rc.DebugSegfault(); err != nil { 354 | t.Fatal(err) 355 | } 356 | } 357 | 358 | // TestDecr reproduces the example from http://redis.io/commands/decr. 359 | func TestDecr(t *testing.T) { 360 | rc.Del("mykey") 361 | defer rc.Del("mykey") 362 | rc.Set("mykey", "10") 363 | if n, err := rc.Decr("mykey"); err != nil { 364 | t.Fatalf(errUnexpected, err) 365 | } else if n != 9 { 366 | t.Fatalf(errUnexpected, n) 367 | } 368 | } 369 | 370 | // TestDecrBy reproduces the example from http://redis.io/commands/decrby. 371 | func TestDecrBy(t *testing.T) { 372 | rc.Del("mykey") 373 | defer rc.Del("mykey") 374 | rc.Set("mykey", "10") 375 | if n, err := rc.DecrBy("mykey", 5); err != nil { 376 | t.Fatalf(errUnexpected, err) 377 | } else if n != 5 { 378 | t.Fatalf(errUnexpected, n) 379 | } 380 | } 381 | 382 | // TestIncr reproduces the example from http://redis.io/commands/incr. 383 | func TestIncr(t *testing.T) { 384 | rc.Del("mykey") 385 | defer rc.Del("mykey") 386 | rc.Set("mykey", "0") 387 | if n, err := rc.Incr("mykey"); err != nil { 388 | t.Fatalf(errUnexpected, err) 389 | } else if n != 1 { 390 | t.Fatalf(errUnexpected, n) 391 | } 392 | } 393 | 394 | // TestIncrBy reproduces the example from http://redis.io/commands/incrby. 395 | func TestIncrBy(t *testing.T) { 396 | rc.Del("mykey") 397 | defer rc.Del("mykey") 398 | rc.Set("mykey", "0") 399 | if n, err := rc.IncrBy("mykey", 5); err != nil { 400 | t.Fatalf(errUnexpected, err) 401 | } else if n != 5 { 402 | t.Fatalf(errUnexpected, n) 403 | } 404 | } 405 | 406 | // TestDel creates 1024 keys and deletes them. 407 | func TestDel(t *testing.T) { 408 | keys := make([]string, 1024) 409 | for n := 0; n < cap(keys); n++ { 410 | k := randomString(4) + string(n) 411 | v := randomString(32) 412 | if err := rc.Set(k, v); err != nil { 413 | t.Fatal(err) 414 | } else { 415 | keys[n] = k 416 | } 417 | } 418 | if deleted, err := rc.Del(keys...); err != nil { 419 | t.Fatal(err) 420 | } else if deleted != cap(keys) { 421 | t.Fatalf(errUnexpected, deleted) 422 | } 423 | } 424 | 425 | // TODO: TestDiscard 426 | 427 | // TestDump reproduces the example from http://redis.io/commands/dump. 428 | func TestDump(t *testing.T) { 429 | defer rc.Del("mykey") 430 | rc.Set("mykey", "10") 431 | if v, err := rc.Dump("mykey"); err != nil { 432 | t.Fatal(err) 433 | } else if v != "\u0000\xC0\n\u0006\u0000\xF8r?\xC5\xFB\xFB_(" { 434 | t.Fatalf(errUnexpected, v) 435 | } 436 | } 437 | 438 | // TestDump reproduces the example from http://redis.io/commands/echo. 439 | func TestEcho(t *testing.T) { 440 | m := "Hello World!" 441 | if v, err := rc.Echo(m); err != nil { 442 | t.Fatal(err) 443 | } else if v != m { 444 | t.Fatalf(errUnexpected, v) 445 | } 446 | } 447 | 448 | // TestEval tests server side Lua script. 449 | // TODO: fix the response. 450 | func TestEval(t *testing.T) { 451 | if _, err := rc.Eval( 452 | "return {1,{2,3,'foo'},KEYS[1],KEYS[2],ARGV[1],ARGV[2]}", 453 | 2, // numkeys 454 | []string{"key1", "key2"}, // keys 455 | []string{"first", "second"}, // args 456 | ); err != nil { 457 | t.Fatal(err) 458 | } 459 | //t.Log("v=%#v\n", v) 460 | } 461 | 462 | // TestEvalSha tests server side Lua script. 463 | // TestEvalSha preloads the script with ScriptLoad. 464 | // TODO: fix the response. 465 | func TestEvalSha(t *testing.T) { 466 | sha1, err := rc.ScriptLoad("return {1,{2,3,'foo'},KEYS[1],KEYS[2],ARGV[1],ARGV[2]}") 467 | if err != nil { 468 | t.Fatal(err) 469 | } 470 | if _, err = rc.EvalSha( 471 | sha1, // pre-loaded script 472 | 2, // numkeys 473 | []string{"key1", "key2"}, // keys 474 | []string{"first", "second"}, // args 475 | ); err != nil { 476 | t.Fatal(err) 477 | } 478 | //t.Log("v=%#v\n", v) 479 | } 480 | 481 | // TODO: TestExec 482 | 483 | // TestExists reproduces the example from http://redis.io/commands/exists. 484 | func TestExists(t *testing.T) { 485 | rc.Del("key1", "key2") 486 | defer rc.Del("key1", "key2") 487 | rc.Set("key1", "Hello") 488 | if ok, err := rc.Exists("key1"); err != nil { 489 | t.Fatal(err) 490 | } else if !ok { 491 | t.Fatalf(errUnexpected, ok) 492 | } 493 | if ok, err := rc.Exists("key2"); err != nil { 494 | t.Fatal(err) 495 | } else if ok { 496 | t.Fatalf(errUnexpected, ok) 497 | } 498 | } 499 | 500 | // TestExpire reproduces the example from http://redis.io/commands/expire. 501 | // TestExpire also tests the TTL command. 502 | func TestExpire(t *testing.T) { 503 | defer rc.Del("mykey") 504 | rc.Set("mykey", "hello") 505 | if ok, err := rc.Expire("mykey", 10); err != nil { 506 | t.Fatal(err) 507 | } else if !ok { 508 | t.Fatalf(errUnexpected, ok) 509 | } 510 | if ttl, err := rc.TTL("mykey"); err != nil { 511 | t.Fatal(err) 512 | } else if ttl != 10 { 513 | t.Fatalf(errUnexpected, ttl) 514 | } 515 | rc.Set("mykey", "Hello World") 516 | if ttl, err := rc.TTL("mykey"); err != nil { 517 | t.Fatal(err) 518 | } else if ttl != -1 { 519 | t.Fatalf(errUnexpected, ttl) 520 | } 521 | } 522 | 523 | // TestExpireAt reproduces the example from http://redis.io/commands/expire. 524 | func TestExpireAt(t *testing.T) { 525 | defer rc.Del("mykey") 526 | rc.Set("mykey", "hello") 527 | if ok, err := rc.Exists("mykey"); err != nil { 528 | t.Fatal(err) 529 | } else if !ok { 530 | t.Fatalf(errUnexpected, ok) 531 | } 532 | if ok, err := rc.ExpireAt("mykey", 1293840000); err != nil { 533 | t.Fatal(err) 534 | } else if !ok { 535 | t.Fatalf(errUnexpected, ok) 536 | } 537 | if ok, err := rc.Exists("mykey"); err != nil { 538 | t.Fatal(err) 539 | } else if ok { 540 | t.Fatal(errUnexpected, ok) 541 | } 542 | } 543 | 544 | // FlushAll and FlushDB are not required because they never fail. 545 | 546 | // TestGet reproduces the example from http://redis.io/commands/get 547 | func TestGet(t *testing.T) { 548 | rc.Del("nonexisting") 549 | if v, err := rc.Get("nonexisting"); err != nil { 550 | t.Fatal(err) 551 | } else if v != "" { 552 | t.Fatalf(errUnexpected, v) 553 | } 554 | rc.Set("mykey", "Hello") 555 | defer rc.Del("mykey") 556 | if v, err := rc.Get("mykey"); err != nil { 557 | t.Fatal(err) 558 | } else if v == "" { 559 | t.Fatalf(errUnexpected, v) 560 | } 561 | } 562 | 563 | // TestGetBit reproduces the example from http://redis.io/commands/getbit. 564 | // TestGetBit also tests SetBit. 565 | func TestGetBit(t *testing.T) { 566 | defer rc.Del("mykey") 567 | if _, err := rc.SetBit("mykey", 7, 1); err != nil { 568 | t.Fatal(err) 569 | } 570 | if v, err := rc.GetBit("mykey", 0); err != nil { 571 | t.Fatal(err) 572 | } else if v != 0 { 573 | t.Fatalf(errUnexpected, v) 574 | } 575 | if v, err := rc.GetBit("mykey", 7); err != nil { 576 | t.Fatal(err) 577 | } else if v != 1 { 578 | t.Fatalf(errUnexpected, v) 579 | } 580 | } 581 | 582 | // TestGetRange reproduces the example from http://redis.io/commands/getrange. 583 | func TestGetRange(t *testing.T) { 584 | defer rc.Del("mykey") 585 | rc.Set("mykey", "This is a string") 586 | if v, err := rc.GetRange("mykey", 0, 3); err != nil { 587 | t.Fatal(err) 588 | } else if v != "This" { 589 | t.Fatalf(errUnexpected, v) 590 | } 591 | if v, err := rc.GetRange("mykey", -3, -1); err != nil { 592 | t.Fatal(err) 593 | } else if v != "ing" { 594 | t.Fatalf(errUnexpected, v) 595 | } 596 | if v, err := rc.GetRange("mykey", 0, -1); err != nil { 597 | t.Fatal(err) 598 | } else if v != "This is a string" { 599 | t.Fatalf(errUnexpected, v) 600 | } 601 | if v, err := rc.GetRange("mykey", 10, 100); err != nil { 602 | t.Fatal(err) 603 | } else if v != "string" { 604 | t.Fatal(errUnexpected, v) 605 | } 606 | } 607 | 608 | // TestGetSet reproduces the example from http://redis.io/commands/getset. 609 | func TestGetSet(t *testing.T) { 610 | rc.Del("mycounter") 611 | defer rc.Del("mycounter") 612 | rc.Incr("mycounter") 613 | if v, err := rc.GetSet("mycounter", "0"); err != nil { 614 | t.Fatal(err) 615 | } else if v != "1" { 616 | t.Fatalf(errUnexpected, v) 617 | } 618 | if v, err := rc.Get("mycounter"); err != nil { 619 | t.Fatal(err) 620 | } else if v != "0" { 621 | t.Fatalf(errUnexpected, v) 622 | } 623 | } 624 | 625 | // TestMGet reproduces the example from http://redis.io/commands/mget. 626 | func TestMGet(t *testing.T) { 627 | defer rc.Del("key1", "key2") 628 | rc.Set("key1", "Hello") 629 | rc.Set("key2", "World") 630 | if items, err := rc.MGet("key1", "key2"); err != nil { 631 | t.Fatal(err) 632 | } else if items[0] != "Hello" || items[1] != "World" { 633 | t.Fatalf(errUnexpected, items) 634 | } 635 | } 636 | 637 | // TestMSet reproduces the example from http://redis.io/commands/mset. 638 | func TestMSet(t *testing.T) { 639 | rc.Del("key1", "key2") 640 | defer rc.Del("key1", "key2") 641 | if err := rc.MSet(map[string]string{ 642 | "key1": "Hello", "key2": "World", 643 | }); err != nil { 644 | t.Fatal(err) 645 | } 646 | v1, _ := rc.Get("key1") 647 | v2, _ := rc.Get("key2") 648 | if v1 != "Hello" || v2 != "World" { 649 | t.Fatalf(errUnexpected, v1+", "+v2) 650 | } 651 | } 652 | 653 | // TestKeys reproduces the example from http://redis.io/commands/keys 654 | func TestKeys(t *testing.T) { 655 | rc.MSet(map[string]string{ 656 | "one": "1", "two": "2", "three": "3", "four": "4", 657 | }) 658 | defer rc.Del("one", "two", "three", "four") 659 | keys, err := rc.Keys("*o*") 660 | if err != nil { 661 | t.Fatal(err) 662 | } 663 | c := 0 664 | for _, k := range keys { 665 | switch k { 666 | case "one", "two", "four": 667 | c++ 668 | } 669 | } 670 | if c != 3 { 671 | t.Fatalf(errUnexpected, keys) 672 | } 673 | } 674 | 675 | func TestScan(t *testing.T) { 676 | rc.MSet(map[string]string{ 677 | "TESTSCANone": "1", "TESTSCANtwo": "2", "TESTSCANthree": "3", "TESTSCANfour": "4", 678 | }) 679 | defer rc.Del("TESTSCANone", "TESTSCANtwo", "TESTSCANthree", "TESTSCANfour") 680 | 681 | c := "0" 682 | var res []string 683 | for { 684 | cursor, keys, err := rc.Scan(c, "match", "TESTSCAN*") 685 | if err != nil { 686 | t.Fatal(err) 687 | } 688 | 689 | for _, k := range keys { 690 | res = append(res, k) 691 | } 692 | 693 | if cursor == "0" { 694 | break 695 | } 696 | 697 | c = cursor // update cursor 698 | } 699 | 700 | if len(res) != 4 { 701 | t.Fatalf(errUnexpected, res) 702 | } 703 | } 704 | 705 | func TestSScan(t *testing.T) { 706 | rc.SAdd("myset", "TESTSCANone", "TESTSCANtwo", "TESTSCANthree", "TESTSCANfour") 707 | defer rc.Del("myset") 708 | 709 | c := "0" 710 | var res []string 711 | for { 712 | cursor, keys, err := rc.SScan("myset", c, "match", "TESTSCAN*") 713 | if err != nil { 714 | t.Fatal(err) 715 | } 716 | 717 | for _, k := range keys { 718 | res = append(res, k) 719 | } 720 | 721 | if cursor == "0" { 722 | break 723 | } 724 | 725 | c = cursor // update cursor 726 | } 727 | 728 | if len(res) != 4 { 729 | t.Fatalf(errUnexpected, res) 730 | } 731 | } 732 | 733 | func TestHScan(t *testing.T) { 734 | rc.HMSet("mykey", map[string]string{ 735 | "TESTSCANone": "1", 736 | "TESTSCANtwo": "2", 737 | "TESTSCANthree": "3", 738 | "TESTSCANfour": "4", 739 | }) 740 | defer rc.Del("mykey") 741 | 742 | c := "0" 743 | var res []string 744 | for { 745 | cursor, keys, err := rc.HScan("mykey", c, "match", "TESTSCAN*") 746 | if err != nil { 747 | t.Fatal(err) 748 | } 749 | 750 | for _, k := range keys { 751 | res = append(res, k) 752 | } 753 | 754 | if cursor == "0" { 755 | break 756 | } 757 | 758 | c = cursor // update cursor 759 | } 760 | 761 | if len(res) != 4 { 762 | t.Fatalf(errUnexpected, res) 763 | } 764 | } 765 | 766 | func TestZScan(t *testing.T) { 767 | rc.ZAdd("myzset", 1, "TESTSCANone", 2, "TESTSCANtwo", 3, "TESTSCANthree", 4, "TESTSCANfour") 768 | defer rc.Del("myzset") 769 | 770 | c := "0" 771 | var res []string 772 | for { 773 | cursor, keys, err := rc.ZScan("myzset", c, "match", "TESTSCAN*") 774 | if err != nil { 775 | t.Fatal(err) 776 | } 777 | 778 | for _, k := range keys { 779 | res = append(res, k) 780 | } 781 | 782 | if cursor == "0" { 783 | break 784 | } 785 | 786 | c = cursor // update cursor 787 | } 788 | 789 | if len(res) != 4 { 790 | t.Fatalf(errUnexpected, res) 791 | } 792 | } 793 | 794 | func TestSAdd(t *testing.T) { 795 | k := randomString(1024) 796 | defer rc.Del(k) 797 | 798 | //singles 799 | for i := 0; i < 10; i++ { 800 | v := randomString(32) 801 | _, err := rc.SAdd(k, v) 802 | if err != nil { 803 | t.Fatal(err) 804 | } 805 | } 806 | 807 | //multiple 808 | _, err := rc.SAdd(k, "setuno", "setdue") 809 | if err != nil { 810 | t.Fatal(err) 811 | } 812 | } 813 | 814 | func TestSRem(t *testing.T) { 815 | k := randomString(1024) 816 | defer rc.Del(k) 817 | 818 | //singles 819 | for i := 0; i < 10; i++ { 820 | v := randomString(32) 821 | _, err := rc.SRem(k, v) 822 | if err != nil { 823 | t.Fatal(err) 824 | } 825 | } 826 | 827 | _, err := rc.SAdd(k, "setuno", "setdue") 828 | if err != nil { 829 | t.Fatal(err) 830 | } 831 | 832 | n, err := rc.SRem(k, "setuno", "setdue") 833 | if err != nil { 834 | t.Fatal(err) 835 | } 836 | 837 | if n != 2 { 838 | t.Fatal("Failed to remove entries via SREM") 839 | } 840 | } 841 | 842 | func TestSMembers(t *testing.T) { 843 | k := randomString(1024) 844 | defer rc.Del(k) 845 | rc.SAdd(k, "setuno", "setdue") 846 | members, err := rc.SMembers(k) 847 | if err != nil { 848 | t.Fatal(err) 849 | } 850 | 851 | if len(members) != 2 { 852 | t.Fatalf(errUnexpected, len(members)) 853 | } 854 | } 855 | 856 | func TestSMove(t *testing.T) { 857 | s := randomString(1024) 858 | d := randomString(1024) 859 | v := "setuno" 860 | rc.SAdd(s, v) 861 | defer rc.Del(d) 862 | 863 | isMoved, err := rc.SMove(s, d, v) 864 | if err != nil { 865 | t.Fatal(err) 866 | } 867 | 868 | if isMoved == 0 { 869 | t.Fatal("Failed to move an entry via SMOVE") 870 | } 871 | 872 | isMember, _ := rc.SIsMember(s, v) 873 | if isMember == 1 { 874 | t.Fatal("Failed to move an entry via SMOVE") 875 | } 876 | 877 | isMember, _ = rc.SIsMember(d, v) 878 | if isMember == 0 { 879 | t.Fatal("Failed to move an entry via SMOVE") 880 | } 881 | } 882 | 883 | func TestSRandMember(t *testing.T) { 884 | k := randomString(1024) 885 | defer rc.Del(k) 886 | rc.SAdd(k, "setuno", "setdue") 887 | members, err := rc.SRandMember(k, 1) 888 | if err != nil { 889 | t.Fatal(err) 890 | } 891 | 892 | if len(members) != 1 { 893 | t.Fatalf(errUnexpected, len(members)) 894 | } 895 | 896 | members, err = rc.SRandMember(k, 2) 897 | if err != nil { 898 | t.Fatal(err) 899 | } 900 | 901 | if len(members) != 2 { 902 | t.Fatalf(errUnexpected, len(members)) 903 | } 904 | } 905 | 906 | func TestSIsMember(t *testing.T) { 907 | k := randomString(1024) 908 | defer rc.Del(k) 909 | rc.SAdd(k, "setuno") 910 | 911 | isMember, err := rc.SIsMember(k, "setuno") 912 | if err != nil { 913 | t.Fatal(err) 914 | } 915 | if isMember != 1 { 916 | t.Fatalf(errUnexpected, isMember) 917 | } 918 | 919 | isNoMember, err := rc.SIsMember(k, "setdue") 920 | if err != nil { 921 | t.Fatal(err) 922 | } 923 | if isNoMember != 0 { 924 | t.Fatalf(errUnexpected, isMember) 925 | } 926 | } 927 | 928 | func TestSCard(t *testing.T) { 929 | k := randomString(1024) 930 | defer rc.Del(k) 931 | 932 | cardinalityOfZero, err := rc.SCard(k) 933 | if err != nil { 934 | t.Fatal(err) 935 | } 936 | if cardinalityOfZero != 0 { 937 | t.Fatalf(errUnexpected, cardinalityOfZero) 938 | } 939 | 940 | rc.SAdd(k, "setuno") 941 | cardinalityOfOne, err := rc.SCard(k) 942 | if err != nil { 943 | t.Fatal(err) 944 | } 945 | if cardinalityOfOne != 1 { 946 | t.Fatalf(errUnexpected, cardinalityOfOne) 947 | } 948 | 949 | rc.SAdd(k, "setdue") 950 | cardinalityOfTwo, err := rc.SCard(k) 951 | if err != nil { 952 | t.Fatal(err) 953 | } 954 | if cardinalityOfTwo != 2 { 955 | t.Fatalf(errUnexpected, cardinalityOfTwo) 956 | } 957 | } 958 | 959 | // TestSetAndGet sets a key, fetches it, and compare the results. 960 | func _TestSetAndGet(t *testing.T) { 961 | k := randomString(1024) 962 | v := randomString(16 * 1024 * 1024) 963 | defer rc.Del(k) 964 | if err := rc.Set(k, v); err != nil { 965 | t.Fatal(err) 966 | } 967 | if val, err := rc.Get(k); err != nil { 968 | t.Fatal(err) 969 | } else if val != v { 970 | t.Fatalf(errUnexpected, val) 971 | } 972 | } 973 | 974 | // TestSetEx sets a key to expire in 10s and checks the result. 975 | func TestSetEx(t *testing.T) { 976 | k := randomString(16) 977 | defer rc.Del(k) 978 | if err := rc.SetEx(k, 10, "foobar"); err != nil { 979 | t.Fatal(err) 980 | } 981 | } 982 | 983 | func TestSetNx(t *testing.T) { 984 | k := randomString(16) 985 | defer rc.Del(k) 986 | ok, err := rc.SetNx(k, "foobar") 987 | if err != nil { 988 | t.Fatal(err) 989 | } 990 | if ok != 1 { 991 | t.Fatal("Key Already exists") 992 | } 993 | 994 | ok, err = rc.SetNx(k, "foobar") 995 | if err != nil { 996 | t.Fatal(err) 997 | } 998 | 999 | if ok != 0 { 1000 | t.Fatal("Key shouldnt be allowed") 1001 | } 1002 | 1003 | } 1004 | 1005 | func TestPing(t *testing.T) { 1006 | if err := rc.Ping(); err != nil { 1007 | t.Fatal(err) 1008 | } 1009 | } 1010 | 1011 | // TestHIncrBy 1012 | func TestHIncrBy(t *testing.T) { 1013 | rc.Del("mykey") 1014 | defer rc.Del("mykey") 1015 | if n, err := rc.HIncrBy("mykey", "beavis", 5); err != nil { 1016 | t.Fatalf(errUnexpected, err) 1017 | } else if n != 5 { 1018 | t.Fatalf(errUnexpected, n) 1019 | } 1020 | } 1021 | 1022 | // TestHSet sets the a field in the hash and checks the result. 1023 | func TestHSet(t *testing.T) { 1024 | rc.Del("mykey") 1025 | defer rc.Del("mykey") 1026 | if err := rc.HSet("mykey", "foo", "bar"); err != nil { 1027 | t.Fatalf(errUnexpected, err) 1028 | } 1029 | } 1030 | 1031 | // TestHDel sets the a field in the hash and checks the result. 1032 | func TestHDel(t *testing.T) { 1033 | rc.Del("mykey") 1034 | defer rc.Del("mykey") 1035 | rc.HSet("mykey", "foo", "bar") 1036 | if err := rc.HDel("mykey", "foo"); err != nil { 1037 | t.Fatalf(errUnexpected, err) 1038 | } 1039 | } 1040 | 1041 | // TestHGet sets a key and gets its value, checking the results. 1042 | func TestHGet(t *testing.T) { 1043 | rc.Del("mykey") 1044 | defer rc.Del("mykey") 1045 | rc.HSet("mykey", "foo", "bar") 1046 | if n, err := rc.HGet("mykey", "foo"); err != nil { 1047 | t.Fatalf(errUnexpected, err) 1048 | } else if n != "bar" { 1049 | t.Fatalf(errUnexpected, n) 1050 | } 1051 | } 1052 | 1053 | // TestHMSet sets multiple fields in the hash and checks the result. 1054 | func TestHMSet(t *testing.T) { 1055 | rc.Del("mykey") 1056 | defer rc.Del("mykey") 1057 | if err := rc.HMSet("mykey", map[string]string{ 1058 | "foo": "bar", 1059 | "hello": "world", 1060 | }); err != nil { 1061 | t.Fatalf(errUnexpected, err) 1062 | } 1063 | } 1064 | 1065 | // TestHMGet sets fields in the hash, then get them and checks the results. 1066 | func TestHMGet(t *testing.T) { 1067 | rc.Del("mykey") 1068 | defer rc.Del("mykey") 1069 | rc.HMSet("mykey", map[string]string{ 1070 | "foo": "bar", 1071 | "hello": "world", 1072 | }) 1073 | if v, err := rc.HMGet("mykey", "foo", "hello"); err != nil { 1074 | t.Fatalf(errUnexpected, err) 1075 | } else if len(v) != 2 || v[0] != "bar" || v[1] != "world" { 1076 | t.Fatalf(errUnexpected, v) 1077 | } 1078 | } 1079 | 1080 | // TestHGetAll sets fields in the hash, get them all and checks the results. 1081 | func TestHGetAll(t *testing.T) { 1082 | rc.Del("mykey") 1083 | defer rc.Del("mykey") 1084 | rc.HMSet("mykey", map[string]string{ 1085 | "foo": "bar", 1086 | "hello": "world", 1087 | }) 1088 | if v, err := rc.HGetAll("mykey"); err != nil { 1089 | t.Fatalf(errUnexpected, err) 1090 | } else if len(v) != 2 || v["foo"] != "bar" || v["hello"] != "world" { 1091 | t.Fatalf(errUnexpected, v) 1092 | } 1093 | } 1094 | 1095 | // TestZIncrBy increments a field in a hash and checks the result. 1096 | func TestZIncrBy(t *testing.T) { 1097 | rc.Del("mykey") 1098 | defer rc.Del("mykey") 1099 | if n, err := rc.ZIncrBy("mykey", 5, "beavis"); err != nil { 1100 | t.Fatalf(errUnexpected, err) 1101 | } else if n != "5" { 1102 | t.Fatalf(errUnexpected, n) 1103 | } 1104 | } 1105 | 1106 | // TestZScore 1107 | func TestZScore(t *testing.T) { 1108 | rc.Del("mykey") 1109 | defer rc.Del("mykey") 1110 | if _, err := rc.ZIncrBy("mykey", 5, "beavis"); err != nil { 1111 | t.Fatalf(errUnexpected, err) 1112 | } 1113 | if n, err := rc.ZScore("mykey", "beavis"); err != nil { 1114 | t.Fatalf(errUnexpected, err) 1115 | } else if n != "5" { 1116 | t.Fatalf(errUnexpected, n) 1117 | } 1118 | } 1119 | 1120 | // Test ZAdd 1121 | func TestZAdd(t *testing.T) { 1122 | rc.Del("myzset") 1123 | defer rc.Del("myzset") 1124 | if _, err := rc.ZAdd("myzset", 1, "beavis"); err != nil { 1125 | t.Fatalf(errUnexpected, err) 1126 | } 1127 | if n, err := rc.ZAdd("myzset", 2, "butthead", 3, "professor_buzzcut"); err != nil { 1128 | t.Fatalf(errUnexpected, err) 1129 | } else if n != 2 { 1130 | t.Fatalf(errUnexpected, n) 1131 | } 1132 | } 1133 | 1134 | // Test ZRem 1135 | func TestZRem(t *testing.T) { 1136 | rc.Del("myzset") 1137 | defer rc.Del("myzset") 1138 | if _, err := rc.ZAdd("myzset", 1, "beavis", 2, "butthead", 3, "professor_buzzcut"); err != nil { 1139 | t.Fatalf(errUnexpected, err) 1140 | } 1141 | if n, err := rc.ZRem("myzset", "beavis", "butthead", "professor_buzzcut"); err != nil { 1142 | t.Fatalf(errUnexpected, err) 1143 | } else if n != 3 { 1144 | t.Fatalf(errUnexpected, n) 1145 | } 1146 | } 1147 | 1148 | // Test ZRemRangeByScore 1149 | func TestZRemRangeByScore(t *testing.T) { 1150 | rc.Del("myzset") 1151 | defer rc.Del("myzset") 1152 | if _, err := rc.ZAdd("myzset", 1, "beavis", 2, "butthead", 3, "professor_buzzcut"); err != nil { 1153 | t.Fatalf(errUnexpected, err) 1154 | } 1155 | if n, err := rc.ZRemRangeByScore("myzset", "-inf", "(2"); err != nil { 1156 | t.Fatalf(errUnexpected, err) 1157 | } else if n != 1 { 1158 | t.Fatalf(errUnexpected, n) 1159 | } 1160 | } 1161 | 1162 | // Test ZRange 1163 | func TestZRange(t *testing.T) { 1164 | rc.Del("myzset") 1165 | defer rc.Del("myzset") 1166 | if _, err := rc.ZAdd("myzset", 1, "beavis", 2, "butthead", 3, "professor_buzzcut"); err != nil { 1167 | t.Fatalf(errUnexpected, err) 1168 | } 1169 | 1170 | if n, err := rc.ZRange("myzset", 0, 1, false); err != nil { 1171 | t.Fatalf(errUnexpected, err) 1172 | } else if len(n) != 2 { 1173 | t.Fatalf(errUnexpected, n) 1174 | } 1175 | 1176 | if n, err := rc.ZRange("myzset", 0, 1, true); err != nil { 1177 | t.Fatalf(errUnexpected, err) 1178 | } else if len(n) != 4 { 1179 | t.Fatalf(errUnexpected, n) 1180 | } else if n[0] != "beavis" && n[2] != "butthead" { 1181 | t.Fatalf(errUnexpected, n) 1182 | } 1183 | } 1184 | 1185 | // Test ZRevRange 1186 | func TestZRevRange(t *testing.T) { 1187 | rc.Del("myzset") 1188 | defer rc.Del("myzset") 1189 | if _, err := rc.ZAdd("myzset", 1, "beavis", 2, "butthead", 3, "professor_buzzcut"); err != nil { 1190 | t.Fatalf(errUnexpected, err) 1191 | } 1192 | 1193 | if n, err := rc.ZRevRange("myzset", 0, 1, false); err != nil { 1194 | t.Fatalf(errUnexpected, err) 1195 | } else if len(n) != 2 { 1196 | t.Fatalf(errUnexpected, n) 1197 | } 1198 | 1199 | if n, err := rc.ZRevRange("myzset", 0, 1, true); err != nil { 1200 | t.Fatalf(errUnexpected, err) 1201 | } else if len(n) != 4 { 1202 | t.Fatalf(errUnexpected, n) 1203 | } else if n[0] != "professor_buzzcut" && n[2] != "butthead" { 1204 | t.Fatalf(errUnexpected, n) 1205 | } 1206 | } 1207 | 1208 | // Test ZRangeByScore 1209 | func TestZRangeByScore(t *testing.T) { 1210 | rc.Del("myzset") 1211 | defer rc.Del("myzset") 1212 | if _, err := rc.ZAdd("myzset", 1, "beavis", 2, "butthead", 3, "professor_buzzcut"); err != nil { 1213 | t.Fatalf(errUnexpected, err) 1214 | } 1215 | 1216 | if n, err := rc.ZRangeByScore("myzset", 0, 2, false, false, 0, 0); err != nil { 1217 | t.Fatalf(errUnexpected, err) 1218 | } else if len(n) != 2 { 1219 | t.Fatalf(errUnexpected, n) 1220 | } else if n[0] != "beavis" { 1221 | t.Fatalf("Wrong sort order, expected 'beavis', got %s", n[0]) 1222 | } 1223 | 1224 | // get 2 elements WITHSCORES 1225 | if n, err := rc.ZRangeByScore("myzset", 0, 2, true, false, 0, 0); err != nil { 1226 | t.Fatalf(errUnexpected, err) 1227 | } else if len(n) != 4 { 1228 | t.Fatalf(errUnexpected, n) 1229 | } else if n[0] != "beavis" && n[2] != "butthead" { 1230 | t.Fatalf(errUnexpected, n) 1231 | } 1232 | 1233 | // ask 3 elements, set a limit of 2 offset from 1 so we test both limit and offset 1234 | if n, err := rc.ZRangeByScore("myzset", 0, 2, true, true, 1, 2); err != nil { 1235 | t.Fatalf(errUnexpected, err) 1236 | } else if len(n) != 2 { 1237 | t.Fatalf(errUnexpected, n) 1238 | } else if n[0] != "butthead" { 1239 | t.Fatalf(errUnexpected, n) 1240 | } 1241 | } 1242 | 1243 | // Test ZRevRangeByScore 1244 | func TestZRevRangeByScore(t *testing.T) { 1245 | rc.Del("myzset") 1246 | defer rc.Del("myzset") 1247 | if _, err := rc.ZAdd("myzset", 1, "beavis", 2, "butthead", 3, "professor_buzzcut"); err != nil { 1248 | t.Fatalf(errUnexpected, err) 1249 | } 1250 | 1251 | if n, err := rc.ZRevRangeByScore("myzset", 2, 0, false, false, 0, 0); err != nil { 1252 | t.Fatalf(errUnexpected, err) 1253 | } else if len(n) != 2 { 1254 | t.Fatalf(errUnexpected, n) 1255 | } else if n[0] != "butthead" { 1256 | t.Fatalf("Wrong sort order, expected 'beavis', got %s", n[0]) 1257 | } 1258 | 1259 | // get 2 elements WITHSCORES 1260 | if n, err := rc.ZRevRangeByScore("myzset", 2, 0, true, false, 0, 0); err != nil { 1261 | t.Fatalf(errUnexpected, err) 1262 | } else if len(n) != 4 { 1263 | t.Fatalf(errUnexpected, n) 1264 | } else if n[0] != "butthead" && n[2] != "beavis" { 1265 | t.Fatalf(errUnexpected, n) 1266 | } 1267 | 1268 | // ask 3 elements, set a limit of 2 offset from 1 so we test both limit and offset 1269 | if n, err := rc.ZRevRangeByScore("myzset", 2, 0, true, true, 1, 2); err != nil { 1270 | t.Fatalf(errUnexpected, err) 1271 | } else if len(n) != 2 { 1272 | t.Fatalf(errUnexpected, n) 1273 | } else if n[0] != "beavis" { 1274 | t.Fatalf(errUnexpected, n) 1275 | } 1276 | } 1277 | 1278 | // Test ZCard 1279 | func TestZCard(t *testing.T) { 1280 | rc.Del("myzset") 1281 | defer rc.Del("myzset") 1282 | if _, err := rc.ZAdd("myzset", 1, "beavis", 2, "butthead", 3, "professor_buzzcut"); err != nil { 1283 | t.Fatalf(errUnexpected, err) 1284 | } 1285 | 1286 | if n, err := rc.ZCard("myzset"); err != nil { 1287 | t.Fatalf(errUnexpected, err) 1288 | } else if n != 3 { 1289 | t.Fatalf(errUnexpected, n) 1290 | } 1291 | } 1292 | 1293 | // Test ZCount 1294 | func TestZCount(t *testing.T) { 1295 | rc.Del("myzset") 1296 | defer rc.Del("myzset") 1297 | if _, err := rc.ZAdd("myzset", 1, "beavis", 2, "butthead", 3, "professor_buzzcut"); err != nil { 1298 | t.Fatalf(errUnexpected, err) 1299 | } 1300 | 1301 | if n, err := rc.ZCount("myzset", 0, 2); err != nil { 1302 | t.Fatalf(errUnexpected, err) 1303 | } else if n != 2 { 1304 | t.Fatalf(errUnexpected, n) 1305 | } 1306 | } 1307 | 1308 | // Test PFAdd 1309 | func TestPFAdd(t *testing.T) { 1310 | k := randomString(1024) 1311 | defer rc.Del(k) 1312 | 1313 | //singles 1314 | for i := 0; i < 10; i++ { 1315 | v := randomString(32) 1316 | _, err := rc.PFAdd(k, v) 1317 | if err != nil { 1318 | t.Fatal(err) 1319 | } 1320 | } 1321 | 1322 | //multiple 1323 | _, err := rc.PFAdd(k, "setuno", "setdue") 1324 | if err != nil { 1325 | t.Fatal(err) 1326 | } 1327 | } 1328 | 1329 | func TestPFCount(t *testing.T) { 1330 | k := randomString(1024) 1331 | defer rc.Del(k) 1332 | 1333 | // add some stuff for 1 key 1334 | _, err := rc.PFAdd(k, "one", "two", "three") 1335 | if err != nil { 1336 | t.Fatal(err) 1337 | } 1338 | 1339 | v, err := rc.PFCount(k) 1340 | 1341 | if err != nil { 1342 | t.Fatal(err) 1343 | } 1344 | if v != 3 { 1345 | t.Fatal("Wrong item count") 1346 | } 1347 | } 1348 | 1349 | func TestPFMerge(t *testing.T) { 1350 | k1 := randomString(1024) 1351 | defer rc.Del(k1) 1352 | 1353 | k2 := randomString(1024) 1354 | defer rc.Del(k2) 1355 | 1356 | // add some stuff for 1 key 1357 | _, err := rc.PFAdd(k1, "one", "two", "three") 1358 | if err != nil { 1359 | t.Fatal(err) 1360 | } 1361 | 1362 | // add some stuff for another key 1363 | _, err = rc.PFAdd(k2, "four", "five", "six") 1364 | if err != nil { 1365 | t.Fatal(err) 1366 | } 1367 | 1368 | err = rc.PFMerge(k1, k2) 1369 | 1370 | if err != nil { 1371 | t.Fatal(err) 1372 | } 1373 | } 1374 | 1375 | // Benchmark plain Set 1376 | func BenchmarkSet(b *testing.B) { 1377 | for i := 0; i < b.N; i++ { 1378 | if err := rc.Set("foo", "bar"); err != nil { 1379 | b.Fatal(err) 1380 | } 1381 | } 1382 | } 1383 | 1384 | // Benchmark plain Get 1385 | func BenchmarkGet(b *testing.B) { 1386 | defer rc.Del("foo") 1387 | rc.Set("foo", "bar") 1388 | for i := 0; i < b.N; i++ { 1389 | if v, err := rc.Get("foo"); err != nil { 1390 | b.Fatal(err) 1391 | } else if v != "bar" { 1392 | b.Fatalf(errUnexpected, v) 1393 | } 1394 | } 1395 | } 1396 | 1397 | // Test/Benchmark INCRBY 1398 | func BenchmarkIncrBy(b *testing.B) { 1399 | rc.Del("foo") 1400 | if err := rc.Set("foo", "0"); err != nil { 1401 | b.Fatal(err) 1402 | } 1403 | for i := 0; i < b.N; i++ { 1404 | if _, err := rc.IncrBy("foo", 1); err != nil { 1405 | b.Fatal(err) 1406 | } 1407 | } 1408 | if v, err := rc.Get("foo"); err != nil { 1409 | b.Fatal(err) 1410 | } else if v != strconv.Itoa(b.N) { 1411 | b.Fatalf(errUnexpected, v) 1412 | } 1413 | } 1414 | 1415 | // Benchmark DECR 1416 | func BenchmarkDecrBy(b *testing.B) { 1417 | defer rc.Del("foo") 1418 | if err := rc.Set("foo", strconv.Itoa(b.N)); err != nil { 1419 | b.Fatal(err) 1420 | } 1421 | for i := 0; i < b.N; i++ { 1422 | if _, err := rc.DecrBy("foo", 1); err != nil { 1423 | b.Fatal(err) 1424 | } 1425 | } 1426 | if v, err := rc.Get("foo"); err != nil { 1427 | b.Fatal(err) 1428 | } else if v != "0" { 1429 | b.Fatalf(errUnexpected, v) 1430 | } 1431 | } 1432 | -------------------------------------------------------------------------------- /redis/pubsub_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013-2015 go-redis authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package redis 16 | 17 | import "testing" 18 | 19 | func TestPubSub(t *testing.T) { 20 | k := randomString(16) 21 | v := "pubsubis" 22 | 23 | ch := make(chan PubSubMessage) 24 | stop := make(chan bool) 25 | err := rc.Subscribe(k, ch, stop) 26 | if err != nil { 27 | t.Fatal(err) 28 | } 29 | 30 | kids := make(chan bool) 31 | counter := 100 32 | go func() { 33 | run := true 34 | for run { 35 | select { 36 | case vv := <-ch: 37 | //fmt.Println(vv.Channel, vv.Value, vv.Error) 38 | if vv.Error != nil { 39 | run = false 40 | } 41 | } 42 | } 43 | kids <- true 44 | }() 45 | 46 | go func() { 47 | for i := counter; i > 0; i-- { 48 | err := rc.Publish(k, v) 49 | if err != nil { 50 | t.Error(err) 51 | return 52 | } 53 | counter-- 54 | } 55 | close(stop) 56 | }() 57 | 58 | <-kids 59 | 60 | if counter > 0 { 61 | t.Fatal("Failed to parse PubSub messages ", counter) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /redis/redis.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013-2015 go-redis authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // This is a modified version of gomemcache adapted to redis. 16 | // Original code and license at https://github.com/bradfitz/gomemcache/ 17 | 18 | // Package redis provides a client for the redis cache server. 19 | package redis 20 | 21 | import ( 22 | "bufio" 23 | "errors" 24 | "fmt" 25 | "io" 26 | "net" 27 | "strconv" 28 | "strings" 29 | "sync" 30 | "sync/atomic" 31 | "time" 32 | ) 33 | 34 | var ( 35 | // MaxIdleConnsPerAddr is the maximum number of connections 36 | // (per server address) idling in the pool. 37 | // 38 | // Only update this value before creating or using the client. 39 | MaxIdleConnsPerAddr = 10 40 | 41 | // ErrNoServers is returned when no servers are configured or available. 42 | ErrNoServers = errors.New("redis: no servers configured or available") 43 | 44 | // ErrServerError means that a server error occurred. 45 | ErrServerError = errors.New("redis: server error") 46 | 47 | // ErrTimedOut is returned when a Read or Write operation times out 48 | ErrTimedOut = errors.New("redis: timed out") 49 | ) 50 | 51 | // DefaultTimeout is the default socket read/write timeout. 52 | const DefaultTimeout = time.Second 53 | 54 | // resumableError returns true if err is only a protocol-level cache error. 55 | // This is used to determine whether or not a server connection should 56 | // be re-used or not. If an error occurs, by default we don't reuse the 57 | // connection, unless it was just a cache error. 58 | func resumableError(err error) bool { 59 | if err == ErrServerError { 60 | return true 61 | } 62 | return false // time outs, broken pipes, etc 63 | } 64 | 65 | // New returns a redis client using the provided server(s) with equal weight. 66 | // If a server is listed multiple times, it gets a proportional amount of 67 | // weight. 68 | // 69 | // New supports ip:port or /unix/path, and optional *db* and *passwd* arguments. 70 | // Example: 71 | // 72 | // rc := redis.New("ip:port db=N passwd=foobared") 73 | // rc := redis.New("/tmp/redis.sock db=N passwd=foobared") 74 | // 75 | // New panics if the configured servers point to names that cannot 76 | // be resolved to an address, or unix socket path. 77 | func New(server ...string) *Client { 78 | rc, err := NewClient(server...) 79 | if err != nil { 80 | panic(err) 81 | } 82 | return rc 83 | } 84 | 85 | // NewClient is like New, but returns an error in case of failure. 86 | func NewClient(server ...string) (*Client, error) { 87 | ss := new(ServerList) 88 | if len(server) == 0 { 89 | server = []string{"localhost:6379"} 90 | } 91 | if err := ss.SetServers(server...); err != nil { 92 | return nil, err 93 | } 94 | return NewFromSelector(ss), nil 95 | } 96 | 97 | // NewFromSelector returns a new Client using the provided ServerSelector. 98 | func NewFromSelector(ss ServerSelector) *Client { 99 | return &Client{selector: ss} 100 | } 101 | 102 | // Client is a redis client. 103 | // It is safe for use by multiple concurrent goroutines. 104 | type Client struct { 105 | timeout time.Duration 106 | selector ServerSelector 107 | lk sync.Mutex 108 | freeconn map[string][]*conn 109 | } 110 | 111 | // SetTimeout sets the client timeout for read/write operations. 112 | func (c *Client) SetTimeout(max time.Duration) { 113 | atomic.StoreInt64((*int64)(&c.timeout), (int64)(max)) 114 | } 115 | 116 | // conn is a connection to a server. 117 | type conn struct { 118 | nc net.Conn 119 | rw *bufio.ReadWriter 120 | srv ServerInfo 121 | c *Client 122 | } 123 | 124 | // release returns this connection back to the client's free pool 125 | func (cn *conn) release() { 126 | cn.c.putFreeConn(cn.srv.Addr, cn) 127 | } 128 | 129 | func (cn *conn) extendDeadline(delta time.Duration) { 130 | cn.nc.SetDeadline(time.Now().Add(cn.c.netTimeout() + delta)) 131 | } 132 | 133 | // condRelease releases this connection if the error pointed to by err 134 | // is nil (not an error) or is only a protocol level error. 135 | // The purpose is to not recycle TCP connections that are bad. 136 | func (cn *conn) condRelease(err *error) { 137 | if *err == nil || resumableError(*err) { 138 | cn.release() 139 | } else { 140 | cn.nc.Close() 141 | } 142 | } 143 | 144 | func (c *Client) putFreeConn(addr net.Addr, cn *conn) { 145 | c.lk.Lock() 146 | defer c.lk.Unlock() 147 | if c.freeconn == nil { 148 | c.freeconn = make(map[string][]*conn) 149 | } 150 | freelist := c.freeconn[addr.String()] 151 | if len(freelist) >= MaxIdleConnsPerAddr { 152 | cn.nc.Close() 153 | return 154 | } 155 | cn.nc.SetDeadline(time.Time{}) // no deadline 156 | c.freeconn[addr.String()] = append(freelist, cn) 157 | } 158 | 159 | func (c *Client) getFreeConn(srv ServerInfo) (cn *conn, ok bool) { 160 | c.lk.Lock() 161 | defer c.lk.Unlock() 162 | if c.freeconn == nil { 163 | return nil, false 164 | } 165 | freelist, ok := c.freeconn[srv.Addr.String()] 166 | if !ok || len(freelist) == 0 { 167 | return nil, false 168 | } 169 | cn = freelist[len(freelist)-1] 170 | c.freeconn[srv.Addr.String()] = freelist[:len(freelist)-1] 171 | return cn, true 172 | } 173 | 174 | func (c *Client) netTimeout() time.Duration { 175 | t := (time.Duration)(atomic.LoadInt64((*int64)(&c.timeout))) 176 | if t != 0 { 177 | return t 178 | } 179 | return DefaultTimeout 180 | } 181 | 182 | // ConnectTimeoutError is the error type used when it takes 183 | // too long to connect to the desired host. This level of 184 | // detail can generally be ignored. 185 | type ConnectTimeoutError struct { 186 | Addr net.Addr 187 | } 188 | 189 | func (cte *ConnectTimeoutError) Error() string { 190 | return "redis: connection timeout to " + cte.Addr.String() 191 | } 192 | 193 | func (c *Client) dial(addr net.Addr) (net.Conn, error) { 194 | type dialRes struct { 195 | cn net.Conn 196 | err error 197 | } 198 | ch := make(chan dialRes) 199 | go func() { 200 | nc, err := net.Dial(addr.Network(), addr.String()) 201 | ch <- dialRes{nc, err} 202 | }() 203 | select { 204 | case ce := <-ch: 205 | return ce.cn, ce.err 206 | case <-time.After(c.netTimeout()): 207 | // Too slow. Fall through. 208 | } 209 | // Close the conn if it does end up finally coming in 210 | go func() { 211 | ce := <-ch 212 | if ce.err == nil { 213 | ce.cn.Close() 214 | } 215 | }() 216 | return nil, &ConnectTimeoutError{addr} 217 | } 218 | 219 | func (c *Client) getConn(srv ServerInfo) (*conn, error) { 220 | cn, ok := c.getFreeConn(srv) 221 | if ok { 222 | cn.extendDeadline(0) 223 | return cn, nil 224 | } 225 | nc, err := c.dial(srv.Addr) 226 | if err != nil { 227 | return nil, err 228 | } 229 | cn = &conn{ 230 | nc: nc, 231 | srv: srv, 232 | rw: c.notifyClose(nc), 233 | c: c, 234 | } 235 | cn.extendDeadline(0) 236 | if srv.Passwd != "" { 237 | _, err := c.executeURP(cn.rw, "AUTH", srv.Passwd) 238 | if err != nil { 239 | return nil, err 240 | } 241 | } 242 | if srv.DB != "" { 243 | _, err := c.execute(cn.rw, "SELECT", srv.DB) 244 | if err != nil { 245 | return nil, err 246 | } 247 | } 248 | return cn, nil 249 | } 250 | 251 | func (c *Client) notifyClose(nc net.Conn) *bufio.ReadWriter { 252 | pr, pw := io.Pipe() 253 | rw := bufio.NewReadWriter(bufio.NewReader(pr), bufio.NewWriter(nc)) 254 | go func() { 255 | _, err := io.Copy(pw, nc) 256 | if err == nil { 257 | err = io.EOF 258 | } 259 | pw.CloseWithError(err) 260 | c.cleanupFreeConn(nc) 261 | }() 262 | return rw 263 | } 264 | 265 | func (c *Client) cleanupFreeConn(nc net.Conn) { 266 | c.lk.Lock() 267 | defer c.lk.Unlock() 268 | if c.freeconn == nil { 269 | return 270 | } 271 | freelist, ok := c.freeconn[nc.RemoteAddr().String()] 272 | if !ok || len(freelist) == 0 { 273 | return 274 | } 275 | nc.Close() 276 | for n, conn := range freelist { 277 | if nc == conn.nc { 278 | // TODO: optimize 279 | copy(freelist[n:], freelist[n+1:]) 280 | freelist[len(freelist)-1] = nil 281 | freelist = freelist[:len(freelist)-1] 282 | c.freeconn[nc.RemoteAddr().String()] = freelist 283 | break 284 | } 285 | } 286 | } 287 | 288 | // Close closes all connections in the pool. 289 | func (c *Client) Close() { 290 | for _, cns := range c.freeconn { 291 | for _, cn := range cns { 292 | c.cleanupFreeConn(cn.nc) 293 | } 294 | } 295 | } 296 | 297 | // execWithKey picks a server based on the key, and executes a command in redis. 298 | func (c *Client) execWithKey(urp bool, cmd, key string, a ...interface{}) (interface{}, error) { 299 | srv, err := c.selector.PickServer(key) 300 | if err != nil { 301 | return nil, err 302 | } 303 | x := []interface{}{cmd, key} 304 | return c.execWithAddr(urp, srv, append(x, a...)...) 305 | } 306 | 307 | // execWithKeyTimeout picks a server based on the key, and executes a command 308 | // in redis, extending the connection timeout for the given command. 309 | func (c *Client) execWithKeyTimeout(urp bool, timeout int, cmd, key string, a ...interface{}) (interface{}, error) { 310 | srv, err := c.selector.PickServer(key) 311 | if err != nil { 312 | return nil, err 313 | } 314 | x := []interface{}{cmd, key} 315 | return c.execWithAddrTimeout(urp, srv, timeout, append(x, a...)...) 316 | } 317 | 318 | // execWithKeys calls execWithKey for each key, returns an array of results. 319 | func (c *Client) execWithKeys(urp bool, cmd string, keys []string, a ...interface{}) ([]interface{}, error) { 320 | var v []interface{} 321 | for _, k := range keys { 322 | tmp, err := c.execWithKey(urp, cmd, k, a...) 323 | if err != nil { 324 | return nil, err 325 | } 326 | v = append(v, tmp) 327 | } 328 | return v, nil 329 | } 330 | 331 | // execOnFirst executes a command on the first listed server. 332 | // execOnFirst is used by commands that are not bound to a key. e.g.: ping, info 333 | func (c *Client) execOnFirst(urp bool, a ...interface{}) (interface{}, error) { 334 | srv, err := c.selector.PickServer("") 335 | if err != nil { 336 | return nil, err 337 | } 338 | return c.execWithAddr(urp, srv, a...) 339 | } 340 | 341 | // execWithAddr executes a command in a specific redis server. 342 | func (c *Client) execWithAddr(urp bool, srv ServerInfo, a ...interface{}) (v interface{}, err error) { 343 | cn, err := c.getConn(srv) 344 | if err != nil { 345 | return nil, err 346 | } 347 | defer cn.condRelease(&err) 348 | if urp { 349 | v, err = c.executeURP(cn.rw, a...) 350 | } else { 351 | v, err = c.execute(cn.rw, a...) 352 | } 353 | return v, err 354 | } 355 | 356 | // execWithAddrTimeout executes a command in a specific redis server, 357 | // extending the connection timeout for the given command. 358 | func (c *Client) execWithAddrTimeout(urp bool, srv ServerInfo, timeout int, a ...interface{}) (v interface{}, err error) { 359 | cn, err := c.getConn(srv) 360 | if err != nil { 361 | return nil, err 362 | } 363 | cn.extendDeadline(time.Duration(timeout) * time.Second) 364 | defer cn.condRelease(&err) 365 | if urp { 366 | v, err = c.executeURP(cn.rw, a...) 367 | } else { 368 | v, err = c.execute(cn.rw, a...) 369 | } 370 | return v, err 371 | } 372 | 373 | // execute sends a command to redis and returns a parsed response. 374 | // It uses the old protocol and can be used by simple commands, such as DB. 375 | // Redis protocol: http://redis.io/topics/protocol. 376 | func (c *Client) execute(rw *bufio.ReadWriter, a ...interface{}) (interface{}, error) { 377 | //fmt.Printf("\nSending: %#v\n", a) 378 | // old redis protocol. 379 | _, err := fmt.Fprintf(rw, strings.Join(viface2vstr(a), " ")+"\r\n") 380 | if err != nil { 381 | return nil, err 382 | } 383 | if err = rw.Flush(); err != nil { 384 | return nil, err 385 | } 386 | return c.parseResponse(rw.Reader) 387 | } 388 | 389 | // executeURP sends a command to redis and returns a parsed response. 390 | // It uses the current protocol and must be used by most commands, 391 | // such as SET. 392 | // Redis protocol: http://redis.io/topics/protocol. 393 | func (c *Client) executeURP(rw *bufio.ReadWriter, a ...interface{}) (interface{}, error) { 394 | //log.Printf("\nSending: %#v\n", a) 395 | // unified request protocol 396 | s := viface2vstr(a) 397 | _, err := fmt.Fprintf(rw, "*%d\r\n", len(a)) 398 | if err != nil { 399 | return nil, err 400 | } 401 | for _, i := range s { 402 | _, err = fmt.Fprintf(rw, "$%d\r\n%s\r\n", len(i), i) 403 | if err != nil { 404 | return nil, err 405 | } 406 | } 407 | if err = rw.Flush(); err != nil { 408 | return nil, err 409 | } 410 | return c.parseResponse(rw.Reader) 411 | } 412 | 413 | // parseResponse reads and parses a single response from redis. 414 | func (c *Client) parseResponse(r *bufio.Reader) (interface{}, error) { 415 | line, err := r.ReadString('\n') 416 | if err != nil { 417 | if neterr, ok := err.(net.Error); ok && neterr.Timeout() { 418 | err = ErrTimedOut 419 | } 420 | return nil, err 421 | } 422 | //log.Printf("line=%#v %x\n", line, &r) 423 | if len(line) < 1 { 424 | return nil, ErrServerError 425 | } 426 | reply := byte(line[0]) 427 | lineLen := len(line) 428 | if len(line) > 2 && line[lineLen-2:] == "\r\n" { 429 | line = line[1 : lineLen-2] 430 | } 431 | switch reply { 432 | case '-': // Error reply 433 | return nil, errors.New(line) 434 | case '+': // Status reply 435 | return line, nil 436 | case ':': // Integer reply 437 | resp, err := strconv.Atoi(line) 438 | if err != nil { 439 | return nil, err 440 | } 441 | return resp, nil 442 | case '$': // Bulk reply 443 | valueLen, err := strconv.Atoi(line) 444 | if err != nil { 445 | return nil, err 446 | } 447 | if valueLen == -1 { 448 | return "", nil // err = ErrCacheMiss? 449 | } 450 | b := make([]byte, valueLen+2) // 2==crlf 451 | var s byte 452 | for n := 0; n < cap(b); n++ { 453 | s, err = r.ReadByte() 454 | if err != nil { 455 | return nil, err 456 | } 457 | b[n] = s 458 | } 459 | if len(b) != cap(b) { 460 | return nil, fmt.Errorf("unexpected response: %#v", line) 461 | } 462 | return string(b[:valueLen]), nil // strip off trailing crlf 463 | case '*': // Multi-bulk reply 464 | //fmt.Printf("multibulk line=%#v\n", line) 465 | nitems, err := strconv.Atoi(line) 466 | if err != nil { 467 | return nil, err 468 | } 469 | if nitems < 1 { 470 | return nil, nil 471 | } 472 | resp := make([]interface{}, nitems) 473 | for n := 0; n < nitems; n++ { 474 | resp[n], err = c.parseResponse(r) 475 | if err != nil { 476 | return nil, err 477 | } 478 | } 479 | //log.Printf("multibulk=%#v\n", resp) 480 | return resp, nil 481 | default: 482 | return nil, ErrServerError 483 | } 484 | } 485 | -------------------------------------------------------------------------------- /redis/redis_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013-2015 go-redis authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package redis 16 | 17 | import ( 18 | "math/rand" 19 | "os" 20 | "testing" 21 | "time" 22 | ) 23 | 24 | // rc is the redis client handler used for all tests. 25 | // Make sure redis-server is running before starting the tests. 26 | var rc *Client 27 | 28 | func TestMain(m *testing.M) { 29 | rand.Seed(time.Now().UTC().UnixNano()) 30 | rc = New("localhost:6379") 31 | os.Exit(m.Run()) 32 | } 33 | 34 | // auxiliary functions for tests. 35 | 36 | func randomString(l int) string { 37 | bytes := make([]byte, l) 38 | for i := 0; i < l; i++ { 39 | bytes[i] = byte(randInt(65, 90)) 40 | } 41 | return string(bytes) 42 | } 43 | 44 | func randInt(min int, max int) int { 45 | return min + rand.Intn(max-min) 46 | } 47 | -------------------------------------------------------------------------------- /redis/selector.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013-2015 go-redis authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // This is a modified version of gomemcache adapted to redis. 16 | // Original code and license at https://github.com/bradfitz/gomemcache/ 17 | 18 | package redis 19 | 20 | import ( 21 | "errors" 22 | "fmt" 23 | "hash/crc32" 24 | "net" 25 | "strings" 26 | "sync" 27 | ) 28 | 29 | // ServerSelector is the interface that selects a redis server as a function 30 | // of the item's key. 31 | // 32 | // All ServerSelector implementations must be threadsafe. 33 | type ServerSelector interface { 34 | // PickServer returns the server address that a given item 35 | // should be shared onto, or the first listed server if an 36 | // empty key is given. 37 | PickServer(key string) (ServerInfo, error) 38 | 39 | // Sharding indicates that the client can connect to multiple servers. 40 | Sharding() bool 41 | } 42 | 43 | // ServerInfo stores parsed the server information. 44 | type ServerInfo struct { 45 | Addr net.Addr 46 | DB string 47 | Passwd string 48 | } 49 | 50 | // ServerList is a simple ServerSelector. Its zero value is usable. 51 | type ServerList struct { 52 | lk sync.RWMutex 53 | servers []ServerInfo 54 | sharding bool 55 | } 56 | 57 | func parseOptions(srv *ServerInfo, opts []string) error { 58 | for _, opt := range opts { 59 | items := strings.Split(opt, "=") 60 | if len(items) != 2 { 61 | return errors.New("Unknown option " + opt) 62 | } 63 | switch items[0] { 64 | case "db": 65 | srv.DB = items[1] 66 | case "passwd": 67 | srv.Passwd = items[1] 68 | default: 69 | return errors.New("Unknown option " + opt) 70 | } 71 | } 72 | return nil 73 | } 74 | 75 | // SetServers changes a ServerList's set of servers at runtime and is 76 | // threadsafe. 77 | // 78 | // Each server is given equal weight. A server is given more weight 79 | // if it's listed multiple times. 80 | // 81 | // SetServers returns an error if any of the server names fail to 82 | // resolve. No attempt is made to connect to the server. If any error 83 | // is returned, no changes are made to the ServerList. 84 | func (ss *ServerList) SetServers(servers ...string) error { 85 | var err error 86 | var fs, addr net.Addr 87 | nsrv := make([]ServerInfo, len(servers)) 88 | for i, server := range servers { 89 | // addr db=N passwd=foobar 90 | items := strings.Split(server, " ") 91 | if strings.Contains(items[0], "/") { 92 | addr, err = net.ResolveUnixAddr("unix", items[0]) 93 | } else { 94 | addr, err = net.ResolveTCPAddr("tcp", items[0]) 95 | } 96 | if err != nil { 97 | return fmt.Errorf( 98 | "Invalid redis server '%s': %s", 99 | server, err) 100 | } 101 | nsrv[i].Addr = addr 102 | // parse connection options 103 | if len(items) > 1 { 104 | if err := parseOptions(&nsrv[i], items[1:]); err != nil { 105 | return fmt.Errorf( 106 | "Invalid redis server '%s': %s", 107 | server, err) 108 | } 109 | } 110 | if i == 0 { 111 | fs = addr 112 | } else if fs != addr && !ss.sharding { 113 | ss.sharding = true 114 | } 115 | } 116 | ss.lk.Lock() 117 | defer ss.lk.Unlock() 118 | ss.servers = nsrv 119 | return nil 120 | } 121 | 122 | // Sharding implements the ServerSelector interface. 123 | func (ss *ServerList) Sharding() bool { 124 | return ss.sharding 125 | } 126 | 127 | // PickServer implements the ServerSelector interface. 128 | func (ss *ServerList) PickServer(key string) (srv ServerInfo, err error) { 129 | ss.lk.RLock() 130 | defer ss.lk.RUnlock() 131 | if len(ss.servers) == 0 { 132 | err = ErrNoServers 133 | return 134 | } 135 | if key == "" { 136 | return ss.servers[0], nil 137 | } 138 | return ss.servers[crc32.ChecksumIEEE([]byte(key))%uint32(len(ss.servers))], nil 139 | } 140 | -------------------------------------------------------------------------------- /redis/selector_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013-2015 go-redis authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package redis 16 | 17 | import "testing" 18 | 19 | func TestServerList(t *testing.T) { 20 | sl := &ServerList{} 21 | sl.SetServers("10.0.0.1:6379", "10.0.0.2:6379") 22 | if !sl.Sharding() { 23 | t.Fatal("server list of 2 hosts should support sharding") 24 | } 25 | si1, _ := sl.PickServer("alice") 26 | si2, _ := sl.PickServer("bob") 27 | if si1.Addr == si2.Addr { 28 | t.Fatal("same server for keys a and b, not sharding") 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /redis/types.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013-2015 go-redis authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package redis 16 | 17 | import ( 18 | "fmt" 19 | "strconv" 20 | ) 21 | 22 | // vstr2iface converts an array of strings to an array of empty interfaces 23 | func vstr2iface(a []string) (r []interface{}) { 24 | r = make([]interface{}, len(a)) 25 | for n, item := range a { 26 | r[n] = item 27 | } 28 | return 29 | } 30 | 31 | // iface2vstr converts an interface to an array of strings 32 | func iface2vstr(a interface{}) []string { 33 | r := []string{} 34 | switch a.(type) { 35 | case []interface{}: 36 | for _, item := range a.([]interface{}) { 37 | switch item.(type) { 38 | case string: 39 | r = append(r, item.(string)) 40 | } 41 | } 42 | } 43 | return r 44 | } 45 | 46 | // iface2strmap converts an interface to map of strings 47 | func iface2strmap(a interface{}) map[string]string { 48 | tmp := iface2vstr(a) 49 | m := make(map[string]string) 50 | for n := 0; n < len(tmp)/2; n++ { 51 | m[tmp[n*2]] = tmp[(n*2)+1] 52 | } 53 | return m 54 | } 55 | 56 | // iface2bool validates and converts interface (int) to bool 57 | func iface2bool(a interface{}) (bool, error) { 58 | switch a.(type) { 59 | case int: 60 | if a.(int) == 1 { 61 | return true, nil 62 | } 63 | return false, nil 64 | } 65 | return false, fmt.Errorf("redis: %#v is not boolean", a) 66 | } 67 | 68 | // iface2int validates and converts interface to int 69 | func iface2int(a interface{}) (int, error) { 70 | switch a.(type) { 71 | case int: 72 | return a.(int), nil 73 | } 74 | return 0, fmt.Errorf("redis: %#v is not integer", a) 75 | } 76 | 77 | // iface2str validates and converts interface to string 78 | func iface2str(a interface{}) (string, error) { 79 | switch a.(type) { 80 | case string: 81 | return a.(string), nil 82 | } 83 | return "", fmt.Errorf("redis: %#v is not string", a) 84 | } 85 | 86 | // viface2vstr converts commands' arguments from multiple types to string, 87 | // so they can be sent to the server. e.g. rc.IncrBy("k", 1) -> "k", "1" 88 | func viface2vstr(a []interface{}) []string { 89 | s := make([]string, len(a)) 90 | for n, item := range a { 91 | switch item.(type) { 92 | case int: 93 | s[n] = strconv.Itoa(item.(int)) 94 | case string: 95 | s[n] = item.(string) 96 | default: 97 | // TODO: use iface2n, maybe 98 | panic(fmt.Sprintf("redis: unsupported parameter type: %#v", item)) 99 | } 100 | } 101 | return s 102 | } 103 | --------------------------------------------------------------------------------