├── .travis.yml ├── internal ├── commandinfo_test.go ├── commandinfo.go └── redistest │ └── testdb.go ├── redisx ├── doc.go ├── connmux.go └── connmux_test.go ├── redis ├── pre_go17.go ├── go17.go ├── redis.go ├── zpop_example_test.go ├── script_test.go ├── script.go ├── log.go ├── pubsub.go ├── reply_test.go ├── pubsub_test.go ├── test_test.go ├── doc.go ├── pool.go ├── scan_test.go ├── reply.go ├── scan.go ├── pool_test.go ├── conn.go └── conn_test.go ├── README.markdown └── LICENSE /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | sudo: false 3 | services: 4 | - redis-server 5 | 6 | go: 7 | - 1.4 8 | - 1.5 9 | - 1.6 10 | - 1.7 11 | - tip 12 | 13 | script: 14 | - go get -t -v ./... 15 | - diff -u <(echo -n) <(gofmt -d .) 16 | - go vet $(go list ./... | grep -v /vendor/) 17 | - go test -v -race ./... 18 | -------------------------------------------------------------------------------- /internal/commandinfo_test.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import "testing" 4 | 5 | func TestLookupCommandInfo(t *testing.T) { 6 | for _, n := range []string{"watch", "WATCH", "wAtch"} { 7 | if LookupCommandInfo(n) == (CommandInfo{}) { 8 | t.Errorf("LookupCommandInfo(%q) = CommandInfo{}, expected non-zero value", n) 9 | } 10 | } 11 | } 12 | 13 | func benchmarkLookupCommandInfo(b *testing.B, names ...string) { 14 | for i := 0; i < b.N; i++ { 15 | for _, c := range names { 16 | LookupCommandInfo(c) 17 | } 18 | } 19 | } 20 | 21 | func BenchmarkLookupCommandInfoCorrectCase(b *testing.B) { 22 | benchmarkLookupCommandInfo(b, "watch", "WATCH", "monitor", "MONITOR") 23 | } 24 | 25 | func BenchmarkLookupCommandInfoMixedCase(b *testing.B) { 26 | benchmarkLookupCommandInfo(b, "wAtch", "WeTCH", "monItor", "MONiTOR") 27 | } 28 | -------------------------------------------------------------------------------- /redisx/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // 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, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | // Package redisx contains experimental features for Redigo. Features in this 16 | // package may be modified or deleted at any time. 17 | package redisx // import "github.com/garyburd/redigo/redisx" 18 | -------------------------------------------------------------------------------- /redis/pre_go17.go: -------------------------------------------------------------------------------- 1 | // +build !go1.7 2 | 3 | package redis 4 | 5 | import "crypto/tls" 6 | 7 | // similar cloneTLSClientConfig in the stdlib, but also honor skipVerify for the nil case 8 | func cloneTLSClientConfig(cfg *tls.Config, skipVerify bool) *tls.Config { 9 | if cfg == nil { 10 | return &tls.Config{InsecureSkipVerify: skipVerify} 11 | } 12 | return &tls.Config{ 13 | Rand: cfg.Rand, 14 | Time: cfg.Time, 15 | Certificates: cfg.Certificates, 16 | NameToCertificate: cfg.NameToCertificate, 17 | GetCertificate: cfg.GetCertificate, 18 | RootCAs: cfg.RootCAs, 19 | NextProtos: cfg.NextProtos, 20 | ServerName: cfg.ServerName, 21 | ClientAuth: cfg.ClientAuth, 22 | ClientCAs: cfg.ClientCAs, 23 | InsecureSkipVerify: cfg.InsecureSkipVerify, 24 | CipherSuites: cfg.CipherSuites, 25 | PreferServerCipherSuites: cfg.PreferServerCipherSuites, 26 | ClientSessionCache: cfg.ClientSessionCache, 27 | MinVersion: cfg.MinVersion, 28 | MaxVersion: cfg.MaxVersion, 29 | CurvePreferences: cfg.CurvePreferences, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /redis/go17.go: -------------------------------------------------------------------------------- 1 | // +build go1.7 2 | 3 | package redis 4 | 5 | import "crypto/tls" 6 | 7 | // similar cloneTLSClientConfig in the stdlib, but also honor skipVerify for the nil case 8 | func cloneTLSClientConfig(cfg *tls.Config, skipVerify bool) *tls.Config { 9 | if cfg == nil { 10 | return &tls.Config{InsecureSkipVerify: skipVerify} 11 | } 12 | return &tls.Config{ 13 | Rand: cfg.Rand, 14 | Time: cfg.Time, 15 | Certificates: cfg.Certificates, 16 | NameToCertificate: cfg.NameToCertificate, 17 | GetCertificate: cfg.GetCertificate, 18 | RootCAs: cfg.RootCAs, 19 | NextProtos: cfg.NextProtos, 20 | ServerName: cfg.ServerName, 21 | ClientAuth: cfg.ClientAuth, 22 | ClientCAs: cfg.ClientCAs, 23 | InsecureSkipVerify: cfg.InsecureSkipVerify, 24 | CipherSuites: cfg.CipherSuites, 25 | PreferServerCipherSuites: cfg.PreferServerCipherSuites, 26 | ClientSessionCache: cfg.ClientSessionCache, 27 | MinVersion: cfg.MinVersion, 28 | MaxVersion: cfg.MaxVersion, 29 | CurvePreferences: cfg.CurvePreferences, 30 | DynamicRecordSizingDisabled: cfg.DynamicRecordSizingDisabled, 31 | Renegotiation: cfg.Renegotiation, 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /internal/commandinfo.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // 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, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package internal // import "github.com/garyburd/redigo/internal" 16 | 17 | import ( 18 | "strings" 19 | ) 20 | 21 | const ( 22 | WatchState = 1 << iota 23 | MultiState 24 | SubscribeState 25 | MonitorState 26 | ) 27 | 28 | type CommandInfo struct { 29 | Set, Clear int 30 | } 31 | 32 | var commandInfos = map[string]CommandInfo{ 33 | "WATCH": {Set: WatchState}, 34 | "UNWATCH": {Clear: WatchState}, 35 | "MULTI": {Set: MultiState}, 36 | "EXEC": {Clear: WatchState | MultiState}, 37 | "DISCARD": {Clear: WatchState | MultiState}, 38 | "PSUBSCRIBE": {Set: SubscribeState}, 39 | "SUBSCRIBE": {Set: SubscribeState}, 40 | "MONITOR": {Set: MonitorState}, 41 | } 42 | 43 | func init() { 44 | for n, ci := range commandInfos { 45 | commandInfos[strings.ToLower(n)] = ci 46 | } 47 | } 48 | 49 | func LookupCommandInfo(commandName string) CommandInfo { 50 | if ci, ok := commandInfos[commandName]; ok { 51 | return ci 52 | } 53 | return commandInfos[strings.ToUpper(commandName)] 54 | } 55 | -------------------------------------------------------------------------------- /redis/redis.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // 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, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package redis 16 | 17 | // Error represents an error returned in a command reply. 18 | type Error string 19 | 20 | func (err Error) Error() string { return string(err) } 21 | 22 | // Conn represents a connection to a Redis server. 23 | type Conn interface { 24 | // Close closes the connection. 25 | Close() error 26 | 27 | // Err returns a non-nil value if the connection is broken. The returned 28 | // value is either the first non-nil value returned from the underlying 29 | // network connection or a protocol parsing error. Applications should 30 | // close broken connections. 31 | Err() error 32 | 33 | // Do sends a command to the server and returns the received reply. 34 | Do(commandName string, args ...interface{}) (reply interface{}, err error) 35 | 36 | // Send writes the command to the client's output buffer. 37 | Send(commandName string, args ...interface{}) error 38 | 39 | // Flush flushes the output buffer to the Redis server. 40 | Flush() error 41 | 42 | // Receive receives a single reply from the Redis server 43 | Receive() (reply interface{}, err error) 44 | } 45 | -------------------------------------------------------------------------------- /internal/redistest/testdb.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // 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, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | // Package redistest contains utilities for writing Redigo tests. 16 | package redistest 17 | 18 | import ( 19 | "errors" 20 | "time" 21 | 22 | "github.com/garyburd/redigo/redis" 23 | ) 24 | 25 | type testConn struct { 26 | redis.Conn 27 | } 28 | 29 | func (t testConn) Close() error { 30 | _, err := t.Conn.Do("SELECT", "9") 31 | if err != nil { 32 | return nil 33 | } 34 | _, err = t.Conn.Do("FLUSHDB") 35 | if err != nil { 36 | return err 37 | } 38 | return t.Conn.Close() 39 | } 40 | 41 | // Dial dials the local Redis server and selects database 9. To prevent 42 | // stomping on real data, DialTestDB fails if database 9 contains data. The 43 | // returned connection flushes database 9 on close. 44 | func Dial() (redis.Conn, error) { 45 | c, err := redis.DialTimeout("tcp", ":6379", 0, 1*time.Second, 1*time.Second) 46 | if err != nil { 47 | return nil, err 48 | } 49 | 50 | _, err = c.Do("SELECT", "9") 51 | if err != nil { 52 | c.Close() 53 | return nil, err 54 | } 55 | 56 | n, err := redis.Int(c.Do("DBSIZE")) 57 | if err != nil { 58 | c.Close() 59 | return nil, err 60 | } 61 | 62 | if n != 0 { 63 | c.Close() 64 | return nil, errors.New("database #9 is not empty, test can not continue") 65 | } 66 | 67 | return testConn{c}, nil 68 | } 69 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | Redigo 2 | ====== 3 | 4 | [![Build Status](https://travis-ci.org/garyburd/redigo.svg?branch=master)](https://travis-ci.org/garyburd/redigo) 5 | [![GoDoc](https://godoc.org/github.com/garyburd/redigo/redis?status.svg)](https://godoc.org/github.com/garyburd/redigo/redis) 6 | 7 | Redigo is a [Go](http://golang.org/) client for the [Redis](http://redis.io/) database. 8 | 9 | Features 10 | ------- 11 | 12 | * A [Print-like](http://godoc.org/github.com/garyburd/redigo/redis#hdr-Executing_Commands) API with support for all Redis commands. 13 | * [Pipelining](http://godoc.org/github.com/garyburd/redigo/redis#hdr-Pipelining), including pipelined transactions. 14 | * [Publish/Subscribe](http://godoc.org/github.com/garyburd/redigo/redis#hdr-Publish_and_Subscribe). 15 | * [Connection pooling](http://godoc.org/github.com/garyburd/redigo/redis#Pool). 16 | * [Script helper type](http://godoc.org/github.com/garyburd/redigo/redis#Script) with optimistic use of EVALSHA. 17 | * [Helper functions](http://godoc.org/github.com/garyburd/redigo/redis#hdr-Reply_Helpers) for working with command replies. 18 | 19 | Documentation 20 | ------------- 21 | 22 | - [API Reference](http://godoc.org/github.com/garyburd/redigo/redis) 23 | - [FAQ](https://github.com/garyburd/redigo/wiki/FAQ) 24 | 25 | Installation 26 | ------------ 27 | 28 | Install Redigo using the "go get" command: 29 | 30 | go get github.com/garyburd/redigo/redis 31 | 32 | The Go distribution is Redigo's only dependency. 33 | 34 | Related Projects 35 | ---------------- 36 | 37 | - [rafaeljusto/redigomock](https://godoc.org/github.com/rafaeljusto/redigomock) - A mock library for Redigo. 38 | - [chasex/redis-go-cluster](https://github.com/chasex/redis-go-cluster) - A Redis cluster client implementation. 39 | - [FZambia/go-sentinel](https://github.com/FZambia/go-sentinel) - Redis Sentinel support for Redigo 40 | - [PuerkitoBio/redisc](https://github.com/PuerkitoBio/redisc) - Redis Cluster client built on top of Redigo 41 | 42 | Contributing 43 | ------------ 44 | 45 | Send email to Gary Burd (address in GitHub profile) before doing any work on Redigo. 46 | 47 | License 48 | ------- 49 | 50 | Redigo is available under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.html). 51 | -------------------------------------------------------------------------------- /redis/zpop_example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // 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, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package redis_test 16 | 17 | import ( 18 | "fmt" 19 | "github.com/garyburd/redigo/redis" 20 | ) 21 | 22 | // zpop pops a value from the ZSET key using WATCH/MULTI/EXEC commands. 23 | func zpop(c redis.Conn, key string) (result string, err error) { 24 | 25 | defer func() { 26 | // Return connection to normal state on error. 27 | if err != nil { 28 | c.Do("DISCARD") 29 | } 30 | }() 31 | 32 | // Loop until transaction is successful. 33 | for { 34 | if _, err := c.Do("WATCH", key); err != nil { 35 | return "", err 36 | } 37 | 38 | members, err := redis.Strings(c.Do("ZRANGE", key, 0, 0)) 39 | if err != nil { 40 | return "", err 41 | } 42 | if len(members) != 1 { 43 | return "", redis.ErrNil 44 | } 45 | 46 | c.Send("MULTI") 47 | c.Send("ZREM", key, members[0]) 48 | queued, err := c.Do("EXEC") 49 | if err != nil { 50 | return "", err 51 | } 52 | 53 | if queued != nil { 54 | result = members[0] 55 | break 56 | } 57 | } 58 | 59 | return result, nil 60 | } 61 | 62 | // zpopScript pops a value from a ZSET. 63 | var zpopScript = redis.NewScript(1, ` 64 | local r = redis.call('ZRANGE', KEYS[1], 0, 0) 65 | if r ~= nil then 66 | r = r[1] 67 | redis.call('ZREM', KEYS[1], r) 68 | end 69 | return r 70 | `) 71 | 72 | // This example implements ZPOP as described at 73 | // http://redis.io/topics/transactions using WATCH/MULTI/EXEC and scripting. 74 | func Example_zpop() { 75 | c, err := dial() 76 | if err != nil { 77 | fmt.Println(err) 78 | return 79 | } 80 | defer c.Close() 81 | 82 | // Add test data using a pipeline. 83 | 84 | for i, member := range []string{"red", "blue", "green"} { 85 | c.Send("ZADD", "zset", i, member) 86 | } 87 | if _, err := c.Do(""); err != nil { 88 | fmt.Println(err) 89 | return 90 | } 91 | 92 | // Pop using WATCH/MULTI/EXEC 93 | 94 | v, err := zpop(c, "zset") 95 | if err != nil { 96 | fmt.Println(err) 97 | return 98 | } 99 | fmt.Println(v) 100 | 101 | // Pop using a script. 102 | 103 | v, err = redis.String(zpopScript.Do(c, "zset")) 104 | if err != nil { 105 | fmt.Println(err) 106 | return 107 | } 108 | fmt.Println(v) 109 | 110 | // Output: 111 | // red 112 | // blue 113 | } 114 | -------------------------------------------------------------------------------- /redis/script_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // 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, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package redis_test 16 | 17 | import ( 18 | "fmt" 19 | "reflect" 20 | "testing" 21 | "time" 22 | 23 | "github.com/garyburd/redigo/redis" 24 | ) 25 | 26 | var ( 27 | // These variables are declared at package level to remove distracting 28 | // details from the examples. 29 | c redis.Conn 30 | reply interface{} 31 | err error 32 | ) 33 | 34 | func ExampleScript() { 35 | // Initialize a package-level variable with a script. 36 | var getScript = redis.NewScript(1, `return redis.call('get', KEYS[1])`) 37 | 38 | // In a function, use the script Do method to evaluate the script. The Do 39 | // method optimistically uses the EVALSHA command. If the script is not 40 | // loaded, then the Do method falls back to the EVAL command. 41 | reply, err = getScript.Do(c, "foo") 42 | } 43 | 44 | func TestScript(t *testing.T) { 45 | c, err := redis.DialDefaultServer() 46 | if err != nil { 47 | t.Fatalf("error connection to database, %v", err) 48 | } 49 | defer c.Close() 50 | 51 | // To test fall back in Do, we make script unique by adding comment with current time. 52 | script := fmt.Sprintf("--%d\nreturn {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}", time.Now().UnixNano()) 53 | s := redis.NewScript(2, script) 54 | reply := []interface{}{[]byte("key1"), []byte("key2"), []byte("arg1"), []byte("arg2")} 55 | 56 | v, err := s.Do(c, "key1", "key2", "arg1", "arg2") 57 | if err != nil { 58 | t.Errorf("s.Do(c, ...) returned %v", err) 59 | } 60 | 61 | if !reflect.DeepEqual(v, reply) { 62 | t.Errorf("s.Do(c, ..); = %v, want %v", v, reply) 63 | } 64 | 65 | err = s.Load(c) 66 | if err != nil { 67 | t.Errorf("s.Load(c) returned %v", err) 68 | } 69 | 70 | err = s.SendHash(c, "key1", "key2", "arg1", "arg2") 71 | if err != nil { 72 | t.Errorf("s.SendHash(c, ...) returned %v", err) 73 | } 74 | 75 | err = c.Flush() 76 | if err != nil { 77 | t.Errorf("c.Flush() returned %v", err) 78 | } 79 | 80 | v, err = c.Receive() 81 | if !reflect.DeepEqual(v, reply) { 82 | t.Errorf("s.SendHash(c, ..); c.Receive() = %v, want %v", v, reply) 83 | } 84 | 85 | err = s.Send(c, "key1", "key2", "arg1", "arg2") 86 | if err != nil { 87 | t.Errorf("s.Send(c, ...) returned %v", err) 88 | } 89 | 90 | err = c.Flush() 91 | if err != nil { 92 | t.Errorf("c.Flush() returned %v", err) 93 | } 94 | 95 | v, err = c.Receive() 96 | if !reflect.DeepEqual(v, reply) { 97 | t.Errorf("s.Send(c, ..); c.Receive() = %v, want %v", v, reply) 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /redis/script.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // 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, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package redis 16 | 17 | import ( 18 | "crypto/sha1" 19 | "encoding/hex" 20 | "io" 21 | "strings" 22 | ) 23 | 24 | // Script encapsulates the source, hash and key count for a Lua script. See 25 | // http://redis.io/commands/eval for information on scripts in Redis. 26 | type Script struct { 27 | keyCount int 28 | src string 29 | hash string 30 | } 31 | 32 | // NewScript returns a new script object. If keyCount is greater than or equal 33 | // to zero, then the count is automatically inserted in the EVAL command 34 | // argument list. If keyCount is less than zero, then the application supplies 35 | // the count as the first value in the keysAndArgs argument to the Do, Send and 36 | // SendHash methods. 37 | func NewScript(keyCount int, src string) *Script { 38 | h := sha1.New() 39 | io.WriteString(h, src) 40 | return &Script{keyCount, src, hex.EncodeToString(h.Sum(nil))} 41 | } 42 | 43 | func (s *Script) args(spec string, keysAndArgs []interface{}) []interface{} { 44 | var args []interface{} 45 | if s.keyCount < 0 { 46 | args = make([]interface{}, 1+len(keysAndArgs)) 47 | args[0] = spec 48 | copy(args[1:], keysAndArgs) 49 | } else { 50 | args = make([]interface{}, 2+len(keysAndArgs)) 51 | args[0] = spec 52 | args[1] = s.keyCount 53 | copy(args[2:], keysAndArgs) 54 | } 55 | return args 56 | } 57 | 58 | // Do evaluates the script. Under the covers, Do optimistically evaluates the 59 | // script using the EVALSHA command. If the command fails because the script is 60 | // not loaded, then Do evaluates the script using the EVAL command (thus 61 | // causing the script to load). 62 | func (s *Script) Do(c Conn, keysAndArgs ...interface{}) (interface{}, error) { 63 | v, err := c.Do("EVALSHA", s.args(s.hash, keysAndArgs)...) 64 | if e, ok := err.(Error); ok && strings.HasPrefix(string(e), "NOSCRIPT ") { 65 | v, err = c.Do("EVAL", s.args(s.src, keysAndArgs)...) 66 | } 67 | return v, err 68 | } 69 | 70 | // SendHash evaluates the script without waiting for the reply. The script is 71 | // evaluated with the EVALSHA command. The application must ensure that the 72 | // script is loaded by a previous call to Send, Do or Load methods. 73 | func (s *Script) SendHash(c Conn, keysAndArgs ...interface{}) error { 74 | return c.Send("EVALSHA", s.args(s.hash, keysAndArgs)...) 75 | } 76 | 77 | // Send evaluates the script without waiting for the reply. 78 | func (s *Script) Send(c Conn, keysAndArgs ...interface{}) error { 79 | return c.Send("EVAL", s.args(s.src, keysAndArgs)...) 80 | } 81 | 82 | // Load loads the script without evaluating it. 83 | func (s *Script) Load(c Conn) error { 84 | _, err := c.Do("SCRIPT", "LOAD", s.src) 85 | return err 86 | } 87 | -------------------------------------------------------------------------------- /redis/log.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // 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, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package redis 16 | 17 | import ( 18 | "bytes" 19 | "fmt" 20 | "log" 21 | ) 22 | 23 | // NewLoggingConn returns a logging wrapper around a connection. 24 | func NewLoggingConn(conn Conn, logger *log.Logger, prefix string) Conn { 25 | if prefix != "" { 26 | prefix = prefix + "." 27 | } 28 | return &loggingConn{conn, logger, prefix} 29 | } 30 | 31 | type loggingConn struct { 32 | Conn 33 | logger *log.Logger 34 | prefix string 35 | } 36 | 37 | func (c *loggingConn) Close() error { 38 | err := c.Conn.Close() 39 | var buf bytes.Buffer 40 | fmt.Fprintf(&buf, "%sClose() -> (%v)", c.prefix, err) 41 | c.logger.Output(2, buf.String()) 42 | return err 43 | } 44 | 45 | func (c *loggingConn) printValue(buf *bytes.Buffer, v interface{}) { 46 | const chop = 32 47 | switch v := v.(type) { 48 | case []byte: 49 | if len(v) > chop { 50 | fmt.Fprintf(buf, "%q...", v[:chop]) 51 | } else { 52 | fmt.Fprintf(buf, "%q", v) 53 | } 54 | case string: 55 | if len(v) > chop { 56 | fmt.Fprintf(buf, "%q...", v[:chop]) 57 | } else { 58 | fmt.Fprintf(buf, "%q", v) 59 | } 60 | case []interface{}: 61 | if len(v) == 0 { 62 | buf.WriteString("[]") 63 | } else { 64 | sep := "[" 65 | fin := "]" 66 | if len(v) > chop { 67 | v = v[:chop] 68 | fin = "...]" 69 | } 70 | for _, vv := range v { 71 | buf.WriteString(sep) 72 | c.printValue(buf, vv) 73 | sep = ", " 74 | } 75 | buf.WriteString(fin) 76 | } 77 | default: 78 | fmt.Fprint(buf, v) 79 | } 80 | } 81 | 82 | func (c *loggingConn) print(method, commandName string, args []interface{}, reply interface{}, err error) { 83 | var buf bytes.Buffer 84 | fmt.Fprintf(&buf, "%s%s(", c.prefix, method) 85 | if method != "Receive" { 86 | buf.WriteString(commandName) 87 | for _, arg := range args { 88 | buf.WriteString(", ") 89 | c.printValue(&buf, arg) 90 | } 91 | } 92 | buf.WriteString(") -> (") 93 | if method != "Send" { 94 | c.printValue(&buf, reply) 95 | buf.WriteString(", ") 96 | } 97 | fmt.Fprintf(&buf, "%v)", err) 98 | c.logger.Output(3, buf.String()) 99 | } 100 | 101 | func (c *loggingConn) Do(commandName string, args ...interface{}) (interface{}, error) { 102 | reply, err := c.Conn.Do(commandName, args...) 103 | c.print("Do", commandName, args, reply, err) 104 | return reply, err 105 | } 106 | 107 | func (c *loggingConn) Send(commandName string, args ...interface{}) error { 108 | err := c.Conn.Send(commandName, args...) 109 | c.print("Send", commandName, args, nil, err) 110 | return err 111 | } 112 | 113 | func (c *loggingConn) Receive() (interface{}, error) { 114 | reply, err := c.Conn.Receive() 115 | c.print("Receive", "", nil, reply, err) 116 | return reply, err 117 | } 118 | -------------------------------------------------------------------------------- /redisx/connmux.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // 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, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package redisx 16 | 17 | import ( 18 | "errors" 19 | "sync" 20 | 21 | "github.com/garyburd/redigo/internal" 22 | "github.com/garyburd/redigo/redis" 23 | ) 24 | 25 | // ConnMux multiplexes one or more connections to a single underlying 26 | // connection. The ConnMux connections do not support concurrency, commands 27 | // that associate server side state with the connection or commands that put 28 | // the connection in a special mode. 29 | type ConnMux struct { 30 | c redis.Conn 31 | 32 | sendMu sync.Mutex 33 | sendID uint 34 | 35 | recvMu sync.Mutex 36 | recvID uint 37 | recvWait map[uint]chan struct{} 38 | } 39 | 40 | func NewConnMux(c redis.Conn) *ConnMux { 41 | return &ConnMux{c: c, recvWait: make(map[uint]chan struct{})} 42 | } 43 | 44 | // Get gets a connection. The application must close the returned connection. 45 | func (p *ConnMux) Get() redis.Conn { 46 | c := &muxConn{p: p} 47 | c.ids = c.buf[:0] 48 | return c 49 | } 50 | 51 | // Close closes the underlying connection. 52 | func (p *ConnMux) Close() error { 53 | return p.c.Close() 54 | } 55 | 56 | type muxConn struct { 57 | p *ConnMux 58 | ids []uint 59 | buf [8]uint 60 | } 61 | 62 | func (c *muxConn) send(flush bool, cmd string, args ...interface{}) error { 63 | if internal.LookupCommandInfo(cmd).Set != 0 { 64 | return errors.New("command not supported by mux pool") 65 | } 66 | p := c.p 67 | p.sendMu.Lock() 68 | id := p.sendID 69 | c.ids = append(c.ids, id) 70 | p.sendID++ 71 | err := p.c.Send(cmd, args...) 72 | if flush { 73 | err = p.c.Flush() 74 | } 75 | p.sendMu.Unlock() 76 | return err 77 | } 78 | 79 | func (c *muxConn) Send(cmd string, args ...interface{}) error { 80 | return c.send(false, cmd, args...) 81 | } 82 | 83 | func (c *muxConn) Flush() error { 84 | p := c.p 85 | p.sendMu.Lock() 86 | err := p.c.Flush() 87 | p.sendMu.Unlock() 88 | return err 89 | } 90 | 91 | func (c *muxConn) Receive() (interface{}, error) { 92 | if len(c.ids) == 0 { 93 | return nil, errors.New("mux pool underflow") 94 | } 95 | 96 | id := c.ids[0] 97 | c.ids = c.ids[1:] 98 | if len(c.ids) == 0 { 99 | c.ids = c.buf[:0] 100 | } 101 | 102 | p := c.p 103 | p.recvMu.Lock() 104 | if p.recvID != id { 105 | ch := make(chan struct{}) 106 | p.recvWait[id] = ch 107 | p.recvMu.Unlock() 108 | <-ch 109 | p.recvMu.Lock() 110 | if p.recvID != id { 111 | panic("out of sync") 112 | } 113 | } 114 | 115 | v, err := p.c.Receive() 116 | 117 | id++ 118 | p.recvID = id 119 | ch, ok := p.recvWait[id] 120 | if ok { 121 | delete(p.recvWait, id) 122 | } 123 | p.recvMu.Unlock() 124 | if ok { 125 | ch <- struct{}{} 126 | } 127 | 128 | return v, err 129 | } 130 | 131 | func (c *muxConn) Close() error { 132 | var err error 133 | if len(c.ids) == 0 { 134 | return nil 135 | } 136 | c.Flush() 137 | for _ = range c.ids { 138 | _, err = c.Receive() 139 | } 140 | return err 141 | } 142 | 143 | func (c *muxConn) Do(cmd string, args ...interface{}) (interface{}, error) { 144 | if err := c.send(true, cmd, args...); err != nil { 145 | return nil, err 146 | } 147 | return c.Receive() 148 | } 149 | 150 | func (c *muxConn) Err() error { 151 | return c.p.c.Err() 152 | } 153 | -------------------------------------------------------------------------------- /redis/pubsub.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // 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, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package redis 16 | 17 | import "errors" 18 | 19 | // Subscription represents a subscribe or unsubscribe notification. 20 | type Subscription struct { 21 | 22 | // Kind is "subscribe", "unsubscribe", "psubscribe" or "punsubscribe" 23 | Kind string 24 | 25 | // The channel that was changed. 26 | Channel string 27 | 28 | // The current number of subscriptions for connection. 29 | Count int 30 | } 31 | 32 | // Message represents a message notification. 33 | type Message struct { 34 | 35 | // The originating channel. 36 | Channel string 37 | 38 | // The message data. 39 | Data []byte 40 | } 41 | 42 | // PMessage represents a pmessage notification. 43 | type PMessage struct { 44 | 45 | // The matched pattern. 46 | Pattern string 47 | 48 | // The originating channel. 49 | Channel string 50 | 51 | // The message data. 52 | Data []byte 53 | } 54 | 55 | // Pong represents a pubsub pong notification. 56 | type Pong struct { 57 | Data string 58 | } 59 | 60 | // PubSubConn wraps a Conn with convenience methods for subscribers. 61 | type PubSubConn struct { 62 | Conn Conn 63 | } 64 | 65 | // Close closes the connection. 66 | func (c PubSubConn) Close() error { 67 | return c.Conn.Close() 68 | } 69 | 70 | // Subscribe subscribes the connection to the specified channels. 71 | func (c PubSubConn) Subscribe(channel ...interface{}) error { 72 | c.Conn.Send("SUBSCRIBE", channel...) 73 | return c.Conn.Flush() 74 | } 75 | 76 | // PSubscribe subscribes the connection to the given patterns. 77 | func (c PubSubConn) PSubscribe(channel ...interface{}) error { 78 | c.Conn.Send("PSUBSCRIBE", channel...) 79 | return c.Conn.Flush() 80 | } 81 | 82 | // Unsubscribe unsubscribes the connection from the given channels, or from all 83 | // of them if none is given. 84 | func (c PubSubConn) Unsubscribe(channel ...interface{}) error { 85 | c.Conn.Send("UNSUBSCRIBE", channel...) 86 | return c.Conn.Flush() 87 | } 88 | 89 | // PUnsubscribe unsubscribes the connection from the given patterns, or from all 90 | // of them if none is given. 91 | func (c PubSubConn) PUnsubscribe(channel ...interface{}) error { 92 | c.Conn.Send("PUNSUBSCRIBE", channel...) 93 | return c.Conn.Flush() 94 | } 95 | 96 | // Ping sends a PING to the server with the specified data. 97 | func (c PubSubConn) Ping(data string) error { 98 | c.Conn.Send("PING", data) 99 | return c.Conn.Flush() 100 | } 101 | 102 | // Receive returns a pushed message as a Subscription, Message, PMessage, Pong 103 | // or error. The return value is intended to be used directly in a type switch 104 | // as illustrated in the PubSubConn example. 105 | func (c PubSubConn) Receive() interface{} { 106 | reply, err := Values(c.Conn.Receive()) 107 | if err != nil { 108 | return err 109 | } 110 | 111 | var kind string 112 | reply, err = Scan(reply, &kind) 113 | if err != nil { 114 | return err 115 | } 116 | 117 | switch kind { 118 | case "message": 119 | var m Message 120 | if _, err := Scan(reply, &m.Channel, &m.Data); err != nil { 121 | return err 122 | } 123 | return m 124 | case "pmessage": 125 | var pm PMessage 126 | if _, err := Scan(reply, &pm.Pattern, &pm.Channel, &pm.Data); err != nil { 127 | return err 128 | } 129 | return pm 130 | case "subscribe", "psubscribe", "unsubscribe", "punsubscribe": 131 | s := Subscription{Kind: kind} 132 | if _, err := Scan(reply, &s.Channel, &s.Count); err != nil { 133 | return err 134 | } 135 | return s 136 | case "pong": 137 | var p Pong 138 | if _, err := Scan(reply, &p.Data); err != nil { 139 | return err 140 | } 141 | return p 142 | } 143 | return errors.New("redigo: unknown pubsub notification") 144 | } 145 | -------------------------------------------------------------------------------- /redis/reply_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // 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, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package redis_test 16 | 17 | import ( 18 | "fmt" 19 | "reflect" 20 | "testing" 21 | 22 | "github.com/garyburd/redigo/redis" 23 | ) 24 | 25 | type valueError struct { 26 | v interface{} 27 | err error 28 | } 29 | 30 | func ve(v interface{}, err error) valueError { 31 | return valueError{v, err} 32 | } 33 | 34 | var replyTests = []struct { 35 | name interface{} 36 | actual valueError 37 | expected valueError 38 | }{ 39 | { 40 | "ints([v1, v2])", 41 | ve(redis.Ints([]interface{}{[]byte("4"), []byte("5")}, nil)), 42 | ve([]int{4, 5}, nil), 43 | }, 44 | { 45 | "ints(nil)", 46 | ve(redis.Ints(nil, nil)), 47 | ve([]int(nil), redis.ErrNil), 48 | }, 49 | { 50 | "strings([v1, v2])", 51 | ve(redis.Strings([]interface{}{[]byte("v1"), []byte("v2")}, nil)), 52 | ve([]string{"v1", "v2"}, nil), 53 | }, 54 | { 55 | "strings(nil)", 56 | ve(redis.Strings(nil, nil)), 57 | ve([]string(nil), redis.ErrNil), 58 | }, 59 | { 60 | "byteslices([v1, v2])", 61 | ve(redis.ByteSlices([]interface{}{[]byte("v1"), []byte("v2")}, nil)), 62 | ve([][]byte{[]byte("v1"), []byte("v2")}, nil), 63 | }, 64 | { 65 | "byteslices(nil)", 66 | ve(redis.ByteSlices(nil, nil)), 67 | ve([][]byte(nil), redis.ErrNil), 68 | }, 69 | { 70 | "values([v1, v2])", 71 | ve(redis.Values([]interface{}{[]byte("v1"), []byte("v2")}, nil)), 72 | ve([]interface{}{[]byte("v1"), []byte("v2")}, nil), 73 | }, 74 | { 75 | "values(nil)", 76 | ve(redis.Values(nil, nil)), 77 | ve([]interface{}(nil), redis.ErrNil), 78 | }, 79 | { 80 | "float64(1.0)", 81 | ve(redis.Float64([]byte("1.0"), nil)), 82 | ve(float64(1.0), nil), 83 | }, 84 | { 85 | "float64(nil)", 86 | ve(redis.Float64(nil, nil)), 87 | ve(float64(0.0), redis.ErrNil), 88 | }, 89 | { 90 | "uint64(1)", 91 | ve(redis.Uint64(int64(1), nil)), 92 | ve(uint64(1), nil), 93 | }, 94 | { 95 | "uint64(-1)", 96 | ve(redis.Uint64(int64(-1), nil)), 97 | ve(uint64(0), redis.ErrNegativeInt), 98 | }, 99 | } 100 | 101 | func TestReply(t *testing.T) { 102 | for _, rt := range replyTests { 103 | if rt.actual.err != rt.expected.err { 104 | t.Errorf("%s returned err %v, want %v", rt.name, rt.actual.err, rt.expected.err) 105 | continue 106 | } 107 | if !reflect.DeepEqual(rt.actual.v, rt.expected.v) { 108 | t.Errorf("%s=%+v, want %+v", rt.name, rt.actual.v, rt.expected.v) 109 | } 110 | } 111 | } 112 | 113 | // dial wraps DialDefaultServer() with a more suitable function name for examples. 114 | func dial() (redis.Conn, error) { 115 | return redis.DialDefaultServer() 116 | } 117 | 118 | func ExampleBool() { 119 | c, err := dial() 120 | if err != nil { 121 | fmt.Println(err) 122 | return 123 | } 124 | defer c.Close() 125 | 126 | c.Do("SET", "foo", 1) 127 | exists, _ := redis.Bool(c.Do("EXISTS", "foo")) 128 | fmt.Printf("%#v\n", exists) 129 | // Output: 130 | // true 131 | } 132 | 133 | func ExampleInt() { 134 | c, err := dial() 135 | if err != nil { 136 | fmt.Println(err) 137 | return 138 | } 139 | defer c.Close() 140 | 141 | c.Do("SET", "k1", 1) 142 | n, _ := redis.Int(c.Do("GET", "k1")) 143 | fmt.Printf("%#v\n", n) 144 | n, _ = redis.Int(c.Do("INCR", "k1")) 145 | fmt.Printf("%#v\n", n) 146 | // Output: 147 | // 1 148 | // 2 149 | } 150 | 151 | func ExampleInts() { 152 | c, err := dial() 153 | if err != nil { 154 | fmt.Println(err) 155 | return 156 | } 157 | defer c.Close() 158 | 159 | c.Do("SADD", "set_with_integers", 4, 5, 6) 160 | ints, _ := redis.Ints(c.Do("SMEMBERS", "set_with_integers")) 161 | fmt.Printf("%#v\n", ints) 162 | // Output: 163 | // []int{4, 5, 6} 164 | } 165 | 166 | func ExampleString() { 167 | c, err := dial() 168 | if err != nil { 169 | fmt.Println(err) 170 | return 171 | } 172 | defer c.Close() 173 | 174 | c.Do("SET", "hello", "world") 175 | s, err := redis.String(c.Do("GET", "hello")) 176 | fmt.Printf("%#v\n", s) 177 | // Output: 178 | // "world" 179 | } 180 | -------------------------------------------------------------------------------- /redis/pubsub_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // 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, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package redis_test 16 | 17 | import ( 18 | "fmt" 19 | "reflect" 20 | "sync" 21 | "testing" 22 | 23 | "github.com/garyburd/redigo/redis" 24 | ) 25 | 26 | func publish(channel, value interface{}) { 27 | c, err := dial() 28 | if err != nil { 29 | fmt.Println(err) 30 | return 31 | } 32 | defer c.Close() 33 | c.Do("PUBLISH", channel, value) 34 | } 35 | 36 | // Applications can receive pushed messages from one goroutine and manage subscriptions from another goroutine. 37 | func ExamplePubSubConn() { 38 | c, err := dial() 39 | if err != nil { 40 | fmt.Println(err) 41 | return 42 | } 43 | defer c.Close() 44 | var wg sync.WaitGroup 45 | wg.Add(2) 46 | 47 | psc := redis.PubSubConn{Conn: c} 48 | 49 | // This goroutine receives and prints pushed notifications from the server. 50 | // The goroutine exits when the connection is unsubscribed from all 51 | // channels or there is an error. 52 | go func() { 53 | defer wg.Done() 54 | for { 55 | switch n := psc.Receive().(type) { 56 | case redis.Message: 57 | fmt.Printf("Message: %s %s\n", n.Channel, n.Data) 58 | case redis.PMessage: 59 | fmt.Printf("PMessage: %s %s %s\n", n.Pattern, n.Channel, n.Data) 60 | case redis.Subscription: 61 | fmt.Printf("Subscription: %s %s %d\n", n.Kind, n.Channel, n.Count) 62 | if n.Count == 0 { 63 | return 64 | } 65 | case error: 66 | fmt.Printf("error: %v\n", n) 67 | return 68 | } 69 | } 70 | }() 71 | 72 | // This goroutine manages subscriptions for the connection. 73 | go func() { 74 | defer wg.Done() 75 | 76 | psc.Subscribe("example") 77 | psc.PSubscribe("p*") 78 | 79 | // The following function calls publish a message using another 80 | // connection to the Redis server. 81 | publish("example", "hello") 82 | publish("example", "world") 83 | publish("pexample", "foo") 84 | publish("pexample", "bar") 85 | 86 | // Unsubscribe from all connections. This will cause the receiving 87 | // goroutine to exit. 88 | psc.Unsubscribe() 89 | psc.PUnsubscribe() 90 | }() 91 | 92 | wg.Wait() 93 | 94 | // Output: 95 | // Subscription: subscribe example 1 96 | // Subscription: psubscribe p* 2 97 | // Message: example hello 98 | // Message: example world 99 | // PMessage: p* pexample foo 100 | // PMessage: p* pexample bar 101 | // Subscription: unsubscribe example 1 102 | // Subscription: punsubscribe p* 0 103 | } 104 | 105 | func expectPushed(t *testing.T, c redis.PubSubConn, message string, expected interface{}) { 106 | actual := c.Receive() 107 | if !reflect.DeepEqual(actual, expected) { 108 | t.Errorf("%s = %v, want %v", message, actual, expected) 109 | } 110 | } 111 | 112 | func TestPushed(t *testing.T) { 113 | pc, err := redis.DialDefaultServer() 114 | if err != nil { 115 | t.Fatalf("error connection to database, %v", err) 116 | } 117 | defer pc.Close() 118 | 119 | sc, err := redis.DialDefaultServer() 120 | if err != nil { 121 | t.Fatalf("error connection to database, %v", err) 122 | } 123 | defer sc.Close() 124 | 125 | c := redis.PubSubConn{Conn: sc} 126 | 127 | c.Subscribe("c1") 128 | expectPushed(t, c, "Subscribe(c1)", redis.Subscription{Kind: "subscribe", Channel: "c1", Count: 1}) 129 | c.Subscribe("c2") 130 | expectPushed(t, c, "Subscribe(c2)", redis.Subscription{Kind: "subscribe", Channel: "c2", Count: 2}) 131 | c.PSubscribe("p1") 132 | expectPushed(t, c, "PSubscribe(p1)", redis.Subscription{Kind: "psubscribe", Channel: "p1", Count: 3}) 133 | c.PSubscribe("p2") 134 | expectPushed(t, c, "PSubscribe(p2)", redis.Subscription{Kind: "psubscribe", Channel: "p2", Count: 4}) 135 | c.PUnsubscribe() 136 | expectPushed(t, c, "Punsubscribe(p1)", redis.Subscription{Kind: "punsubscribe", Channel: "p1", Count: 3}) 137 | expectPushed(t, c, "Punsubscribe()", redis.Subscription{Kind: "punsubscribe", Channel: "p2", Count: 2}) 138 | 139 | pc.Do("PUBLISH", "c1", "hello") 140 | expectPushed(t, c, "PUBLISH c1 hello", redis.Message{Channel: "c1", Data: []byte("hello")}) 141 | 142 | c.Ping("hello") 143 | expectPushed(t, c, `Ping("hello")`, redis.Pong{Data: "hello"}) 144 | 145 | c.Conn.Send("PING") 146 | c.Conn.Flush() 147 | expectPushed(t, c, `Send("PING")`, redis.Pong{}) 148 | } 149 | -------------------------------------------------------------------------------- /redis/test_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // 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, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package redis 16 | 17 | import ( 18 | "bufio" 19 | "errors" 20 | "flag" 21 | "fmt" 22 | "io" 23 | "io/ioutil" 24 | "os" 25 | "os/exec" 26 | "strconv" 27 | "strings" 28 | "sync" 29 | "testing" 30 | "time" 31 | ) 32 | 33 | func SetNowFunc(f func() time.Time) { 34 | nowFunc = f 35 | } 36 | 37 | var ( 38 | ErrNegativeInt = errNegativeInt 39 | 40 | serverPath = flag.String("redis-server", "redis-server", "Path to redis server binary") 41 | serverBasePort = flag.Int("redis-port", 16379, "Beginning of port range for test servers") 42 | serverLogName = flag.String("redis-log", "", "Write Redis server logs to `filename`") 43 | serverLog = ioutil.Discard 44 | 45 | defaultServerMu sync.Mutex 46 | defaultServer *Server 47 | defaultServerErr error 48 | ) 49 | 50 | type Server struct { 51 | name string 52 | cmd *exec.Cmd 53 | done chan struct{} 54 | } 55 | 56 | func NewServer(name string, args ...string) (*Server, error) { 57 | s := &Server{ 58 | name: name, 59 | cmd: exec.Command(*serverPath, args...), 60 | done: make(chan struct{}), 61 | } 62 | 63 | r, err := s.cmd.StdoutPipe() 64 | if err != nil { 65 | return nil, err 66 | } 67 | 68 | err = s.cmd.Start() 69 | if err != nil { 70 | return nil, err 71 | } 72 | 73 | ready := make(chan error, 1) 74 | go s.watch(r, ready) 75 | 76 | select { 77 | case err = <-ready: 78 | case <-time.After(time.Second * 10): 79 | err = errors.New("timeout waiting for server to start") 80 | } 81 | 82 | if err != nil { 83 | s.Stop() 84 | return nil, err 85 | } 86 | 87 | return s, nil 88 | } 89 | 90 | func (s *Server) watch(r io.Reader, ready chan error) { 91 | fmt.Fprintf(serverLog, "%d START %s \n", s.cmd.Process.Pid, s.name) 92 | var listening bool 93 | var text string 94 | scn := bufio.NewScanner(r) 95 | for scn.Scan() { 96 | text = scn.Text() 97 | fmt.Fprintf(serverLog, "%s\n", text) 98 | if !listening { 99 | if strings.Contains(text, "The server is now ready to accept connections on port") { 100 | listening = true 101 | ready <- nil 102 | } 103 | } 104 | } 105 | if !listening { 106 | ready <- fmt.Errorf("server exited: %s", text) 107 | } 108 | s.cmd.Wait() 109 | fmt.Fprintf(serverLog, "%d STOP %s \n", s.cmd.Process.Pid, s.name) 110 | close(s.done) 111 | } 112 | 113 | func (s *Server) Stop() { 114 | s.cmd.Process.Signal(os.Interrupt) 115 | <-s.done 116 | } 117 | 118 | // stopDefaultServer stops the server created by DialDefaultServer. 119 | func stopDefaultServer() { 120 | defaultServerMu.Lock() 121 | defer defaultServerMu.Unlock() 122 | if defaultServer != nil { 123 | defaultServer.Stop() 124 | defaultServer = nil 125 | } 126 | } 127 | 128 | // startDefaultServer starts the default server if not already running. 129 | func startDefaultServer() error { 130 | defaultServerMu.Lock() 131 | defer defaultServerMu.Unlock() 132 | if defaultServer != nil || defaultServerErr != nil { 133 | return defaultServerErr 134 | } 135 | defaultServer, defaultServerErr = NewServer( 136 | "default", 137 | "--port", strconv.Itoa(*serverBasePort), 138 | "--save", "", 139 | "--appendonly", "no") 140 | return defaultServerErr 141 | } 142 | 143 | // DialDefaultServer starts the test server if not already started and dials a 144 | // connection to the server. 145 | func DialDefaultServer() (Conn, error) { 146 | if err := startDefaultServer(); err != nil { 147 | return nil, err 148 | } 149 | c, err := Dial("tcp", fmt.Sprintf(":%d", *serverBasePort), DialReadTimeout(1*time.Second), DialWriteTimeout(1*time.Second)) 150 | if err != nil { 151 | return nil, err 152 | } 153 | c.Do("FLUSHDB") 154 | return c, nil 155 | } 156 | 157 | func TestMain(m *testing.M) { 158 | os.Exit(func() int { 159 | flag.Parse() 160 | 161 | var f *os.File 162 | if *serverLogName != "" { 163 | var err error 164 | f, err = os.OpenFile(*serverLogName, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0600) 165 | if err != nil { 166 | fmt.Fprintf(os.Stderr, "Error opening redis-log: %v\n", err) 167 | return 1 168 | } 169 | defer f.Close() 170 | serverLog = f 171 | } 172 | 173 | defer stopDefaultServer() 174 | 175 | return m.Run() 176 | }()) 177 | } 178 | -------------------------------------------------------------------------------- /redisx/connmux_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // 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, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package redisx_test 16 | 17 | import ( 18 | "net/textproto" 19 | "sync" 20 | "testing" 21 | 22 | "github.com/garyburd/redigo/internal/redistest" 23 | "github.com/garyburd/redigo/redis" 24 | "github.com/garyburd/redigo/redisx" 25 | ) 26 | 27 | func TestConnMux(t *testing.T) { 28 | c, err := redistest.Dial() 29 | if err != nil { 30 | t.Fatalf("error connection to database, %v", err) 31 | } 32 | m := redisx.NewConnMux(c) 33 | defer m.Close() 34 | 35 | c1 := m.Get() 36 | c2 := m.Get() 37 | c1.Send("ECHO", "hello") 38 | c2.Send("ECHO", "world") 39 | c1.Flush() 40 | c2.Flush() 41 | s, err := redis.String(c1.Receive()) 42 | if err != nil { 43 | t.Fatal(err) 44 | } 45 | if s != "hello" { 46 | t.Fatalf("echo returned %q, want %q", s, "hello") 47 | } 48 | s, err = redis.String(c2.Receive()) 49 | if err != nil { 50 | t.Fatal(err) 51 | } 52 | if s != "world" { 53 | t.Fatalf("echo returned %q, want %q", s, "world") 54 | } 55 | c1.Close() 56 | c2.Close() 57 | } 58 | 59 | func TestConnMuxClose(t *testing.T) { 60 | c, err := redistest.Dial() 61 | if err != nil { 62 | t.Fatalf("error connection to database, %v", err) 63 | } 64 | m := redisx.NewConnMux(c) 65 | defer m.Close() 66 | 67 | c1 := m.Get() 68 | c2 := m.Get() 69 | 70 | if err := c1.Send("ECHO", "hello"); err != nil { 71 | t.Fatal(err) 72 | } 73 | if err := c1.Close(); err != nil { 74 | t.Fatal(err) 75 | } 76 | 77 | if err := c2.Send("ECHO", "world"); err != nil { 78 | t.Fatal(err) 79 | } 80 | if err := c2.Flush(); err != nil { 81 | t.Fatal(err) 82 | } 83 | 84 | s, err := redis.String(c2.Receive()) 85 | if err != nil { 86 | t.Fatal(err) 87 | } 88 | if s != "world" { 89 | t.Fatalf("echo returned %q, want %q", s, "world") 90 | } 91 | c2.Close() 92 | } 93 | 94 | func BenchmarkConn(b *testing.B) { 95 | b.StopTimer() 96 | c, err := redistest.Dial() 97 | if err != nil { 98 | b.Fatalf("error connection to database, %v", err) 99 | } 100 | defer c.Close() 101 | b.StartTimer() 102 | 103 | for i := 0; i < b.N; i++ { 104 | if _, err := c.Do("PING"); err != nil { 105 | b.Fatal(err) 106 | } 107 | } 108 | } 109 | 110 | func BenchmarkConnMux(b *testing.B) { 111 | b.StopTimer() 112 | c, err := redistest.Dial() 113 | if err != nil { 114 | b.Fatalf("error connection to database, %v", err) 115 | } 116 | m := redisx.NewConnMux(c) 117 | defer m.Close() 118 | 119 | b.StartTimer() 120 | 121 | for i := 0; i < b.N; i++ { 122 | c := m.Get() 123 | if _, err := c.Do("PING"); err != nil { 124 | b.Fatal(err) 125 | } 126 | c.Close() 127 | } 128 | } 129 | 130 | func BenchmarkPool(b *testing.B) { 131 | b.StopTimer() 132 | 133 | p := redis.Pool{Dial: redistest.Dial, MaxIdle: 1} 134 | defer p.Close() 135 | 136 | // Fill the pool. 137 | c := p.Get() 138 | if err := c.Err(); err != nil { 139 | b.Fatal(err) 140 | } 141 | c.Close() 142 | 143 | b.StartTimer() 144 | 145 | for i := 0; i < b.N; i++ { 146 | c := p.Get() 147 | if _, err := c.Do("PING"); err != nil { 148 | b.Fatal(err) 149 | } 150 | c.Close() 151 | } 152 | } 153 | 154 | const numConcurrent = 10 155 | 156 | func BenchmarkConnMuxConcurrent(b *testing.B) { 157 | b.StopTimer() 158 | c, err := redistest.Dial() 159 | if err != nil { 160 | b.Fatalf("error connection to database, %v", err) 161 | } 162 | defer c.Close() 163 | 164 | m := redisx.NewConnMux(c) 165 | 166 | var wg sync.WaitGroup 167 | wg.Add(numConcurrent) 168 | 169 | b.StartTimer() 170 | 171 | for i := 0; i < numConcurrent; i++ { 172 | go func() { 173 | defer wg.Done() 174 | for i := 0; i < b.N; i++ { 175 | c := m.Get() 176 | if _, err := c.Do("PING"); err != nil { 177 | b.Fatal(err) 178 | } 179 | c.Close() 180 | } 181 | }() 182 | } 183 | wg.Wait() 184 | } 185 | 186 | func BenchmarkPoolConcurrent(b *testing.B) { 187 | b.StopTimer() 188 | 189 | p := redis.Pool{Dial: redistest.Dial, MaxIdle: numConcurrent} 190 | defer p.Close() 191 | 192 | // Fill the pool. 193 | conns := make([]redis.Conn, numConcurrent) 194 | for i := range conns { 195 | c := p.Get() 196 | if err := c.Err(); err != nil { 197 | b.Fatal(err) 198 | } 199 | conns[i] = c 200 | } 201 | for _, c := range conns { 202 | c.Close() 203 | } 204 | 205 | var wg sync.WaitGroup 206 | wg.Add(numConcurrent) 207 | 208 | b.StartTimer() 209 | 210 | for i := 0; i < numConcurrent; i++ { 211 | go func() { 212 | defer wg.Done() 213 | for i := 0; i < b.N; i++ { 214 | c := p.Get() 215 | if _, err := c.Do("PING"); err != nil { 216 | b.Fatal(err) 217 | } 218 | c.Close() 219 | } 220 | }() 221 | } 222 | wg.Wait() 223 | } 224 | 225 | func BenchmarkPipelineConcurrency(b *testing.B) { 226 | b.StopTimer() 227 | c, err := redistest.Dial() 228 | if err != nil { 229 | b.Fatalf("error connection to database, %v", err) 230 | } 231 | defer c.Close() 232 | 233 | var wg sync.WaitGroup 234 | wg.Add(numConcurrent) 235 | 236 | var pipeline textproto.Pipeline 237 | 238 | b.StartTimer() 239 | 240 | for i := 0; i < numConcurrent; i++ { 241 | go func() { 242 | defer wg.Done() 243 | for i := 0; i < b.N; i++ { 244 | id := pipeline.Next() 245 | pipeline.StartRequest(id) 246 | c.Send("PING") 247 | c.Flush() 248 | pipeline.EndRequest(id) 249 | pipeline.StartResponse(id) 250 | _, err := c.Receive() 251 | if err != nil { 252 | b.Fatal(err) 253 | } 254 | pipeline.EndResponse(id) 255 | } 256 | }() 257 | } 258 | wg.Wait() 259 | } 260 | -------------------------------------------------------------------------------- /redis/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // 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, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | // Package redis is a client for the Redis database. 16 | // 17 | // The Redigo FAQ (https://github.com/garyburd/redigo/wiki/FAQ) contains more 18 | // documentation about this package. 19 | // 20 | // Connections 21 | // 22 | // The Conn interface is the primary interface for working with Redis. 23 | // Applications create connections by calling the Dial, DialWithTimeout or 24 | // NewConn functions. In the future, functions will be added for creating 25 | // sharded and other types of connections. 26 | // 27 | // The application must call the connection Close method when the application 28 | // is done with the connection. 29 | // 30 | // Executing Commands 31 | // 32 | // The Conn interface has a generic method for executing Redis commands: 33 | // 34 | // Do(commandName string, args ...interface{}) (reply interface{}, err error) 35 | // 36 | // The Redis command reference (http://redis.io/commands) lists the available 37 | // commands. An example of using the Redis APPEND command is: 38 | // 39 | // n, err := conn.Do("APPEND", "key", "value") 40 | // 41 | // The Do method converts command arguments to binary strings for transmission 42 | // to the server as follows: 43 | // 44 | // Go Type Conversion 45 | // []byte Sent as is 46 | // string Sent as is 47 | // int, int64 strconv.FormatInt(v) 48 | // float64 strconv.FormatFloat(v, 'g', -1, 64) 49 | // bool true -> "1", false -> "0" 50 | // nil "" 51 | // all other types fmt.Print(v) 52 | // 53 | // Redis command reply types are represented using the following Go types: 54 | // 55 | // Redis type Go type 56 | // error redis.Error 57 | // integer int64 58 | // simple string string 59 | // bulk string []byte or nil if value not present. 60 | // array []interface{} or nil if value not present. 61 | // 62 | // Use type assertions or the reply helper functions to convert from 63 | // interface{} to the specific Go type for the command result. 64 | // 65 | // Pipelining 66 | // 67 | // Connections support pipelining using the Send, Flush and Receive methods. 68 | // 69 | // Send(commandName string, args ...interface{}) error 70 | // Flush() error 71 | // Receive() (reply interface{}, err error) 72 | // 73 | // Send writes the command to the connection's output buffer. Flush flushes the 74 | // connection's output buffer to the server. Receive reads a single reply from 75 | // the server. The following example shows a simple pipeline. 76 | // 77 | // c.Send("SET", "foo", "bar") 78 | // c.Send("GET", "foo") 79 | // c.Flush() 80 | // c.Receive() // reply from SET 81 | // v, err = c.Receive() // reply from GET 82 | // 83 | // The Do method combines the functionality of the Send, Flush and Receive 84 | // methods. The Do method starts by writing the command and flushing the output 85 | // buffer. Next, the Do method receives all pending replies including the reply 86 | // for the command just sent by Do. If any of the received replies is an error, 87 | // then Do returns the error. If there are no errors, then Do returns the last 88 | // reply. If the command argument to the Do method is "", then the Do method 89 | // will flush the output buffer and receive pending replies without sending a 90 | // command. 91 | // 92 | // Use the Send and Do methods to implement pipelined transactions. 93 | // 94 | // c.Send("MULTI") 95 | // c.Send("INCR", "foo") 96 | // c.Send("INCR", "bar") 97 | // r, err := c.Do("EXEC") 98 | // fmt.Println(r) // prints [1, 1] 99 | // 100 | // Concurrency 101 | // 102 | // Connections support one concurrent caller to the Receive method and one 103 | // concurrent caller to the Send and Flush methods. No other concurrency is 104 | // supported including concurrent calls to the Do method. 105 | // 106 | // For full concurrent access to Redis, use the thread-safe Pool to get, use 107 | // and release a connection from within a goroutine. Connections returned from 108 | // a Pool have the concurrency restrictions described in the previous 109 | // paragraph. 110 | // 111 | // Publish and Subscribe 112 | // 113 | // Use the Send, Flush and Receive methods to implement Pub/Sub subscribers. 114 | // 115 | // c.Send("SUBSCRIBE", "example") 116 | // c.Flush() 117 | // for { 118 | // reply, err := c.Receive() 119 | // if err != nil { 120 | // return err 121 | // } 122 | // // process pushed message 123 | // } 124 | // 125 | // The PubSubConn type wraps a Conn with convenience methods for implementing 126 | // subscribers. The Subscribe, PSubscribe, Unsubscribe and PUnsubscribe methods 127 | // send and flush a subscription management command. The receive method 128 | // converts a pushed message to convenient types for use in a type switch. 129 | // 130 | // psc := redis.PubSubConn{c} 131 | // psc.Subscribe("example") 132 | // for { 133 | // switch v := psc.Receive().(type) { 134 | // case redis.Message: 135 | // fmt.Printf("%s: message: %s\n", v.Channel, v.Data) 136 | // case redis.Subscription: 137 | // fmt.Printf("%s: %s %d\n", v.Channel, v.Kind, v.Count) 138 | // case error: 139 | // return v 140 | // } 141 | // } 142 | // 143 | // Reply Helpers 144 | // 145 | // The Bool, Int, Bytes, String, Strings and Values functions convert a reply 146 | // to a value of a specific type. To allow convenient wrapping of calls to the 147 | // connection Do and Receive methods, the functions take a second argument of 148 | // type error. If the error is non-nil, then the helper function returns the 149 | // error. If the error is nil, the function converts the reply to the specified 150 | // type: 151 | // 152 | // exists, err := redis.Bool(c.Do("EXISTS", "foo")) 153 | // if err != nil { 154 | // // handle error return from c.Do or type conversion error. 155 | // } 156 | // 157 | // The Scan function converts elements of a array reply to Go types: 158 | // 159 | // var value1 int 160 | // var value2 string 161 | // reply, err := redis.Values(c.Do("MGET", "key1", "key2")) 162 | // if err != nil { 163 | // // handle error 164 | // } 165 | // if _, err := redis.Scan(reply, &value1, &value2); err != nil { 166 | // // handle error 167 | // } 168 | package redis // import "github.com/garyburd/redigo/redis" 169 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /redis/pool.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // 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, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package redis 16 | 17 | import ( 18 | "bytes" 19 | "container/list" 20 | "crypto/rand" 21 | "crypto/sha1" 22 | "errors" 23 | "io" 24 | "strconv" 25 | "sync" 26 | "time" 27 | 28 | "github.com/garyburd/redigo/internal" 29 | ) 30 | 31 | var nowFunc = time.Now // for testing 32 | 33 | // ErrPoolExhausted is returned from a pool connection method (Do, Send, 34 | // Receive, Flush, Err) when the maximum number of database connections in the 35 | // pool has been reached. 36 | var ErrPoolExhausted = errors.New("redigo: connection pool exhausted") 37 | 38 | var ( 39 | errPoolClosed = errors.New("redigo: connection pool closed") 40 | errConnClosed = errors.New("redigo: connection closed") 41 | ) 42 | 43 | // Pool maintains a pool of connections. The application calls the Get method 44 | // to get a connection from the pool and the connection's Close method to 45 | // return the connection's resources to the pool. 46 | // 47 | // The following example shows how to use a pool in a web application. The 48 | // application creates a pool at application startup and makes it available to 49 | // request handlers using a global variable. The pool configuration used here 50 | // is an example, not a recommendation. 51 | // 52 | // func newPool(server, password string) *redis.Pool { 53 | // return &redis.Pool{ 54 | // MaxIdle: 3, 55 | // IdleTimeout: 240 * time.Second, 56 | // Dial: func () (redis.Conn, error) { 57 | // c, err := redis.Dial("tcp", server) 58 | // if err != nil { 59 | // return nil, err 60 | // } 61 | // if _, err := c.Do("AUTH", password); err != nil { 62 | // c.Close() 63 | // return nil, err 64 | // } 65 | // return c, err 66 | // }, 67 | // TestOnBorrow: func(c redis.Conn, t time.Time) error { 68 | // if time.Since(t) < time.Minute { 69 | // return nil 70 | // } 71 | // _, err := c.Do("PING") 72 | // return err 73 | // }, 74 | // } 75 | // } 76 | // 77 | // var ( 78 | // pool *redis.Pool 79 | // redisServer = flag.String("redisServer", ":6379", "") 80 | // redisPassword = flag.String("redisPassword", "", "") 81 | // ) 82 | // 83 | // func main() { 84 | // flag.Parse() 85 | // pool = newPool(*redisServer, *redisPassword) 86 | // ... 87 | // } 88 | // 89 | // A request handler gets a connection from the pool and closes the connection 90 | // when the handler is done: 91 | // 92 | // func serveHome(w http.ResponseWriter, r *http.Request) { 93 | // conn := pool.Get() 94 | // defer conn.Close() 95 | // .... 96 | // } 97 | // 98 | type Pool struct { 99 | 100 | // Dial is an application supplied function for creating and configuring a 101 | // connection. 102 | // 103 | // The connection returned from Dial must not be in a special state 104 | // (subscribed to pubsub channel, transaction started, ...). 105 | Dial func() (Conn, error) 106 | 107 | // TestOnBorrow is an optional application supplied function for checking 108 | // the health of an idle connection before the connection is used again by 109 | // the application. Argument t is the time that the connection was returned 110 | // to the pool. If the function returns an error, then the connection is 111 | // closed. 112 | TestOnBorrow func(c Conn, t time.Time) error 113 | 114 | // Maximum number of idle connections in the pool. 115 | MaxIdle int 116 | 117 | // Maximum number of connections allocated by the pool at a given time. 118 | // When zero, there is no limit on the number of connections in the pool. 119 | MaxActive int 120 | 121 | // Close connections after remaining idle for this duration. If the value 122 | // is zero, then idle connections are not closed. Applications should set 123 | // the timeout to a value less than the server's timeout. 124 | IdleTimeout time.Duration 125 | 126 | // If Wait is true and the pool is at the MaxActive limit, then Get() waits 127 | // for a connection to be returned to the pool before returning. 128 | Wait bool 129 | 130 | // mu protects fields defined below. 131 | mu sync.Mutex 132 | cond *sync.Cond 133 | closed bool 134 | active int 135 | 136 | // Stack of idleConn with most recently used at the front. 137 | idle list.List 138 | } 139 | 140 | type idleConn struct { 141 | c Conn 142 | t time.Time 143 | } 144 | 145 | // NewPool creates a new pool. 146 | // 147 | // Deprecated: Initialize the Pool directory as shown in the example. 148 | func NewPool(newFn func() (Conn, error), maxIdle int) *Pool { 149 | return &Pool{Dial: newFn, MaxIdle: maxIdle} 150 | } 151 | 152 | // Get gets a connection. The application must close the returned connection. 153 | // This method always returns a valid connection so that applications can defer 154 | // error handling to the first use of the connection. If there is an error 155 | // getting an underlying connection, then the connection Err, Do, Send, Flush 156 | // and Receive methods return that error. 157 | func (p *Pool) Get() Conn { 158 | c, err := p.get() 159 | if err != nil { 160 | return errorConnection{err} 161 | } 162 | return &pooledConnection{p: p, c: c} 163 | } 164 | 165 | // ActiveCount returns the number of active connections in the pool. 166 | func (p *Pool) ActiveCount() int { 167 | p.mu.Lock() 168 | active := p.active 169 | p.mu.Unlock() 170 | return active 171 | } 172 | 173 | // Close releases the resources used by the pool. 174 | func (p *Pool) Close() error { 175 | p.mu.Lock() 176 | idle := p.idle 177 | p.idle.Init() 178 | p.closed = true 179 | p.active -= idle.Len() 180 | if p.cond != nil { 181 | p.cond.Broadcast() 182 | } 183 | p.mu.Unlock() 184 | for e := idle.Front(); e != nil; e = e.Next() { 185 | e.Value.(idleConn).c.Close() 186 | } 187 | return nil 188 | } 189 | 190 | // release decrements the active count and signals waiters. The caller must 191 | // hold p.mu during the call. 192 | func (p *Pool) release() { 193 | p.active -= 1 194 | if p.cond != nil { 195 | p.cond.Signal() 196 | } 197 | } 198 | 199 | // get prunes stale connections and returns a connection from the idle list or 200 | // creates a new connection. 201 | func (p *Pool) get() (Conn, error) { 202 | p.mu.Lock() 203 | 204 | // Prune stale connections. 205 | 206 | if timeout := p.IdleTimeout; timeout > 0 { 207 | for i, n := 0, p.idle.Len(); i < n; i++ { 208 | e := p.idle.Back() 209 | if e == nil { 210 | break 211 | } 212 | ic := e.Value.(idleConn) 213 | if ic.t.Add(timeout).After(nowFunc()) { 214 | break 215 | } 216 | p.idle.Remove(e) 217 | p.release() 218 | p.mu.Unlock() 219 | ic.c.Close() 220 | p.mu.Lock() 221 | } 222 | } 223 | 224 | for { 225 | 226 | // Get idle connection. 227 | 228 | for i, n := 0, p.idle.Len(); i < n; i++ { 229 | e := p.idle.Front() 230 | if e == nil { 231 | break 232 | } 233 | ic := e.Value.(idleConn) 234 | p.idle.Remove(e) 235 | test := p.TestOnBorrow 236 | p.mu.Unlock() 237 | if test == nil || test(ic.c, ic.t) == nil { 238 | return ic.c, nil 239 | } 240 | ic.c.Close() 241 | p.mu.Lock() 242 | p.release() 243 | } 244 | 245 | // Check for pool closed before dialing a new connection. 246 | 247 | if p.closed { 248 | p.mu.Unlock() 249 | return nil, errors.New("redigo: get on closed pool") 250 | } 251 | 252 | // Dial new connection if under limit. 253 | 254 | if p.MaxActive == 0 || p.active < p.MaxActive { 255 | dial := p.Dial 256 | p.active += 1 257 | p.mu.Unlock() 258 | c, err := dial() 259 | if err != nil { 260 | p.mu.Lock() 261 | p.release() 262 | p.mu.Unlock() 263 | c = nil 264 | } 265 | return c, err 266 | } 267 | 268 | if !p.Wait { 269 | p.mu.Unlock() 270 | return nil, ErrPoolExhausted 271 | } 272 | 273 | if p.cond == nil { 274 | p.cond = sync.NewCond(&p.mu) 275 | } 276 | p.cond.Wait() 277 | } 278 | } 279 | 280 | func (p *Pool) put(c Conn, forceClose bool) error { 281 | err := c.Err() 282 | p.mu.Lock() 283 | if !p.closed && err == nil && !forceClose { 284 | p.idle.PushFront(idleConn{t: nowFunc(), c: c}) 285 | if p.idle.Len() > p.MaxIdle { 286 | c = p.idle.Remove(p.idle.Back()).(idleConn).c 287 | } else { 288 | c = nil 289 | } 290 | } 291 | 292 | if c == nil { 293 | if p.cond != nil { 294 | p.cond.Signal() 295 | } 296 | p.mu.Unlock() 297 | return nil 298 | } 299 | 300 | p.release() 301 | p.mu.Unlock() 302 | return c.Close() 303 | } 304 | 305 | type pooledConnection struct { 306 | p *Pool 307 | c Conn 308 | state int 309 | } 310 | 311 | var ( 312 | sentinel []byte 313 | sentinelOnce sync.Once 314 | ) 315 | 316 | func initSentinel() { 317 | p := make([]byte, 64) 318 | if _, err := rand.Read(p); err == nil { 319 | sentinel = p 320 | } else { 321 | h := sha1.New() 322 | io.WriteString(h, "Oops, rand failed. Use time instead.") 323 | io.WriteString(h, strconv.FormatInt(time.Now().UnixNano(), 10)) 324 | sentinel = h.Sum(nil) 325 | } 326 | } 327 | 328 | func (pc *pooledConnection) Close() error { 329 | c := pc.c 330 | if _, ok := c.(errorConnection); ok { 331 | return nil 332 | } 333 | pc.c = errorConnection{errConnClosed} 334 | 335 | if pc.state&internal.MultiState != 0 { 336 | c.Send("DISCARD") 337 | pc.state &^= (internal.MultiState | internal.WatchState) 338 | } else if pc.state&internal.WatchState != 0 { 339 | c.Send("UNWATCH") 340 | pc.state &^= internal.WatchState 341 | } 342 | if pc.state&internal.SubscribeState != 0 { 343 | c.Send("UNSUBSCRIBE") 344 | c.Send("PUNSUBSCRIBE") 345 | // To detect the end of the message stream, ask the server to echo 346 | // a sentinel value and read until we see that value. 347 | sentinelOnce.Do(initSentinel) 348 | c.Send("ECHO", sentinel) 349 | c.Flush() 350 | for { 351 | p, err := c.Receive() 352 | if err != nil { 353 | break 354 | } 355 | if p, ok := p.([]byte); ok && bytes.Equal(p, sentinel) { 356 | pc.state &^= internal.SubscribeState 357 | break 358 | } 359 | } 360 | } 361 | c.Do("") 362 | pc.p.put(c, pc.state != 0) 363 | return nil 364 | } 365 | 366 | func (pc *pooledConnection) Err() error { 367 | return pc.c.Err() 368 | } 369 | 370 | func (pc *pooledConnection) Do(commandName string, args ...interface{}) (reply interface{}, err error) { 371 | ci := internal.LookupCommandInfo(commandName) 372 | pc.state = (pc.state | ci.Set) &^ ci.Clear 373 | return pc.c.Do(commandName, args...) 374 | } 375 | 376 | func (pc *pooledConnection) Send(commandName string, args ...interface{}) error { 377 | ci := internal.LookupCommandInfo(commandName) 378 | pc.state = (pc.state | ci.Set) &^ ci.Clear 379 | return pc.c.Send(commandName, args...) 380 | } 381 | 382 | func (pc *pooledConnection) Flush() error { 383 | return pc.c.Flush() 384 | } 385 | 386 | func (pc *pooledConnection) Receive() (reply interface{}, err error) { 387 | return pc.c.Receive() 388 | } 389 | 390 | type errorConnection struct{ err error } 391 | 392 | func (ec errorConnection) Do(string, ...interface{}) (interface{}, error) { return nil, ec.err } 393 | func (ec errorConnection) Send(string, ...interface{}) error { return ec.err } 394 | func (ec errorConnection) Err() error { return ec.err } 395 | func (ec errorConnection) Close() error { return ec.err } 396 | func (ec errorConnection) Flush() error { return ec.err } 397 | func (ec errorConnection) Receive() (interface{}, error) { return nil, ec.err } 398 | -------------------------------------------------------------------------------- /redis/scan_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // 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, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package redis_test 16 | 17 | import ( 18 | "fmt" 19 | "math" 20 | "reflect" 21 | "testing" 22 | 23 | "github.com/garyburd/redigo/redis" 24 | ) 25 | 26 | var scanConversionTests = []struct { 27 | src interface{} 28 | dest interface{} 29 | }{ 30 | {[]byte("-inf"), math.Inf(-1)}, 31 | {[]byte("+inf"), math.Inf(1)}, 32 | {[]byte("0"), float64(0)}, 33 | {[]byte("3.14159"), float64(3.14159)}, 34 | {[]byte("3.14"), float32(3.14)}, 35 | {[]byte("-100"), int(-100)}, 36 | {[]byte("101"), int(101)}, 37 | {int64(102), int(102)}, 38 | {[]byte("103"), uint(103)}, 39 | {int64(104), uint(104)}, 40 | {[]byte("105"), int8(105)}, 41 | {int64(106), int8(106)}, 42 | {[]byte("107"), uint8(107)}, 43 | {int64(108), uint8(108)}, 44 | {[]byte("0"), false}, 45 | {int64(0), false}, 46 | {[]byte("f"), false}, 47 | {[]byte("1"), true}, 48 | {int64(1), true}, 49 | {[]byte("t"), true}, 50 | {"hello", "hello"}, 51 | {[]byte("hello"), "hello"}, 52 | {[]byte("world"), []byte("world")}, 53 | {[]interface{}{[]byte("foo")}, []interface{}{[]byte("foo")}}, 54 | {[]interface{}{[]byte("foo")}, []string{"foo"}}, 55 | {[]interface{}{[]byte("hello"), []byte("world")}, []string{"hello", "world"}}, 56 | {[]interface{}{[]byte("bar")}, [][]byte{[]byte("bar")}}, 57 | {[]interface{}{[]byte("1")}, []int{1}}, 58 | {[]interface{}{[]byte("1"), []byte("2")}, []int{1, 2}}, 59 | {[]interface{}{[]byte("1"), []byte("2")}, []float64{1, 2}}, 60 | {[]interface{}{[]byte("1")}, []byte{1}}, 61 | {[]interface{}{[]byte("1")}, []bool{true}}, 62 | } 63 | 64 | func TestScanConversion(t *testing.T) { 65 | for _, tt := range scanConversionTests { 66 | values := []interface{}{tt.src} 67 | dest := reflect.New(reflect.TypeOf(tt.dest)) 68 | values, err := redis.Scan(values, dest.Interface()) 69 | if err != nil { 70 | t.Errorf("Scan(%v) returned error %v", tt, err) 71 | continue 72 | } 73 | if !reflect.DeepEqual(tt.dest, dest.Elem().Interface()) { 74 | t.Errorf("Scan(%v) returned %v, want %v", tt, dest.Elem().Interface(), tt.dest) 75 | } 76 | } 77 | } 78 | 79 | var scanConversionErrorTests = []struct { 80 | src interface{} 81 | dest interface{} 82 | }{ 83 | {[]byte("1234"), byte(0)}, 84 | {int64(1234), byte(0)}, 85 | {[]byte("-1"), byte(0)}, 86 | {int64(-1), byte(0)}, 87 | {[]byte("junk"), false}, 88 | {redis.Error("blah"), false}, 89 | } 90 | 91 | func TestScanConversionError(t *testing.T) { 92 | for _, tt := range scanConversionErrorTests { 93 | values := []interface{}{tt.src} 94 | dest := reflect.New(reflect.TypeOf(tt.dest)) 95 | values, err := redis.Scan(values, dest.Interface()) 96 | if err == nil { 97 | t.Errorf("Scan(%v) did not return error", tt) 98 | } 99 | } 100 | } 101 | 102 | func ExampleScan() { 103 | c, err := dial() 104 | if err != nil { 105 | fmt.Println(err) 106 | return 107 | } 108 | defer c.Close() 109 | 110 | c.Send("HMSET", "album:1", "title", "Red", "rating", 5) 111 | c.Send("HMSET", "album:2", "title", "Earthbound", "rating", 1) 112 | c.Send("HMSET", "album:3", "title", "Beat") 113 | c.Send("LPUSH", "albums", "1") 114 | c.Send("LPUSH", "albums", "2") 115 | c.Send("LPUSH", "albums", "3") 116 | values, err := redis.Values(c.Do("SORT", "albums", 117 | "BY", "album:*->rating", 118 | "GET", "album:*->title", 119 | "GET", "album:*->rating")) 120 | if err != nil { 121 | fmt.Println(err) 122 | return 123 | } 124 | 125 | for len(values) > 0 { 126 | var title string 127 | rating := -1 // initialize to illegal value to detect nil. 128 | values, err = redis.Scan(values, &title, &rating) 129 | if err != nil { 130 | fmt.Println(err) 131 | return 132 | } 133 | if rating == -1 { 134 | fmt.Println(title, "not-rated") 135 | } else { 136 | fmt.Println(title, rating) 137 | } 138 | } 139 | // Output: 140 | // Beat not-rated 141 | // Earthbound 1 142 | // Red 5 143 | } 144 | 145 | type s0 struct { 146 | X int 147 | Y int `redis:"y"` 148 | Bt bool 149 | } 150 | 151 | type s1 struct { 152 | X int `redis:"-"` 153 | I int `redis:"i"` 154 | U uint `redis:"u"` 155 | S string `redis:"s"` 156 | P []byte `redis:"p"` 157 | B bool `redis:"b"` 158 | Bt bool 159 | Bf bool 160 | s0 161 | } 162 | 163 | var scanStructTests = []struct { 164 | title string 165 | reply []string 166 | value interface{} 167 | }{ 168 | {"basic", 169 | []string{"i", "-1234", "u", "5678", "s", "hello", "p", "world", "b", "t", "Bt", "1", "Bf", "0", "X", "123", "y", "456"}, 170 | &s1{I: -1234, U: 5678, S: "hello", P: []byte("world"), B: true, Bt: true, Bf: false, s0: s0{X: 123, Y: 456}}, 171 | }, 172 | } 173 | 174 | func TestScanStruct(t *testing.T) { 175 | for _, tt := range scanStructTests { 176 | 177 | var reply []interface{} 178 | for _, v := range tt.reply { 179 | reply = append(reply, []byte(v)) 180 | } 181 | 182 | value := reflect.New(reflect.ValueOf(tt.value).Type().Elem()) 183 | 184 | if err := redis.ScanStruct(reply, value.Interface()); err != nil { 185 | t.Fatalf("ScanStruct(%s) returned error %v", tt.title, err) 186 | } 187 | 188 | if !reflect.DeepEqual(value.Interface(), tt.value) { 189 | t.Fatalf("ScanStruct(%s) returned %v, want %v", tt.title, value.Interface(), tt.value) 190 | } 191 | } 192 | } 193 | 194 | func TestBadScanStructArgs(t *testing.T) { 195 | x := []interface{}{"A", "b"} 196 | test := func(v interface{}) { 197 | if err := redis.ScanStruct(x, v); err == nil { 198 | t.Errorf("Expect error for ScanStruct(%T, %T)", x, v) 199 | } 200 | } 201 | 202 | test(nil) 203 | 204 | var v0 *struct{} 205 | test(v0) 206 | 207 | var v1 int 208 | test(&v1) 209 | 210 | x = x[:1] 211 | v2 := struct{ A string }{} 212 | test(&v2) 213 | } 214 | 215 | var scanSliceTests = []struct { 216 | src []interface{} 217 | fieldNames []string 218 | ok bool 219 | dest interface{} 220 | }{ 221 | { 222 | []interface{}{[]byte("1"), nil, []byte("-1")}, 223 | nil, 224 | true, 225 | []int{1, 0, -1}, 226 | }, 227 | { 228 | []interface{}{[]byte("1"), nil, []byte("2")}, 229 | nil, 230 | true, 231 | []uint{1, 0, 2}, 232 | }, 233 | { 234 | []interface{}{[]byte("-1")}, 235 | nil, 236 | false, 237 | []uint{1}, 238 | }, 239 | { 240 | []interface{}{[]byte("hello"), nil, []byte("world")}, 241 | nil, 242 | true, 243 | [][]byte{[]byte("hello"), nil, []byte("world")}, 244 | }, 245 | { 246 | []interface{}{[]byte("hello"), nil, []byte("world")}, 247 | nil, 248 | true, 249 | []string{"hello", "", "world"}, 250 | }, 251 | { 252 | []interface{}{[]byte("a1"), []byte("b1"), []byte("a2"), []byte("b2")}, 253 | nil, 254 | true, 255 | []struct{ A, B string }{{"a1", "b1"}, {"a2", "b2"}}, 256 | }, 257 | { 258 | []interface{}{[]byte("a1"), []byte("b1")}, 259 | nil, 260 | false, 261 | []struct{ A, B, C string }{{"a1", "b1", ""}}, 262 | }, 263 | { 264 | []interface{}{[]byte("a1"), []byte("b1"), []byte("a2"), []byte("b2")}, 265 | nil, 266 | true, 267 | []*struct{ A, B string }{{"a1", "b1"}, {"a2", "b2"}}, 268 | }, 269 | { 270 | []interface{}{[]byte("a1"), []byte("b1"), []byte("a2"), []byte("b2")}, 271 | []string{"A", "B"}, 272 | true, 273 | []struct{ A, C, B string }{{"a1", "", "b1"}, {"a2", "", "b2"}}, 274 | }, 275 | { 276 | []interface{}{[]byte("a1"), []byte("b1"), []byte("a2"), []byte("b2")}, 277 | nil, 278 | false, 279 | []struct{}{}, 280 | }, 281 | } 282 | 283 | func TestScanSlice(t *testing.T) { 284 | for _, tt := range scanSliceTests { 285 | 286 | typ := reflect.ValueOf(tt.dest).Type() 287 | dest := reflect.New(typ) 288 | 289 | err := redis.ScanSlice(tt.src, dest.Interface(), tt.fieldNames...) 290 | if tt.ok != (err == nil) { 291 | t.Errorf("ScanSlice(%v, []%s, %v) returned error %v", tt.src, typ, tt.fieldNames, err) 292 | continue 293 | } 294 | if tt.ok && !reflect.DeepEqual(dest.Elem().Interface(), tt.dest) { 295 | t.Errorf("ScanSlice(src, []%s) returned %#v, want %#v", typ, dest.Elem().Interface(), tt.dest) 296 | } 297 | } 298 | } 299 | 300 | func ExampleScanSlice() { 301 | c, err := dial() 302 | if err != nil { 303 | fmt.Println(err) 304 | return 305 | } 306 | defer c.Close() 307 | 308 | c.Send("HMSET", "album:1", "title", "Red", "rating", 5) 309 | c.Send("HMSET", "album:2", "title", "Earthbound", "rating", 1) 310 | c.Send("HMSET", "album:3", "title", "Beat", "rating", 4) 311 | c.Send("LPUSH", "albums", "1") 312 | c.Send("LPUSH", "albums", "2") 313 | c.Send("LPUSH", "albums", "3") 314 | values, err := redis.Values(c.Do("SORT", "albums", 315 | "BY", "album:*->rating", 316 | "GET", "album:*->title", 317 | "GET", "album:*->rating")) 318 | if err != nil { 319 | fmt.Println(err) 320 | return 321 | } 322 | 323 | var albums []struct { 324 | Title string 325 | Rating int 326 | } 327 | if err := redis.ScanSlice(values, &albums); err != nil { 328 | fmt.Println(err) 329 | return 330 | } 331 | fmt.Printf("%v\n", albums) 332 | // Output: 333 | // [{Earthbound 1} {Beat 4} {Red 5}] 334 | } 335 | 336 | var argsTests = []struct { 337 | title string 338 | actual redis.Args 339 | expected redis.Args 340 | }{ 341 | {"struct ptr", 342 | redis.Args{}.AddFlat(&struct { 343 | I int `redis:"i"` 344 | U uint `redis:"u"` 345 | S string `redis:"s"` 346 | P []byte `redis:"p"` 347 | M map[string]string `redis:"m"` 348 | Bt bool 349 | Bf bool 350 | }{ 351 | -1234, 5678, "hello", []byte("world"), map[string]string{"hello": "world"}, true, false, 352 | }), 353 | redis.Args{"i", int(-1234), "u", uint(5678), "s", "hello", "p", []byte("world"), "m", map[string]string{"hello": "world"}, "Bt", true, "Bf", false}, 354 | }, 355 | {"struct", 356 | redis.Args{}.AddFlat(struct{ I int }{123}), 357 | redis.Args{"I", 123}, 358 | }, 359 | {"slice", 360 | redis.Args{}.Add(1).AddFlat([]string{"a", "b", "c"}).Add(2), 361 | redis.Args{1, "a", "b", "c", 2}, 362 | }, 363 | {"struct omitempty", 364 | redis.Args{}.AddFlat(&struct { 365 | I int `redis:"i,omitempty"` 366 | U uint `redis:"u,omitempty"` 367 | S string `redis:"s,omitempty"` 368 | P []byte `redis:"p,omitempty"` 369 | M map[string]string `redis:"m,omitempty"` 370 | Bt bool `redis:"Bt,omitempty"` 371 | Bf bool `redis:"Bf,omitempty"` 372 | }{ 373 | 0, 0, "", []byte{}, map[string]string{}, true, false, 374 | }), 375 | redis.Args{"Bt", true}, 376 | }, 377 | } 378 | 379 | func TestArgs(t *testing.T) { 380 | for _, tt := range argsTests { 381 | if !reflect.DeepEqual(tt.actual, tt.expected) { 382 | t.Fatalf("%s is %v, want %v", tt.title, tt.actual, tt.expected) 383 | } 384 | } 385 | } 386 | 387 | func ExampleArgs() { 388 | c, err := dial() 389 | if err != nil { 390 | fmt.Println(err) 391 | return 392 | } 393 | defer c.Close() 394 | 395 | var p1, p2 struct { 396 | Title string `redis:"title"` 397 | Author string `redis:"author"` 398 | Body string `redis:"body"` 399 | } 400 | 401 | p1.Title = "Example" 402 | p1.Author = "Gary" 403 | p1.Body = "Hello" 404 | 405 | if _, err := c.Do("HMSET", redis.Args{}.Add("id1").AddFlat(&p1)...); err != nil { 406 | fmt.Println(err) 407 | return 408 | } 409 | 410 | m := map[string]string{ 411 | "title": "Example2", 412 | "author": "Steve", 413 | "body": "Map", 414 | } 415 | 416 | if _, err := c.Do("HMSET", redis.Args{}.Add("id2").AddFlat(m)...); err != nil { 417 | fmt.Println(err) 418 | return 419 | } 420 | 421 | for _, id := range []string{"id1", "id2"} { 422 | 423 | v, err := redis.Values(c.Do("HGETALL", id)) 424 | if err != nil { 425 | fmt.Println(err) 426 | return 427 | } 428 | 429 | if err := redis.ScanStruct(v, &p2); err != nil { 430 | fmt.Println(err) 431 | return 432 | } 433 | 434 | fmt.Printf("%+v\n", p2) 435 | } 436 | 437 | // Output: 438 | // {Title:Example Author:Gary Body:Hello} 439 | // {Title:Example2 Author:Steve Body:Map} 440 | } 441 | -------------------------------------------------------------------------------- /redis/reply.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // 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, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package redis 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | "strconv" 21 | ) 22 | 23 | // ErrNil indicates that a reply value is nil. 24 | var ErrNil = errors.New("redigo: nil returned") 25 | 26 | // Int is a helper that converts a command reply to an integer. If err is not 27 | // equal to nil, then Int returns 0, err. Otherwise, Int converts the 28 | // reply to an int as follows: 29 | // 30 | // Reply type Result 31 | // integer int(reply), nil 32 | // bulk string parsed reply, nil 33 | // nil 0, ErrNil 34 | // other 0, error 35 | func Int(reply interface{}, err error) (int, error) { 36 | if err != nil { 37 | return 0, err 38 | } 39 | switch reply := reply.(type) { 40 | case int64: 41 | x := int(reply) 42 | if int64(x) != reply { 43 | return 0, strconv.ErrRange 44 | } 45 | return x, nil 46 | case []byte: 47 | n, err := strconv.ParseInt(string(reply), 10, 0) 48 | return int(n), err 49 | case nil: 50 | return 0, ErrNil 51 | case Error: 52 | return 0, reply 53 | } 54 | return 0, fmt.Errorf("redigo: unexpected type for Int, got type %T", reply) 55 | } 56 | 57 | // Int64 is a helper that converts a command reply to 64 bit integer. If err is 58 | // not equal to nil, then Int returns 0, err. Otherwise, Int64 converts the 59 | // reply to an int64 as follows: 60 | // 61 | // Reply type Result 62 | // integer reply, nil 63 | // bulk string parsed reply, nil 64 | // nil 0, ErrNil 65 | // other 0, error 66 | func Int64(reply interface{}, err error) (int64, error) { 67 | if err != nil { 68 | return 0, err 69 | } 70 | switch reply := reply.(type) { 71 | case int64: 72 | return reply, nil 73 | case []byte: 74 | n, err := strconv.ParseInt(string(reply), 10, 64) 75 | return n, err 76 | case nil: 77 | return 0, ErrNil 78 | case Error: 79 | return 0, reply 80 | } 81 | return 0, fmt.Errorf("redigo: unexpected type for Int64, got type %T", reply) 82 | } 83 | 84 | var errNegativeInt = errors.New("redigo: unexpected value for Uint64") 85 | 86 | // Uint64 is a helper that converts a command reply to 64 bit integer. If err is 87 | // not equal to nil, then Int returns 0, err. Otherwise, Int64 converts the 88 | // reply to an int64 as follows: 89 | // 90 | // Reply type Result 91 | // integer reply, nil 92 | // bulk string parsed reply, nil 93 | // nil 0, ErrNil 94 | // other 0, error 95 | func Uint64(reply interface{}, err error) (uint64, error) { 96 | if err != nil { 97 | return 0, err 98 | } 99 | switch reply := reply.(type) { 100 | case int64: 101 | if reply < 0 { 102 | return 0, errNegativeInt 103 | } 104 | return uint64(reply), nil 105 | case []byte: 106 | n, err := strconv.ParseUint(string(reply), 10, 64) 107 | return n, err 108 | case nil: 109 | return 0, ErrNil 110 | case Error: 111 | return 0, reply 112 | } 113 | return 0, fmt.Errorf("redigo: unexpected type for Uint64, got type %T", reply) 114 | } 115 | 116 | // Float64 is a helper that converts a command reply to 64 bit float. If err is 117 | // not equal to nil, then Float64 returns 0, err. Otherwise, Float64 converts 118 | // the reply to an int as follows: 119 | // 120 | // Reply type Result 121 | // bulk string parsed reply, nil 122 | // nil 0, ErrNil 123 | // other 0, error 124 | func Float64(reply interface{}, err error) (float64, error) { 125 | if err != nil { 126 | return 0, err 127 | } 128 | switch reply := reply.(type) { 129 | case []byte: 130 | n, err := strconv.ParseFloat(string(reply), 64) 131 | return n, err 132 | case nil: 133 | return 0, ErrNil 134 | case Error: 135 | return 0, reply 136 | } 137 | return 0, fmt.Errorf("redigo: unexpected type for Float64, got type %T", reply) 138 | } 139 | 140 | // String is a helper that converts a command reply to a string. If err is not 141 | // equal to nil, then String returns "", err. Otherwise String converts the 142 | // reply to a string as follows: 143 | // 144 | // Reply type Result 145 | // bulk string string(reply), nil 146 | // simple string reply, nil 147 | // nil "", ErrNil 148 | // other "", error 149 | func String(reply interface{}, err error) (string, error) { 150 | if err != nil { 151 | return "", err 152 | } 153 | switch reply := reply.(type) { 154 | case []byte: 155 | return string(reply), nil 156 | case string: 157 | return reply, nil 158 | case nil: 159 | return "", ErrNil 160 | case Error: 161 | return "", reply 162 | } 163 | return "", fmt.Errorf("redigo: unexpected type for String, got type %T", reply) 164 | } 165 | 166 | // Bytes is a helper that converts a command reply to a slice of bytes. If err 167 | // is not equal to nil, then Bytes returns nil, err. Otherwise Bytes converts 168 | // the reply to a slice of bytes as follows: 169 | // 170 | // Reply type Result 171 | // bulk string reply, nil 172 | // simple string []byte(reply), nil 173 | // nil nil, ErrNil 174 | // other nil, error 175 | func Bytes(reply interface{}, err error) ([]byte, error) { 176 | if err != nil { 177 | return nil, err 178 | } 179 | switch reply := reply.(type) { 180 | case []byte: 181 | return reply, nil 182 | case string: 183 | return []byte(reply), nil 184 | case nil: 185 | return nil, ErrNil 186 | case Error: 187 | return nil, reply 188 | } 189 | return nil, fmt.Errorf("redigo: unexpected type for Bytes, got type %T", reply) 190 | } 191 | 192 | // Bool is a helper that converts a command reply to a boolean. If err is not 193 | // equal to nil, then Bool returns false, err. Otherwise Bool converts the 194 | // reply to boolean as follows: 195 | // 196 | // Reply type Result 197 | // integer value != 0, nil 198 | // bulk string strconv.ParseBool(reply) 199 | // nil false, ErrNil 200 | // other false, error 201 | func Bool(reply interface{}, err error) (bool, error) { 202 | if err != nil { 203 | return false, err 204 | } 205 | switch reply := reply.(type) { 206 | case int64: 207 | return reply != 0, nil 208 | case []byte: 209 | return strconv.ParseBool(string(reply)) 210 | case nil: 211 | return false, ErrNil 212 | case Error: 213 | return false, reply 214 | } 215 | return false, fmt.Errorf("redigo: unexpected type for Bool, got type %T", reply) 216 | } 217 | 218 | // MultiBulk is a helper that converts an array command reply to a []interface{}. 219 | // 220 | // Deprecated: Use Values instead. 221 | func MultiBulk(reply interface{}, err error) ([]interface{}, error) { return Values(reply, err) } 222 | 223 | // Values is a helper that converts an array command reply to a []interface{}. 224 | // If err is not equal to nil, then Values returns nil, err. Otherwise, Values 225 | // converts the reply as follows: 226 | // 227 | // Reply type Result 228 | // array reply, nil 229 | // nil nil, ErrNil 230 | // other nil, error 231 | func Values(reply interface{}, err error) ([]interface{}, error) { 232 | if err != nil { 233 | return nil, err 234 | } 235 | switch reply := reply.(type) { 236 | case []interface{}: 237 | return reply, nil 238 | case nil: 239 | return nil, ErrNil 240 | case Error: 241 | return nil, reply 242 | } 243 | return nil, fmt.Errorf("redigo: unexpected type for Values, got type %T", reply) 244 | } 245 | 246 | // Strings is a helper that converts an array command reply to a []string. If 247 | // err is not equal to nil, then Strings returns nil, err. Nil array items are 248 | // converted to "" in the output slice. Strings returns an error if an array 249 | // item is not a bulk string or nil. 250 | func Strings(reply interface{}, err error) ([]string, error) { 251 | if err != nil { 252 | return nil, err 253 | } 254 | switch reply := reply.(type) { 255 | case []interface{}: 256 | result := make([]string, len(reply)) 257 | for i := range reply { 258 | if reply[i] == nil { 259 | continue 260 | } 261 | p, ok := reply[i].([]byte) 262 | if !ok { 263 | return nil, fmt.Errorf("redigo: unexpected element type for Strings, got type %T", reply[i]) 264 | } 265 | result[i] = string(p) 266 | } 267 | return result, nil 268 | case nil: 269 | return nil, ErrNil 270 | case Error: 271 | return nil, reply 272 | } 273 | return nil, fmt.Errorf("redigo: unexpected type for Strings, got type %T", reply) 274 | } 275 | 276 | // ByteSlices is a helper that converts an array command reply to a [][]byte. 277 | // If err is not equal to nil, then ByteSlices returns nil, err. Nil array 278 | // items are stay nil. ByteSlices returns an error if an array item is not a 279 | // bulk string or nil. 280 | func ByteSlices(reply interface{}, err error) ([][]byte, error) { 281 | if err != nil { 282 | return nil, err 283 | } 284 | switch reply := reply.(type) { 285 | case []interface{}: 286 | result := make([][]byte, len(reply)) 287 | for i := range reply { 288 | if reply[i] == nil { 289 | continue 290 | } 291 | p, ok := reply[i].([]byte) 292 | if !ok { 293 | return nil, fmt.Errorf("redigo: unexpected element type for ByteSlices, got type %T", reply[i]) 294 | } 295 | result[i] = p 296 | } 297 | return result, nil 298 | case nil: 299 | return nil, ErrNil 300 | case Error: 301 | return nil, reply 302 | } 303 | return nil, fmt.Errorf("redigo: unexpected type for ByteSlices, got type %T", reply) 304 | } 305 | 306 | // Ints is a helper that converts an array command reply to a []int. If 307 | // err is not equal to nil, then Ints returns nil, err. 308 | func Ints(reply interface{}, err error) ([]int, error) { 309 | var ints []int 310 | values, err := Values(reply, err) 311 | if err != nil { 312 | return ints, err 313 | } 314 | if err := ScanSlice(values, &ints); err != nil { 315 | return ints, err 316 | } 317 | return ints, nil 318 | } 319 | 320 | // StringMap is a helper that converts an array of strings (alternating key, value) 321 | // into a map[string]string. The HGETALL and CONFIG GET commands return replies in this format. 322 | // Requires an even number of values in result. 323 | func StringMap(result interface{}, err error) (map[string]string, error) { 324 | values, err := Values(result, err) 325 | if err != nil { 326 | return nil, err 327 | } 328 | if len(values)%2 != 0 { 329 | return nil, errors.New("redigo: StringMap expects even number of values result") 330 | } 331 | m := make(map[string]string, len(values)/2) 332 | for i := 0; i < len(values); i += 2 { 333 | key, okKey := values[i].([]byte) 334 | value, okValue := values[i+1].([]byte) 335 | if !okKey || !okValue { 336 | return nil, errors.New("redigo: ScanMap key not a bulk string value") 337 | } 338 | m[string(key)] = string(value) 339 | } 340 | return m, nil 341 | } 342 | 343 | // IntMap is a helper that converts an array of strings (alternating key, value) 344 | // into a map[string]int. The HGETALL commands return replies in this format. 345 | // Requires an even number of values in result. 346 | func IntMap(result interface{}, err error) (map[string]int, error) { 347 | values, err := Values(result, err) 348 | if err != nil { 349 | return nil, err 350 | } 351 | if len(values)%2 != 0 { 352 | return nil, errors.New("redigo: IntMap expects even number of values result") 353 | } 354 | m := make(map[string]int, len(values)/2) 355 | for i := 0; i < len(values); i += 2 { 356 | key, ok := values[i].([]byte) 357 | if !ok { 358 | return nil, errors.New("redigo: ScanMap key not a bulk string value") 359 | } 360 | value, err := Int(values[i+1], nil) 361 | if err != nil { 362 | return nil, err 363 | } 364 | m[string(key)] = value 365 | } 366 | return m, nil 367 | } 368 | 369 | // Int64Map is a helper that converts an array of strings (alternating key, value) 370 | // into a map[string]int64. The HGETALL commands return replies in this format. 371 | // Requires an even number of values in result. 372 | func Int64Map(result interface{}, err error) (map[string]int64, error) { 373 | values, err := Values(result, err) 374 | if err != nil { 375 | return nil, err 376 | } 377 | if len(values)%2 != 0 { 378 | return nil, errors.New("redigo: Int64Map expects even number of values result") 379 | } 380 | m := make(map[string]int64, len(values)/2) 381 | for i := 0; i < len(values); i += 2 { 382 | key, ok := values[i].([]byte) 383 | if !ok { 384 | return nil, errors.New("redigo: ScanMap key not a bulk string value") 385 | } 386 | value, err := Int64(values[i+1], nil) 387 | if err != nil { 388 | return nil, err 389 | } 390 | m[string(key)] = value 391 | } 392 | return m, nil 393 | } 394 | -------------------------------------------------------------------------------- /redis/scan.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // 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, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package redis 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | "reflect" 21 | "strconv" 22 | "strings" 23 | "sync" 24 | ) 25 | 26 | func ensureLen(d reflect.Value, n int) { 27 | if n > d.Cap() { 28 | d.Set(reflect.MakeSlice(d.Type(), n, n)) 29 | } else { 30 | d.SetLen(n) 31 | } 32 | } 33 | 34 | func cannotConvert(d reflect.Value, s interface{}) error { 35 | var sname string 36 | switch s.(type) { 37 | case string: 38 | sname = "Redis simple string" 39 | case Error: 40 | sname = "Redis error" 41 | case int64: 42 | sname = "Redis integer" 43 | case []byte: 44 | sname = "Redis bulk string" 45 | case []interface{}: 46 | sname = "Redis array" 47 | default: 48 | sname = reflect.TypeOf(s).String() 49 | } 50 | return fmt.Errorf("cannot convert from %s to %s", sname, d.Type()) 51 | } 52 | 53 | func convertAssignBulkString(d reflect.Value, s []byte) (err error) { 54 | switch d.Type().Kind() { 55 | case reflect.Float32, reflect.Float64: 56 | var x float64 57 | x, err = strconv.ParseFloat(string(s), d.Type().Bits()) 58 | d.SetFloat(x) 59 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 60 | var x int64 61 | x, err = strconv.ParseInt(string(s), 10, d.Type().Bits()) 62 | d.SetInt(x) 63 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 64 | var x uint64 65 | x, err = strconv.ParseUint(string(s), 10, d.Type().Bits()) 66 | d.SetUint(x) 67 | case reflect.Bool: 68 | var x bool 69 | x, err = strconv.ParseBool(string(s)) 70 | d.SetBool(x) 71 | case reflect.String: 72 | d.SetString(string(s)) 73 | case reflect.Slice: 74 | if d.Type().Elem().Kind() != reflect.Uint8 { 75 | err = cannotConvert(d, s) 76 | } else { 77 | d.SetBytes(s) 78 | } 79 | default: 80 | err = cannotConvert(d, s) 81 | } 82 | return 83 | } 84 | 85 | func convertAssignInt(d reflect.Value, s int64) (err error) { 86 | switch d.Type().Kind() { 87 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 88 | d.SetInt(s) 89 | if d.Int() != s { 90 | err = strconv.ErrRange 91 | d.SetInt(0) 92 | } 93 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 94 | if s < 0 { 95 | err = strconv.ErrRange 96 | } else { 97 | x := uint64(s) 98 | d.SetUint(x) 99 | if d.Uint() != x { 100 | err = strconv.ErrRange 101 | d.SetUint(0) 102 | } 103 | } 104 | case reflect.Bool: 105 | d.SetBool(s != 0) 106 | default: 107 | err = cannotConvert(d, s) 108 | } 109 | return 110 | } 111 | 112 | func convertAssignValue(d reflect.Value, s interface{}) (err error) { 113 | switch s := s.(type) { 114 | case []byte: 115 | err = convertAssignBulkString(d, s) 116 | case int64: 117 | err = convertAssignInt(d, s) 118 | default: 119 | err = cannotConvert(d, s) 120 | } 121 | return err 122 | } 123 | 124 | func convertAssignArray(d reflect.Value, s []interface{}) error { 125 | if d.Type().Kind() != reflect.Slice { 126 | return cannotConvert(d, s) 127 | } 128 | ensureLen(d, len(s)) 129 | for i := 0; i < len(s); i++ { 130 | if err := convertAssignValue(d.Index(i), s[i]); err != nil { 131 | return err 132 | } 133 | } 134 | return nil 135 | } 136 | 137 | func convertAssign(d interface{}, s interface{}) (err error) { 138 | // Handle the most common destination types using type switches and 139 | // fall back to reflection for all other types. 140 | switch s := s.(type) { 141 | case nil: 142 | // ingore 143 | case []byte: 144 | switch d := d.(type) { 145 | case *string: 146 | *d = string(s) 147 | case *int: 148 | *d, err = strconv.Atoi(string(s)) 149 | case *bool: 150 | *d, err = strconv.ParseBool(string(s)) 151 | case *[]byte: 152 | *d = s 153 | case *interface{}: 154 | *d = s 155 | case nil: 156 | // skip value 157 | default: 158 | if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr { 159 | err = cannotConvert(d, s) 160 | } else { 161 | err = convertAssignBulkString(d.Elem(), s) 162 | } 163 | } 164 | case int64: 165 | switch d := d.(type) { 166 | case *int: 167 | x := int(s) 168 | if int64(x) != s { 169 | err = strconv.ErrRange 170 | x = 0 171 | } 172 | *d = x 173 | case *bool: 174 | *d = s != 0 175 | case *interface{}: 176 | *d = s 177 | case nil: 178 | // skip value 179 | default: 180 | if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr { 181 | err = cannotConvert(d, s) 182 | } else { 183 | err = convertAssignInt(d.Elem(), s) 184 | } 185 | } 186 | case string: 187 | switch d := d.(type) { 188 | case *string: 189 | *d = string(s) 190 | default: 191 | err = cannotConvert(reflect.ValueOf(d), s) 192 | } 193 | case []interface{}: 194 | switch d := d.(type) { 195 | case *[]interface{}: 196 | *d = s 197 | case *interface{}: 198 | *d = s 199 | case nil: 200 | // skip value 201 | default: 202 | if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr { 203 | err = cannotConvert(d, s) 204 | } else { 205 | err = convertAssignArray(d.Elem(), s) 206 | } 207 | } 208 | case Error: 209 | err = s 210 | default: 211 | err = cannotConvert(reflect.ValueOf(d), s) 212 | } 213 | return 214 | } 215 | 216 | // Scan copies from src to the values pointed at by dest. 217 | // 218 | // The values pointed at by dest must be an integer, float, boolean, string, 219 | // []byte, interface{} or slices of these types. Scan uses the standard strconv 220 | // package to convert bulk strings to numeric and boolean types. 221 | // 222 | // If a dest value is nil, then the corresponding src value is skipped. 223 | // 224 | // If a src element is nil, then the corresponding dest value is not modified. 225 | // 226 | // To enable easy use of Scan in a loop, Scan returns the slice of src 227 | // following the copied values. 228 | func Scan(src []interface{}, dest ...interface{}) ([]interface{}, error) { 229 | if len(src) < len(dest) { 230 | return nil, errors.New("redigo.Scan: array short") 231 | } 232 | var err error 233 | for i, d := range dest { 234 | err = convertAssign(d, src[i]) 235 | if err != nil { 236 | err = fmt.Errorf("redigo.Scan: cannot assign to dest %d: %v", i, err) 237 | break 238 | } 239 | } 240 | return src[len(dest):], err 241 | } 242 | 243 | type fieldSpec struct { 244 | name string 245 | index []int 246 | omitEmpty bool 247 | } 248 | 249 | type structSpec struct { 250 | m map[string]*fieldSpec 251 | l []*fieldSpec 252 | } 253 | 254 | func (ss *structSpec) fieldSpec(name []byte) *fieldSpec { 255 | return ss.m[string(name)] 256 | } 257 | 258 | func compileStructSpec(t reflect.Type, depth map[string]int, index []int, ss *structSpec) { 259 | for i := 0; i < t.NumField(); i++ { 260 | f := t.Field(i) 261 | switch { 262 | case f.PkgPath != "" && !f.Anonymous: 263 | // Ignore unexported fields. 264 | case f.Anonymous: 265 | // TODO: Handle pointers. Requires change to decoder and 266 | // protection against infinite recursion. 267 | if f.Type.Kind() == reflect.Struct { 268 | compileStructSpec(f.Type, depth, append(index, i), ss) 269 | } 270 | default: 271 | fs := &fieldSpec{name: f.Name} 272 | tag := f.Tag.Get("redis") 273 | p := strings.Split(tag, ",") 274 | if len(p) > 0 { 275 | if p[0] == "-" { 276 | continue 277 | } 278 | if len(p[0]) > 0 { 279 | fs.name = p[0] 280 | } 281 | for _, s := range p[1:] { 282 | switch s { 283 | case "omitempty": 284 | fs.omitEmpty = true 285 | default: 286 | panic(fmt.Errorf("redigo: unknown field tag %s for type %s", s, t.Name())) 287 | } 288 | } 289 | } 290 | d, found := depth[fs.name] 291 | if !found { 292 | d = 1 << 30 293 | } 294 | switch { 295 | case len(index) == d: 296 | // At same depth, remove from result. 297 | delete(ss.m, fs.name) 298 | j := 0 299 | for i := 0; i < len(ss.l); i++ { 300 | if fs.name != ss.l[i].name { 301 | ss.l[j] = ss.l[i] 302 | j += 1 303 | } 304 | } 305 | ss.l = ss.l[:j] 306 | case len(index) < d: 307 | fs.index = make([]int, len(index)+1) 308 | copy(fs.index, index) 309 | fs.index[len(index)] = i 310 | depth[fs.name] = len(index) 311 | ss.m[fs.name] = fs 312 | ss.l = append(ss.l, fs) 313 | } 314 | } 315 | } 316 | } 317 | 318 | var ( 319 | structSpecMutex sync.RWMutex 320 | structSpecCache = make(map[reflect.Type]*structSpec) 321 | defaultFieldSpec = &fieldSpec{} 322 | ) 323 | 324 | func structSpecForType(t reflect.Type) *structSpec { 325 | 326 | structSpecMutex.RLock() 327 | ss, found := structSpecCache[t] 328 | structSpecMutex.RUnlock() 329 | if found { 330 | return ss 331 | } 332 | 333 | structSpecMutex.Lock() 334 | defer structSpecMutex.Unlock() 335 | ss, found = structSpecCache[t] 336 | if found { 337 | return ss 338 | } 339 | 340 | ss = &structSpec{m: make(map[string]*fieldSpec)} 341 | compileStructSpec(t, make(map[string]int), nil, ss) 342 | structSpecCache[t] = ss 343 | return ss 344 | } 345 | 346 | var errScanStructValue = errors.New("redigo.ScanStruct: value must be non-nil pointer to a struct") 347 | 348 | // ScanStruct scans alternating names and values from src to a struct. The 349 | // HGETALL and CONFIG GET commands return replies in this format. 350 | // 351 | // ScanStruct uses exported field names to match values in the response. Use 352 | // 'redis' field tag to override the name: 353 | // 354 | // Field int `redis:"myName"` 355 | // 356 | // Fields with the tag redis:"-" are ignored. 357 | // 358 | // Integer, float, boolean, string and []byte fields are supported. Scan uses the 359 | // standard strconv package to convert bulk string values to numeric and 360 | // boolean types. 361 | // 362 | // If a src element is nil, then the corresponding field is not modified. 363 | func ScanStruct(src []interface{}, dest interface{}) error { 364 | d := reflect.ValueOf(dest) 365 | if d.Kind() != reflect.Ptr || d.IsNil() { 366 | return errScanStructValue 367 | } 368 | d = d.Elem() 369 | if d.Kind() != reflect.Struct { 370 | return errScanStructValue 371 | } 372 | ss := structSpecForType(d.Type()) 373 | 374 | if len(src)%2 != 0 { 375 | return errors.New("redigo.ScanStruct: number of values not a multiple of 2") 376 | } 377 | 378 | for i := 0; i < len(src); i += 2 { 379 | s := src[i+1] 380 | if s == nil { 381 | continue 382 | } 383 | name, ok := src[i].([]byte) 384 | if !ok { 385 | return fmt.Errorf("redigo.ScanStruct: key %d not a bulk string value", i) 386 | } 387 | fs := ss.fieldSpec(name) 388 | if fs == nil { 389 | continue 390 | } 391 | if err := convertAssignValue(d.FieldByIndex(fs.index), s); err != nil { 392 | return fmt.Errorf("redigo.ScanStruct: cannot assign field %s: %v", fs.name, err) 393 | } 394 | } 395 | return nil 396 | } 397 | 398 | var ( 399 | errScanSliceValue = errors.New("redigo.ScanSlice: dest must be non-nil pointer to a struct") 400 | ) 401 | 402 | // ScanSlice scans src to the slice pointed to by dest. The elements the dest 403 | // slice must be integer, float, boolean, string, struct or pointer to struct 404 | // values. 405 | // 406 | // Struct fields must be integer, float, boolean or string values. All struct 407 | // fields are used unless a subset is specified using fieldNames. 408 | func ScanSlice(src []interface{}, dest interface{}, fieldNames ...string) error { 409 | d := reflect.ValueOf(dest) 410 | if d.Kind() != reflect.Ptr || d.IsNil() { 411 | return errScanSliceValue 412 | } 413 | d = d.Elem() 414 | if d.Kind() != reflect.Slice { 415 | return errScanSliceValue 416 | } 417 | 418 | isPtr := false 419 | t := d.Type().Elem() 420 | if t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct { 421 | isPtr = true 422 | t = t.Elem() 423 | } 424 | 425 | if t.Kind() != reflect.Struct { 426 | ensureLen(d, len(src)) 427 | for i, s := range src { 428 | if s == nil { 429 | continue 430 | } 431 | if err := convertAssignValue(d.Index(i), s); err != nil { 432 | return fmt.Errorf("redigo.ScanSlice: cannot assign element %d: %v", i, err) 433 | } 434 | } 435 | return nil 436 | } 437 | 438 | ss := structSpecForType(t) 439 | fss := ss.l 440 | if len(fieldNames) > 0 { 441 | fss = make([]*fieldSpec, len(fieldNames)) 442 | for i, name := range fieldNames { 443 | fss[i] = ss.m[name] 444 | if fss[i] == nil { 445 | return fmt.Errorf("redigo.ScanSlice: ScanSlice bad field name %s", name) 446 | } 447 | } 448 | } 449 | 450 | if len(fss) == 0 { 451 | return errors.New("redigo.ScanSlice: no struct fields") 452 | } 453 | 454 | n := len(src) / len(fss) 455 | if n*len(fss) != len(src) { 456 | return errors.New("redigo.ScanSlice: length not a multiple of struct field count") 457 | } 458 | 459 | ensureLen(d, n) 460 | for i := 0; i < n; i++ { 461 | d := d.Index(i) 462 | if isPtr { 463 | if d.IsNil() { 464 | d.Set(reflect.New(t)) 465 | } 466 | d = d.Elem() 467 | } 468 | for j, fs := range fss { 469 | s := src[i*len(fss)+j] 470 | if s == nil { 471 | continue 472 | } 473 | if err := convertAssignValue(d.FieldByIndex(fs.index), s); err != nil { 474 | return fmt.Errorf("redigo.ScanSlice: cannot assign element %d to field %s: %v", i*len(fss)+j, fs.name, err) 475 | } 476 | } 477 | } 478 | return nil 479 | } 480 | 481 | // Args is a helper for constructing command arguments from structured values. 482 | type Args []interface{} 483 | 484 | // Add returns the result of appending value to args. 485 | func (args Args) Add(value ...interface{}) Args { 486 | return append(args, value...) 487 | } 488 | 489 | // AddFlat returns the result of appending the flattened value of v to args. 490 | // 491 | // Maps are flattened by appending the alternating keys and map values to args. 492 | // 493 | // Slices are flattened by appending the slice elements to args. 494 | // 495 | // Structs are flattened by appending the alternating names and values of 496 | // exported fields to args. If v is a nil struct pointer, then nothing is 497 | // appended. The 'redis' field tag overrides struct field names. See ScanStruct 498 | // for more information on the use of the 'redis' field tag. 499 | // 500 | // Other types are appended to args as is. 501 | func (args Args) AddFlat(v interface{}) Args { 502 | rv := reflect.ValueOf(v) 503 | switch rv.Kind() { 504 | case reflect.Struct: 505 | args = flattenStruct(args, rv) 506 | case reflect.Slice: 507 | for i := 0; i < rv.Len(); i++ { 508 | args = append(args, rv.Index(i).Interface()) 509 | } 510 | case reflect.Map: 511 | for _, k := range rv.MapKeys() { 512 | args = append(args, k.Interface(), rv.MapIndex(k).Interface()) 513 | } 514 | case reflect.Ptr: 515 | if rv.Type().Elem().Kind() == reflect.Struct { 516 | if !rv.IsNil() { 517 | args = flattenStruct(args, rv.Elem()) 518 | } 519 | } else { 520 | args = append(args, v) 521 | } 522 | default: 523 | args = append(args, v) 524 | } 525 | return args 526 | } 527 | 528 | func flattenStruct(args Args, v reflect.Value) Args { 529 | ss := structSpecForType(v.Type()) 530 | for _, fs := range ss.l { 531 | fv := v.FieldByIndex(fs.index) 532 | if fs.omitEmpty { 533 | var empty = false 534 | switch fv.Kind() { 535 | case reflect.Array, reflect.Map, reflect.Slice, reflect.String: 536 | empty = fv.Len() == 0 537 | case reflect.Bool: 538 | empty = !fv.Bool() 539 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 540 | empty = fv.Int() == 0 541 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 542 | empty = fv.Uint() == 0 543 | case reflect.Float32, reflect.Float64: 544 | empty = fv.Float() == 0 545 | case reflect.Interface, reflect.Ptr: 546 | empty = fv.IsNil() 547 | } 548 | if empty { 549 | continue 550 | } 551 | } 552 | args = append(args, fs.name, fv.Interface()) 553 | } 554 | return args 555 | } 556 | -------------------------------------------------------------------------------- /redis/pool_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // 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, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package redis_test 16 | 17 | import ( 18 | "errors" 19 | "io" 20 | "reflect" 21 | "sync" 22 | "testing" 23 | "time" 24 | 25 | "github.com/garyburd/redigo/redis" 26 | ) 27 | 28 | type poolTestConn struct { 29 | d *poolDialer 30 | err error 31 | redis.Conn 32 | } 33 | 34 | func (c *poolTestConn) Close() error { 35 | c.d.mu.Lock() 36 | c.d.open -= 1 37 | c.d.mu.Unlock() 38 | return c.Conn.Close() 39 | } 40 | 41 | func (c *poolTestConn) Err() error { return c.err } 42 | 43 | func (c *poolTestConn) Do(commandName string, args ...interface{}) (interface{}, error) { 44 | if commandName == "ERR" { 45 | c.err = args[0].(error) 46 | commandName = "PING" 47 | } 48 | if commandName != "" { 49 | c.d.commands = append(c.d.commands, commandName) 50 | } 51 | return c.Conn.Do(commandName, args...) 52 | } 53 | 54 | func (c *poolTestConn) Send(commandName string, args ...interface{}) error { 55 | c.d.commands = append(c.d.commands, commandName) 56 | return c.Conn.Send(commandName, args...) 57 | } 58 | 59 | type poolDialer struct { 60 | mu sync.Mutex 61 | t *testing.T 62 | dialed int 63 | open int 64 | commands []string 65 | dialErr error 66 | } 67 | 68 | func (d *poolDialer) dial() (redis.Conn, error) { 69 | d.mu.Lock() 70 | d.dialed += 1 71 | dialErr := d.dialErr 72 | d.mu.Unlock() 73 | if dialErr != nil { 74 | return nil, d.dialErr 75 | } 76 | c, err := redis.DialDefaultServer() 77 | if err != nil { 78 | return nil, err 79 | } 80 | d.mu.Lock() 81 | d.open += 1 82 | d.mu.Unlock() 83 | return &poolTestConn{d: d, Conn: c}, nil 84 | } 85 | 86 | func (d *poolDialer) check(message string, p *redis.Pool, dialed, open int) { 87 | d.mu.Lock() 88 | if d.dialed != dialed { 89 | d.t.Errorf("%s: dialed=%d, want %d", message, d.dialed, dialed) 90 | } 91 | if d.open != open { 92 | d.t.Errorf("%s: open=%d, want %d", message, d.open, open) 93 | } 94 | if active := p.ActiveCount(); active != open { 95 | d.t.Errorf("%s: active=%d, want %d", message, active, open) 96 | } 97 | d.mu.Unlock() 98 | } 99 | 100 | func TestPoolReuse(t *testing.T) { 101 | d := poolDialer{t: t} 102 | p := &redis.Pool{ 103 | MaxIdle: 2, 104 | Dial: d.dial, 105 | } 106 | 107 | for i := 0; i < 10; i++ { 108 | c1 := p.Get() 109 | c1.Do("PING") 110 | c2 := p.Get() 111 | c2.Do("PING") 112 | c1.Close() 113 | c2.Close() 114 | } 115 | 116 | d.check("before close", p, 2, 2) 117 | p.Close() 118 | d.check("after close", p, 2, 0) 119 | } 120 | 121 | func TestPoolMaxIdle(t *testing.T) { 122 | d := poolDialer{t: t} 123 | p := &redis.Pool{ 124 | MaxIdle: 2, 125 | Dial: d.dial, 126 | } 127 | defer p.Close() 128 | 129 | for i := 0; i < 10; i++ { 130 | c1 := p.Get() 131 | c1.Do("PING") 132 | c2 := p.Get() 133 | c2.Do("PING") 134 | c3 := p.Get() 135 | c3.Do("PING") 136 | c1.Close() 137 | c2.Close() 138 | c3.Close() 139 | } 140 | d.check("before close", p, 12, 2) 141 | p.Close() 142 | d.check("after close", p, 12, 0) 143 | } 144 | 145 | func TestPoolError(t *testing.T) { 146 | d := poolDialer{t: t} 147 | p := &redis.Pool{ 148 | MaxIdle: 2, 149 | Dial: d.dial, 150 | } 151 | defer p.Close() 152 | 153 | c := p.Get() 154 | c.Do("ERR", io.EOF) 155 | if c.Err() == nil { 156 | t.Errorf("expected c.Err() != nil") 157 | } 158 | c.Close() 159 | 160 | c = p.Get() 161 | c.Do("ERR", io.EOF) 162 | c.Close() 163 | 164 | d.check(".", p, 2, 0) 165 | } 166 | 167 | func TestPoolClose(t *testing.T) { 168 | d := poolDialer{t: t} 169 | p := &redis.Pool{ 170 | MaxIdle: 2, 171 | Dial: d.dial, 172 | } 173 | defer p.Close() 174 | 175 | c1 := p.Get() 176 | c1.Do("PING") 177 | c2 := p.Get() 178 | c2.Do("PING") 179 | c3 := p.Get() 180 | c3.Do("PING") 181 | 182 | c1.Close() 183 | if _, err := c1.Do("PING"); err == nil { 184 | t.Errorf("expected error after connection closed") 185 | } 186 | 187 | c2.Close() 188 | c2.Close() 189 | 190 | p.Close() 191 | 192 | d.check("after pool close", p, 3, 1) 193 | 194 | if _, err := c1.Do("PING"); err == nil { 195 | t.Errorf("expected error after connection and pool closed") 196 | } 197 | 198 | c3.Close() 199 | 200 | d.check("after conn close", p, 3, 0) 201 | 202 | c1 = p.Get() 203 | if _, err := c1.Do("PING"); err == nil { 204 | t.Errorf("expected error after pool closed") 205 | } 206 | } 207 | 208 | func TestPoolTimeout(t *testing.T) { 209 | d := poolDialer{t: t} 210 | p := &redis.Pool{ 211 | MaxIdle: 2, 212 | IdleTimeout: 300 * time.Second, 213 | Dial: d.dial, 214 | } 215 | defer p.Close() 216 | 217 | now := time.Now() 218 | redis.SetNowFunc(func() time.Time { return now }) 219 | defer redis.SetNowFunc(time.Now) 220 | 221 | c := p.Get() 222 | c.Do("PING") 223 | c.Close() 224 | 225 | d.check("1", p, 1, 1) 226 | 227 | now = now.Add(p.IdleTimeout) 228 | 229 | c = p.Get() 230 | c.Do("PING") 231 | c.Close() 232 | 233 | d.check("2", p, 2, 1) 234 | } 235 | 236 | func TestPoolConcurrenSendReceive(t *testing.T) { 237 | p := &redis.Pool{ 238 | Dial: redis.DialDefaultServer, 239 | } 240 | defer p.Close() 241 | 242 | c := p.Get() 243 | done := make(chan error, 1) 244 | go func() { 245 | _, err := c.Receive() 246 | done <- err 247 | }() 248 | c.Send("PING") 249 | c.Flush() 250 | err := <-done 251 | if err != nil { 252 | t.Fatalf("Receive() returned error %v", err) 253 | } 254 | _, err = c.Do("") 255 | if err != nil { 256 | t.Fatalf("Do() returned error %v", err) 257 | } 258 | c.Close() 259 | } 260 | 261 | func TestPoolBorrowCheck(t *testing.T) { 262 | d := poolDialer{t: t} 263 | p := &redis.Pool{ 264 | MaxIdle: 2, 265 | Dial: d.dial, 266 | TestOnBorrow: func(redis.Conn, time.Time) error { return redis.Error("BLAH") }, 267 | } 268 | defer p.Close() 269 | 270 | for i := 0; i < 10; i++ { 271 | c := p.Get() 272 | c.Do("PING") 273 | c.Close() 274 | } 275 | d.check("1", p, 10, 1) 276 | } 277 | 278 | func TestPoolMaxActive(t *testing.T) { 279 | d := poolDialer{t: t} 280 | p := &redis.Pool{ 281 | MaxIdle: 2, 282 | MaxActive: 2, 283 | Dial: d.dial, 284 | } 285 | defer p.Close() 286 | 287 | c1 := p.Get() 288 | c1.Do("PING") 289 | c2 := p.Get() 290 | c2.Do("PING") 291 | 292 | d.check("1", p, 2, 2) 293 | 294 | c3 := p.Get() 295 | if _, err := c3.Do("PING"); err != redis.ErrPoolExhausted { 296 | t.Errorf("expected pool exhausted") 297 | } 298 | 299 | c3.Close() 300 | d.check("2", p, 2, 2) 301 | c2.Close() 302 | d.check("3", p, 2, 2) 303 | 304 | c3 = p.Get() 305 | if _, err := c3.Do("PING"); err != nil { 306 | t.Errorf("expected good channel, err=%v", err) 307 | } 308 | c3.Close() 309 | 310 | d.check("4", p, 2, 2) 311 | } 312 | 313 | func TestPoolMonitorCleanup(t *testing.T) { 314 | d := poolDialer{t: t} 315 | p := &redis.Pool{ 316 | MaxIdle: 2, 317 | MaxActive: 2, 318 | Dial: d.dial, 319 | } 320 | defer p.Close() 321 | 322 | c := p.Get() 323 | c.Send("MONITOR") 324 | c.Close() 325 | 326 | d.check("", p, 1, 0) 327 | } 328 | 329 | func TestPoolPubSubCleanup(t *testing.T) { 330 | d := poolDialer{t: t} 331 | p := &redis.Pool{ 332 | MaxIdle: 2, 333 | MaxActive: 2, 334 | Dial: d.dial, 335 | } 336 | defer p.Close() 337 | 338 | c := p.Get() 339 | c.Send("SUBSCRIBE", "x") 340 | c.Close() 341 | 342 | want := []string{"SUBSCRIBE", "UNSUBSCRIBE", "PUNSUBSCRIBE", "ECHO"} 343 | if !reflect.DeepEqual(d.commands, want) { 344 | t.Errorf("got commands %v, want %v", d.commands, want) 345 | } 346 | d.commands = nil 347 | 348 | c = p.Get() 349 | c.Send("PSUBSCRIBE", "x*") 350 | c.Close() 351 | 352 | want = []string{"PSUBSCRIBE", "UNSUBSCRIBE", "PUNSUBSCRIBE", "ECHO"} 353 | if !reflect.DeepEqual(d.commands, want) { 354 | t.Errorf("got commands %v, want %v", d.commands, want) 355 | } 356 | d.commands = nil 357 | } 358 | 359 | func TestPoolTransactionCleanup(t *testing.T) { 360 | d := poolDialer{t: t} 361 | p := &redis.Pool{ 362 | MaxIdle: 2, 363 | MaxActive: 2, 364 | Dial: d.dial, 365 | } 366 | defer p.Close() 367 | 368 | c := p.Get() 369 | c.Do("WATCH", "key") 370 | c.Do("PING") 371 | c.Close() 372 | 373 | want := []string{"WATCH", "PING", "UNWATCH"} 374 | if !reflect.DeepEqual(d.commands, want) { 375 | t.Errorf("got commands %v, want %v", d.commands, want) 376 | } 377 | d.commands = nil 378 | 379 | c = p.Get() 380 | c.Do("WATCH", "key") 381 | c.Do("UNWATCH") 382 | c.Do("PING") 383 | c.Close() 384 | 385 | want = []string{"WATCH", "UNWATCH", "PING"} 386 | if !reflect.DeepEqual(d.commands, want) { 387 | t.Errorf("got commands %v, want %v", d.commands, want) 388 | } 389 | d.commands = nil 390 | 391 | c = p.Get() 392 | c.Do("WATCH", "key") 393 | c.Do("MULTI") 394 | c.Do("PING") 395 | c.Close() 396 | 397 | want = []string{"WATCH", "MULTI", "PING", "DISCARD"} 398 | if !reflect.DeepEqual(d.commands, want) { 399 | t.Errorf("got commands %v, want %v", d.commands, want) 400 | } 401 | d.commands = nil 402 | 403 | c = p.Get() 404 | c.Do("WATCH", "key") 405 | c.Do("MULTI") 406 | c.Do("DISCARD") 407 | c.Do("PING") 408 | c.Close() 409 | 410 | want = []string{"WATCH", "MULTI", "DISCARD", "PING"} 411 | if !reflect.DeepEqual(d.commands, want) { 412 | t.Errorf("got commands %v, want %v", d.commands, want) 413 | } 414 | d.commands = nil 415 | 416 | c = p.Get() 417 | c.Do("WATCH", "key") 418 | c.Do("MULTI") 419 | c.Do("EXEC") 420 | c.Do("PING") 421 | c.Close() 422 | 423 | want = []string{"WATCH", "MULTI", "EXEC", "PING"} 424 | if !reflect.DeepEqual(d.commands, want) { 425 | t.Errorf("got commands %v, want %v", d.commands, want) 426 | } 427 | d.commands = nil 428 | } 429 | 430 | func startGoroutines(p *redis.Pool, cmd string, args ...interface{}) chan error { 431 | errs := make(chan error, 10) 432 | for i := 0; i < cap(errs); i++ { 433 | go func() { 434 | c := p.Get() 435 | _, err := c.Do(cmd, args...) 436 | errs <- err 437 | c.Close() 438 | }() 439 | } 440 | 441 | // Wait for goroutines to block. 442 | time.Sleep(time.Second / 4) 443 | 444 | return errs 445 | } 446 | 447 | func TestWaitPool(t *testing.T) { 448 | d := poolDialer{t: t} 449 | p := &redis.Pool{ 450 | MaxIdle: 1, 451 | MaxActive: 1, 452 | Dial: d.dial, 453 | Wait: true, 454 | } 455 | defer p.Close() 456 | 457 | c := p.Get() 458 | errs := startGoroutines(p, "PING") 459 | d.check("before close", p, 1, 1) 460 | c.Close() 461 | timeout := time.After(2 * time.Second) 462 | for i := 0; i < cap(errs); i++ { 463 | select { 464 | case err := <-errs: 465 | if err != nil { 466 | t.Fatal(err) 467 | } 468 | case <-timeout: 469 | t.Fatalf("timeout waiting for blocked goroutine %d", i) 470 | } 471 | } 472 | d.check("done", p, 1, 1) 473 | } 474 | 475 | func TestWaitPoolClose(t *testing.T) { 476 | d := poolDialer{t: t} 477 | p := &redis.Pool{ 478 | MaxIdle: 1, 479 | MaxActive: 1, 480 | Dial: d.dial, 481 | Wait: true, 482 | } 483 | defer p.Close() 484 | 485 | c := p.Get() 486 | if _, err := c.Do("PING"); err != nil { 487 | t.Fatal(err) 488 | } 489 | errs := startGoroutines(p, "PING") 490 | d.check("before close", p, 1, 1) 491 | p.Close() 492 | timeout := time.After(2 * time.Second) 493 | for i := 0; i < cap(errs); i++ { 494 | select { 495 | case err := <-errs: 496 | switch err { 497 | case nil: 498 | t.Fatal("blocked goroutine did not get error") 499 | case redis.ErrPoolExhausted: 500 | t.Fatal("blocked goroutine got pool exhausted error") 501 | } 502 | case <-timeout: 503 | t.Fatal("timeout waiting for blocked goroutine") 504 | } 505 | } 506 | c.Close() 507 | d.check("done", p, 1, 0) 508 | } 509 | 510 | func TestWaitPoolCommandError(t *testing.T) { 511 | testErr := errors.New("test") 512 | d := poolDialer{t: t} 513 | p := &redis.Pool{ 514 | MaxIdle: 1, 515 | MaxActive: 1, 516 | Dial: d.dial, 517 | Wait: true, 518 | } 519 | defer p.Close() 520 | 521 | c := p.Get() 522 | errs := startGoroutines(p, "ERR", testErr) 523 | d.check("before close", p, 1, 1) 524 | c.Close() 525 | timeout := time.After(2 * time.Second) 526 | for i := 0; i < cap(errs); i++ { 527 | select { 528 | case err := <-errs: 529 | if err != nil { 530 | t.Fatal(err) 531 | } 532 | case <-timeout: 533 | t.Fatalf("timeout waiting for blocked goroutine %d", i) 534 | } 535 | } 536 | d.check("done", p, cap(errs), 0) 537 | } 538 | 539 | func TestWaitPoolDialError(t *testing.T) { 540 | testErr := errors.New("test") 541 | d := poolDialer{t: t} 542 | p := &redis.Pool{ 543 | MaxIdle: 1, 544 | MaxActive: 1, 545 | Dial: d.dial, 546 | Wait: true, 547 | } 548 | defer p.Close() 549 | 550 | c := p.Get() 551 | errs := startGoroutines(p, "ERR", testErr) 552 | d.check("before close", p, 1, 1) 553 | 554 | d.dialErr = errors.New("dial") 555 | c.Close() 556 | 557 | nilCount := 0 558 | errCount := 0 559 | timeout := time.After(2 * time.Second) 560 | for i := 0; i < cap(errs); i++ { 561 | select { 562 | case err := <-errs: 563 | switch err { 564 | case nil: 565 | nilCount++ 566 | case d.dialErr: 567 | errCount++ 568 | default: 569 | t.Fatalf("expected dial error or nil, got %v", err) 570 | } 571 | case <-timeout: 572 | t.Fatalf("timeout waiting for blocked goroutine %d", i) 573 | } 574 | } 575 | if nilCount != 1 { 576 | t.Errorf("expected one nil error, got %d", nilCount) 577 | } 578 | if errCount != cap(errs)-1 { 579 | t.Errorf("expected %d dial errors, got %d", cap(errs)-1, errCount) 580 | } 581 | d.check("done", p, cap(errs), 0) 582 | } 583 | 584 | // Borrowing requires us to iterate over the idle connections, unlock the pool, 585 | // and perform a blocking operation to check the connection still works. If 586 | // TestOnBorrow fails, we must reacquire the lock and continue iteration. This 587 | // test ensures that iteration will work correctly if multiple threads are 588 | // iterating simultaneously. 589 | func TestLocking_TestOnBorrowFails_PoolDoesntCrash(t *testing.T) { 590 | const count = 100 591 | 592 | // First we'll Create a pool where the pilfering of idle connections fails. 593 | d := poolDialer{t: t} 594 | p := &redis.Pool{ 595 | MaxIdle: count, 596 | MaxActive: count, 597 | Dial: d.dial, 598 | TestOnBorrow: func(c redis.Conn, t time.Time) error { 599 | return errors.New("No way back into the real world.") 600 | }, 601 | } 602 | defer p.Close() 603 | 604 | // Fill the pool with idle connections. 605 | conns := make([]redis.Conn, count) 606 | for i := range conns { 607 | conns[i] = p.Get() 608 | } 609 | for i := range conns { 610 | conns[i].Close() 611 | } 612 | 613 | // Spawn a bunch of goroutines to thrash the pool. 614 | var wg sync.WaitGroup 615 | wg.Add(count) 616 | for i := 0; i < count; i++ { 617 | go func() { 618 | c := p.Get() 619 | if c.Err() != nil { 620 | t.Errorf("pool get failed: %v", c.Err()) 621 | } 622 | c.Close() 623 | wg.Done() 624 | }() 625 | } 626 | wg.Wait() 627 | if d.dialed != count*2 { 628 | t.Errorf("Expected %d dials, got %d", count*2, d.dialed) 629 | } 630 | } 631 | 632 | func BenchmarkPoolGet(b *testing.B) { 633 | b.StopTimer() 634 | p := redis.Pool{Dial: redis.DialDefaultServer, MaxIdle: 2} 635 | c := p.Get() 636 | if err := c.Err(); err != nil { 637 | b.Fatal(err) 638 | } 639 | c.Close() 640 | defer p.Close() 641 | b.StartTimer() 642 | for i := 0; i < b.N; i++ { 643 | c = p.Get() 644 | c.Close() 645 | } 646 | } 647 | 648 | func BenchmarkPoolGetErr(b *testing.B) { 649 | b.StopTimer() 650 | p := redis.Pool{Dial: redis.DialDefaultServer, MaxIdle: 2} 651 | c := p.Get() 652 | if err := c.Err(); err != nil { 653 | b.Fatal(err) 654 | } 655 | c.Close() 656 | defer p.Close() 657 | b.StartTimer() 658 | for i := 0; i < b.N; i++ { 659 | c = p.Get() 660 | if err := c.Err(); err != nil { 661 | b.Fatal(err) 662 | } 663 | c.Close() 664 | } 665 | } 666 | 667 | func BenchmarkPoolGetPing(b *testing.B) { 668 | b.StopTimer() 669 | p := redis.Pool{Dial: redis.DialDefaultServer, MaxIdle: 2} 670 | c := p.Get() 671 | if err := c.Err(); err != nil { 672 | b.Fatal(err) 673 | } 674 | c.Close() 675 | defer p.Close() 676 | b.StartTimer() 677 | for i := 0; i < b.N; i++ { 678 | c = p.Get() 679 | if _, err := c.Do("PING"); err != nil { 680 | b.Fatal(err) 681 | } 682 | c.Close() 683 | } 684 | } 685 | -------------------------------------------------------------------------------- /redis/conn.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // 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, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package redis 16 | 17 | import ( 18 | "bufio" 19 | "bytes" 20 | "crypto/tls" 21 | "errors" 22 | "fmt" 23 | "io" 24 | "net" 25 | "net/url" 26 | "regexp" 27 | "strconv" 28 | "sync" 29 | "time" 30 | ) 31 | 32 | // conn is the low-level implementation of Conn 33 | type conn struct { 34 | 35 | // Shared 36 | mu sync.Mutex 37 | pending int 38 | err error 39 | conn net.Conn 40 | 41 | // Read 42 | readTimeout time.Duration 43 | br *bufio.Reader 44 | 45 | // Write 46 | writeTimeout time.Duration 47 | bw *bufio.Writer 48 | 49 | // Scratch space for formatting argument length. 50 | // '*' or '$', length, "\r\n" 51 | lenScratch [32]byte 52 | 53 | // Scratch space for formatting integers and floats. 54 | numScratch [40]byte 55 | } 56 | 57 | // DialTimeout acts like Dial but takes timeouts for establishing the 58 | // connection to the server, writing a command and reading a reply. 59 | // 60 | // Deprecated: Use Dial with options instead. 61 | func DialTimeout(network, address string, connectTimeout, readTimeout, writeTimeout time.Duration) (Conn, error) { 62 | return Dial(network, address, 63 | DialConnectTimeout(connectTimeout), 64 | DialReadTimeout(readTimeout), 65 | DialWriteTimeout(writeTimeout)) 66 | } 67 | 68 | // DialOption specifies an option for dialing a Redis server. 69 | type DialOption struct { 70 | f func(*dialOptions) 71 | } 72 | 73 | type dialOptions struct { 74 | readTimeout time.Duration 75 | writeTimeout time.Duration 76 | dial func(network, addr string) (net.Conn, error) 77 | db int 78 | password string 79 | dialTLS bool 80 | skipVerify bool 81 | tlsConfig *tls.Config 82 | } 83 | 84 | // DialReadTimeout specifies the timeout for reading a single command reply. 85 | func DialReadTimeout(d time.Duration) DialOption { 86 | return DialOption{func(do *dialOptions) { 87 | do.readTimeout = d 88 | }} 89 | } 90 | 91 | // DialWriteTimeout specifies the timeout for writing a single command. 92 | func DialWriteTimeout(d time.Duration) DialOption { 93 | return DialOption{func(do *dialOptions) { 94 | do.writeTimeout = d 95 | }} 96 | } 97 | 98 | // DialConnectTimeout specifies the timeout for connecting to the Redis server. 99 | func DialConnectTimeout(d time.Duration) DialOption { 100 | return DialOption{func(do *dialOptions) { 101 | dialer := net.Dialer{Timeout: d} 102 | do.dial = dialer.Dial 103 | }} 104 | } 105 | 106 | // DialNetDial specifies a custom dial function for creating TCP 107 | // connections. If this option is left out, then net.Dial is 108 | // used. DialNetDial overrides DialConnectTimeout. 109 | func DialNetDial(dial func(network, addr string) (net.Conn, error)) DialOption { 110 | return DialOption{func(do *dialOptions) { 111 | do.dial = dial 112 | }} 113 | } 114 | 115 | // DialDatabase specifies the database to select when dialing a connection. 116 | func DialDatabase(db int) DialOption { 117 | return DialOption{func(do *dialOptions) { 118 | do.db = db 119 | }} 120 | } 121 | 122 | // DialPassword specifies the password to use when connecting to 123 | // the Redis server. 124 | func DialPassword(password string) DialOption { 125 | return DialOption{func(do *dialOptions) { 126 | do.password = password 127 | }} 128 | } 129 | 130 | // DialTLSConfig specifies the config to use when a TLS connection is dialed. 131 | // Has no effect when not dialing a TLS connection. 132 | func DialTLSConfig(c *tls.Config) DialOption { 133 | return DialOption{func(do *dialOptions) { 134 | do.tlsConfig = c 135 | }} 136 | } 137 | 138 | // DialTLSSkipVerify to disable server name verification when connecting 139 | // over TLS. Has no effect when not dialing a TLS connection. 140 | func DialTLSSkipVerify(skip bool) DialOption { 141 | return DialOption{func(do *dialOptions) { 142 | do.skipVerify = skip 143 | }} 144 | } 145 | 146 | // Dial connects to the Redis server at the given network and 147 | // address using the specified options. 148 | func Dial(network, address string, options ...DialOption) (Conn, error) { 149 | do := dialOptions{ 150 | dial: net.Dial, 151 | } 152 | for _, option := range options { 153 | option.f(&do) 154 | } 155 | 156 | netConn, err := do.dial(network, address) 157 | if err != nil { 158 | return nil, err 159 | } 160 | 161 | if do.dialTLS { 162 | tlsConfig := cloneTLSClientConfig(do.tlsConfig, do.skipVerify) 163 | if tlsConfig.ServerName == "" { 164 | host, _, err := net.SplitHostPort(address) 165 | if err != nil { 166 | netConn.Close() 167 | return nil, err 168 | } 169 | tlsConfig.ServerName = host 170 | } 171 | 172 | tlsConn := tls.Client(netConn, tlsConfig) 173 | if err := tlsConn.Handshake(); err != nil { 174 | netConn.Close() 175 | return nil, err 176 | } 177 | netConn = tlsConn 178 | } 179 | 180 | c := &conn{ 181 | conn: netConn, 182 | bw: bufio.NewWriter(netConn), 183 | br: bufio.NewReader(netConn), 184 | readTimeout: do.readTimeout, 185 | writeTimeout: do.writeTimeout, 186 | } 187 | 188 | if do.password != "" { 189 | if _, err := c.Do("AUTH", do.password); err != nil { 190 | netConn.Close() 191 | return nil, err 192 | } 193 | } 194 | 195 | if do.db != 0 { 196 | if _, err := c.Do("SELECT", do.db); err != nil { 197 | netConn.Close() 198 | return nil, err 199 | } 200 | } 201 | 202 | return c, nil 203 | } 204 | 205 | func dialTLS(do *dialOptions) { 206 | do.dialTLS = true 207 | } 208 | 209 | var pathDBRegexp = regexp.MustCompile(`/(\d*)\z`) 210 | 211 | // DialURL connects to a Redis server at the given URL using the Redis 212 | // URI scheme. URLs should follow the draft IANA specification for the 213 | // scheme (https://www.iana.org/assignments/uri-schemes/prov/redis). 214 | func DialURL(rawurl string, options ...DialOption) (Conn, error) { 215 | u, err := url.Parse(rawurl) 216 | if err != nil { 217 | return nil, err 218 | } 219 | 220 | if u.Scheme != "redis" && u.Scheme != "rediss" { 221 | return nil, fmt.Errorf("invalid redis URL scheme: %s", u.Scheme) 222 | } 223 | 224 | // As per the IANA draft spec, the host defaults to localhost and 225 | // the port defaults to 6379. 226 | host, port, err := net.SplitHostPort(u.Host) 227 | if err != nil { 228 | // assume port is missing 229 | host = u.Host 230 | port = "6379" 231 | } 232 | if host == "" { 233 | host = "localhost" 234 | } 235 | address := net.JoinHostPort(host, port) 236 | 237 | if u.User != nil { 238 | password, isSet := u.User.Password() 239 | if isSet { 240 | options = append(options, DialPassword(password)) 241 | } 242 | } 243 | 244 | match := pathDBRegexp.FindStringSubmatch(u.Path) 245 | if len(match) == 2 { 246 | db := 0 247 | if len(match[1]) > 0 { 248 | db, err = strconv.Atoi(match[1]) 249 | if err != nil { 250 | return nil, fmt.Errorf("invalid database: %s", u.Path[1:]) 251 | } 252 | } 253 | if db != 0 { 254 | options = append(options, DialDatabase(db)) 255 | } 256 | } else if u.Path != "" { 257 | return nil, fmt.Errorf("invalid database: %s", u.Path[1:]) 258 | } 259 | 260 | if u.Scheme == "rediss" { 261 | options = append([]DialOption{{dialTLS}}, options...) 262 | } 263 | 264 | return Dial("tcp", address, options...) 265 | } 266 | 267 | // NewConn returns a new Redigo connection for the given net connection. 268 | func NewConn(netConn net.Conn, readTimeout, writeTimeout time.Duration) Conn { 269 | return &conn{ 270 | conn: netConn, 271 | bw: bufio.NewWriter(netConn), 272 | br: bufio.NewReader(netConn), 273 | readTimeout: readTimeout, 274 | writeTimeout: writeTimeout, 275 | } 276 | } 277 | 278 | func (c *conn) Close() error { 279 | c.mu.Lock() 280 | err := c.err 281 | if c.err == nil { 282 | c.err = errors.New("redigo: closed") 283 | err = c.conn.Close() 284 | } 285 | c.mu.Unlock() 286 | return err 287 | } 288 | 289 | func (c *conn) fatal(err error) error { 290 | c.mu.Lock() 291 | if c.err == nil { 292 | c.err = err 293 | // Close connection to force errors on subsequent calls and to unblock 294 | // other reader or writer. 295 | c.conn.Close() 296 | } 297 | c.mu.Unlock() 298 | return err 299 | } 300 | 301 | func (c *conn) Err() error { 302 | c.mu.Lock() 303 | err := c.err 304 | c.mu.Unlock() 305 | return err 306 | } 307 | 308 | func (c *conn) writeLen(prefix byte, n int) error { 309 | c.lenScratch[len(c.lenScratch)-1] = '\n' 310 | c.lenScratch[len(c.lenScratch)-2] = '\r' 311 | i := len(c.lenScratch) - 3 312 | for { 313 | c.lenScratch[i] = byte('0' + n%10) 314 | i -= 1 315 | n = n / 10 316 | if n == 0 { 317 | break 318 | } 319 | } 320 | c.lenScratch[i] = prefix 321 | _, err := c.bw.Write(c.lenScratch[i:]) 322 | return err 323 | } 324 | 325 | func (c *conn) writeString(s string) error { 326 | c.writeLen('$', len(s)) 327 | c.bw.WriteString(s) 328 | _, err := c.bw.WriteString("\r\n") 329 | return err 330 | } 331 | 332 | func (c *conn) writeBytes(p []byte) error { 333 | c.writeLen('$', len(p)) 334 | c.bw.Write(p) 335 | _, err := c.bw.WriteString("\r\n") 336 | return err 337 | } 338 | 339 | func (c *conn) writeInt64(n int64) error { 340 | return c.writeBytes(strconv.AppendInt(c.numScratch[:0], n, 10)) 341 | } 342 | 343 | func (c *conn) writeFloat64(n float64) error { 344 | return c.writeBytes(strconv.AppendFloat(c.numScratch[:0], n, 'g', -1, 64)) 345 | } 346 | 347 | func (c *conn) writeCommand(cmd string, args []interface{}) (err error) { 348 | c.writeLen('*', 1+len(args)) 349 | err = c.writeString(cmd) 350 | for _, arg := range args { 351 | if err != nil { 352 | break 353 | } 354 | switch arg := arg.(type) { 355 | case string: 356 | err = c.writeString(arg) 357 | case []byte: 358 | err = c.writeBytes(arg) 359 | case int: 360 | err = c.writeInt64(int64(arg)) 361 | case int64: 362 | err = c.writeInt64(arg) 363 | case float64: 364 | err = c.writeFloat64(arg) 365 | case bool: 366 | if arg { 367 | err = c.writeString("1") 368 | } else { 369 | err = c.writeString("0") 370 | } 371 | case nil: 372 | err = c.writeString("") 373 | default: 374 | var buf bytes.Buffer 375 | fmt.Fprint(&buf, arg) 376 | err = c.writeBytes(buf.Bytes()) 377 | } 378 | } 379 | return err 380 | } 381 | 382 | type protocolError string 383 | 384 | func (pe protocolError) Error() string { 385 | return fmt.Sprintf("redigo: %s (possible server error or unsupported concurrent read by application)", string(pe)) 386 | } 387 | 388 | func (c *conn) readLine() ([]byte, error) { 389 | p, err := c.br.ReadSlice('\n') 390 | if err == bufio.ErrBufferFull { 391 | return nil, protocolError("long response line") 392 | } 393 | if err != nil { 394 | return nil, err 395 | } 396 | i := len(p) - 2 397 | if i < 0 || p[i] != '\r' { 398 | return nil, protocolError("bad response line terminator") 399 | } 400 | return p[:i], nil 401 | } 402 | 403 | // parseLen parses bulk string and array lengths. 404 | func parseLen(p []byte) (int, error) { 405 | if len(p) == 0 { 406 | return -1, protocolError("malformed length") 407 | } 408 | 409 | if p[0] == '-' && len(p) == 2 && p[1] == '1' { 410 | // handle $-1 and $-1 null replies. 411 | return -1, nil 412 | } 413 | 414 | var n int 415 | for _, b := range p { 416 | n *= 10 417 | if b < '0' || b > '9' { 418 | return -1, protocolError("illegal bytes in length") 419 | } 420 | n += int(b - '0') 421 | } 422 | 423 | return n, nil 424 | } 425 | 426 | // parseInt parses an integer reply. 427 | func parseInt(p []byte) (interface{}, error) { 428 | if len(p) == 0 { 429 | return 0, protocolError("malformed integer") 430 | } 431 | 432 | var negate bool 433 | if p[0] == '-' { 434 | negate = true 435 | p = p[1:] 436 | if len(p) == 0 { 437 | return 0, protocolError("malformed integer") 438 | } 439 | } 440 | 441 | var n int64 442 | for _, b := range p { 443 | n *= 10 444 | if b < '0' || b > '9' { 445 | return 0, protocolError("illegal bytes in length") 446 | } 447 | n += int64(b - '0') 448 | } 449 | 450 | if negate { 451 | n = -n 452 | } 453 | return n, nil 454 | } 455 | 456 | var ( 457 | okReply interface{} = "OK" 458 | pongReply interface{} = "PONG" 459 | ) 460 | 461 | func (c *conn) readReply() (interface{}, error) { 462 | line, err := c.readLine() 463 | if err != nil { 464 | return nil, err 465 | } 466 | if len(line) == 0 { 467 | return nil, protocolError("short response line") 468 | } 469 | switch line[0] { 470 | case '+': 471 | switch { 472 | case len(line) == 3 && line[1] == 'O' && line[2] == 'K': 473 | // Avoid allocation for frequent "+OK" response. 474 | return okReply, nil 475 | case len(line) == 5 && line[1] == 'P' && line[2] == 'O' && line[3] == 'N' && line[4] == 'G': 476 | // Avoid allocation in PING command benchmarks :) 477 | return pongReply, nil 478 | default: 479 | return string(line[1:]), nil 480 | } 481 | case '-': 482 | return Error(string(line[1:])), nil 483 | case ':': 484 | return parseInt(line[1:]) 485 | case '$': 486 | n, err := parseLen(line[1:]) 487 | if n < 0 || err != nil { 488 | return nil, err 489 | } 490 | p := make([]byte, n) 491 | _, err = io.ReadFull(c.br, p) 492 | if err != nil { 493 | return nil, err 494 | } 495 | if line, err := c.readLine(); err != nil { 496 | return nil, err 497 | } else if len(line) != 0 { 498 | return nil, protocolError("bad bulk string format") 499 | } 500 | return p, nil 501 | case '*': 502 | n, err := parseLen(line[1:]) 503 | if n < 0 || err != nil { 504 | return nil, err 505 | } 506 | r := make([]interface{}, n) 507 | for i := range r { 508 | r[i], err = c.readReply() 509 | if err != nil { 510 | return nil, err 511 | } 512 | } 513 | return r, nil 514 | } 515 | return nil, protocolError("unexpected response line") 516 | } 517 | 518 | func (c *conn) Send(cmd string, args ...interface{}) error { 519 | c.mu.Lock() 520 | c.pending += 1 521 | c.mu.Unlock() 522 | if c.writeTimeout != 0 { 523 | c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout)) 524 | } 525 | if err := c.writeCommand(cmd, args); err != nil { 526 | return c.fatal(err) 527 | } 528 | return nil 529 | } 530 | 531 | func (c *conn) Flush() error { 532 | if c.writeTimeout != 0 { 533 | c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout)) 534 | } 535 | if err := c.bw.Flush(); err != nil { 536 | return c.fatal(err) 537 | } 538 | return nil 539 | } 540 | 541 | func (c *conn) Receive() (reply interface{}, err error) { 542 | if c.readTimeout != 0 { 543 | c.conn.SetReadDeadline(time.Now().Add(c.readTimeout)) 544 | } 545 | if reply, err = c.readReply(); err != nil { 546 | return nil, c.fatal(err) 547 | } 548 | // When using pub/sub, the number of receives can be greater than the 549 | // number of sends. To enable normal use of the connection after 550 | // unsubscribing from all channels, we do not decrement pending to a 551 | // negative value. 552 | // 553 | // The pending field is decremented after the reply is read to handle the 554 | // case where Receive is called before Send. 555 | c.mu.Lock() 556 | if c.pending > 0 { 557 | c.pending -= 1 558 | } 559 | c.mu.Unlock() 560 | if err, ok := reply.(Error); ok { 561 | return nil, err 562 | } 563 | return 564 | } 565 | 566 | func (c *conn) Do(cmd string, args ...interface{}) (interface{}, error) { 567 | c.mu.Lock() 568 | pending := c.pending 569 | c.pending = 0 570 | c.mu.Unlock() 571 | 572 | if cmd == "" && pending == 0 { 573 | return nil, nil 574 | } 575 | 576 | if c.writeTimeout != 0 { 577 | c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout)) 578 | } 579 | 580 | if cmd != "" { 581 | if err := c.writeCommand(cmd, args); err != nil { 582 | return nil, c.fatal(err) 583 | } 584 | } 585 | 586 | if err := c.bw.Flush(); err != nil { 587 | return nil, c.fatal(err) 588 | } 589 | 590 | if c.readTimeout != 0 { 591 | c.conn.SetReadDeadline(time.Now().Add(c.readTimeout)) 592 | } 593 | 594 | if cmd == "" { 595 | reply := make([]interface{}, pending) 596 | for i := range reply { 597 | r, e := c.readReply() 598 | if e != nil { 599 | return nil, c.fatal(e) 600 | } 601 | reply[i] = r 602 | } 603 | return reply, nil 604 | } 605 | 606 | var err error 607 | var reply interface{} 608 | for i := 0; i <= pending; i++ { 609 | var e error 610 | if reply, e = c.readReply(); e != nil { 611 | return nil, c.fatal(e) 612 | } 613 | if e, ok := reply.(Error); ok && err == nil { 614 | err = e 615 | } 616 | } 617 | return reply, err 618 | } 619 | -------------------------------------------------------------------------------- /redis/conn_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // 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, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package redis_test 16 | 17 | import ( 18 | "bytes" 19 | "io" 20 | "math" 21 | "net" 22 | "os" 23 | "reflect" 24 | "strings" 25 | "testing" 26 | "time" 27 | 28 | "github.com/garyburd/redigo/redis" 29 | ) 30 | 31 | type testConn struct { 32 | io.Reader 33 | io.Writer 34 | } 35 | 36 | func (*testConn) Close() error { return nil } 37 | func (*testConn) LocalAddr() net.Addr { return nil } 38 | func (*testConn) RemoteAddr() net.Addr { return nil } 39 | func (*testConn) SetDeadline(t time.Time) error { return nil } 40 | func (*testConn) SetReadDeadline(t time.Time) error { return nil } 41 | func (*testConn) SetWriteDeadline(t time.Time) error { return nil } 42 | 43 | func dialTestConn(r io.Reader, w io.Writer) redis.DialOption { 44 | return redis.DialNetDial(func(net, addr string) (net.Conn, error) { 45 | return &testConn{Reader: r, Writer: w}, nil 46 | }) 47 | } 48 | 49 | var writeTests = []struct { 50 | args []interface{} 51 | expected string 52 | }{ 53 | { 54 | []interface{}{"SET", "key", "value"}, 55 | "*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n", 56 | }, 57 | { 58 | []interface{}{"SET", "key", "value"}, 59 | "*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n", 60 | }, 61 | { 62 | []interface{}{"SET", "key", byte(100)}, 63 | "*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$3\r\n100\r\n", 64 | }, 65 | { 66 | []interface{}{"SET", "key", 100}, 67 | "*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$3\r\n100\r\n", 68 | }, 69 | { 70 | []interface{}{"SET", "key", int64(math.MinInt64)}, 71 | "*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$20\r\n-9223372036854775808\r\n", 72 | }, 73 | { 74 | []interface{}{"SET", "key", float64(1349673917.939762)}, 75 | "*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$21\r\n1.349673917939762e+09\r\n", 76 | }, 77 | { 78 | []interface{}{"SET", "key", ""}, 79 | "*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$0\r\n\r\n", 80 | }, 81 | { 82 | []interface{}{"SET", "key", nil}, 83 | "*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$0\r\n\r\n", 84 | }, 85 | { 86 | []interface{}{"ECHO", true, false}, 87 | "*3\r\n$4\r\nECHO\r\n$1\r\n1\r\n$1\r\n0\r\n", 88 | }, 89 | } 90 | 91 | func TestWrite(t *testing.T) { 92 | for _, tt := range writeTests { 93 | var buf bytes.Buffer 94 | c, _ := redis.Dial("", "", dialTestConn(nil, &buf)) 95 | err := c.Send(tt.args[0].(string), tt.args[1:]...) 96 | if err != nil { 97 | t.Errorf("Send(%v) returned error %v", tt.args, err) 98 | continue 99 | } 100 | c.Flush() 101 | actual := buf.String() 102 | if actual != tt.expected { 103 | t.Errorf("Send(%v) = %q, want %q", tt.args, actual, tt.expected) 104 | } 105 | } 106 | } 107 | 108 | var errorSentinel = &struct{}{} 109 | 110 | var readTests = []struct { 111 | reply string 112 | expected interface{} 113 | }{ 114 | { 115 | "+OK\r\n", 116 | "OK", 117 | }, 118 | { 119 | "+PONG\r\n", 120 | "PONG", 121 | }, 122 | { 123 | "@OK\r\n", 124 | errorSentinel, 125 | }, 126 | { 127 | "$6\r\nfoobar\r\n", 128 | []byte("foobar"), 129 | }, 130 | { 131 | "$-1\r\n", 132 | nil, 133 | }, 134 | { 135 | ":1\r\n", 136 | int64(1), 137 | }, 138 | { 139 | ":-2\r\n", 140 | int64(-2), 141 | }, 142 | { 143 | "*0\r\n", 144 | []interface{}{}, 145 | }, 146 | { 147 | "*-1\r\n", 148 | nil, 149 | }, 150 | { 151 | "*4\r\n$3\r\nfoo\r\n$3\r\nbar\r\n$5\r\nHello\r\n$5\r\nWorld\r\n", 152 | []interface{}{[]byte("foo"), []byte("bar"), []byte("Hello"), []byte("World")}, 153 | }, 154 | { 155 | "*3\r\n$3\r\nfoo\r\n$-1\r\n$3\r\nbar\r\n", 156 | []interface{}{[]byte("foo"), nil, []byte("bar")}, 157 | }, 158 | 159 | { 160 | // "x" is not a valid length 161 | "$x\r\nfoobar\r\n", 162 | errorSentinel, 163 | }, 164 | { 165 | // -2 is not a valid length 166 | "$-2\r\n", 167 | errorSentinel, 168 | }, 169 | { 170 | // "x" is not a valid integer 171 | ":x\r\n", 172 | errorSentinel, 173 | }, 174 | { 175 | // missing \r\n following value 176 | "$6\r\nfoobar", 177 | errorSentinel, 178 | }, 179 | { 180 | // short value 181 | "$6\r\nxx", 182 | errorSentinel, 183 | }, 184 | { 185 | // long value 186 | "$6\r\nfoobarx\r\n", 187 | errorSentinel, 188 | }, 189 | } 190 | 191 | func TestRead(t *testing.T) { 192 | for _, tt := range readTests { 193 | c, _ := redis.Dial("", "", dialTestConn(strings.NewReader(tt.reply), nil)) 194 | actual, err := c.Receive() 195 | if tt.expected == errorSentinel { 196 | if err == nil { 197 | t.Errorf("Receive(%q) did not return expected error", tt.reply) 198 | } 199 | } else { 200 | if err != nil { 201 | t.Errorf("Receive(%q) returned error %v", tt.reply, err) 202 | continue 203 | } 204 | if !reflect.DeepEqual(actual, tt.expected) { 205 | t.Errorf("Receive(%q) = %v, want %v", tt.reply, actual, tt.expected) 206 | } 207 | } 208 | } 209 | } 210 | 211 | var testCommands = []struct { 212 | args []interface{} 213 | expected interface{} 214 | }{ 215 | { 216 | []interface{}{"PING"}, 217 | "PONG", 218 | }, 219 | { 220 | []interface{}{"SET", "foo", "bar"}, 221 | "OK", 222 | }, 223 | { 224 | []interface{}{"GET", "foo"}, 225 | []byte("bar"), 226 | }, 227 | { 228 | []interface{}{"GET", "nokey"}, 229 | nil, 230 | }, 231 | { 232 | []interface{}{"MGET", "nokey", "foo"}, 233 | []interface{}{nil, []byte("bar")}, 234 | }, 235 | { 236 | []interface{}{"INCR", "mycounter"}, 237 | int64(1), 238 | }, 239 | { 240 | []interface{}{"LPUSH", "mylist", "foo"}, 241 | int64(1), 242 | }, 243 | { 244 | []interface{}{"LPUSH", "mylist", "bar"}, 245 | int64(2), 246 | }, 247 | { 248 | []interface{}{"LRANGE", "mylist", 0, -1}, 249 | []interface{}{[]byte("bar"), []byte("foo")}, 250 | }, 251 | { 252 | []interface{}{"MULTI"}, 253 | "OK", 254 | }, 255 | { 256 | []interface{}{"LRANGE", "mylist", 0, -1}, 257 | "QUEUED", 258 | }, 259 | { 260 | []interface{}{"PING"}, 261 | "QUEUED", 262 | }, 263 | { 264 | []interface{}{"EXEC"}, 265 | []interface{}{ 266 | []interface{}{[]byte("bar"), []byte("foo")}, 267 | "PONG", 268 | }, 269 | }, 270 | } 271 | 272 | func TestDoCommands(t *testing.T) { 273 | c, err := redis.DialDefaultServer() 274 | if err != nil { 275 | t.Fatalf("error connection to database, %v", err) 276 | } 277 | defer c.Close() 278 | 279 | for _, cmd := range testCommands { 280 | actual, err := c.Do(cmd.args[0].(string), cmd.args[1:]...) 281 | if err != nil { 282 | t.Errorf("Do(%v) returned error %v", cmd.args, err) 283 | continue 284 | } 285 | if !reflect.DeepEqual(actual, cmd.expected) { 286 | t.Errorf("Do(%v) = %v, want %v", cmd.args, actual, cmd.expected) 287 | } 288 | } 289 | } 290 | 291 | func TestPipelineCommands(t *testing.T) { 292 | c, err := redis.DialDefaultServer() 293 | if err != nil { 294 | t.Fatalf("error connection to database, %v", err) 295 | } 296 | defer c.Close() 297 | 298 | for _, cmd := range testCommands { 299 | if err := c.Send(cmd.args[0].(string), cmd.args[1:]...); err != nil { 300 | t.Fatalf("Send(%v) returned error %v", cmd.args, err) 301 | } 302 | } 303 | if err := c.Flush(); err != nil { 304 | t.Errorf("Flush() returned error %v", err) 305 | } 306 | for _, cmd := range testCommands { 307 | actual, err := c.Receive() 308 | if err != nil { 309 | t.Fatalf("Receive(%v) returned error %v", cmd.args, err) 310 | } 311 | if !reflect.DeepEqual(actual, cmd.expected) { 312 | t.Errorf("Receive(%v) = %v, want %v", cmd.args, actual, cmd.expected) 313 | } 314 | } 315 | } 316 | 317 | func TestBlankCommmand(t *testing.T) { 318 | c, err := redis.DialDefaultServer() 319 | if err != nil { 320 | t.Fatalf("error connection to database, %v", err) 321 | } 322 | defer c.Close() 323 | 324 | for _, cmd := range testCommands { 325 | if err := c.Send(cmd.args[0].(string), cmd.args[1:]...); err != nil { 326 | t.Fatalf("Send(%v) returned error %v", cmd.args, err) 327 | } 328 | } 329 | reply, err := redis.Values(c.Do("")) 330 | if err != nil { 331 | t.Fatalf("Do() returned error %v", err) 332 | } 333 | if len(reply) != len(testCommands) { 334 | t.Fatalf("len(reply)=%d, want %d", len(reply), len(testCommands)) 335 | } 336 | for i, cmd := range testCommands { 337 | actual := reply[i] 338 | if !reflect.DeepEqual(actual, cmd.expected) { 339 | t.Errorf("Receive(%v) = %v, want %v", cmd.args, actual, cmd.expected) 340 | } 341 | } 342 | } 343 | 344 | func TestRecvBeforeSend(t *testing.T) { 345 | c, err := redis.DialDefaultServer() 346 | if err != nil { 347 | t.Fatalf("error connection to database, %v", err) 348 | } 349 | defer c.Close() 350 | done := make(chan struct{}) 351 | go func() { 352 | c.Receive() 353 | close(done) 354 | }() 355 | time.Sleep(time.Millisecond) 356 | c.Send("PING") 357 | c.Flush() 358 | <-done 359 | _, err = c.Do("") 360 | if err != nil { 361 | t.Fatalf("error=%v", err) 362 | } 363 | } 364 | 365 | func TestError(t *testing.T) { 366 | c, err := redis.DialDefaultServer() 367 | if err != nil { 368 | t.Fatalf("error connection to database, %v", err) 369 | } 370 | defer c.Close() 371 | 372 | c.Do("SET", "key", "val") 373 | _, err = c.Do("HSET", "key", "fld", "val") 374 | if err == nil { 375 | t.Errorf("Expected err for HSET on string key.") 376 | } 377 | if c.Err() != nil { 378 | t.Errorf("Conn has Err()=%v, expect nil", c.Err()) 379 | } 380 | _, err = c.Do("SET", "key", "val") 381 | if err != nil { 382 | t.Errorf("Do(SET, key, val) returned error %v, expected nil.", err) 383 | } 384 | } 385 | 386 | func TestReadTimeout(t *testing.T) { 387 | l, err := net.Listen("tcp", "127.0.0.1:0") 388 | if err != nil { 389 | t.Fatalf("net.Listen returned %v", err) 390 | } 391 | defer l.Close() 392 | 393 | go func() { 394 | for { 395 | c, err := l.Accept() 396 | if err != nil { 397 | return 398 | } 399 | go func() { 400 | time.Sleep(time.Second) 401 | c.Write([]byte("+OK\r\n")) 402 | c.Close() 403 | }() 404 | } 405 | }() 406 | 407 | // Do 408 | 409 | c1, err := redis.Dial(l.Addr().Network(), l.Addr().String(), redis.DialReadTimeout(time.Millisecond)) 410 | if err != nil { 411 | t.Fatalf("redis.Dial returned %v", err) 412 | } 413 | defer c1.Close() 414 | 415 | _, err = c1.Do("PING") 416 | if err == nil { 417 | t.Fatalf("c1.Do() returned nil, expect error") 418 | } 419 | if c1.Err() == nil { 420 | t.Fatalf("c1.Err() = nil, expect error") 421 | } 422 | 423 | // Send/Flush/Receive 424 | 425 | c2, err := redis.Dial(l.Addr().Network(), l.Addr().String(), redis.DialReadTimeout(time.Millisecond)) 426 | if err != nil { 427 | t.Fatalf("redis.Dial returned %v", err) 428 | } 429 | defer c2.Close() 430 | 431 | c2.Send("PING") 432 | c2.Flush() 433 | _, err = c2.Receive() 434 | if err == nil { 435 | t.Fatalf("c2.Receive() returned nil, expect error") 436 | } 437 | if c2.Err() == nil { 438 | t.Fatalf("c2.Err() = nil, expect error") 439 | } 440 | } 441 | 442 | var dialErrors = []struct { 443 | rawurl string 444 | expectedError string 445 | }{ 446 | { 447 | "localhost", 448 | "invalid redis URL scheme", 449 | }, 450 | // The error message for invalid hosts is diffferent in different 451 | // versions of Go, so just check that there is an error message. 452 | { 453 | "redis://weird url", 454 | "", 455 | }, 456 | { 457 | "redis://foo:bar:baz", 458 | "", 459 | }, 460 | { 461 | "http://www.google.com", 462 | "invalid redis URL scheme: http", 463 | }, 464 | { 465 | "redis://localhost:6379/abc123", 466 | "invalid database: abc123", 467 | }, 468 | } 469 | 470 | func TestDialURLErrors(t *testing.T) { 471 | for _, d := range dialErrors { 472 | _, err := redis.DialURL(d.rawurl) 473 | if err == nil || !strings.Contains(err.Error(), d.expectedError) { 474 | t.Errorf("DialURL did not return expected error (expected %v to contain %s)", err, d.expectedError) 475 | } 476 | } 477 | } 478 | 479 | func TestDialURLPort(t *testing.T) { 480 | checkPort := func(network, address string) (net.Conn, error) { 481 | if address != "localhost:6379" { 482 | t.Errorf("DialURL did not set port to 6379 by default (got %v)", address) 483 | } 484 | return nil, nil 485 | } 486 | _, err := redis.DialURL("redis://localhost", redis.DialNetDial(checkPort)) 487 | if err != nil { 488 | t.Error("dial error:", err) 489 | } 490 | } 491 | 492 | func TestDialURLHost(t *testing.T) { 493 | checkHost := func(network, address string) (net.Conn, error) { 494 | if address != "localhost:6379" { 495 | t.Errorf("DialURL did not set host to localhost by default (got %v)", address) 496 | } 497 | return nil, nil 498 | } 499 | _, err := redis.DialURL("redis://:6379", redis.DialNetDial(checkHost)) 500 | if err != nil { 501 | t.Error("dial error:", err) 502 | } 503 | } 504 | 505 | func TestDialURLPassword(t *testing.T) { 506 | var buf bytes.Buffer 507 | _, err := redis.DialURL("redis://x:abc123@localhost", dialTestConn(strings.NewReader("+OK\r\n"), &buf)) 508 | if err != nil { 509 | t.Error("dial error:", err) 510 | } 511 | expected := "*2\r\n$4\r\nAUTH\r\n$6\r\nabc123\r\n" 512 | actual := buf.String() 513 | if actual != expected { 514 | t.Errorf("commands = %q, want %q", actual, expected) 515 | } 516 | } 517 | 518 | func TestDialURLDatabase(t *testing.T) { 519 | var buf3 bytes.Buffer 520 | _, err3 := redis.DialURL("redis://localhost/3", dialTestConn(strings.NewReader("+OK\r\n"), &buf3)) 521 | if err3 != nil { 522 | t.Error("dial error:", err3) 523 | } 524 | expected3 := "*2\r\n$6\r\nSELECT\r\n$1\r\n3\r\n" 525 | actual3 := buf3.String() 526 | if actual3 != expected3 { 527 | t.Errorf("commands = %q, want %q", actual3, expected3) 528 | } 529 | // empty DB means 0 530 | var buf0 bytes.Buffer 531 | _, err0 := redis.DialURL("redis://localhost/", dialTestConn(strings.NewReader("+OK\r\n"), &buf0)) 532 | if err0 != nil { 533 | t.Error("dial error:", err0) 534 | } 535 | expected0 := "" 536 | actual0 := buf0.String() 537 | if actual0 != expected0 { 538 | t.Errorf("commands = %q, want %q", actual0, expected0) 539 | } 540 | } 541 | 542 | // Connect to local instance of Redis running on the default port. 543 | func ExampleDial() { 544 | c, err := redis.Dial("tcp", ":6379") 545 | if err != nil { 546 | // handle error 547 | } 548 | defer c.Close() 549 | } 550 | 551 | // Connect to remote instance of Redis using a URL. 552 | func ExampleDialURL() { 553 | c, err := redis.DialURL(os.Getenv("REDIS_URL")) 554 | if err != nil { 555 | // handle connection error 556 | } 557 | defer c.Close() 558 | } 559 | 560 | // TextExecError tests handling of errors in a transaction. See 561 | // http://redis.io/topics/transactions for information on how Redis handles 562 | // errors in a transaction. 563 | func TestExecError(t *testing.T) { 564 | c, err := redis.DialDefaultServer() 565 | if err != nil { 566 | t.Fatalf("error connection to database, %v", err) 567 | } 568 | defer c.Close() 569 | 570 | // Execute commands that fail before EXEC is called. 571 | 572 | c.Do("DEL", "k0") 573 | c.Do("ZADD", "k0", 0, 0) 574 | c.Send("MULTI") 575 | c.Send("NOTACOMMAND", "k0", 0, 0) 576 | c.Send("ZINCRBY", "k0", 0, 0) 577 | v, err := c.Do("EXEC") 578 | if err == nil { 579 | t.Fatalf("EXEC returned values %v, expected error", v) 580 | } 581 | 582 | // Execute commands that fail after EXEC is called. The first command 583 | // returns an error. 584 | 585 | c.Do("DEL", "k1") 586 | c.Do("ZADD", "k1", 0, 0) 587 | c.Send("MULTI") 588 | c.Send("HSET", "k1", 0, 0) 589 | c.Send("ZINCRBY", "k1", 0, 0) 590 | v, err = c.Do("EXEC") 591 | if err != nil { 592 | t.Fatalf("EXEC returned error %v", err) 593 | } 594 | 595 | vs, err := redis.Values(v, nil) 596 | if err != nil { 597 | t.Fatalf("Values(v) returned error %v", err) 598 | } 599 | 600 | if len(vs) != 2 { 601 | t.Fatalf("len(vs) == %d, want 2", len(vs)) 602 | } 603 | 604 | if _, ok := vs[0].(error); !ok { 605 | t.Fatalf("first result is type %T, expected error", vs[0]) 606 | } 607 | 608 | if _, ok := vs[1].([]byte); !ok { 609 | t.Fatalf("second result is type %T, expected []byte", vs[1]) 610 | } 611 | 612 | // Execute commands that fail after EXEC is called. The second command 613 | // returns an error. 614 | 615 | c.Do("ZADD", "k2", 0, 0) 616 | c.Send("MULTI") 617 | c.Send("ZINCRBY", "k2", 0, 0) 618 | c.Send("HSET", "k2", 0, 0) 619 | v, err = c.Do("EXEC") 620 | if err != nil { 621 | t.Fatalf("EXEC returned error %v", err) 622 | } 623 | 624 | vs, err = redis.Values(v, nil) 625 | if err != nil { 626 | t.Fatalf("Values(v) returned error %v", err) 627 | } 628 | 629 | if len(vs) != 2 { 630 | t.Fatalf("len(vs) == %d, want 2", len(vs)) 631 | } 632 | 633 | if _, ok := vs[0].([]byte); !ok { 634 | t.Fatalf("first result is type %T, expected []byte", vs[0]) 635 | } 636 | 637 | if _, ok := vs[1].(error); !ok { 638 | t.Fatalf("second result is type %T, expected error", vs[2]) 639 | } 640 | } 641 | 642 | func BenchmarkDoEmpty(b *testing.B) { 643 | b.StopTimer() 644 | c, err := redis.DialDefaultServer() 645 | if err != nil { 646 | b.Fatal(err) 647 | } 648 | defer c.Close() 649 | b.StartTimer() 650 | for i := 0; i < b.N; i++ { 651 | if _, err := c.Do(""); err != nil { 652 | b.Fatal(err) 653 | } 654 | } 655 | } 656 | 657 | func BenchmarkDoPing(b *testing.B) { 658 | b.StopTimer() 659 | c, err := redis.DialDefaultServer() 660 | if err != nil { 661 | b.Fatal(err) 662 | } 663 | defer c.Close() 664 | b.StartTimer() 665 | for i := 0; i < b.N; i++ { 666 | if _, err := c.Do("PING"); err != nil { 667 | b.Fatal(err) 668 | } 669 | } 670 | } 671 | --------------------------------------------------------------------------------