summaryrefslogtreecommitdiffstats
path: root/Godeps/_workspace/src/github.com/garyburd/redigo
diff options
context:
space:
mode:
author=Corey Hulen <corey@hulen.com>2015-07-29 01:26:10 -0800
committer=Corey Hulen <corey@hulen.com>2015-07-29 01:26:10 -0800
commit9677a9f71777d75f3def0b0cb238050a30ec6a67 (patch)
tree8579fae8134ef2167a2cc97d95b0225e8acfdb46 /Godeps/_workspace/src/github.com/garyburd/redigo
parent81e55eb57c367a1403f693712411c0781287ea55 (diff)
downloadchat-9677a9f71777d75f3def0b0cb238050a30ec6a67.tar.gz
chat-9677a9f71777d75f3def0b0cb238050a30ec6a67.tar.bz2
chat-9677a9f71777d75f3def0b0cb238050a30ec6a67.zip
Fixes mm-1355 adds rate limiting apis
Diffstat (limited to 'Godeps/_workspace/src/github.com/garyburd/redigo')
-rw-r--r--Godeps/_workspace/src/github.com/garyburd/redigo/internal/commandinfo.go54
-rw-r--r--Godeps/_workspace/src/github.com/garyburd/redigo/internal/commandinfo_test.go27
-rw-r--r--Godeps/_workspace/src/github.com/garyburd/redigo/internal/redistest/testdb.go65
-rw-r--r--Godeps/_workspace/src/github.com/garyburd/redigo/redis/conn.go455
-rw-r--r--Godeps/_workspace/src/github.com/garyburd/redigo/redis/conn_test.go542
-rw-r--r--Godeps/_workspace/src/github.com/garyburd/redigo/redis/doc.go169
-rw-r--r--Godeps/_workspace/src/github.com/garyburd/redigo/redis/log.go117
-rw-r--r--Godeps/_workspace/src/github.com/garyburd/redigo/redis/pool.go389
-rw-r--r--Godeps/_workspace/src/github.com/garyburd/redigo/redis/pool_test.go674
-rw-r--r--Godeps/_workspace/src/github.com/garyburd/redigo/redis/pubsub.go144
-rw-r--r--Godeps/_workspace/src/github.com/garyburd/redigo/redis/pubsub_test.go150
-rw-r--r--Godeps/_workspace/src/github.com/garyburd/redigo/redis/redis.go44
-rw-r--r--Godeps/_workspace/src/github.com/garyburd/redigo/redis/reply.go364
-rw-r--r--Godeps/_workspace/src/github.com/garyburd/redigo/redis/reply_test.go166
-rw-r--r--Godeps/_workspace/src/github.com/garyburd/redigo/redis/scan.go513
-rw-r--r--Godeps/_workspace/src/github.com/garyburd/redigo/redis/scan_test.go412
-rw-r--r--Godeps/_workspace/src/github.com/garyburd/redigo/redis/script.go86
-rw-r--r--Godeps/_workspace/src/github.com/garyburd/redigo/redis/script_test.go93
-rw-r--r--Godeps/_workspace/src/github.com/garyburd/redigo/redis/test_test.go38
-rw-r--r--Godeps/_workspace/src/github.com/garyburd/redigo/redis/zpop_example_test.go113
20 files changed, 4615 insertions, 0 deletions
diff --git a/Godeps/_workspace/src/github.com/garyburd/redigo/internal/commandinfo.go b/Godeps/_workspace/src/github.com/garyburd/redigo/internal/commandinfo.go
new file mode 100644
index 000000000..dbc60fc8e
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/garyburd/redigo/internal/commandinfo.go
@@ -0,0 +1,54 @@
+// Copyright 2014 Gary Burd
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package internal
+
+import (
+ "strings"
+)
+
+const (
+ WatchState = 1 << iota
+ MultiState
+ SubscribeState
+ MonitorState
+)
+
+type CommandInfo struct {
+ Set, Clear int
+}
+
+var commandInfos = map[string]CommandInfo{
+ "WATCH": {Set: WatchState},
+ "UNWATCH": {Clear: WatchState},
+ "MULTI": {Set: MultiState},
+ "EXEC": {Clear: WatchState | MultiState},
+ "DISCARD": {Clear: WatchState | MultiState},
+ "PSUBSCRIBE": {Set: SubscribeState},
+ "SUBSCRIBE": {Set: SubscribeState},
+ "MONITOR": {Set: MonitorState},
+}
+
+func init() {
+ for n, ci := range commandInfos {
+ commandInfos[strings.ToLower(n)] = ci
+ }
+}
+
+func LookupCommandInfo(commandName string) CommandInfo {
+ if ci, ok := commandInfos[commandName]; ok {
+ return ci
+ }
+ return commandInfos[strings.ToUpper(commandName)]
+}
diff --git a/Godeps/_workspace/src/github.com/garyburd/redigo/internal/commandinfo_test.go b/Godeps/_workspace/src/github.com/garyburd/redigo/internal/commandinfo_test.go
new file mode 100644
index 000000000..118e94b67
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/garyburd/redigo/internal/commandinfo_test.go
@@ -0,0 +1,27 @@
+package internal
+
+import "testing"
+
+func TestLookupCommandInfo(t *testing.T) {
+ for _, n := range []string{"watch", "WATCH", "wAtch"} {
+ if LookupCommandInfo(n) == (CommandInfo{}) {
+ t.Errorf("LookupCommandInfo(%q) = CommandInfo{}, expected non-zero value", n)
+ }
+ }
+}
+
+func benchmarkLookupCommandInfo(b *testing.B, names ...string) {
+ for i := 0; i < b.N; i++ {
+ for _, c := range names {
+ LookupCommandInfo(c)
+ }
+ }
+}
+
+func BenchmarkLookupCommandInfoCorrectCase(b *testing.B) {
+ benchmarkLookupCommandInfo(b, "watch", "WATCH", "monitor", "MONITOR")
+}
+
+func BenchmarkLookupCommandInfoMixedCase(b *testing.B) {
+ benchmarkLookupCommandInfo(b, "wAtch", "WeTCH", "monItor", "MONiTOR")
+}
diff --git a/Godeps/_workspace/src/github.com/garyburd/redigo/internal/redistest/testdb.go b/Godeps/_workspace/src/github.com/garyburd/redigo/internal/redistest/testdb.go
new file mode 100644
index 000000000..5f955c424
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/garyburd/redigo/internal/redistest/testdb.go
@@ -0,0 +1,65 @@
+// Copyright 2014 Gary Burd
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+// Package redistest contains utilities for writing Redigo tests.
+package redistest
+
+import (
+ "errors"
+ "time"
+
+ "github.com/garyburd/redigo/redis"
+)
+
+type testConn struct {
+ redis.Conn
+}
+
+func (t testConn) Close() error {
+ _, err := t.Conn.Do("SELECT", "9")
+ if err != nil {
+ return nil
+ }
+ _, err = t.Conn.Do("FLUSHDB")
+ if err != nil {
+ return err
+ }
+ return t.Conn.Close()
+}
+
+// Dial dials the local Redis server and selects database 9. To prevent
+// stomping on real data, DialTestDB fails if database 9 contains data. The
+// returned connection flushes database 9 on close.
+func Dial() (redis.Conn, error) {
+ c, err := redis.DialTimeout("tcp", ":6379", 0, 1*time.Second, 1*time.Second)
+ if err != nil {
+ return nil, err
+ }
+
+ _, err = c.Do("SELECT", "9")
+ if err != nil {
+ return nil, err
+ }
+
+ n, err := redis.Int(c.Do("DBSIZE"))
+ if err != nil {
+ return nil, err
+ }
+
+ if n != 0 {
+ return nil, errors.New("database #9 is not empty, test can not continue")
+ }
+
+ return testConn{c}, nil
+}
diff --git a/Godeps/_workspace/src/github.com/garyburd/redigo/redis/conn.go b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/conn.go
new file mode 100644
index 000000000..ac0e971c4
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/conn.go
@@ -0,0 +1,455 @@
+// Copyright 2012 Gary Burd
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package redis
+
+import (
+ "bufio"
+ "bytes"
+ "errors"
+ "fmt"
+ "io"
+ "net"
+ "strconv"
+ "sync"
+ "time"
+)
+
+// conn is the low-level implementation of Conn
+type conn struct {
+
+ // Shared
+ mu sync.Mutex
+ pending int
+ err error
+ conn net.Conn
+
+ // Read
+ readTimeout time.Duration
+ br *bufio.Reader
+
+ // Write
+ writeTimeout time.Duration
+ bw *bufio.Writer
+
+ // Scratch space for formatting argument length.
+ // '*' or '$', length, "\r\n"
+ lenScratch [32]byte
+
+ // Scratch space for formatting integers and floats.
+ numScratch [40]byte
+}
+
+// Dial connects to the Redis server at the given network and address.
+func Dial(network, address string) (Conn, error) {
+ dialer := xDialer{}
+ return dialer.Dial(network, address)
+}
+
+// DialTimeout acts like Dial but takes timeouts for establishing the
+// connection to the server, writing a command and reading a reply.
+func DialTimeout(network, address string, connectTimeout, readTimeout, writeTimeout time.Duration) (Conn, error) {
+ netDialer := net.Dialer{Timeout: connectTimeout}
+ dialer := xDialer{
+ NetDial: netDialer.Dial,
+ ReadTimeout: readTimeout,
+ WriteTimeout: writeTimeout,
+ }
+ return dialer.Dial(network, address)
+}
+
+// A Dialer specifies options for connecting to a Redis server.
+type xDialer struct {
+ // NetDial specifies the dial function for creating TCP connections. If
+ // NetDial is nil, then net.Dial is used.
+ NetDial func(network, addr string) (net.Conn, error)
+
+ // ReadTimeout specifies the timeout for reading a single command
+ // reply. If ReadTimeout is zero, then no timeout is used.
+ ReadTimeout time.Duration
+
+ // WriteTimeout specifies the timeout for writing a single command. If
+ // WriteTimeout is zero, then no timeout is used.
+ WriteTimeout time.Duration
+}
+
+// Dial connects to the Redis server at address on the named network.
+func (d *xDialer) Dial(network, address string) (Conn, error) {
+ dial := d.NetDial
+ if dial == nil {
+ dial = net.Dial
+ }
+ netConn, err := dial(network, address)
+ if err != nil {
+ return nil, err
+ }
+ return &conn{
+ conn: netConn,
+ bw: bufio.NewWriter(netConn),
+ br: bufio.NewReader(netConn),
+ readTimeout: d.ReadTimeout,
+ writeTimeout: d.WriteTimeout,
+ }, nil
+}
+
+// NewConn returns a new Redigo connection for the given net connection.
+func NewConn(netConn net.Conn, readTimeout, writeTimeout time.Duration) Conn {
+ return &conn{
+ conn: netConn,
+ bw: bufio.NewWriter(netConn),
+ br: bufio.NewReader(netConn),
+ readTimeout: readTimeout,
+ writeTimeout: writeTimeout,
+ }
+}
+
+func (c *conn) Close() error {
+ c.mu.Lock()
+ err := c.err
+ if c.err == nil {
+ c.err = errors.New("redigo: closed")
+ err = c.conn.Close()
+ }
+ c.mu.Unlock()
+ return err
+}
+
+func (c *conn) fatal(err error) error {
+ c.mu.Lock()
+ if c.err == nil {
+ c.err = err
+ // Close connection to force errors on subsequent calls and to unblock
+ // other reader or writer.
+ c.conn.Close()
+ }
+ c.mu.Unlock()
+ return err
+}
+
+func (c *conn) Err() error {
+ c.mu.Lock()
+ err := c.err
+ c.mu.Unlock()
+ return err
+}
+
+func (c *conn) writeLen(prefix byte, n int) error {
+ c.lenScratch[len(c.lenScratch)-1] = '\n'
+ c.lenScratch[len(c.lenScratch)-2] = '\r'
+ i := len(c.lenScratch) - 3
+ for {
+ c.lenScratch[i] = byte('0' + n%10)
+ i -= 1
+ n = n / 10
+ if n == 0 {
+ break
+ }
+ }
+ c.lenScratch[i] = prefix
+ _, err := c.bw.Write(c.lenScratch[i:])
+ return err
+}
+
+func (c *conn) writeString(s string) error {
+ c.writeLen('$', len(s))
+ c.bw.WriteString(s)
+ _, err := c.bw.WriteString("\r\n")
+ return err
+}
+
+func (c *conn) writeBytes(p []byte) error {
+ c.writeLen('$', len(p))
+ c.bw.Write(p)
+ _, err := c.bw.WriteString("\r\n")
+ return err
+}
+
+func (c *conn) writeInt64(n int64) error {
+ return c.writeBytes(strconv.AppendInt(c.numScratch[:0], n, 10))
+}
+
+func (c *conn) writeFloat64(n float64) error {
+ return c.writeBytes(strconv.AppendFloat(c.numScratch[:0], n, 'g', -1, 64))
+}
+
+func (c *conn) writeCommand(cmd string, args []interface{}) (err error) {
+ c.writeLen('*', 1+len(args))
+ err = c.writeString(cmd)
+ for _, arg := range args {
+ if err != nil {
+ break
+ }
+ switch arg := arg.(type) {
+ case string:
+ err = c.writeString(arg)
+ case []byte:
+ err = c.writeBytes(arg)
+ case int:
+ err = c.writeInt64(int64(arg))
+ case int64:
+ err = c.writeInt64(arg)
+ case float64:
+ err = c.writeFloat64(arg)
+ case bool:
+ if arg {
+ err = c.writeString("1")
+ } else {
+ err = c.writeString("0")
+ }
+ case nil:
+ err = c.writeString("")
+ default:
+ var buf bytes.Buffer
+ fmt.Fprint(&buf, arg)
+ err = c.writeBytes(buf.Bytes())
+ }
+ }
+ return err
+}
+
+type protocolError string
+
+func (pe protocolError) Error() string {
+ return fmt.Sprintf("redigo: %s (possible server error or unsupported concurrent read by application)", string(pe))
+}
+
+func (c *conn) readLine() ([]byte, error) {
+ p, err := c.br.ReadSlice('\n')
+ if err == bufio.ErrBufferFull {
+ return nil, protocolError("long response line")
+ }
+ if err != nil {
+ return nil, err
+ }
+ i := len(p) - 2
+ if i < 0 || p[i] != '\r' {
+ return nil, protocolError("bad response line terminator")
+ }
+ return p[:i], nil
+}
+
+// parseLen parses bulk string and array lengths.
+func parseLen(p []byte) (int, error) {
+ if len(p) == 0 {
+ return -1, protocolError("malformed length")
+ }
+
+ if p[0] == '-' && len(p) == 2 && p[1] == '1' {
+ // handle $-1 and $-1 null replies.
+ return -1, nil
+ }
+
+ var n int
+ for _, b := range p {
+ n *= 10
+ if b < '0' || b > '9' {
+ return -1, protocolError("illegal bytes in length")
+ }
+ n += int(b - '0')
+ }
+
+ return n, nil
+}
+
+// parseInt parses an integer reply.
+func parseInt(p []byte) (interface{}, error) {
+ if len(p) == 0 {
+ return 0, protocolError("malformed integer")
+ }
+
+ var negate bool
+ if p[0] == '-' {
+ negate = true
+ p = p[1:]
+ if len(p) == 0 {
+ return 0, protocolError("malformed integer")
+ }
+ }
+
+ var n int64
+ for _, b := range p {
+ n *= 10
+ if b < '0' || b > '9' {
+ return 0, protocolError("illegal bytes in length")
+ }
+ n += int64(b - '0')
+ }
+
+ if negate {
+ n = -n
+ }
+ return n, nil
+}
+
+var (
+ okReply interface{} = "OK"
+ pongReply interface{} = "PONG"
+)
+
+func (c *conn) readReply() (interface{}, error) {
+ line, err := c.readLine()
+ if err != nil {
+ return nil, err
+ }
+ if len(line) == 0 {
+ return nil, protocolError("short response line")
+ }
+ switch line[0] {
+ case '+':
+ switch {
+ case len(line) == 3 && line[1] == 'O' && line[2] == 'K':
+ // Avoid allocation for frequent "+OK" response.
+ return okReply, nil
+ case len(line) == 5 && line[1] == 'P' && line[2] == 'O' && line[3] == 'N' && line[4] == 'G':
+ // Avoid allocation in PING command benchmarks :)
+ return pongReply, nil
+ default:
+ return string(line[1:]), nil
+ }
+ case '-':
+ return Error(string(line[1:])), nil
+ case ':':
+ return parseInt(line[1:])
+ case '$':
+ n, err := parseLen(line[1:])
+ if n < 0 || err != nil {
+ return nil, err
+ }
+ p := make([]byte, n)
+ _, err = io.ReadFull(c.br, p)
+ if err != nil {
+ return nil, err
+ }
+ if line, err := c.readLine(); err != nil {
+ return nil, err
+ } else if len(line) != 0 {
+ return nil, protocolError("bad bulk string format")
+ }
+ return p, nil
+ case '*':
+ n, err := parseLen(line[1:])
+ if n < 0 || err != nil {
+ return nil, err
+ }
+ r := make([]interface{}, n)
+ for i := range r {
+ r[i], err = c.readReply()
+ if err != nil {
+ return nil, err
+ }
+ }
+ return r, nil
+ }
+ return nil, protocolError("unexpected response line")
+}
+
+func (c *conn) Send(cmd string, args ...interface{}) error {
+ c.mu.Lock()
+ c.pending += 1
+ c.mu.Unlock()
+ if c.writeTimeout != 0 {
+ c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout))
+ }
+ if err := c.writeCommand(cmd, args); err != nil {
+ return c.fatal(err)
+ }
+ return nil
+}
+
+func (c *conn) Flush() error {
+ if c.writeTimeout != 0 {
+ c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout))
+ }
+ if err := c.bw.Flush(); err != nil {
+ return c.fatal(err)
+ }
+ return nil
+}
+
+func (c *conn) Receive() (reply interface{}, err error) {
+ if c.readTimeout != 0 {
+ c.conn.SetReadDeadline(time.Now().Add(c.readTimeout))
+ }
+ if reply, err = c.readReply(); err != nil {
+ return nil, c.fatal(err)
+ }
+ // When using pub/sub, the number of receives can be greater than the
+ // number of sends. To enable normal use of the connection after
+ // unsubscribing from all channels, we do not decrement pending to a
+ // negative value.
+ //
+ // The pending field is decremented after the reply is read to handle the
+ // case where Receive is called before Send.
+ c.mu.Lock()
+ if c.pending > 0 {
+ c.pending -= 1
+ }
+ c.mu.Unlock()
+ if err, ok := reply.(Error); ok {
+ return nil, err
+ }
+ return
+}
+
+func (c *conn) Do(cmd string, args ...interface{}) (interface{}, error) {
+ c.mu.Lock()
+ pending := c.pending
+ c.pending = 0
+ c.mu.Unlock()
+
+ if cmd == "" && pending == 0 {
+ return nil, nil
+ }
+
+ if c.writeTimeout != 0 {
+ c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout))
+ }
+
+ if cmd != "" {
+ c.writeCommand(cmd, args)
+ }
+
+ if err := c.bw.Flush(); err != nil {
+ return nil, c.fatal(err)
+ }
+
+ if c.readTimeout != 0 {
+ c.conn.SetReadDeadline(time.Now().Add(c.readTimeout))
+ }
+
+ if cmd == "" {
+ reply := make([]interface{}, pending)
+ for i := range reply {
+ r, e := c.readReply()
+ if e != nil {
+ return nil, c.fatal(e)
+ }
+ reply[i] = r
+ }
+ return reply, nil
+ }
+
+ var err error
+ var reply interface{}
+ for i := 0; i <= pending; i++ {
+ var e error
+ if reply, e = c.readReply(); e != nil {
+ return nil, c.fatal(e)
+ }
+ if e, ok := reply.(Error); ok && err == nil {
+ err = e
+ }
+ }
+ return reply, err
+}
diff --git a/Godeps/_workspace/src/github.com/garyburd/redigo/redis/conn_test.go b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/conn_test.go
new file mode 100644
index 000000000..800370136
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/conn_test.go
@@ -0,0 +1,542 @@
+// Copyright 2012 Gary Burd
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package redis_test
+
+import (
+ "bufio"
+ "bytes"
+ "math"
+ "net"
+ "reflect"
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/garyburd/redigo/internal/redistest"
+ "github.com/garyburd/redigo/redis"
+)
+
+var writeTests = []struct {
+ args []interface{}
+ expected string
+}{
+ {
+ []interface{}{"SET", "key", "value"},
+ "*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n",
+ },
+ {
+ []interface{}{"SET", "key", "value"},
+ "*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n",
+ },
+ {
+ []interface{}{"SET", "key", byte(100)},
+ "*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$3\r\n100\r\n",
+ },
+ {
+ []interface{}{"SET", "key", 100},
+ "*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$3\r\n100\r\n",
+ },
+ {
+ []interface{}{"SET", "key", int64(math.MinInt64)},
+ "*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$20\r\n-9223372036854775808\r\n",
+ },
+ {
+ []interface{}{"SET", "key", float64(1349673917.939762)},
+ "*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$21\r\n1.349673917939762e+09\r\n",
+ },
+ {
+ []interface{}{"SET", "key", ""},
+ "*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$0\r\n\r\n",
+ },
+ {
+ []interface{}{"SET", "key", nil},
+ "*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$0\r\n\r\n",
+ },
+ {
+ []interface{}{"ECHO", true, false},
+ "*3\r\n$4\r\nECHO\r\n$1\r\n1\r\n$1\r\n0\r\n",
+ },
+}
+
+func TestWrite(t *testing.T) {
+ for _, tt := range writeTests {
+ var buf bytes.Buffer
+ rw := bufio.ReadWriter{Writer: bufio.NewWriter(&buf)}
+ c := redis.NewConnBufio(rw)
+ err := c.Send(tt.args[0].(string), tt.args[1:]...)
+ if err != nil {
+ t.Errorf("Send(%v) returned error %v", tt.args, err)
+ continue
+ }
+ rw.Flush()
+ actual := buf.String()
+ if actual != tt.expected {
+ t.Errorf("Send(%v) = %q, want %q", tt.args, actual, tt.expected)
+ }
+ }
+}
+
+var errorSentinel = &struct{}{}
+
+var readTests = []struct {
+ reply string
+ expected interface{}
+}{
+ {
+ "+OK\r\n",
+ "OK",
+ },
+ {
+ "+PONG\r\n",
+ "PONG",
+ },
+ {
+ "@OK\r\n",
+ errorSentinel,
+ },
+ {
+ "$6\r\nfoobar\r\n",
+ []byte("foobar"),
+ },
+ {
+ "$-1\r\n",
+ nil,
+ },
+ {
+ ":1\r\n",
+ int64(1),
+ },
+ {
+ ":-2\r\n",
+ int64(-2),
+ },
+ {
+ "*0\r\n",
+ []interface{}{},
+ },
+ {
+ "*-1\r\n",
+ nil,
+ },
+ {
+ "*4\r\n$3\r\nfoo\r\n$3\r\nbar\r\n$5\r\nHello\r\n$5\r\nWorld\r\n",
+ []interface{}{[]byte("foo"), []byte("bar"), []byte("Hello"), []byte("World")},
+ },
+ {
+ "*3\r\n$3\r\nfoo\r\n$-1\r\n$3\r\nbar\r\n",
+ []interface{}{[]byte("foo"), nil, []byte("bar")},
+ },
+
+ {
+ // "x" is not a valid length
+ "$x\r\nfoobar\r\n",
+ errorSentinel,
+ },
+ {
+ // -2 is not a valid length
+ "$-2\r\n",
+ errorSentinel,
+ },
+ {
+ // "x" is not a valid integer
+ ":x\r\n",
+ errorSentinel,
+ },
+ {
+ // missing \r\n following value
+ "$6\r\nfoobar",
+ errorSentinel,
+ },
+ {
+ // short value
+ "$6\r\nxx",
+ errorSentinel,
+ },
+ {
+ // long value
+ "$6\r\nfoobarx\r\n",
+ errorSentinel,
+ },
+}
+
+func TestRead(t *testing.T) {
+ for _, tt := range readTests {
+ rw := bufio.ReadWriter{
+ Reader: bufio.NewReader(strings.NewReader(tt.reply)),
+ Writer: bufio.NewWriter(nil), // writer need to support Flush
+ }
+ c := redis.NewConnBufio(rw)
+ actual, err := c.Receive()
+ if tt.expected == errorSentinel {
+ if err == nil {
+ t.Errorf("Receive(%q) did not return expected error", tt.reply)
+ }
+ } else {
+ if err != nil {
+ t.Errorf("Receive(%q) returned error %v", tt.reply, err)
+ continue
+ }
+ if !reflect.DeepEqual(actual, tt.expected) {
+ t.Errorf("Receive(%q) = %v, want %v", tt.reply, actual, tt.expected)
+ }
+ }
+ }
+}
+
+var testCommands = []struct {
+ args []interface{}
+ expected interface{}
+}{
+ {
+ []interface{}{"PING"},
+ "PONG",
+ },
+ {
+ []interface{}{"SET", "foo", "bar"},
+ "OK",
+ },
+ {
+ []interface{}{"GET", "foo"},
+ []byte("bar"),
+ },
+ {
+ []interface{}{"GET", "nokey"},
+ nil,
+ },
+ {
+ []interface{}{"MGET", "nokey", "foo"},
+ []interface{}{nil, []byte("bar")},
+ },
+ {
+ []interface{}{"INCR", "mycounter"},
+ int64(1),
+ },
+ {
+ []interface{}{"LPUSH", "mylist", "foo"},
+ int64(1),
+ },
+ {
+ []interface{}{"LPUSH", "mylist", "bar"},
+ int64(2),
+ },
+ {
+ []interface{}{"LRANGE", "mylist", 0, -1},
+ []interface{}{[]byte("bar"), []byte("foo")},
+ },
+ {
+ []interface{}{"MULTI"},
+ "OK",
+ },
+ {
+ []interface{}{"LRANGE", "mylist", 0, -1},
+ "QUEUED",
+ },
+ {
+ []interface{}{"PING"},
+ "QUEUED",
+ },
+ {
+ []interface{}{"EXEC"},
+ []interface{}{
+ []interface{}{[]byte("bar"), []byte("foo")},
+ "PONG",
+ },
+ },
+}
+
+func TestDoCommands(t *testing.T) {
+ c, err := redistest.Dial()
+ if err != nil {
+ t.Fatalf("error connection to database, %v", err)
+ }
+ defer c.Close()
+
+ for _, cmd := range testCommands {
+ actual, err := c.Do(cmd.args[0].(string), cmd.args[1:]...)
+ if err != nil {
+ t.Errorf("Do(%v) returned error %v", cmd.args, err)
+ continue
+ }
+ if !reflect.DeepEqual(actual, cmd.expected) {
+ t.Errorf("Do(%v) = %v, want %v", cmd.args, actual, cmd.expected)
+ }
+ }
+}
+
+func TestPipelineCommands(t *testing.T) {
+ c, err := redistest.Dial()
+ if err != nil {
+ t.Fatalf("error connection to database, %v", err)
+ }
+ defer c.Close()
+
+ for _, cmd := range testCommands {
+ if err := c.Send(cmd.args[0].(string), cmd.args[1:]...); err != nil {
+ t.Fatalf("Send(%v) returned error %v", cmd.args, err)
+ }
+ }
+ if err := c.Flush(); err != nil {
+ t.Errorf("Flush() returned error %v", err)
+ }
+ for _, cmd := range testCommands {
+ actual, err := c.Receive()
+ if err != nil {
+ t.Fatalf("Receive(%v) returned error %v", cmd.args, err)
+ }
+ if !reflect.DeepEqual(actual, cmd.expected) {
+ t.Errorf("Receive(%v) = %v, want %v", cmd.args, actual, cmd.expected)
+ }
+ }
+}
+
+func TestBlankCommmand(t *testing.T) {
+ c, err := redistest.Dial()
+ if err != nil {
+ t.Fatalf("error connection to database, %v", err)
+ }
+ defer c.Close()
+
+ for _, cmd := range testCommands {
+ if err := c.Send(cmd.args[0].(string), cmd.args[1:]...); err != nil {
+ t.Fatalf("Send(%v) returned error %v", cmd.args, err)
+ }
+ }
+ reply, err := redis.Values(c.Do(""))
+ if err != nil {
+ t.Fatalf("Do() returned error %v", err)
+ }
+ if len(reply) != len(testCommands) {
+ t.Fatalf("len(reply)=%d, want %d", len(reply), len(testCommands))
+ }
+ for i, cmd := range testCommands {
+ actual := reply[i]
+ if !reflect.DeepEqual(actual, cmd.expected) {
+ t.Errorf("Receive(%v) = %v, want %v", cmd.args, actual, cmd.expected)
+ }
+ }
+}
+
+func TestRecvBeforeSend(t *testing.T) {
+ c, err := redistest.Dial()
+ if err != nil {
+ t.Fatalf("error connection to database, %v", err)
+ }
+ defer c.Close()
+ done := make(chan struct{})
+ go func() {
+ c.Receive()
+ close(done)
+ }()
+ time.Sleep(time.Millisecond)
+ c.Send("PING")
+ c.Flush()
+ <-done
+ _, err = c.Do("")
+ if err != nil {
+ t.Fatalf("error=%v", err)
+ }
+}
+
+func TestError(t *testing.T) {
+ c, err := redistest.Dial()
+ if err != nil {
+ t.Fatalf("error connection to database, %v", err)
+ }
+ defer c.Close()
+
+ c.Do("SET", "key", "val")
+ _, err = c.Do("HSET", "key", "fld", "val")
+ if err == nil {
+ t.Errorf("Expected err for HSET on string key.")
+ }
+ if c.Err() != nil {
+ t.Errorf("Conn has Err()=%v, expect nil", c.Err())
+ }
+ _, err = c.Do("SET", "key", "val")
+ if err != nil {
+ t.Errorf("Do(SET, key, val) returned error %v, expected nil.", err)
+ }
+}
+
+func TestReadDeadline(t *testing.T) {
+ l, err := net.Listen("tcp", "127.0.0.1:0")
+ if err != nil {
+ t.Fatalf("net.Listen returned %v", err)
+ }
+ defer l.Close()
+
+ go func() {
+ for {
+ c, err := l.Accept()
+ if err != nil {
+ return
+ }
+ go func() {
+ time.Sleep(time.Second)
+ c.Write([]byte("+OK\r\n"))
+ c.Close()
+ }()
+ }
+ }()
+
+ c1, err := redis.DialTimeout(l.Addr().Network(), l.Addr().String(), 0, time.Millisecond, 0)
+ if err != nil {
+ t.Fatalf("redis.Dial returned %v", err)
+ }
+ defer c1.Close()
+
+ _, err = c1.Do("PING")
+ if err == nil {
+ t.Fatalf("c1.Do() returned nil, expect error")
+ }
+ if c1.Err() == nil {
+ t.Fatalf("c1.Err() = nil, expect error")
+ }
+
+ c2, err := redis.DialTimeout(l.Addr().Network(), l.Addr().String(), 0, time.Millisecond, 0)
+ if err != nil {
+ t.Fatalf("redis.Dial returned %v", err)
+ }
+ defer c2.Close()
+
+ c2.Send("PING")
+ c2.Flush()
+ _, err = c2.Receive()
+ if err == nil {
+ t.Fatalf("c2.Receive() returned nil, expect error")
+ }
+ if c2.Err() == nil {
+ t.Fatalf("c2.Err() = nil, expect error")
+ }
+}
+
+// Connect to local instance of Redis running on the default port.
+func ExampleDial(x int) {
+ c, err := redis.Dial("tcp", ":6379")
+ if err != nil {
+ // handle error
+ }
+ defer c.Close()
+}
+
+// TextExecError tests handling of errors in a transaction. See
+// http://redis.io/topics/transactions for information on how Redis handles
+// errors in a transaction.
+func TestExecError(t *testing.T) {
+ c, err := redistest.Dial()
+ if err != nil {
+ t.Fatalf("error connection to database, %v", err)
+ }
+ defer c.Close()
+
+ // Execute commands that fail before EXEC is called.
+
+ c.Do("ZADD", "k0", 0, 0)
+ c.Send("MULTI")
+ c.Send("NOTACOMMAND", "k0", 0, 0)
+ c.Send("ZINCRBY", "k0", 0, 0)
+ v, err := c.Do("EXEC")
+ if err == nil {
+ t.Fatalf("EXEC returned values %v, expected error", v)
+ }
+
+ // Execute commands that fail after EXEC is called. The first command
+ // returns an error.
+
+ c.Do("ZADD", "k1", 0, 0)
+ c.Send("MULTI")
+ c.Send("HSET", "k1", 0, 0)
+ c.Send("ZINCRBY", "k1", 0, 0)
+ v, err = c.Do("EXEC")
+ if err != nil {
+ t.Fatalf("EXEC returned error %v", err)
+ }
+
+ vs, err := redis.Values(v, nil)
+ if err != nil {
+ t.Fatalf("Values(v) returned error %v", err)
+ }
+
+ if len(vs) != 2 {
+ t.Fatalf("len(vs) == %d, want 2", len(vs))
+ }
+
+ if _, ok := vs[0].(error); !ok {
+ t.Fatalf("first result is type %T, expected error", vs[0])
+ }
+
+ if _, ok := vs[1].([]byte); !ok {
+ t.Fatalf("second result is type %T, expected []byte", vs[2])
+ }
+
+ // Execute commands that fail after EXEC is called. The second command
+ // returns an error.
+
+ c.Do("ZADD", "k2", 0, 0)
+ c.Send("MULTI")
+ c.Send("ZINCRBY", "k2", 0, 0)
+ c.Send("HSET", "k2", 0, 0)
+ v, err = c.Do("EXEC")
+ if err != nil {
+ t.Fatalf("EXEC returned error %v", err)
+ }
+
+ vs, err = redis.Values(v, nil)
+ if err != nil {
+ t.Fatalf("Values(v) returned error %v", err)
+ }
+
+ if len(vs) != 2 {
+ t.Fatalf("len(vs) == %d, want 2", len(vs))
+ }
+
+ if _, ok := vs[0].([]byte); !ok {
+ t.Fatalf("first result is type %T, expected []byte", vs[0])
+ }
+
+ if _, ok := vs[1].(error); !ok {
+ t.Fatalf("second result is type %T, expected error", vs[2])
+ }
+}
+
+func BenchmarkDoEmpty(b *testing.B) {
+ b.StopTimer()
+ c, err := redistest.Dial()
+ if err != nil {
+ b.Fatal(err)
+ }
+ defer c.Close()
+ b.StartTimer()
+ for i := 0; i < b.N; i++ {
+ if _, err := c.Do(""); err != nil {
+ b.Fatal(err)
+ }
+ }
+}
+
+func BenchmarkDoPing(b *testing.B) {
+ b.StopTimer()
+ c, err := redistest.Dial()
+ if err != nil {
+ b.Fatal(err)
+ }
+ defer c.Close()
+ b.StartTimer()
+ for i := 0; i < b.N; i++ {
+ if _, err := c.Do("PING"); err != nil {
+ b.Fatal(err)
+ }
+ }
+}
diff --git a/Godeps/_workspace/src/github.com/garyburd/redigo/redis/doc.go b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/doc.go
new file mode 100644
index 000000000..1ae6f0cc2
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/doc.go
@@ -0,0 +1,169 @@
+// Copyright 2012 Gary Burd
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+// Package redis is a client for the Redis database.
+//
+// The Redigo FAQ (https://github.com/garyburd/redigo/wiki/FAQ) contains more
+// documentation about this package.
+//
+// Connections
+//
+// The Conn interface is the primary interface for working with Redis.
+// Applications create connections by calling the Dial, DialWithTimeout or
+// NewConn functions. In the future, functions will be added for creating
+// sharded and other types of connections.
+//
+// The application must call the connection Close method when the application
+// is done with the connection.
+//
+// Executing Commands
+//
+// The Conn interface has a generic method for executing Redis commands:
+//
+// Do(commandName string, args ...interface{}) (reply interface{}, err error)
+//
+// The Redis command reference (http://redis.io/commands) lists the available
+// commands. An example of using the Redis APPEND command is:
+//
+// n, err := conn.Do("APPEND", "key", "value")
+//
+// The Do method converts command arguments to binary strings for transmission
+// to the server as follows:
+//
+// Go Type Conversion
+// []byte Sent as is
+// string Sent as is
+// int, int64 strconv.FormatInt(v)
+// float64 strconv.FormatFloat(v, 'g', -1, 64)
+// bool true -> "1", false -> "0"
+// nil ""
+// all other types fmt.Print(v)
+//
+// Redis command reply types are represented using the following Go types:
+//
+// Redis type Go type
+// error redis.Error
+// integer int64
+// simple string string
+// bulk string []byte or nil if value not present.
+// array []interface{} or nil if value not present.
+//
+// Use type assertions or the reply helper functions to convert from
+// interface{} to the specific Go type for the command result.
+//
+// Pipelining
+//
+// Connections support pipelining using the Send, Flush and Receive methods.
+//
+// Send(commandName string, args ...interface{}) error
+// Flush() error
+// Receive() (reply interface{}, err error)
+//
+// Send writes the command to the connection's output buffer. Flush flushes the
+// connection's output buffer to the server. Receive reads a single reply from
+// the server. The following example shows a simple pipeline.
+//
+// c.Send("SET", "foo", "bar")
+// c.Send("GET", "foo")
+// c.Flush()
+// c.Receive() // reply from SET
+// v, err = c.Receive() // reply from GET
+//
+// The Do method combines the functionality of the Send, Flush and Receive
+// methods. The Do method starts by writing the command and flushing the output
+// buffer. Next, the Do method receives all pending replies including the reply
+// for the command just sent by Do. If any of the received replies is an error,
+// then Do returns the error. If there are no errors, then Do returns the last
+// reply. If the command argument to the Do method is "", then the Do method
+// will flush the output buffer and receive pending replies without sending a
+// command.
+//
+// Use the Send and Do methods to implement pipelined transactions.
+//
+// c.Send("MULTI")
+// c.Send("INCR", "foo")
+// c.Send("INCR", "bar")
+// r, err := c.Do("EXEC")
+// fmt.Println(r) // prints [1, 1]
+//
+// Concurrency
+//
+// Connections do not support concurrent calls to the write methods (Send,
+// Flush) or concurrent calls to the read method (Receive). Connections do
+// allow a concurrent reader and writer.
+//
+// Because the Do method combines the functionality of Send, Flush and Receive,
+// the Do method cannot be called concurrently with the other methods.
+//
+// For full concurrent access to Redis, use the thread-safe Pool to get and
+// release connections from within a goroutine.
+//
+// Publish and Subscribe
+//
+// Use the Send, Flush and Receive methods to implement Pub/Sub subscribers.
+//
+// c.Send("SUBSCRIBE", "example")
+// c.Flush()
+// for {
+// reply, err := c.Receive()
+// if err != nil {
+// return err
+// }
+// // process pushed message
+// }
+//
+// The PubSubConn type wraps a Conn with convenience methods for implementing
+// subscribers. The Subscribe, PSubscribe, Unsubscribe and PUnsubscribe methods
+// send and flush a subscription management command. The receive method
+// converts a pushed message to convenient types for use in a type switch.
+//
+// psc := redis.PubSubConn{c}
+// psc.Subscribe("example")
+// for {
+// switch v := psc.Receive().(type) {
+// case redis.Message:
+// fmt.Printf("%s: message: %s\n", v.Channel, v.Data)
+// case redis.Subscription:
+// fmt.Printf("%s: %s %d\n", v.Channel, v.Kind, v.Count)
+// case error:
+// return v
+// }
+// }
+//
+// Reply Helpers
+//
+// The Bool, Int, Bytes, String, Strings and Values functions convert a reply
+// to a value of a specific type. To allow convenient wrapping of calls to the
+// connection Do and Receive methods, the functions take a second argument of
+// type error. If the error is non-nil, then the helper function returns the
+// error. If the error is nil, the function converts the reply to the specified
+// type:
+//
+// exists, err := redis.Bool(c.Do("EXISTS", "foo"))
+// if err != nil {
+// // handle error return from c.Do or type conversion error.
+// }
+//
+// The Scan function converts elements of a array reply to Go types:
+//
+// var value1 int
+// var value2 string
+// reply, err := redis.Values(c.Do("MGET", "key1", "key2"))
+// if err != nil {
+// // handle error
+// }
+// if _, err := redis.Scan(reply, &value1, &value2); err != nil {
+// // handle error
+// }
+package redis
diff --git a/Godeps/_workspace/src/github.com/garyburd/redigo/redis/log.go b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/log.go
new file mode 100644
index 000000000..129b86d67
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/log.go
@@ -0,0 +1,117 @@
+// Copyright 2012 Gary Burd
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package redis
+
+import (
+ "bytes"
+ "fmt"
+ "log"
+)
+
+// NewLoggingConn returns a logging wrapper around a connection.
+func NewLoggingConn(conn Conn, logger *log.Logger, prefix string) Conn {
+ if prefix != "" {
+ prefix = prefix + "."
+ }
+ return &loggingConn{conn, logger, prefix}
+}
+
+type loggingConn struct {
+ Conn
+ logger *log.Logger
+ prefix string
+}
+
+func (c *loggingConn) Close() error {
+ err := c.Conn.Close()
+ var buf bytes.Buffer
+ fmt.Fprintf(&buf, "%sClose() -> (%v)", c.prefix, err)
+ c.logger.Output(2, buf.String())
+ return err
+}
+
+func (c *loggingConn) printValue(buf *bytes.Buffer, v interface{}) {
+ const chop = 32
+ switch v := v.(type) {
+ case []byte:
+ if len(v) > chop {
+ fmt.Fprintf(buf, "%q...", v[:chop])
+ } else {
+ fmt.Fprintf(buf, "%q", v)
+ }
+ case string:
+ if len(v) > chop {
+ fmt.Fprintf(buf, "%q...", v[:chop])
+ } else {
+ fmt.Fprintf(buf, "%q", v)
+ }
+ case []interface{}:
+ if len(v) == 0 {
+ buf.WriteString("[]")
+ } else {
+ sep := "["
+ fin := "]"
+ if len(v) > chop {
+ v = v[:chop]
+ fin = "...]"
+ }
+ for _, vv := range v {
+ buf.WriteString(sep)
+ c.printValue(buf, vv)
+ sep = ", "
+ }
+ buf.WriteString(fin)
+ }
+ default:
+ fmt.Fprint(buf, v)
+ }
+}
+
+func (c *loggingConn) print(method, commandName string, args []interface{}, reply interface{}, err error) {
+ var buf bytes.Buffer
+ fmt.Fprintf(&buf, "%s%s(", c.prefix, method)
+ if method != "Receive" {
+ buf.WriteString(commandName)
+ for _, arg := range args {
+ buf.WriteString(", ")
+ c.printValue(&buf, arg)
+ }
+ }
+ buf.WriteString(") -> (")
+ if method != "Send" {
+ c.printValue(&buf, reply)
+ buf.WriteString(", ")
+ }
+ fmt.Fprintf(&buf, "%v)", err)
+ c.logger.Output(3, buf.String())
+}
+
+func (c *loggingConn) Do(commandName string, args ...interface{}) (interface{}, error) {
+ reply, err := c.Conn.Do(commandName, args...)
+ c.print("Do", commandName, args, reply, err)
+ return reply, err
+}
+
+func (c *loggingConn) Send(commandName string, args ...interface{}) error {
+ err := c.Conn.Send(commandName, args...)
+ c.print("Send", commandName, args, nil, err)
+ return err
+}
+
+func (c *loggingConn) Receive() (interface{}, error) {
+ reply, err := c.Conn.Receive()
+ c.print("Receive", "", nil, reply, err)
+ return reply, err
+}
diff --git a/Godeps/_workspace/src/github.com/garyburd/redigo/redis/pool.go b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/pool.go
new file mode 100644
index 000000000..9daf2e33f
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/pool.go
@@ -0,0 +1,389 @@
+// Copyright 2012 Gary Burd
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package redis
+
+import (
+ "bytes"
+ "container/list"
+ "crypto/rand"
+ "crypto/sha1"
+ "errors"
+ "io"
+ "strconv"
+ "sync"
+ "time"
+
+ "github.com/garyburd/redigo/internal"
+)
+
+var nowFunc = time.Now // for testing
+
+// ErrPoolExhausted is returned from a pool connection method (Do, Send,
+// Receive, Flush, Err) when the maximum number of database connections in the
+// pool has been reached.
+var ErrPoolExhausted = errors.New("redigo: connection pool exhausted")
+
+var (
+ errPoolClosed = errors.New("redigo: connection pool closed")
+ errConnClosed = errors.New("redigo: connection closed")
+)
+
+// Pool maintains a pool of connections. The application calls the Get method
+// to get a connection from the pool and the connection's Close method to
+// return the connection's resources to the pool.
+//
+// The following example shows how to use a pool in a web application. The
+// application creates a pool at application startup and makes it available to
+// request handlers using a global variable.
+//
+// func newPool(server, password string) *redis.Pool {
+// return &redis.Pool{
+// MaxIdle: 3,
+// IdleTimeout: 240 * time.Second,
+// Dial: func () (redis.Conn, error) {
+// c, err := redis.Dial("tcp", server)
+// if err != nil {
+// return nil, err
+// }
+// if _, err := c.Do("AUTH", password); err != nil {
+// c.Close()
+// return nil, err
+// }
+// return c, err
+// },
+// TestOnBorrow: func(c redis.Conn, t time.Time) error {
+// _, err := c.Do("PING")
+// return err
+// },
+// }
+// }
+//
+// var (
+// pool *redis.Pool
+// redisServer = flag.String("redisServer", ":6379", "")
+// redisPassword = flag.String("redisPassword", "", "")
+// )
+//
+// func main() {
+// flag.Parse()
+// pool = newPool(*redisServer, *redisPassword)
+// ...
+// }
+//
+// A request handler gets a connection from the pool and closes the connection
+// when the handler is done:
+//
+// func serveHome(w http.ResponseWriter, r *http.Request) {
+// conn := pool.Get()
+// defer conn.Close()
+// ....
+// }
+//
+type Pool struct {
+
+ // Dial is an application supplied function for creating and configuring a
+ // connection
+ Dial func() (Conn, error)
+
+ // TestOnBorrow is an optional application supplied function for checking
+ // the health of an idle connection before the connection is used again by
+ // the application. Argument t is the time that the connection was returned
+ // to the pool. If the function returns an error, then the connection is
+ // closed.
+ TestOnBorrow func(c Conn, t time.Time) error
+
+ // Maximum number of idle connections in the pool.
+ MaxIdle int
+
+ // Maximum number of connections allocated by the pool at a given time.
+ // When zero, there is no limit on the number of connections in the pool.
+ MaxActive int
+
+ // Close connections after remaining idle for this duration. If the value
+ // is zero, then idle connections are not closed. Applications should set
+ // the timeout to a value less than the server's timeout.
+ IdleTimeout time.Duration
+
+ // If Wait is true and the pool is at the MaxIdle limit, then Get() waits
+ // for a connection to be returned to the pool before returning.
+ Wait bool
+
+ // mu protects fields defined below.
+ mu sync.Mutex
+ cond *sync.Cond
+ closed bool
+ active int
+
+ // Stack of idleConn with most recently used at the front.
+ idle list.List
+}
+
+type idleConn struct {
+ c Conn
+ t time.Time
+}
+
+// NewPool creates a new pool. This function is deprecated. Applications should
+// initialize the Pool fields directly as shown in example.
+func NewPool(newFn func() (Conn, error), maxIdle int) *Pool {
+ return &Pool{Dial: newFn, MaxIdle: maxIdle}
+}
+
+// Get gets a connection. The application must close the returned connection.
+// This method always returns a valid connection so that applications can defer
+// error handling to the first use of the connection. If there is an error
+// getting an underlying connection, then the connection Err, Do, Send, Flush
+// and Receive methods return that error.
+func (p *Pool) Get() Conn {
+ c, err := p.get()
+ if err != nil {
+ return errorConnection{err}
+ }
+ return &pooledConnection{p: p, c: c}
+}
+
+// ActiveCount returns the number of active connections in the pool.
+func (p *Pool) ActiveCount() int {
+ p.mu.Lock()
+ active := p.active
+ p.mu.Unlock()
+ return active
+}
+
+// Close releases the resources used by the pool.
+func (p *Pool) Close() error {
+ p.mu.Lock()
+ idle := p.idle
+ p.idle.Init()
+ p.closed = true
+ p.active -= idle.Len()
+ if p.cond != nil {
+ p.cond.Broadcast()
+ }
+ p.mu.Unlock()
+ for e := idle.Front(); e != nil; e = e.Next() {
+ e.Value.(idleConn).c.Close()
+ }
+ return nil
+}
+
+// release decrements the active count and signals waiters. The caller must
+// hold p.mu during the call.
+func (p *Pool) release() {
+ p.active -= 1
+ if p.cond != nil {
+ p.cond.Signal()
+ }
+}
+
+// get prunes stale connections and returns a connection from the idle list or
+// creates a new connection.
+func (p *Pool) get() (Conn, error) {
+ p.mu.Lock()
+
+ // Prune stale connections.
+
+ if timeout := p.IdleTimeout; timeout > 0 {
+ for i, n := 0, p.idle.Len(); i < n; i++ {
+ e := p.idle.Back()
+ if e == nil {
+ break
+ }
+ ic := e.Value.(idleConn)
+ if ic.t.Add(timeout).After(nowFunc()) {
+ break
+ }
+ p.idle.Remove(e)
+ p.release()
+ p.mu.Unlock()
+ ic.c.Close()
+ p.mu.Lock()
+ }
+ }
+
+ for {
+
+ // Get idle connection.
+
+ for i, n := 0, p.idle.Len(); i < n; i++ {
+ e := p.idle.Front()
+ if e == nil {
+ break
+ }
+ ic := e.Value.(idleConn)
+ p.idle.Remove(e)
+ test := p.TestOnBorrow
+ p.mu.Unlock()
+ if test == nil || test(ic.c, ic.t) == nil {
+ return ic.c, nil
+ }
+ ic.c.Close()
+ p.mu.Lock()
+ p.release()
+ }
+
+ // Check for pool closed before dialing a new connection.
+
+ if p.closed {
+ p.mu.Unlock()
+ return nil, errors.New("redigo: get on closed pool")
+ }
+
+ // Dial new connection if under limit.
+
+ if p.MaxActive == 0 || p.active < p.MaxActive {
+ dial := p.Dial
+ p.active += 1
+ p.mu.Unlock()
+ c, err := dial()
+ if err != nil {
+ p.mu.Lock()
+ p.release()
+ p.mu.Unlock()
+ c = nil
+ }
+ return c, err
+ }
+
+ if !p.Wait {
+ p.mu.Unlock()
+ return nil, ErrPoolExhausted
+ }
+
+ if p.cond == nil {
+ p.cond = sync.NewCond(&p.mu)
+ }
+ p.cond.Wait()
+ }
+}
+
+func (p *Pool) put(c Conn, forceClose bool) error {
+ err := c.Err()
+ p.mu.Lock()
+ if !p.closed && err == nil && !forceClose {
+ p.idle.PushFront(idleConn{t: nowFunc(), c: c})
+ if p.idle.Len() > p.MaxIdle {
+ c = p.idle.Remove(p.idle.Back()).(idleConn).c
+ } else {
+ c = nil
+ }
+ }
+
+ if c == nil {
+ if p.cond != nil {
+ p.cond.Signal()
+ }
+ p.mu.Unlock()
+ return nil
+ }
+
+ p.release()
+ p.mu.Unlock()
+ return c.Close()
+}
+
+type pooledConnection struct {
+ p *Pool
+ c Conn
+ state int
+}
+
+var (
+ sentinel []byte
+ sentinelOnce sync.Once
+)
+
+func initSentinel() {
+ p := make([]byte, 64)
+ if _, err := rand.Read(p); err == nil {
+ sentinel = p
+ } else {
+ h := sha1.New()
+ io.WriteString(h, "Oops, rand failed. Use time instead.")
+ io.WriteString(h, strconv.FormatInt(time.Now().UnixNano(), 10))
+ sentinel = h.Sum(nil)
+ }
+}
+
+func (pc *pooledConnection) Close() error {
+ c := pc.c
+ if _, ok := c.(errorConnection); ok {
+ return nil
+ }
+ pc.c = errorConnection{errConnClosed}
+
+ if pc.state&internal.MultiState != 0 {
+ c.Send("DISCARD")
+ pc.state &^= (internal.MultiState | internal.WatchState)
+ } else if pc.state&internal.WatchState != 0 {
+ c.Send("UNWATCH")
+ pc.state &^= internal.WatchState
+ }
+ if pc.state&internal.SubscribeState != 0 {
+ c.Send("UNSUBSCRIBE")
+ c.Send("PUNSUBSCRIBE")
+ // To detect the end of the message stream, ask the server to echo
+ // a sentinel value and read until we see that value.
+ sentinelOnce.Do(initSentinel)
+ c.Send("ECHO", sentinel)
+ c.Flush()
+ for {
+ p, err := c.Receive()
+ if err != nil {
+ break
+ }
+ if p, ok := p.([]byte); ok && bytes.Equal(p, sentinel) {
+ pc.state &^= internal.SubscribeState
+ break
+ }
+ }
+ }
+ c.Do("")
+ pc.p.put(c, pc.state != 0)
+ return nil
+}
+
+func (pc *pooledConnection) Err() error {
+ return pc.c.Err()
+}
+
+func (pc *pooledConnection) Do(commandName string, args ...interface{}) (reply interface{}, err error) {
+ ci := internal.LookupCommandInfo(commandName)
+ pc.state = (pc.state | ci.Set) &^ ci.Clear
+ return pc.c.Do(commandName, args...)
+}
+
+func (pc *pooledConnection) Send(commandName string, args ...interface{}) error {
+ ci := internal.LookupCommandInfo(commandName)
+ pc.state = (pc.state | ci.Set) &^ ci.Clear
+ return pc.c.Send(commandName, args...)
+}
+
+func (pc *pooledConnection) Flush() error {
+ return pc.c.Flush()
+}
+
+func (pc *pooledConnection) Receive() (reply interface{}, err error) {
+ return pc.c.Receive()
+}
+
+type errorConnection struct{ err error }
+
+func (ec errorConnection) Do(string, ...interface{}) (interface{}, error) { return nil, ec.err }
+func (ec errorConnection) Send(string, ...interface{}) error { return ec.err }
+func (ec errorConnection) Err() error { return ec.err }
+func (ec errorConnection) Close() error { return ec.err }
+func (ec errorConnection) Flush() error { return ec.err }
+func (ec errorConnection) Receive() (interface{}, error) { return nil, ec.err }
diff --git a/Godeps/_workspace/src/github.com/garyburd/redigo/redis/pool_test.go b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/pool_test.go
new file mode 100644
index 000000000..1fe305f16
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/pool_test.go
@@ -0,0 +1,674 @@
+// Copyright 2011 Gary Burd
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package redis_test
+
+import (
+ "errors"
+ "io"
+ "reflect"
+ "sync"
+ "testing"
+ "time"
+
+ "github.com/garyburd/redigo/internal/redistest"
+ "github.com/garyburd/redigo/redis"
+)
+
+type poolTestConn struct {
+ d *poolDialer
+ err error
+ redis.Conn
+}
+
+func (c *poolTestConn) Close() error { c.d.open -= 1; return nil }
+func (c *poolTestConn) Err() error { return c.err }
+
+func (c *poolTestConn) Do(commandName string, args ...interface{}) (reply interface{}, err error) {
+ if commandName == "ERR" {
+ c.err = args[0].(error)
+ commandName = "PING"
+ }
+ if commandName != "" {
+ c.d.commands = append(c.d.commands, commandName)
+ }
+ return c.Conn.Do(commandName, args...)
+}
+
+func (c *poolTestConn) Send(commandName string, args ...interface{}) error {
+ c.d.commands = append(c.d.commands, commandName)
+ return c.Conn.Send(commandName, args...)
+}
+
+type poolDialer struct {
+ t *testing.T
+ dialed int
+ open int
+ commands []string
+ dialErr error
+}
+
+func (d *poolDialer) dial() (redis.Conn, error) {
+ d.dialed += 1
+ if d.dialErr != nil {
+ return nil, d.dialErr
+ }
+ c, err := redistest.Dial()
+ if err != nil {
+ return nil, err
+ }
+ d.open += 1
+ return &poolTestConn{d: d, Conn: c}, nil
+}
+
+func (d *poolDialer) check(message string, p *redis.Pool, dialed, open int) {
+ if d.dialed != dialed {
+ d.t.Errorf("%s: dialed=%d, want %d", message, d.dialed, dialed)
+ }
+ if d.open != open {
+ d.t.Errorf("%s: open=%d, want %d", message, d.open, open)
+ }
+ if active := p.ActiveCount(); active != open {
+ d.t.Errorf("%s: active=%d, want %d", message, active, open)
+ }
+}
+
+func TestPoolReuse(t *testing.T) {
+ d := poolDialer{t: t}
+ p := &redis.Pool{
+ MaxIdle: 2,
+ Dial: d.dial,
+ }
+
+ for i := 0; i < 10; i++ {
+ c1 := p.Get()
+ c1.Do("PING")
+ c2 := p.Get()
+ c2.Do("PING")
+ c1.Close()
+ c2.Close()
+ }
+
+ d.check("before close", p, 2, 2)
+ p.Close()
+ d.check("after close", p, 2, 0)
+}
+
+func TestPoolMaxIdle(t *testing.T) {
+ d := poolDialer{t: t}
+ p := &redis.Pool{
+ MaxIdle: 2,
+ Dial: d.dial,
+ }
+ for i := 0; i < 10; i++ {
+ c1 := p.Get()
+ c1.Do("PING")
+ c2 := p.Get()
+ c2.Do("PING")
+ c3 := p.Get()
+ c3.Do("PING")
+ c1.Close()
+ c2.Close()
+ c3.Close()
+ }
+ d.check("before close", p, 12, 2)
+ p.Close()
+ d.check("after close", p, 12, 0)
+}
+
+func TestPoolError(t *testing.T) {
+ d := poolDialer{t: t}
+ p := &redis.Pool{
+ MaxIdle: 2,
+ Dial: d.dial,
+ }
+
+ c := p.Get()
+ c.Do("ERR", io.EOF)
+ if c.Err() == nil {
+ t.Errorf("expected c.Err() != nil")
+ }
+ c.Close()
+
+ c = p.Get()
+ c.Do("ERR", io.EOF)
+ c.Close()
+
+ d.check(".", p, 2, 0)
+}
+
+func TestPoolClose(t *testing.T) {
+ d := poolDialer{t: t}
+ p := &redis.Pool{
+ MaxIdle: 2,
+ Dial: d.dial,
+ }
+
+ c1 := p.Get()
+ c1.Do("PING")
+ c2 := p.Get()
+ c2.Do("PING")
+ c3 := p.Get()
+ c3.Do("PING")
+
+ c1.Close()
+ if _, err := c1.Do("PING"); err == nil {
+ t.Errorf("expected error after connection closed")
+ }
+
+ c2.Close()
+ c2.Close()
+
+ p.Close()
+
+ d.check("after pool close", p, 3, 1)
+
+ if _, err := c1.Do("PING"); err == nil {
+ t.Errorf("expected error after connection and pool closed")
+ }
+
+ c3.Close()
+
+ d.check("after conn close", p, 3, 0)
+
+ c1 = p.Get()
+ if _, err := c1.Do("PING"); err == nil {
+ t.Errorf("expected error after pool closed")
+ }
+}
+
+func TestPoolTimeout(t *testing.T) {
+ d := poolDialer{t: t}
+ p := &redis.Pool{
+ MaxIdle: 2,
+ IdleTimeout: 300 * time.Second,
+ Dial: d.dial,
+ }
+
+ now := time.Now()
+ redis.SetNowFunc(func() time.Time { return now })
+ defer redis.SetNowFunc(time.Now)
+
+ c := p.Get()
+ c.Do("PING")
+ c.Close()
+
+ d.check("1", p, 1, 1)
+
+ now = now.Add(p.IdleTimeout)
+
+ c = p.Get()
+ c.Do("PING")
+ c.Close()
+
+ d.check("2", p, 2, 1)
+
+ p.Close()
+}
+
+func TestPoolConcurrenSendReceive(t *testing.T) {
+ p := &redis.Pool{
+ Dial: redistest.Dial,
+ }
+ c := p.Get()
+ done := make(chan error, 1)
+ go func() {
+ _, err := c.Receive()
+ done <- err
+ }()
+ c.Send("PING")
+ c.Flush()
+ err := <-done
+ if err != nil {
+ t.Fatalf("Receive() returned error %v", err)
+ }
+ _, err = c.Do("")
+ if err != nil {
+ t.Fatalf("Do() returned error %v", err)
+ }
+ c.Close()
+ p.Close()
+}
+
+func TestPoolBorrowCheck(t *testing.T) {
+ d := poolDialer{t: t}
+ p := &redis.Pool{
+ MaxIdle: 2,
+ Dial: d.dial,
+ TestOnBorrow: func(redis.Conn, time.Time) error { return redis.Error("BLAH") },
+ }
+
+ for i := 0; i < 10; i++ {
+ c := p.Get()
+ c.Do("PING")
+ c.Close()
+ }
+ d.check("1", p, 10, 1)
+ p.Close()
+}
+
+func TestPoolMaxActive(t *testing.T) {
+ d := poolDialer{t: t}
+ p := &redis.Pool{
+ MaxIdle: 2,
+ MaxActive: 2,
+ Dial: d.dial,
+ }
+ c1 := p.Get()
+ c1.Do("PING")
+ c2 := p.Get()
+ c2.Do("PING")
+
+ d.check("1", p, 2, 2)
+
+ c3 := p.Get()
+ if _, err := c3.Do("PING"); err != redis.ErrPoolExhausted {
+ t.Errorf("expected pool exhausted")
+ }
+
+ c3.Close()
+ d.check("2", p, 2, 2)
+ c2.Close()
+ d.check("3", p, 2, 2)
+
+ c3 = p.Get()
+ if _, err := c3.Do("PING"); err != nil {
+ t.Errorf("expected good channel, err=%v", err)
+ }
+ c3.Close()
+
+ d.check("4", p, 2, 2)
+ p.Close()
+}
+
+func TestPoolMonitorCleanup(t *testing.T) {
+ d := poolDialer{t: t}
+ p := &redis.Pool{
+ MaxIdle: 2,
+ MaxActive: 2,
+ Dial: d.dial,
+ }
+ c := p.Get()
+ c.Send("MONITOR")
+ c.Close()
+
+ d.check("", p, 1, 0)
+ p.Close()
+}
+
+func TestPoolPubSubCleanup(t *testing.T) {
+ d := poolDialer{t: t}
+ p := &redis.Pool{
+ MaxIdle: 2,
+ MaxActive: 2,
+ Dial: d.dial,
+ }
+
+ c := p.Get()
+ c.Send("SUBSCRIBE", "x")
+ c.Close()
+
+ want := []string{"SUBSCRIBE", "UNSUBSCRIBE", "PUNSUBSCRIBE", "ECHO"}
+ if !reflect.DeepEqual(d.commands, want) {
+ t.Errorf("got commands %v, want %v", d.commands, want)
+ }
+ d.commands = nil
+
+ c = p.Get()
+ c.Send("PSUBSCRIBE", "x*")
+ c.Close()
+
+ want = []string{"PSUBSCRIBE", "UNSUBSCRIBE", "PUNSUBSCRIBE", "ECHO"}
+ if !reflect.DeepEqual(d.commands, want) {
+ t.Errorf("got commands %v, want %v", d.commands, want)
+ }
+ d.commands = nil
+
+ p.Close()
+}
+
+func TestPoolTransactionCleanup(t *testing.T) {
+ d := poolDialer{t: t}
+ p := &redis.Pool{
+ MaxIdle: 2,
+ MaxActive: 2,
+ Dial: d.dial,
+ }
+
+ c := p.Get()
+ c.Do("WATCH", "key")
+ c.Do("PING")
+ c.Close()
+
+ want := []string{"WATCH", "PING", "UNWATCH"}
+ if !reflect.DeepEqual(d.commands, want) {
+ t.Errorf("got commands %v, want %v", d.commands, want)
+ }
+ d.commands = nil
+
+ c = p.Get()
+ c.Do("WATCH", "key")
+ c.Do("UNWATCH")
+ c.Do("PING")
+ c.Close()
+
+ want = []string{"WATCH", "UNWATCH", "PING"}
+ if !reflect.DeepEqual(d.commands, want) {
+ t.Errorf("got commands %v, want %v", d.commands, want)
+ }
+ d.commands = nil
+
+ c = p.Get()
+ c.Do("WATCH", "key")
+ c.Do("MULTI")
+ c.Do("PING")
+ c.Close()
+
+ want = []string{"WATCH", "MULTI", "PING", "DISCARD"}
+ if !reflect.DeepEqual(d.commands, want) {
+ t.Errorf("got commands %v, want %v", d.commands, want)
+ }
+ d.commands = nil
+
+ c = p.Get()
+ c.Do("WATCH", "key")
+ c.Do("MULTI")
+ c.Do("DISCARD")
+ c.Do("PING")
+ c.Close()
+
+ want = []string{"WATCH", "MULTI", "DISCARD", "PING"}
+ if !reflect.DeepEqual(d.commands, want) {
+ t.Errorf("got commands %v, want %v", d.commands, want)
+ }
+ d.commands = nil
+
+ c = p.Get()
+ c.Do("WATCH", "key")
+ c.Do("MULTI")
+ c.Do("EXEC")
+ c.Do("PING")
+ c.Close()
+
+ want = []string{"WATCH", "MULTI", "EXEC", "PING"}
+ if !reflect.DeepEqual(d.commands, want) {
+ t.Errorf("got commands %v, want %v", d.commands, want)
+ }
+ d.commands = nil
+
+ p.Close()
+}
+
+func startGoroutines(p *redis.Pool, cmd string, args ...interface{}) chan error {
+ errs := make(chan error, 10)
+ for i := 0; i < cap(errs); i++ {
+ go func() {
+ c := p.Get()
+ _, err := c.Do(cmd, args...)
+ errs <- err
+ c.Close()
+ }()
+ }
+
+ // Wait for goroutines to block.
+ time.Sleep(time.Second / 4)
+
+ return errs
+}
+
+func TestWaitPool(t *testing.T) {
+ d := poolDialer{t: t}
+ p := &redis.Pool{
+ MaxIdle: 1,
+ MaxActive: 1,
+ Dial: d.dial,
+ Wait: true,
+ }
+ defer p.Close()
+ c := p.Get()
+ errs := startGoroutines(p, "PING")
+ d.check("before close", p, 1, 1)
+ c.Close()
+ timeout := time.After(2 * time.Second)
+ for i := 0; i < cap(errs); i++ {
+ select {
+ case err := <-errs:
+ if err != nil {
+ t.Fatal(err)
+ }
+ case <-timeout:
+ t.Fatalf("timeout waiting for blocked goroutine %d", i)
+ }
+ }
+ d.check("done", p, 1, 1)
+}
+
+func TestWaitPoolClose(t *testing.T) {
+ d := poolDialer{t: t}
+ p := &redis.Pool{
+ MaxIdle: 1,
+ MaxActive: 1,
+ Dial: d.dial,
+ Wait: true,
+ }
+ c := p.Get()
+ if _, err := c.Do("PING"); err != nil {
+ t.Fatal(err)
+ }
+ errs := startGoroutines(p, "PING")
+ d.check("before close", p, 1, 1)
+ p.Close()
+ timeout := time.After(2 * time.Second)
+ for i := 0; i < cap(errs); i++ {
+ select {
+ case err := <-errs:
+ switch err {
+ case nil:
+ t.Fatal("blocked goroutine did not get error")
+ case redis.ErrPoolExhausted:
+ t.Fatal("blocked goroutine got pool exhausted error")
+ }
+ case <-timeout:
+ t.Fatal("timeout waiting for blocked goroutine")
+ }
+ }
+ c.Close()
+ d.check("done", p, 1, 0)
+}
+
+func TestWaitPoolCommandError(t *testing.T) {
+ testErr := errors.New("test")
+ d := poolDialer{t: t}
+ p := &redis.Pool{
+ MaxIdle: 1,
+ MaxActive: 1,
+ Dial: d.dial,
+ Wait: true,
+ }
+ defer p.Close()
+ c := p.Get()
+ errs := startGoroutines(p, "ERR", testErr)
+ d.check("before close", p, 1, 1)
+ c.Close()
+ timeout := time.After(2 * time.Second)
+ for i := 0; i < cap(errs); i++ {
+ select {
+ case err := <-errs:
+ if err != nil {
+ t.Fatal(err)
+ }
+ case <-timeout:
+ t.Fatalf("timeout waiting for blocked goroutine %d", i)
+ }
+ }
+ d.check("done", p, cap(errs), 0)
+}
+
+func TestWaitPoolDialError(t *testing.T) {
+ testErr := errors.New("test")
+ d := poolDialer{t: t}
+ p := &redis.Pool{
+ MaxIdle: 1,
+ MaxActive: 1,
+ Dial: d.dial,
+ Wait: true,
+ }
+ defer p.Close()
+ c := p.Get()
+ errs := startGoroutines(p, "ERR", testErr)
+ d.check("before close", p, 1, 1)
+
+ d.dialErr = errors.New("dial")
+ c.Close()
+
+ nilCount := 0
+ errCount := 0
+ timeout := time.After(2 * time.Second)
+ for i := 0; i < cap(errs); i++ {
+ select {
+ case err := <-errs:
+ switch err {
+ case nil:
+ nilCount++
+ case d.dialErr:
+ errCount++
+ default:
+ t.Fatalf("expected dial error or nil, got %v", err)
+ }
+ case <-timeout:
+ t.Fatalf("timeout waiting for blocked goroutine %d", i)
+ }
+ }
+ if nilCount != 1 {
+ t.Errorf("expected one nil error, got %d", nilCount)
+ }
+ if errCount != cap(errs)-1 {
+ t.Errorf("expected %d dial erors, got %d", cap(errs)-1, errCount)
+ }
+ d.check("done", p, cap(errs), 0)
+}
+
+// Borrowing requires us to iterate over the idle connections, unlock the pool,
+// and perform a blocking operation to check the connection still works. If
+// TestOnBorrow fails, we must reacquire the lock and continue iteration. This
+// test ensures that iteration will work correctly if multiple threads are
+// iterating simultaneously.
+func TestLocking_TestOnBorrowFails_PoolDoesntCrash(t *testing.T) {
+ count := 100
+
+ // First we'll Create a pool where the pilfering of idle connections fails.
+ d := poolDialer{t: t}
+ p := &redis.Pool{
+ MaxIdle: count,
+ MaxActive: count,
+ Dial: d.dial,
+ TestOnBorrow: func(c redis.Conn, t time.Time) error {
+ return errors.New("No way back into the real world.")
+ },
+ }
+ defer p.Close()
+
+ // Fill the pool with idle connections.
+ b1 := sync.WaitGroup{}
+ b1.Add(count)
+ b2 := sync.WaitGroup{}
+ b2.Add(count)
+ for i := 0; i < count; i++ {
+ go func() {
+ c := p.Get()
+ if c.Err() != nil {
+ t.Errorf("pool get failed: %v", c.Err())
+ }
+ b1.Done()
+ b1.Wait()
+ c.Close()
+ b2.Done()
+ }()
+ }
+ b2.Wait()
+ if d.dialed != count {
+ t.Errorf("Expected %d dials, got %d", count, d.dialed)
+ }
+
+ // Spawn a bunch of goroutines to thrash the pool.
+ b2.Add(count)
+ for i := 0; i < count; i++ {
+ go func() {
+ c := p.Get()
+ if c.Err() != nil {
+ t.Errorf("pool get failed: %v", c.Err())
+ }
+ c.Close()
+ b2.Done()
+ }()
+ }
+ b2.Wait()
+ if d.dialed != count*2 {
+ t.Errorf("Expected %d dials, got %d", count*2, d.dialed)
+ }
+}
+
+func BenchmarkPoolGet(b *testing.B) {
+ b.StopTimer()
+ p := redis.Pool{Dial: redistest.Dial, MaxIdle: 2}
+ c := p.Get()
+ if err := c.Err(); err != nil {
+ b.Fatal(err)
+ }
+ c.Close()
+ defer p.Close()
+ b.StartTimer()
+ for i := 0; i < b.N; i++ {
+ c = p.Get()
+ c.Close()
+ }
+}
+
+func BenchmarkPoolGetErr(b *testing.B) {
+ b.StopTimer()
+ p := redis.Pool{Dial: redistest.Dial, MaxIdle: 2}
+ c := p.Get()
+ if err := c.Err(); err != nil {
+ b.Fatal(err)
+ }
+ c.Close()
+ defer p.Close()
+ b.StartTimer()
+ for i := 0; i < b.N; i++ {
+ c = p.Get()
+ if err := c.Err(); err != nil {
+ b.Fatal(err)
+ }
+ c.Close()
+ }
+}
+
+func BenchmarkPoolGetPing(b *testing.B) {
+ b.StopTimer()
+ p := redis.Pool{Dial: redistest.Dial, MaxIdle: 2}
+ c := p.Get()
+ if err := c.Err(); err != nil {
+ b.Fatal(err)
+ }
+ c.Close()
+ defer p.Close()
+ b.StartTimer()
+ for i := 0; i < b.N; i++ {
+ c = p.Get()
+ if _, err := c.Do("PING"); err != nil {
+ b.Fatal(err)
+ }
+ c.Close()
+ }
+}
diff --git a/Godeps/_workspace/src/github.com/garyburd/redigo/redis/pubsub.go b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/pubsub.go
new file mode 100644
index 000000000..c0ecce824
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/pubsub.go
@@ -0,0 +1,144 @@
+// Copyright 2012 Gary Burd
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package redis
+
+import "errors"
+
+// Subscription represents a subscribe or unsubscribe notification.
+type Subscription struct {
+
+ // Kind is "subscribe", "unsubscribe", "psubscribe" or "punsubscribe"
+ Kind string
+
+ // The channel that was changed.
+ Channel string
+
+ // The current number of subscriptions for connection.
+ Count int
+}
+
+// Message represents a message notification.
+type Message struct {
+
+ // The originating channel.
+ Channel string
+
+ // The message data.
+ Data []byte
+}
+
+// PMessage represents a pmessage notification.
+type PMessage struct {
+
+ // The matched pattern.
+ Pattern string
+
+ // The originating channel.
+ Channel string
+
+ // The message data.
+ Data []byte
+}
+
+// Pong represents a pubsub pong notification.
+type Pong struct {
+ Data string
+}
+
+// PubSubConn wraps a Conn with convenience methods for subscribers.
+type PubSubConn struct {
+ Conn Conn
+}
+
+// Close closes the connection.
+func (c PubSubConn) Close() error {
+ return c.Conn.Close()
+}
+
+// Subscribe subscribes the connection to the specified channels.
+func (c PubSubConn) Subscribe(channel ...interface{}) error {
+ c.Conn.Send("SUBSCRIBE", channel...)
+ return c.Conn.Flush()
+}
+
+// PSubscribe subscribes the connection to the given patterns.
+func (c PubSubConn) PSubscribe(channel ...interface{}) error {
+ c.Conn.Send("PSUBSCRIBE", channel...)
+ return c.Conn.Flush()
+}
+
+// Unsubscribe unsubscribes the connection from the given channels, or from all
+// of them if none is given.
+func (c PubSubConn) Unsubscribe(channel ...interface{}) error {
+ c.Conn.Send("UNSUBSCRIBE", channel...)
+ return c.Conn.Flush()
+}
+
+// PUnsubscribe unsubscribes the connection from the given patterns, or from all
+// of them if none is given.
+func (c PubSubConn) PUnsubscribe(channel ...interface{}) error {
+ c.Conn.Send("PUNSUBSCRIBE", channel...)
+ return c.Conn.Flush()
+}
+
+// Ping sends a PING to the server with the specified data.
+func (c PubSubConn) Ping(data string) error {
+ c.Conn.Send("PING", data)
+ return c.Conn.Flush()
+}
+
+// Receive returns a pushed message as a Subscription, Message, PMessage, Pong
+// or error. The return value is intended to be used directly in a type switch
+// as illustrated in the PubSubConn example.
+func (c PubSubConn) Receive() interface{} {
+ reply, err := Values(c.Conn.Receive())
+ if err != nil {
+ return err
+ }
+
+ var kind string
+ reply, err = Scan(reply, &kind)
+ if err != nil {
+ return err
+ }
+
+ switch kind {
+ case "message":
+ var m Message
+ if _, err := Scan(reply, &m.Channel, &m.Data); err != nil {
+ return err
+ }
+ return m
+ case "pmessage":
+ var pm PMessage
+ if _, err := Scan(reply, &pm.Pattern, &pm.Channel, &pm.Data); err != nil {
+ return err
+ }
+ return pm
+ case "subscribe", "psubscribe", "unsubscribe", "punsubscribe":
+ s := Subscription{Kind: kind}
+ if _, err := Scan(reply, &s.Channel, &s.Count); err != nil {
+ return err
+ }
+ return s
+ case "pong":
+ var p Pong
+ if _, err := Scan(reply, &p.Data); err != nil {
+ return err
+ }
+ return p
+ }
+ return errors.New("redigo: unknown pubsub notification")
+}
diff --git a/Godeps/_workspace/src/github.com/garyburd/redigo/redis/pubsub_test.go b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/pubsub_test.go
new file mode 100644
index 000000000..365a58821
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/pubsub_test.go
@@ -0,0 +1,150 @@
+// Copyright 2012 Gary Burd
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package redis_test
+
+import (
+ "fmt"
+ "net"
+ "reflect"
+ "sync"
+ "testing"
+ "time"
+
+ "github.com/garyburd/redigo/internal/redistest"
+ "github.com/garyburd/redigo/redis"
+)
+
+func publish(channel, value interface{}) {
+ c, err := dial()
+ if err != nil {
+ panic(err)
+ }
+ defer c.Close()
+ c.Do("PUBLISH", channel, value)
+}
+
+// Applications can receive pushed messages from one goroutine and manage subscriptions from another goroutine.
+func ExamplePubSubConn() {
+ c, err := dial()
+ if err != nil {
+ panic(err)
+ }
+ defer c.Close()
+ var wg sync.WaitGroup
+ wg.Add(2)
+
+ psc := redis.PubSubConn{Conn: c}
+
+ // This goroutine receives and prints pushed notifications from the server.
+ // The goroutine exits when the connection is unsubscribed from all
+ // channels or there is an error.
+ go func() {
+ defer wg.Done()
+ for {
+ switch n := psc.Receive().(type) {
+ case redis.Message:
+ fmt.Printf("Message: %s %s\n", n.Channel, n.Data)
+ case redis.PMessage:
+ fmt.Printf("PMessage: %s %s %s\n", n.Pattern, n.Channel, n.Data)
+ case redis.Subscription:
+ fmt.Printf("Subscription: %s %s %d\n", n.Kind, n.Channel, n.Count)
+ if n.Count == 0 {
+ return
+ }
+ case error:
+ fmt.Printf("error: %v\n", n)
+ return
+ }
+ }
+ }()
+
+ // This goroutine manages subscriptions for the connection.
+ go func() {
+ defer wg.Done()
+
+ psc.Subscribe("example")
+ psc.PSubscribe("p*")
+
+ // The following function calls publish a message using another
+ // connection to the Redis server.
+ publish("example", "hello")
+ publish("example", "world")
+ publish("pexample", "foo")
+ publish("pexample", "bar")
+
+ // Unsubscribe from all connections. This will cause the receiving
+ // goroutine to exit.
+ psc.Unsubscribe()
+ psc.PUnsubscribe()
+ }()
+
+ wg.Wait()
+
+ // Output:
+ // Subscription: subscribe example 1
+ // Subscription: psubscribe p* 2
+ // Message: example hello
+ // Message: example world
+ // PMessage: p* pexample foo
+ // PMessage: p* pexample bar
+ // Subscription: unsubscribe example 1
+ // Subscription: punsubscribe p* 0
+}
+
+func expectPushed(t *testing.T, c redis.PubSubConn, message string, expected interface{}) {
+ actual := c.Receive()
+ if !reflect.DeepEqual(actual, expected) {
+ t.Errorf("%s = %v, want %v", message, actual, expected)
+ }
+}
+
+func TestPushed(t *testing.T) {
+ pc, err := redistest.Dial()
+ if err != nil {
+ t.Fatalf("error connection to database, %v", err)
+ }
+ defer pc.Close()
+
+ nc, err := net.Dial("tcp", ":6379")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer nc.Close()
+ nc.SetReadDeadline(time.Now().Add(4 * time.Second))
+
+ c := redis.PubSubConn{Conn: redis.NewConn(nc, 0, 0)}
+
+ c.Subscribe("c1")
+ expectPushed(t, c, "Subscribe(c1)", redis.Subscription{Kind: "subscribe", Channel: "c1", Count: 1})
+ c.Subscribe("c2")
+ expectPushed(t, c, "Subscribe(c2)", redis.Subscription{Kind: "subscribe", Channel: "c2", Count: 2})
+ c.PSubscribe("p1")
+ expectPushed(t, c, "PSubscribe(p1)", redis.Subscription{Kind: "psubscribe", Channel: "p1", Count: 3})
+ c.PSubscribe("p2")
+ expectPushed(t, c, "PSubscribe(p2)", redis.Subscription{Kind: "psubscribe", Channel: "p2", Count: 4})
+ c.PUnsubscribe()
+ expectPushed(t, c, "Punsubscribe(p1)", redis.Subscription{Kind: "punsubscribe", Channel: "p1", Count: 3})
+ expectPushed(t, c, "Punsubscribe()", redis.Subscription{Kind: "punsubscribe", Channel: "p2", Count: 2})
+
+ pc.Do("PUBLISH", "c1", "hello")
+ expectPushed(t, c, "PUBLISH c1 hello", redis.Message{Channel: "c1", Data: []byte("hello")})
+
+ c.Ping("hello")
+ expectPushed(t, c, `Ping("hello")`, redis.Pong{"hello"})
+
+ c.Conn.Send("PING")
+ c.Conn.Flush()
+ expectPushed(t, c, `Send("PING")`, redis.Pong{})
+}
diff --git a/Godeps/_workspace/src/github.com/garyburd/redigo/redis/redis.go b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/redis.go
new file mode 100644
index 000000000..c90a48ed4
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/redis.go
@@ -0,0 +1,44 @@
+// Copyright 2012 Gary Burd
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package redis
+
+// Error represents an error returned in a command reply.
+type Error string
+
+func (err Error) Error() string { return string(err) }
+
+// Conn represents a connection to a Redis server.
+type Conn interface {
+ // Close closes the connection.
+ Close() error
+
+ // Err returns a non-nil value if the connection is broken. The returned
+ // value is either the first non-nil value returned from the underlying
+ // network connection or a protocol parsing error. Applications should
+ // close broken connections.
+ Err() error
+
+ // Do sends a command to the server and returns the received reply.
+ Do(commandName string, args ...interface{}) (reply interface{}, err error)
+
+ // Send writes the command to the client's output buffer.
+ Send(commandName string, args ...interface{}) error
+
+ // Flush flushes the output buffer to the Redis server.
+ Flush() error
+
+ // Receive receives a single reply from the Redis server
+ Receive() (reply interface{}, err error)
+}
diff --git a/Godeps/_workspace/src/github.com/garyburd/redigo/redis/reply.go b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/reply.go
new file mode 100644
index 000000000..5af29bf51
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/reply.go
@@ -0,0 +1,364 @@
+// Copyright 2012 Gary Burd
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package redis
+
+import (
+ "errors"
+ "fmt"
+ "strconv"
+)
+
+// ErrNil indicates that a reply value is nil.
+var ErrNil = errors.New("redigo: nil returned")
+
+// Int is a helper that converts a command reply to an integer. If err is not
+// equal to nil, then Int returns 0, err. Otherwise, Int converts the
+// reply to an int as follows:
+//
+// Reply type Result
+// integer int(reply), nil
+// bulk string parsed reply, nil
+// nil 0, ErrNil
+// other 0, error
+func Int(reply interface{}, err error) (int, error) {
+ if err != nil {
+ return 0, err
+ }
+ switch reply := reply.(type) {
+ case int64:
+ x := int(reply)
+ if int64(x) != reply {
+ return 0, strconv.ErrRange
+ }
+ return x, nil
+ case []byte:
+ n, err := strconv.ParseInt(string(reply), 10, 0)
+ return int(n), err
+ case nil:
+ return 0, ErrNil
+ case Error:
+ return 0, reply
+ }
+ return 0, fmt.Errorf("redigo: unexpected type for Int, got type %T", reply)
+}
+
+// Int64 is a helper that converts a command reply to 64 bit integer. If err is
+// not equal to nil, then Int returns 0, err. Otherwise, Int64 converts the
+// reply to an int64 as follows:
+//
+// Reply type Result
+// integer reply, nil
+// bulk string parsed reply, nil
+// nil 0, ErrNil
+// other 0, error
+func Int64(reply interface{}, err error) (int64, error) {
+ if err != nil {
+ return 0, err
+ }
+ switch reply := reply.(type) {
+ case int64:
+ return reply, nil
+ case []byte:
+ n, err := strconv.ParseInt(string(reply), 10, 64)
+ return n, err
+ case nil:
+ return 0, ErrNil
+ case Error:
+ return 0, reply
+ }
+ return 0, fmt.Errorf("redigo: unexpected type for Int64, got type %T", reply)
+}
+
+var errNegativeInt = errors.New("redigo: unexpected value for Uint64")
+
+// Uint64 is a helper that converts a command reply to 64 bit integer. If err is
+// not equal to nil, then Int returns 0, err. Otherwise, Int64 converts the
+// reply to an int64 as follows:
+//
+// Reply type Result
+// integer reply, nil
+// bulk string parsed reply, nil
+// nil 0, ErrNil
+// other 0, error
+func Uint64(reply interface{}, err error) (uint64, error) {
+ if err != nil {
+ return 0, err
+ }
+ switch reply := reply.(type) {
+ case int64:
+ if reply < 0 {
+ return 0, errNegativeInt
+ }
+ return uint64(reply), nil
+ case []byte:
+ n, err := strconv.ParseUint(string(reply), 10, 64)
+ return n, err
+ case nil:
+ return 0, ErrNil
+ case Error:
+ return 0, reply
+ }
+ return 0, fmt.Errorf("redigo: unexpected type for Uint64, got type %T", reply)
+}
+
+// Float64 is a helper that converts a command reply to 64 bit float. If err is
+// not equal to nil, then Float64 returns 0, err. Otherwise, Float64 converts
+// the reply to an int as follows:
+//
+// Reply type Result
+// bulk string parsed reply, nil
+// nil 0, ErrNil
+// other 0, error
+func Float64(reply interface{}, err error) (float64, error) {
+ if err != nil {
+ return 0, err
+ }
+ switch reply := reply.(type) {
+ case []byte:
+ n, err := strconv.ParseFloat(string(reply), 64)
+ return n, err
+ case nil:
+ return 0, ErrNil
+ case Error:
+ return 0, reply
+ }
+ return 0, fmt.Errorf("redigo: unexpected type for Float64, got type %T", reply)
+}
+
+// String is a helper that converts a command reply to a string. If err is not
+// equal to nil, then String returns "", err. Otherwise String converts the
+// reply to a string as follows:
+//
+// Reply type Result
+// bulk string string(reply), nil
+// simple string reply, nil
+// nil "", ErrNil
+// other "", error
+func String(reply interface{}, err error) (string, error) {
+ if err != nil {
+ return "", err
+ }
+ switch reply := reply.(type) {
+ case []byte:
+ return string(reply), nil
+ case string:
+ return reply, nil
+ case nil:
+ return "", ErrNil
+ case Error:
+ return "", reply
+ }
+ return "", fmt.Errorf("redigo: unexpected type for String, got type %T", reply)
+}
+
+// Bytes is a helper that converts a command reply to a slice of bytes. If err
+// is not equal to nil, then Bytes returns nil, err. Otherwise Bytes converts
+// the reply to a slice of bytes as follows:
+//
+// Reply type Result
+// bulk string reply, nil
+// simple string []byte(reply), nil
+// nil nil, ErrNil
+// other nil, error
+func Bytes(reply interface{}, err error) ([]byte, error) {
+ if err != nil {
+ return nil, err
+ }
+ switch reply := reply.(type) {
+ case []byte:
+ return reply, nil
+ case string:
+ return []byte(reply), nil
+ case nil:
+ return nil, ErrNil
+ case Error:
+ return nil, reply
+ }
+ return nil, fmt.Errorf("redigo: unexpected type for Bytes, got type %T", reply)
+}
+
+// Bool is a helper that converts a command reply to a boolean. If err is not
+// equal to nil, then Bool returns false, err. Otherwise Bool converts the
+// reply to boolean as follows:
+//
+// Reply type Result
+// integer value != 0, nil
+// bulk string strconv.ParseBool(reply)
+// nil false, ErrNil
+// other false, error
+func Bool(reply interface{}, err error) (bool, error) {
+ if err != nil {
+ return false, err
+ }
+ switch reply := reply.(type) {
+ case int64:
+ return reply != 0, nil
+ case []byte:
+ return strconv.ParseBool(string(reply))
+ case nil:
+ return false, ErrNil
+ case Error:
+ return false, reply
+ }
+ return false, fmt.Errorf("redigo: unexpected type for Bool, got type %T", reply)
+}
+
+// MultiBulk is deprecated. Use Values.
+func MultiBulk(reply interface{}, err error) ([]interface{}, error) { return Values(reply, err) }
+
+// Values is a helper that converts an array command reply to a []interface{}.
+// If err is not equal to nil, then Values returns nil, err. Otherwise, Values
+// converts the reply as follows:
+//
+// Reply type Result
+// array reply, nil
+// nil nil, ErrNil
+// other nil, error
+func Values(reply interface{}, err error) ([]interface{}, error) {
+ if err != nil {
+ return nil, err
+ }
+ switch reply := reply.(type) {
+ case []interface{}:
+ return reply, nil
+ case nil:
+ return nil, ErrNil
+ case Error:
+ return nil, reply
+ }
+ return nil, fmt.Errorf("redigo: unexpected type for Values, got type %T", reply)
+}
+
+// Strings is a helper that converts an array command reply to a []string. If
+// err is not equal to nil, then Strings returns nil, err. Nil array items are
+// converted to "" in the output slice. Strings returns an error if an array
+// item is not a bulk string or nil.
+func Strings(reply interface{}, err error) ([]string, error) {
+ if err != nil {
+ return nil, err
+ }
+ switch reply := reply.(type) {
+ case []interface{}:
+ result := make([]string, len(reply))
+ for i := range reply {
+ if reply[i] == nil {
+ continue
+ }
+ p, ok := reply[i].([]byte)
+ if !ok {
+ return nil, fmt.Errorf("redigo: unexpected element type for Strings, got type %T", reply[i])
+ }
+ result[i] = string(p)
+ }
+ return result, nil
+ case nil:
+ return nil, ErrNil
+ case Error:
+ return nil, reply
+ }
+ return nil, fmt.Errorf("redigo: unexpected type for Strings, got type %T", reply)
+}
+
+// Ints is a helper that converts an array command reply to a []int. If
+// err is not equal to nil, then Ints returns nil, err.
+func Ints(reply interface{}, err error) ([]int, error) {
+ var ints []int
+ if reply == nil {
+ return ints, ErrNil
+ }
+ values, err := Values(reply, err)
+ if err != nil {
+ return ints, err
+ }
+ if err := ScanSlice(values, &ints); err != nil {
+ return ints, err
+ }
+ return ints, nil
+}
+
+// StringMap is a helper that converts an array of strings (alternating key, value)
+// into a map[string]string. The HGETALL and CONFIG GET commands return replies in this format.
+// Requires an even number of values in result.
+func StringMap(result interface{}, err error) (map[string]string, error) {
+ values, err := Values(result, err)
+ if err != nil {
+ return nil, err
+ }
+ if len(values)%2 != 0 {
+ return nil, errors.New("redigo: StringMap expects even number of values result")
+ }
+ m := make(map[string]string, len(values)/2)
+ for i := 0; i < len(values); i += 2 {
+ key, okKey := values[i].([]byte)
+ value, okValue := values[i+1].([]byte)
+ if !okKey || !okValue {
+ return nil, errors.New("redigo: ScanMap key not a bulk string value")
+ }
+ m[string(key)] = string(value)
+ }
+ return m, nil
+}
+
+// IntMap is a helper that converts an array of strings (alternating key, value)
+// into a map[string]int. The HGETALL commands return replies in this format.
+// Requires an even number of values in result.
+func IntMap(result interface{}, err error) (map[string]int, error) {
+ values, err := Values(result, err)
+ if err != nil {
+ return nil, err
+ }
+ if len(values)%2 != 0 {
+ return nil, errors.New("redigo: IntMap expects even number of values result")
+ }
+ m := make(map[string]int, len(values)/2)
+ for i := 0; i < len(values); i += 2 {
+ key, ok := values[i].([]byte)
+ if !ok {
+ return nil, errors.New("redigo: ScanMap key not a bulk string value")
+ }
+ value, err := Int(values[i+1], nil)
+ if err != nil {
+ return nil, err
+ }
+ m[string(key)] = value
+ }
+ return m, nil
+}
+
+// Int64Map is a helper that converts an array of strings (alternating key, value)
+// into a map[string]int64. The HGETALL commands return replies in this format.
+// Requires an even number of values in result.
+func Int64Map(result interface{}, err error) (map[string]int64, error) {
+ values, err := Values(result, err)
+ if err != nil {
+ return nil, err
+ }
+ if len(values)%2 != 0 {
+ return nil, errors.New("redigo: Int64Map expects even number of values result")
+ }
+ m := make(map[string]int64, len(values)/2)
+ for i := 0; i < len(values); i += 2 {
+ key, ok := values[i].([]byte)
+ if !ok {
+ return nil, errors.New("redigo: ScanMap key not a bulk string value")
+ }
+ value, err := Int64(values[i+1], nil)
+ if err != nil {
+ return nil, err
+ }
+ m[string(key)] = value
+ }
+ return m, nil
+}
diff --git a/Godeps/_workspace/src/github.com/garyburd/redigo/redis/reply_test.go b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/reply_test.go
new file mode 100644
index 000000000..92744c590
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/reply_test.go
@@ -0,0 +1,166 @@
+// Copyright 2012 Gary Burd
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package redis_test
+
+import (
+ "fmt"
+ "reflect"
+ "testing"
+
+ "github.com/garyburd/redigo/internal/redistest"
+ "github.com/garyburd/redigo/redis"
+)
+
+type valueError struct {
+ v interface{}
+ err error
+}
+
+func ve(v interface{}, err error) valueError {
+ return valueError{v, err}
+}
+
+var replyTests = []struct {
+ name interface{}
+ actual valueError
+ expected valueError
+}{
+ {
+ "ints([v1, v2])",
+ ve(redis.Ints([]interface{}{[]byte("4"), []byte("5")}, nil)),
+ ve([]int{4, 5}, nil),
+ },
+ {
+ "ints(nil)",
+ ve(redis.Ints(nil, nil)),
+ ve([]int(nil), redis.ErrNil),
+ },
+ {
+ "strings([v1, v2])",
+ ve(redis.Strings([]interface{}{[]byte("v1"), []byte("v2")}, nil)),
+ ve([]string{"v1", "v2"}, nil),
+ },
+ {
+ "strings(nil)",
+ ve(redis.Strings(nil, nil)),
+ ve([]string(nil), redis.ErrNil),
+ },
+ {
+ "values([v1, v2])",
+ ve(redis.Values([]interface{}{[]byte("v1"), []byte("v2")}, nil)),
+ ve([]interface{}{[]byte("v1"), []byte("v2")}, nil),
+ },
+ {
+ "values(nil)",
+ ve(redis.Values(nil, nil)),
+ ve([]interface{}(nil), redis.ErrNil),
+ },
+ {
+ "float64(1.0)",
+ ve(redis.Float64([]byte("1.0"), nil)),
+ ve(float64(1.0), nil),
+ },
+ {
+ "float64(nil)",
+ ve(redis.Float64(nil, nil)),
+ ve(float64(0.0), redis.ErrNil),
+ },
+ {
+ "uint64(1)",
+ ve(redis.Uint64(int64(1), nil)),
+ ve(uint64(1), nil),
+ },
+ {
+ "uint64(-1)",
+ ve(redis.Uint64(int64(-1), nil)),
+ ve(uint64(0), redis.ErrNegativeInt),
+ },
+}
+
+func TestReply(t *testing.T) {
+ for _, rt := range replyTests {
+ if rt.actual.err != rt.expected.err {
+ t.Errorf("%s returned err %v, want %v", rt.name, rt.actual.err, rt.expected.err)
+ continue
+ }
+ if !reflect.DeepEqual(rt.actual.v, rt.expected.v) {
+ t.Errorf("%s=%+v, want %+v", rt.name, rt.actual.v, rt.expected.v)
+ }
+ }
+}
+
+// dial wraps DialTestDB() with a more suitable function name for examples.
+func dial() (redis.Conn, error) {
+ return redistest.Dial()
+}
+
+func ExampleBool() {
+ c, err := dial()
+ if err != nil {
+ panic(err)
+ }
+ defer c.Close()
+
+ c.Do("SET", "foo", 1)
+ exists, _ := redis.Bool(c.Do("EXISTS", "foo"))
+ fmt.Printf("%#v\n", exists)
+ // Output:
+ // true
+}
+
+func ExampleInt() {
+ c, err := dial()
+ if err != nil {
+ panic(err)
+ }
+ defer c.Close()
+
+ c.Do("SET", "k1", 1)
+ n, _ := redis.Int(c.Do("GET", "k1"))
+ fmt.Printf("%#v\n", n)
+ n, _ = redis.Int(c.Do("INCR", "k1"))
+ fmt.Printf("%#v\n", n)
+ // Output:
+ // 1
+ // 2
+}
+
+func ExampleInts() {
+ c, err := dial()
+ if err != nil {
+ panic(err)
+ }
+ defer c.Close()
+
+ c.Do("SADD", "set_with_integers", 4, 5, 6)
+ ints, _ := redis.Ints(c.Do("SMEMBERS", "set_with_integers"))
+ fmt.Printf("%#v\n", ints)
+ // Output:
+ // []int{4, 5, 6}
+}
+
+func ExampleString() {
+ c, err := dial()
+ if err != nil {
+ panic(err)
+ }
+ defer c.Close()
+
+ c.Do("SET", "hello", "world")
+ s, err := redis.String(c.Do("GET", "hello"))
+ fmt.Printf("%#v\n", s)
+ // Output:
+ // "world"
+}
diff --git a/Godeps/_workspace/src/github.com/garyburd/redigo/redis/scan.go b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/scan.go
new file mode 100644
index 000000000..8c9cfa18d
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/scan.go
@@ -0,0 +1,513 @@
+// Copyright 2012 Gary Burd
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package redis
+
+import (
+ "errors"
+ "fmt"
+ "reflect"
+ "strconv"
+ "strings"
+ "sync"
+)
+
+func ensureLen(d reflect.Value, n int) {
+ if n > d.Cap() {
+ d.Set(reflect.MakeSlice(d.Type(), n, n))
+ } else {
+ d.SetLen(n)
+ }
+}
+
+func cannotConvert(d reflect.Value, s interface{}) error {
+ return fmt.Errorf("redigo: Scan cannot convert from %s to %s",
+ reflect.TypeOf(s), d.Type())
+}
+
+func convertAssignBytes(d reflect.Value, s []byte) (err error) {
+ switch d.Type().Kind() {
+ case reflect.Float32, reflect.Float64:
+ var x float64
+ x, err = strconv.ParseFloat(string(s), d.Type().Bits())
+ d.SetFloat(x)
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ var x int64
+ x, err = strconv.ParseInt(string(s), 10, d.Type().Bits())
+ d.SetInt(x)
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+ var x uint64
+ x, err = strconv.ParseUint(string(s), 10, d.Type().Bits())
+ d.SetUint(x)
+ case reflect.Bool:
+ var x bool
+ x, err = strconv.ParseBool(string(s))
+ d.SetBool(x)
+ case reflect.String:
+ d.SetString(string(s))
+ case reflect.Slice:
+ if d.Type().Elem().Kind() != reflect.Uint8 {
+ err = cannotConvert(d, s)
+ } else {
+ d.SetBytes(s)
+ }
+ default:
+ err = cannotConvert(d, s)
+ }
+ return
+}
+
+func convertAssignInt(d reflect.Value, s int64) (err error) {
+ switch d.Type().Kind() {
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ d.SetInt(s)
+ if d.Int() != s {
+ err = strconv.ErrRange
+ d.SetInt(0)
+ }
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+ if s < 0 {
+ err = strconv.ErrRange
+ } else {
+ x := uint64(s)
+ d.SetUint(x)
+ if d.Uint() != x {
+ err = strconv.ErrRange
+ d.SetUint(0)
+ }
+ }
+ case reflect.Bool:
+ d.SetBool(s != 0)
+ default:
+ err = cannotConvert(d, s)
+ }
+ return
+}
+
+func convertAssignValue(d reflect.Value, s interface{}) (err error) {
+ switch s := s.(type) {
+ case []byte:
+ err = convertAssignBytes(d, s)
+ case int64:
+ err = convertAssignInt(d, s)
+ default:
+ err = cannotConvert(d, s)
+ }
+ return err
+}
+
+func convertAssignValues(d reflect.Value, s []interface{}) error {
+ if d.Type().Kind() != reflect.Slice {
+ return cannotConvert(d, s)
+ }
+ ensureLen(d, len(s))
+ for i := 0; i < len(s); i++ {
+ if err := convertAssignValue(d.Index(i), s[i]); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func convertAssign(d interface{}, s interface{}) (err error) {
+ // Handle the most common destination types using type switches and
+ // fall back to reflection for all other types.
+ switch s := s.(type) {
+ case nil:
+ // ingore
+ case []byte:
+ switch d := d.(type) {
+ case *string:
+ *d = string(s)
+ case *int:
+ *d, err = strconv.Atoi(string(s))
+ case *bool:
+ *d, err = strconv.ParseBool(string(s))
+ case *[]byte:
+ *d = s
+ case *interface{}:
+ *d = s
+ case nil:
+ // skip value
+ default:
+ if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr {
+ err = cannotConvert(d, s)
+ } else {
+ err = convertAssignBytes(d.Elem(), s)
+ }
+ }
+ case int64:
+ switch d := d.(type) {
+ case *int:
+ x := int(s)
+ if int64(x) != s {
+ err = strconv.ErrRange
+ x = 0
+ }
+ *d = x
+ case *bool:
+ *d = s != 0
+ case *interface{}:
+ *d = s
+ case nil:
+ // skip value
+ default:
+ if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr {
+ err = cannotConvert(d, s)
+ } else {
+ err = convertAssignInt(d.Elem(), s)
+ }
+ }
+ case []interface{}:
+ switch d := d.(type) {
+ case *[]interface{}:
+ *d = s
+ case *interface{}:
+ *d = s
+ case nil:
+ // skip value
+ default:
+ if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr {
+ err = cannotConvert(d, s)
+ } else {
+ err = convertAssignValues(d.Elem(), s)
+ }
+ }
+ case Error:
+ err = s
+ default:
+ err = cannotConvert(reflect.ValueOf(d), s)
+ }
+ return
+}
+
+// Scan copies from src to the values pointed at by dest.
+//
+// The values pointed at by dest must be an integer, float, boolean, string,
+// []byte, interface{} or slices of these types. Scan uses the standard strconv
+// package to convert bulk strings to numeric and boolean types.
+//
+// If a dest value is nil, then the corresponding src value is skipped.
+//
+// If a src element is nil, then the corresponding dest value is not modified.
+//
+// To enable easy use of Scan in a loop, Scan returns the slice of src
+// following the copied values.
+func Scan(src []interface{}, dest ...interface{}) ([]interface{}, error) {
+ if len(src) < len(dest) {
+ return nil, errors.New("redigo: Scan array short")
+ }
+ var err error
+ for i, d := range dest {
+ err = convertAssign(d, src[i])
+ if err != nil {
+ break
+ }
+ }
+ return src[len(dest):], err
+}
+
+type fieldSpec struct {
+ name string
+ index []int
+ //omitEmpty bool
+}
+
+type structSpec struct {
+ m map[string]*fieldSpec
+ l []*fieldSpec
+}
+
+func (ss *structSpec) fieldSpec(name []byte) *fieldSpec {
+ return ss.m[string(name)]
+}
+
+func compileStructSpec(t reflect.Type, depth map[string]int, index []int, ss *structSpec) {
+ for i := 0; i < t.NumField(); i++ {
+ f := t.Field(i)
+ switch {
+ case f.PkgPath != "":
+ // Ignore unexported fields.
+ case f.Anonymous:
+ // TODO: Handle pointers. Requires change to decoder and
+ // protection against infinite recursion.
+ if f.Type.Kind() == reflect.Struct {
+ compileStructSpec(f.Type, depth, append(index, i), ss)
+ }
+ default:
+ fs := &fieldSpec{name: f.Name}
+ tag := f.Tag.Get("redis")
+ p := strings.Split(tag, ",")
+ if len(p) > 0 {
+ if p[0] == "-" {
+ continue
+ }
+ if len(p[0]) > 0 {
+ fs.name = p[0]
+ }
+ for _, s := range p[1:] {
+ switch s {
+ //case "omitempty":
+ // fs.omitempty = true
+ default:
+ panic(errors.New("redigo: unknown field flag " + s + " for type " + t.Name()))
+ }
+ }
+ }
+ d, found := depth[fs.name]
+ if !found {
+ d = 1 << 30
+ }
+ switch {
+ case len(index) == d:
+ // At same depth, remove from result.
+ delete(ss.m, fs.name)
+ j := 0
+ for i := 0; i < len(ss.l); i++ {
+ if fs.name != ss.l[i].name {
+ ss.l[j] = ss.l[i]
+ j += 1
+ }
+ }
+ ss.l = ss.l[:j]
+ case len(index) < d:
+ fs.index = make([]int, len(index)+1)
+ copy(fs.index, index)
+ fs.index[len(index)] = i
+ depth[fs.name] = len(index)
+ ss.m[fs.name] = fs
+ ss.l = append(ss.l, fs)
+ }
+ }
+ }
+}
+
+var (
+ structSpecMutex sync.RWMutex
+ structSpecCache = make(map[reflect.Type]*structSpec)
+ defaultFieldSpec = &fieldSpec{}
+)
+
+func structSpecForType(t reflect.Type) *structSpec {
+
+ structSpecMutex.RLock()
+ ss, found := structSpecCache[t]
+ structSpecMutex.RUnlock()
+ if found {
+ return ss
+ }
+
+ structSpecMutex.Lock()
+ defer structSpecMutex.Unlock()
+ ss, found = structSpecCache[t]
+ if found {
+ return ss
+ }
+
+ ss = &structSpec{m: make(map[string]*fieldSpec)}
+ compileStructSpec(t, make(map[string]int), nil, ss)
+ structSpecCache[t] = ss
+ return ss
+}
+
+var errScanStructValue = errors.New("redigo: ScanStruct value must be non-nil pointer to a struct")
+
+// ScanStruct scans alternating names and values from src to a struct. The
+// HGETALL and CONFIG GET commands return replies in this format.
+//
+// ScanStruct uses exported field names to match values in the response. Use
+// 'redis' field tag to override the name:
+//
+// Field int `redis:"myName"`
+//
+// Fields with the tag redis:"-" are ignored.
+//
+// Integer, float, boolean, string and []byte fields are supported. Scan uses the
+// standard strconv package to convert bulk string values to numeric and
+// boolean types.
+//
+// If a src element is nil, then the corresponding field is not modified.
+func ScanStruct(src []interface{}, dest interface{}) error {
+ d := reflect.ValueOf(dest)
+ if d.Kind() != reflect.Ptr || d.IsNil() {
+ return errScanStructValue
+ }
+ d = d.Elem()
+ if d.Kind() != reflect.Struct {
+ return errScanStructValue
+ }
+ ss := structSpecForType(d.Type())
+
+ if len(src)%2 != 0 {
+ return errors.New("redigo: ScanStruct expects even number of values in values")
+ }
+
+ for i := 0; i < len(src); i += 2 {
+ s := src[i+1]
+ if s == nil {
+ continue
+ }
+ name, ok := src[i].([]byte)
+ if !ok {
+ return errors.New("redigo: ScanStruct key not a bulk string value")
+ }
+ fs := ss.fieldSpec(name)
+ if fs == nil {
+ continue
+ }
+ if err := convertAssignValue(d.FieldByIndex(fs.index), s); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+var (
+ errScanSliceValue = errors.New("redigo: ScanSlice dest must be non-nil pointer to a struct")
+)
+
+// ScanSlice scans src to the slice pointed to by dest. The elements the dest
+// slice must be integer, float, boolean, string, struct or pointer to struct
+// values.
+//
+// Struct fields must be integer, float, boolean or string values. All struct
+// fields are used unless a subset is specified using fieldNames.
+func ScanSlice(src []interface{}, dest interface{}, fieldNames ...string) error {
+ d := reflect.ValueOf(dest)
+ if d.Kind() != reflect.Ptr || d.IsNil() {
+ return errScanSliceValue
+ }
+ d = d.Elem()
+ if d.Kind() != reflect.Slice {
+ return errScanSliceValue
+ }
+
+ isPtr := false
+ t := d.Type().Elem()
+ if t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct {
+ isPtr = true
+ t = t.Elem()
+ }
+
+ if t.Kind() != reflect.Struct {
+ ensureLen(d, len(src))
+ for i, s := range src {
+ if s == nil {
+ continue
+ }
+ if err := convertAssignValue(d.Index(i), s); err != nil {
+ return err
+ }
+ }
+ return nil
+ }
+
+ ss := structSpecForType(t)
+ fss := ss.l
+ if len(fieldNames) > 0 {
+ fss = make([]*fieldSpec, len(fieldNames))
+ for i, name := range fieldNames {
+ fss[i] = ss.m[name]
+ if fss[i] == nil {
+ return errors.New("redigo: ScanSlice bad field name " + name)
+ }
+ }
+ }
+
+ if len(fss) == 0 {
+ return errors.New("redigo: ScanSlice no struct fields")
+ }
+
+ n := len(src) / len(fss)
+ if n*len(fss) != len(src) {
+ return errors.New("redigo: ScanSlice length not a multiple of struct field count")
+ }
+
+ ensureLen(d, n)
+ for i := 0; i < n; i++ {
+ d := d.Index(i)
+ if isPtr {
+ if d.IsNil() {
+ d.Set(reflect.New(t))
+ }
+ d = d.Elem()
+ }
+ for j, fs := range fss {
+ s := src[i*len(fss)+j]
+ if s == nil {
+ continue
+ }
+ if err := convertAssignValue(d.FieldByIndex(fs.index), s); err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
+
+// Args is a helper for constructing command arguments from structured values.
+type Args []interface{}
+
+// Add returns the result of appending value to args.
+func (args Args) Add(value ...interface{}) Args {
+ return append(args, value...)
+}
+
+// AddFlat returns the result of appending the flattened value of v to args.
+//
+// Maps are flattened by appending the alternating keys and map values to args.
+//
+// Slices are flattened by appending the slice elements to args.
+//
+// Structs are flattened by appending the alternating names and values of
+// exported fields to args. If v is a nil struct pointer, then nothing is
+// appended. The 'redis' field tag overrides struct field names. See ScanStruct
+// for more information on the use of the 'redis' field tag.
+//
+// Other types are appended to args as is.
+func (args Args) AddFlat(v interface{}) Args {
+ rv := reflect.ValueOf(v)
+ switch rv.Kind() {
+ case reflect.Struct:
+ args = flattenStruct(args, rv)
+ case reflect.Slice:
+ for i := 0; i < rv.Len(); i++ {
+ args = append(args, rv.Index(i).Interface())
+ }
+ case reflect.Map:
+ for _, k := range rv.MapKeys() {
+ args = append(args, k.Interface(), rv.MapIndex(k).Interface())
+ }
+ case reflect.Ptr:
+ if rv.Type().Elem().Kind() == reflect.Struct {
+ if !rv.IsNil() {
+ args = flattenStruct(args, rv.Elem())
+ }
+ } else {
+ args = append(args, v)
+ }
+ default:
+ args = append(args, v)
+ }
+ return args
+}
+
+func flattenStruct(args Args, v reflect.Value) Args {
+ ss := structSpecForType(v.Type())
+ for _, fs := range ss.l {
+ fv := v.FieldByIndex(fs.index)
+ args = append(args, fs.name, fv.Interface())
+ }
+ return args
+}
diff --git a/Godeps/_workspace/src/github.com/garyburd/redigo/redis/scan_test.go b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/scan_test.go
new file mode 100644
index 000000000..b57dd8969
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/scan_test.go
@@ -0,0 +1,412 @@
+// Copyright 2012 Gary Burd
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package redis_test
+
+import (
+ "fmt"
+ "github.com/garyburd/redigo/redis"
+ "math"
+ "reflect"
+ "testing"
+)
+
+var scanConversionTests = []struct {
+ src interface{}
+ dest interface{}
+}{
+ {[]byte("-inf"), math.Inf(-1)},
+ {[]byte("+inf"), math.Inf(1)},
+ {[]byte("0"), float64(0)},
+ {[]byte("3.14159"), float64(3.14159)},
+ {[]byte("3.14"), float32(3.14)},
+ {[]byte("-100"), int(-100)},
+ {[]byte("101"), int(101)},
+ {int64(102), int(102)},
+ {[]byte("103"), uint(103)},
+ {int64(104), uint(104)},
+ {[]byte("105"), int8(105)},
+ {int64(106), int8(106)},
+ {[]byte("107"), uint8(107)},
+ {int64(108), uint8(108)},
+ {[]byte("0"), false},
+ {int64(0), false},
+ {[]byte("f"), false},
+ {[]byte("1"), true},
+ {int64(1), true},
+ {[]byte("t"), true},
+ {[]byte("hello"), "hello"},
+ {[]byte("world"), []byte("world")},
+ {[]interface{}{[]byte("foo")}, []interface{}{[]byte("foo")}},
+ {[]interface{}{[]byte("foo")}, []string{"foo"}},
+ {[]interface{}{[]byte("hello"), []byte("world")}, []string{"hello", "world"}},
+ {[]interface{}{[]byte("bar")}, [][]byte{[]byte("bar")}},
+ {[]interface{}{[]byte("1")}, []int{1}},
+ {[]interface{}{[]byte("1"), []byte("2")}, []int{1, 2}},
+ {[]interface{}{[]byte("1"), []byte("2")}, []float64{1, 2}},
+ {[]interface{}{[]byte("1")}, []byte{1}},
+ {[]interface{}{[]byte("1")}, []bool{true}},
+}
+
+func TestScanConversion(t *testing.T) {
+ for _, tt := range scanConversionTests {
+ values := []interface{}{tt.src}
+ dest := reflect.New(reflect.TypeOf(tt.dest))
+ values, err := redis.Scan(values, dest.Interface())
+ if err != nil {
+ t.Errorf("Scan(%v) returned error %v", tt, err)
+ continue
+ }
+ if !reflect.DeepEqual(tt.dest, dest.Elem().Interface()) {
+ t.Errorf("Scan(%v) returned %v, want %v", tt, dest.Elem().Interface(), tt.dest)
+ }
+ }
+}
+
+var scanConversionErrorTests = []struct {
+ src interface{}
+ dest interface{}
+}{
+ {[]byte("1234"), byte(0)},
+ {int64(1234), byte(0)},
+ {[]byte("-1"), byte(0)},
+ {int64(-1), byte(0)},
+ {[]byte("junk"), false},
+ {redis.Error("blah"), false},
+}
+
+func TestScanConversionError(t *testing.T) {
+ for _, tt := range scanConversionErrorTests {
+ values := []interface{}{tt.src}
+ dest := reflect.New(reflect.TypeOf(tt.dest))
+ values, err := redis.Scan(values, dest.Interface())
+ if err == nil {
+ t.Errorf("Scan(%v) did not return error", tt)
+ }
+ }
+}
+
+func ExampleScan() {
+ c, err := dial()
+ if err != nil {
+ panic(err)
+ }
+ defer c.Close()
+
+ c.Send("HMSET", "album:1", "title", "Red", "rating", 5)
+ c.Send("HMSET", "album:2", "title", "Earthbound", "rating", 1)
+ c.Send("HMSET", "album:3", "title", "Beat")
+ c.Send("LPUSH", "albums", "1")
+ c.Send("LPUSH", "albums", "2")
+ c.Send("LPUSH", "albums", "3")
+ values, err := redis.Values(c.Do("SORT", "albums",
+ "BY", "album:*->rating",
+ "GET", "album:*->title",
+ "GET", "album:*->rating"))
+ if err != nil {
+ panic(err)
+ }
+
+ for len(values) > 0 {
+ var title string
+ rating := -1 // initialize to illegal value to detect nil.
+ values, err = redis.Scan(values, &title, &rating)
+ if err != nil {
+ panic(err)
+ }
+ if rating == -1 {
+ fmt.Println(title, "not-rated")
+ } else {
+ fmt.Println(title, rating)
+ }
+ }
+ // Output:
+ // Beat not-rated
+ // Earthbound 1
+ // Red 5
+}
+
+type s0 struct {
+ X int
+ Y int `redis:"y"`
+ Bt bool
+}
+
+type s1 struct {
+ X int `redis:"-"`
+ I int `redis:"i"`
+ U uint `redis:"u"`
+ S string `redis:"s"`
+ P []byte `redis:"p"`
+ B bool `redis:"b"`
+ Bt bool
+ Bf bool
+ s0
+}
+
+var scanStructTests = []struct {
+ title string
+ reply []string
+ value interface{}
+}{
+ {"basic",
+ []string{"i", "-1234", "u", "5678", "s", "hello", "p", "world", "b", "t", "Bt", "1", "Bf", "0", "X", "123", "y", "456"},
+ &s1{I: -1234, U: 5678, S: "hello", P: []byte("world"), B: true, Bt: true, Bf: false, s0: s0{X: 123, Y: 456}},
+ },
+}
+
+func TestScanStruct(t *testing.T) {
+ for _, tt := range scanStructTests {
+
+ var reply []interface{}
+ for _, v := range tt.reply {
+ reply = append(reply, []byte(v))
+ }
+
+ value := reflect.New(reflect.ValueOf(tt.value).Type().Elem())
+
+ if err := redis.ScanStruct(reply, value.Interface()); err != nil {
+ t.Fatalf("ScanStruct(%s) returned error %v", tt.title, err)
+ }
+
+ if !reflect.DeepEqual(value.Interface(), tt.value) {
+ t.Fatalf("ScanStruct(%s) returned %v, want %v", tt.title, value.Interface(), tt.value)
+ }
+ }
+}
+
+func TestBadScanStructArgs(t *testing.T) {
+ x := []interface{}{"A", "b"}
+ test := func(v interface{}) {
+ if err := redis.ScanStruct(x, v); err == nil {
+ t.Errorf("Expect error for ScanStruct(%T, %T)", x, v)
+ }
+ }
+
+ test(nil)
+
+ var v0 *struct{}
+ test(v0)
+
+ var v1 int
+ test(&v1)
+
+ x = x[:1]
+ v2 := struct{ A string }{}
+ test(&v2)
+}
+
+var scanSliceTests = []struct {
+ src []interface{}
+ fieldNames []string
+ ok bool
+ dest interface{}
+}{
+ {
+ []interface{}{[]byte("1"), nil, []byte("-1")},
+ nil,
+ true,
+ []int{1, 0, -1},
+ },
+ {
+ []interface{}{[]byte("1"), nil, []byte("2")},
+ nil,
+ true,
+ []uint{1, 0, 2},
+ },
+ {
+ []interface{}{[]byte("-1")},
+ nil,
+ false,
+ []uint{1},
+ },
+ {
+ []interface{}{[]byte("hello"), nil, []byte("world")},
+ nil,
+ true,
+ [][]byte{[]byte("hello"), nil, []byte("world")},
+ },
+ {
+ []interface{}{[]byte("hello"), nil, []byte("world")},
+ nil,
+ true,
+ []string{"hello", "", "world"},
+ },
+ {
+ []interface{}{[]byte("a1"), []byte("b1"), []byte("a2"), []byte("b2")},
+ nil,
+ true,
+ []struct{ A, B string }{{"a1", "b1"}, {"a2", "b2"}},
+ },
+ {
+ []interface{}{[]byte("a1"), []byte("b1")},
+ nil,
+ false,
+ []struct{ A, B, C string }{{"a1", "b1", ""}},
+ },
+ {
+ []interface{}{[]byte("a1"), []byte("b1"), []byte("a2"), []byte("b2")},
+ nil,
+ true,
+ []*struct{ A, B string }{{"a1", "b1"}, {"a2", "b2"}},
+ },
+ {
+ []interface{}{[]byte("a1"), []byte("b1"), []byte("a2"), []byte("b2")},
+ []string{"A", "B"},
+ true,
+ []struct{ A, C, B string }{{"a1", "", "b1"}, {"a2", "", "b2"}},
+ },
+ {
+ []interface{}{[]byte("a1"), []byte("b1"), []byte("a2"), []byte("b2")},
+ nil,
+ false,
+ []struct{}{},
+ },
+}
+
+func TestScanSlice(t *testing.T) {
+ for _, tt := range scanSliceTests {
+
+ typ := reflect.ValueOf(tt.dest).Type()
+ dest := reflect.New(typ)
+
+ err := redis.ScanSlice(tt.src, dest.Interface(), tt.fieldNames...)
+ if tt.ok != (err == nil) {
+ t.Errorf("ScanSlice(%v, []%s, %v) returned error %v", tt.src, typ, tt.fieldNames, err)
+ continue
+ }
+ if tt.ok && !reflect.DeepEqual(dest.Elem().Interface(), tt.dest) {
+ t.Errorf("ScanSlice(src, []%s) returned %#v, want %#v", typ, dest.Elem().Interface(), tt.dest)
+ }
+ }
+}
+
+func ExampleScanSlice() {
+ c, err := dial()
+ if err != nil {
+ panic(err)
+ }
+ defer c.Close()
+
+ c.Send("HMSET", "album:1", "title", "Red", "rating", 5)
+ c.Send("HMSET", "album:2", "title", "Earthbound", "rating", 1)
+ c.Send("HMSET", "album:3", "title", "Beat", "rating", 4)
+ c.Send("LPUSH", "albums", "1")
+ c.Send("LPUSH", "albums", "2")
+ c.Send("LPUSH", "albums", "3")
+ values, err := redis.Values(c.Do("SORT", "albums",
+ "BY", "album:*->rating",
+ "GET", "album:*->title",
+ "GET", "album:*->rating"))
+ if err != nil {
+ panic(err)
+ }
+
+ var albums []struct {
+ Title string
+ Rating int
+ }
+ if err := redis.ScanSlice(values, &albums); err != nil {
+ panic(err)
+ }
+ fmt.Printf("%v\n", albums)
+ // Output:
+ // [{Earthbound 1} {Beat 4} {Red 5}]
+}
+
+var argsTests = []struct {
+ title string
+ actual redis.Args
+ expected redis.Args
+}{
+ {"struct ptr",
+ redis.Args{}.AddFlat(&struct {
+ I int `redis:"i"`
+ U uint `redis:"u"`
+ S string `redis:"s"`
+ P []byte `redis:"p"`
+ Bt bool
+ Bf bool
+ }{
+ -1234, 5678, "hello", []byte("world"), true, false,
+ }),
+ redis.Args{"i", int(-1234), "u", uint(5678), "s", "hello", "p", []byte("world"), "Bt", true, "Bf", false},
+ },
+ {"struct",
+ redis.Args{}.AddFlat(struct{ I int }{123}),
+ redis.Args{"I", 123},
+ },
+ {"slice",
+ redis.Args{}.Add(1).AddFlat([]string{"a", "b", "c"}).Add(2),
+ redis.Args{1, "a", "b", "c", 2},
+ },
+}
+
+func TestArgs(t *testing.T) {
+ for _, tt := range argsTests {
+ if !reflect.DeepEqual(tt.actual, tt.expected) {
+ t.Fatalf("%s is %v, want %v", tt.title, tt.actual, tt.expected)
+ }
+ }
+}
+
+func ExampleArgs() {
+ c, err := dial()
+ if err != nil {
+ panic(err)
+ }
+ defer c.Close()
+
+ var p1, p2 struct {
+ Title string `redis:"title"`
+ Author string `redis:"author"`
+ Body string `redis:"body"`
+ }
+
+ p1.Title = "Example"
+ p1.Author = "Gary"
+ p1.Body = "Hello"
+
+ if _, err := c.Do("HMSET", redis.Args{}.Add("id1").AddFlat(&p1)...); err != nil {
+ panic(err)
+ }
+
+ m := map[string]string{
+ "title": "Example2",
+ "author": "Steve",
+ "body": "Map",
+ }
+
+ if _, err := c.Do("HMSET", redis.Args{}.Add("id2").AddFlat(m)...); err != nil {
+ panic(err)
+ }
+
+ for _, id := range []string{"id1", "id2"} {
+
+ v, err := redis.Values(c.Do("HGETALL", id))
+ if err != nil {
+ panic(err)
+ }
+
+ if err := redis.ScanStruct(v, &p2); err != nil {
+ panic(err)
+ }
+
+ fmt.Printf("%+v\n", p2)
+ }
+
+ // Output:
+ // {Title:Example Author:Gary Body:Hello}
+ // {Title:Example2 Author:Steve Body:Map}
+}
diff --git a/Godeps/_workspace/src/github.com/garyburd/redigo/redis/script.go b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/script.go
new file mode 100644
index 000000000..78605a90a
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/script.go
@@ -0,0 +1,86 @@
+// Copyright 2012 Gary Burd
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package redis
+
+import (
+ "crypto/sha1"
+ "encoding/hex"
+ "io"
+ "strings"
+)
+
+// Script encapsulates the source, hash and key count for a Lua script. See
+// http://redis.io/commands/eval for information on scripts in Redis.
+type Script struct {
+ keyCount int
+ src string
+ hash string
+}
+
+// NewScript returns a new script object. If keyCount is greater than or equal
+// to zero, then the count is automatically inserted in the EVAL command
+// argument list. If keyCount is less than zero, then the application supplies
+// the count as the first value in the keysAndArgs argument to the Do, Send and
+// SendHash methods.
+func NewScript(keyCount int, src string) *Script {
+ h := sha1.New()
+ io.WriteString(h, src)
+ return &Script{keyCount, src, hex.EncodeToString(h.Sum(nil))}
+}
+
+func (s *Script) args(spec string, keysAndArgs []interface{}) []interface{} {
+ var args []interface{}
+ if s.keyCount < 0 {
+ args = make([]interface{}, 1+len(keysAndArgs))
+ args[0] = spec
+ copy(args[1:], keysAndArgs)
+ } else {
+ args = make([]interface{}, 2+len(keysAndArgs))
+ args[0] = spec
+ args[1] = s.keyCount
+ copy(args[2:], keysAndArgs)
+ }
+ return args
+}
+
+// Do evaluates the script. Under the covers, Do optimistically evaluates the
+// script using the EVALSHA command. If the command fails because the script is
+// not loaded, then Do evaluates the script using the EVAL command (thus
+// causing the script to load).
+func (s *Script) Do(c Conn, keysAndArgs ...interface{}) (interface{}, error) {
+ v, err := c.Do("EVALSHA", s.args(s.hash, keysAndArgs)...)
+ if e, ok := err.(Error); ok && strings.HasPrefix(string(e), "NOSCRIPT ") {
+ v, err = c.Do("EVAL", s.args(s.src, keysAndArgs)...)
+ }
+ return v, err
+}
+
+// SendHash evaluates the script without waiting for the reply. The script is
+// evaluated with the EVALSHA command. The application must ensure that the
+// script is loaded by a previous call to Send, Do or Load methods.
+func (s *Script) SendHash(c Conn, keysAndArgs ...interface{}) error {
+ return c.Send("EVALSHA", s.args(s.hash, keysAndArgs)...)
+}
+
+// Send evaluates the script without waiting for the reply.
+func (s *Script) Send(c Conn, keysAndArgs ...interface{}) error {
+ return c.Send("EVAL", s.args(s.src, keysAndArgs)...)
+}
+
+// Load loads the script without evaluating it.
+func (s *Script) Load(c Conn) error {
+ _, err := c.Do("SCRIPT", "LOAD", s.src)
+ return err
+}
diff --git a/Godeps/_workspace/src/github.com/garyburd/redigo/redis/script_test.go b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/script_test.go
new file mode 100644
index 000000000..c9635bf08
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/script_test.go
@@ -0,0 +1,93 @@
+// Copyright 2012 Gary Burd
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package redis_test
+
+import (
+ "fmt"
+ "reflect"
+ "testing"
+ "time"
+
+ "github.com/garyburd/redigo/internal/redistest"
+ "github.com/garyburd/redigo/redis"
+)
+
+func ExampleScript(c redis.Conn, reply interface{}, err error) {
+ // Initialize a package-level variable with a script.
+ var getScript = redis.NewScript(1, `return redis.call('get', KEYS[1])`)
+
+ // In a function, use the script Do method to evaluate the script. The Do
+ // method optimistically uses the EVALSHA command. If the script is not
+ // loaded, then the Do method falls back to the EVAL command.
+ reply, err = getScript.Do(c, "foo")
+}
+
+func TestScript(t *testing.T) {
+ c, err := redistest.Dial()
+ if err != nil {
+ t.Fatalf("error connection to database, %v", err)
+ }
+ defer c.Close()
+
+ // To test fall back in Do, we make script unique by adding comment with current time.
+ script := fmt.Sprintf("--%d\nreturn {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}", time.Now().UnixNano())
+ s := redis.NewScript(2, script)
+ reply := []interface{}{[]byte("key1"), []byte("key2"), []byte("arg1"), []byte("arg2")}
+
+ v, err := s.Do(c, "key1", "key2", "arg1", "arg2")
+ if err != nil {
+ t.Errorf("s.Do(c, ...) returned %v", err)
+ }
+
+ if !reflect.DeepEqual(v, reply) {
+ t.Errorf("s.Do(c, ..); = %v, want %v", v, reply)
+ }
+
+ err = s.Load(c)
+ if err != nil {
+ t.Errorf("s.Load(c) returned %v", err)
+ }
+
+ err = s.SendHash(c, "key1", "key2", "arg1", "arg2")
+ if err != nil {
+ t.Errorf("s.SendHash(c, ...) returned %v", err)
+ }
+
+ err = c.Flush()
+ if err != nil {
+ t.Errorf("c.Flush() returned %v", err)
+ }
+
+ v, err = c.Receive()
+ if !reflect.DeepEqual(v, reply) {
+ t.Errorf("s.SendHash(c, ..); c.Receive() = %v, want %v", v, reply)
+ }
+
+ err = s.Send(c, "key1", "key2", "arg1", "arg2")
+ if err != nil {
+ t.Errorf("s.Send(c, ...) returned %v", err)
+ }
+
+ err = c.Flush()
+ if err != nil {
+ t.Errorf("c.Flush() returned %v", err)
+ }
+
+ v, err = c.Receive()
+ if !reflect.DeepEqual(v, reply) {
+ t.Errorf("s.Send(c, ..); c.Receive() = %v, want %v", v, reply)
+ }
+
+}
diff --git a/Godeps/_workspace/src/github.com/garyburd/redigo/redis/test_test.go b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/test_test.go
new file mode 100644
index 000000000..b959a11f4
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/test_test.go
@@ -0,0 +1,38 @@
+// Copyright 2012 Gary Burd
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package redis
+
+import (
+ "bufio"
+ "net"
+ "time"
+)
+
+func SetNowFunc(f func() time.Time) {
+ nowFunc = f
+}
+
+type nopCloser struct{ net.Conn }
+
+func (nopCloser) Close() error { return nil }
+
+// NewConnBufio is a hook for tests.
+func NewConnBufio(rw bufio.ReadWriter) Conn {
+ return &conn{br: rw.Reader, bw: rw.Writer, conn: nopCloser{}}
+}
+
+var (
+ ErrNegativeInt = errNegativeInt
+)
diff --git a/Godeps/_workspace/src/github.com/garyburd/redigo/redis/zpop_example_test.go b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/zpop_example_test.go
new file mode 100644
index 000000000..1d86ee6ce
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/zpop_example_test.go
@@ -0,0 +1,113 @@
+// Copyright 2013 Gary Burd
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package redis_test
+
+import (
+ "fmt"
+ "github.com/garyburd/redigo/redis"
+)
+
+// zpop pops a value from the ZSET key using WATCH/MULTI/EXEC commands.
+func zpop(c redis.Conn, key string) (result string, err error) {
+
+ defer func() {
+ // Return connection to normal state on error.
+ if err != nil {
+ c.Do("DISCARD")
+ }
+ }()
+
+ // Loop until transaction is successful.
+ for {
+ if _, err := c.Do("WATCH", key); err != nil {
+ return "", err
+ }
+
+ members, err := redis.Strings(c.Do("ZRANGE", key, 0, 0))
+ if err != nil {
+ return "", err
+ }
+ if len(members) != 1 {
+ return "", redis.ErrNil
+ }
+
+ c.Send("MULTI")
+ c.Send("ZREM", key, members[0])
+ queued, err := c.Do("EXEC")
+ if err != nil {
+ return "", err
+ }
+
+ if queued != nil {
+ result = members[0]
+ break
+ }
+ }
+
+ return result, nil
+}
+
+// zpopScript pops a value from a ZSET.
+var zpopScript = redis.NewScript(1, `
+ local r = redis.call('ZRANGE', KEYS[1], 0, 0)
+ if r ~= nil then
+ r = r[1]
+ redis.call('ZREM', KEYS[1], r)
+ end
+ return r
+`)
+
+// This example implements ZPOP as described at
+// http://redis.io/topics/transactions using WATCH/MULTI/EXEC and scripting.
+func Example_zpop() {
+ c, err := dial()
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ defer c.Close()
+
+ // Add test data using a pipeline.
+
+ for i, member := range []string{"red", "blue", "green"} {
+ c.Send("ZADD", "zset", i, member)
+ }
+ if _, err := c.Do(""); err != nil {
+ fmt.Println(err)
+ return
+ }
+
+ // Pop using WATCH/MULTI/EXEC
+
+ v, err := zpop(c, "zset")
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ fmt.Println(v)
+
+ // Pop using a script.
+
+ v, err = redis.String(zpopScript.Do(c, "zset"))
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ fmt.Println(v)
+
+ // Output:
+ // red
+ // blue
+}