all: switch to gopkg.in.

This commit is contained in:
Vladimir Mihailenco 2014-05-11 10:42:40 +03:00
parent df12cdcfaf
commit 3bea997988
30 changed files with 1610 additions and 7602 deletions

View File

@ -1,2 +1,2 @@
all: all:
go test ./v2 go test gopkg.in/redis.v1

View File

@ -1,4 +1,45 @@
New version Redis client for Golang [![Build Status](https://travis-ci.org/vmihailenco/redis.png?branch=master)](https://travis-ci.org/go-redis/redis)
=========== =======================
Make sure to check new version of the client that got better API and timeout support: https://github.com/vmihailenco/redis/tree/master/v2 Supports:
- Redis 2.8 commands except QUIT, MONITOR, SLOWLOG and SYNC.
- Pub/sub.
- Transactions.
- Pipelining.
- Connection pool.
- TLS connections.
- Thread safety.
- Timeouts.
API docs: http://godoc.org/gopkg.in/redis.v1.
Examples: http://godoc.org/gopkg.in/redis.v1#pkg-examples.
Installation
------------
Install:
go get gopkg.in/redis.v1
Look and feel
-------------
Some corner cases:
SORT list LIMIT 0 2 ASC
vals, err := client.Sort("list", redis.Sort{Offset: 0, Count: 2, Order: "ASC"}).Result()
ZRANGEBYSCORE zset -inf +inf WITHSCORES LIMIT 0 2
vals, err := client.ZRangeByScoreWithScores("zset", redis.ZRangeByScore{
Min: "-inf",
Max: "+inf",
Offset: 0,
Count: 2,
}).Result()
ZINTERSTORE out 2 zset1 zset2 WEIGHTS 2 3 AGGREGATE SUM
vals, err := client.ZInterStore("out", redis.ZStore{Weights: []int64{2, 3}}, "zset1", "zset2").Result()
EVAL "return {KEYS[1],ARGV[1]}" 1 "key" "hello"
vals, err := client.Eval("return {KEYS[1],ARGV[1]}", []string{"key"}, []string{"hello"}).Result()

File diff suppressed because it is too large Load Diff

View File

@ -1,197 +0,0 @@
package redis
import (
"log"
"net"
"os"
"sync"
"github.com/vmihailenco/bufio"
)
type Conn struct {
RW net.Conn
Rd reader
}
func NewConn(rw net.Conn) *Conn {
return &Conn{
RW: rw,
Rd: bufio.NewReaderSize(rw, 1024),
}
}
type ConnPool interface {
Get() (*Conn, bool, error)
Add(*Conn) error
Remove(*Conn) error
Len() int
Close() error
}
//------------------------------------------------------------------------------
type MultiConnPool struct {
Logger *log.Logger
cond *sync.Cond
conns []*Conn
OpenConn OpenConnFunc
CloseConn CloseConnFunc
cap, MaxCap int
}
func NewMultiConnPool(openConn OpenConnFunc, closeConn CloseConnFunc, maxCap int) *MultiConnPool {
logger := log.New(
os.Stdout,
"redis.connpool: ",
log.Ldate|log.Ltime|log.Lshortfile,
)
return &MultiConnPool{
cond: sync.NewCond(&sync.Mutex{}),
Logger: logger,
conns: make([]*Conn, 0),
OpenConn: openConn,
CloseConn: closeConn,
MaxCap: maxCap,
}
}
func (p *MultiConnPool) Get() (*Conn, bool, error) {
p.cond.L.Lock()
defer p.cond.L.Unlock()
for len(p.conns) == 0 && p.cap >= p.MaxCap {
p.cond.Wait()
}
if len(p.conns) == 0 {
rw, err := p.OpenConn()
if err != nil {
return nil, false, err
}
p.cap++
return NewConn(rw), true, nil
}
last := len(p.conns) - 1
conn := p.conns[last]
p.conns[last] = nil
p.conns = p.conns[:last]
return conn, false, nil
}
func (p *MultiConnPool) Add(conn *Conn) error {
p.cond.L.Lock()
defer p.cond.L.Unlock()
p.conns = append(p.conns, conn)
p.cond.Signal()
return nil
}
func (p *MultiConnPool) Remove(conn *Conn) error {
defer func() {
p.cond.L.Lock()
p.cap--
p.cond.Signal()
p.cond.L.Unlock()
}()
if conn == nil {
return nil
}
return p.closeConn(conn)
}
func (p *MultiConnPool) Len() int {
return len(p.conns)
}
func (p *MultiConnPool) Close() error {
p.cond.L.Lock()
defer p.cond.L.Unlock()
for _, conn := range p.conns {
err := p.closeConn(conn)
if err != nil {
return err
}
}
p.conns = make([]*Conn, 0)
p.cap = 0
return nil
}
func (p *MultiConnPool) closeConn(conn *Conn) error {
if p.CloseConn != nil {
err := p.CloseConn(conn.RW)
if err != nil {
return err
}
}
return conn.RW.Close()
}
//------------------------------------------------------------------------------
type SingleConnPool struct {
mtx sync.Mutex
pool ConnPool
conn *Conn
isReusable bool
}
func NewSingleConnPoolConn(pool ConnPool, conn *Conn, isReusable bool) *SingleConnPool {
return &SingleConnPool{
pool: pool,
conn: conn,
isReusable: isReusable,
}
}
func NewSingleConnPool(pool ConnPool, isReusable bool) *SingleConnPool {
return NewSingleConnPoolConn(pool, nil, isReusable)
}
func (p *SingleConnPool) Get() (*Conn, bool, error) {
p.mtx.Lock()
defer p.mtx.Unlock()
if p.conn != nil {
return p.conn, false, nil
}
conn, isNew, err := p.pool.Get()
if err != nil {
return nil, false, err
}
p.conn = conn
return p.conn, isNew, nil
}
func (p *SingleConnPool) Add(conn *Conn) error {
return nil
}
func (p *SingleConnPool) Remove(conn *Conn) error {
return nil
}
func (p *SingleConnPool) Len() int {
return 1
}
func (p *SingleConnPool) Close() error {
p.mtx.Lock()
defer p.mtx.Unlock()
if p.conn == nil {
return nil
}
var err error
if p.isReusable {
err = p.pool.Add(p.conn)
} else {
err = p.pool.Remove(p.conn)
}
p.conn = nil
return err
}

131
doc.go
View File

@ -1,133 +1,4 @@
/* /*
Package github.com/vmihailenco/redis implements a Redis client. Package gopkg.in/redis.v1 implements a Redis client.
Let's start with connecting to Redis using TCP:
password := "" // no password set
db := int64(-1) // use default DB
client := redis.NewTCPClient("localhost:6379", password, db)
defer client.Close()
ping := client.Ping()
fmt.Println(ping.Err(), ping.Val())
// Output: <nil> PONG
or using Unix socket:
client := redis.NewUnixClient("/tmp/redis.sock", "", -1)
defer client.Close()
ping := client.Ping()
fmt.Println(ping.Err(), ping.Val())
// Output: <nil> PONG
Then we can start sending commands:
set := client.Set("foo", "bar")
fmt.Println(set.Err(), set.Val())
get := client.Get("foo")
fmt.Println(get.Err(), get.Val())
// Output: <nil> OK
// <nil> bar
We can also pipeline two commands together:
var set *redis.StatusReq
var get *redis.StringReq
reqs, err := client.Pipelined(func(c *redis.PipelineClient) {
set = c.Set("key1", "hello1")
get = c.Get("key2")
})
fmt.Println(err, reqs)
fmt.Println(set)
fmt.Println(get)
// Output: <nil> [SET key1 hello1: OK GET key2: (nil)]
// SET key1 hello1: OK
// GET key2: (nil)
or:
var set *redis.StatusReq
var get *redis.StringReq
reqs, err := client.Pipelined(func(c *redis.PipelineClient) {
set = c.Set("key1", "hello1")
get = c.Get("key2")
})
fmt.Println(err, reqs)
fmt.Println(set)
fmt.Println(get)
// Output: <nil> [SET key1 hello1 GET key2]
// SET key1 hello1
// GET key2
We can also send several commands in transaction:
func transaction(multi *redis.MultiClient) ([]redis.Req, error) {
get := multi.Get("key")
if err := get.Err(); err != nil && err != redis.Nil {
return nil, err
}
val, _ := strconv.ParseInt(get.Val(), 10, 64)
reqs, err := multi.Exec(func() {
multi.Set("key", strconv.FormatInt(val+1, 10))
})
// Transaction failed. Repeat.
if err == redis.Nil {
return transaction(multi)
}
return reqs, err
}
multi, err := client.MultiClient()
_ = err
defer multi.Close()
watch := multi.Watch("key")
_ = watch.Err()
reqs, err := transaction(multi)
fmt.Println(err, reqs)
// Output: <nil> [SET key 1: OK]
To subscribe to the channel:
pubsub, err := client.PubSubClient()
defer pubsub.Close()
ch, err := pubsub.Subscribe("mychannel")
_ = err
subscribeMsg := <-ch
fmt.Println(subscribeMsg.Err, subscribeMsg.Name)
pub := client.Publish("mychannel", "hello")
_ = pub.Err()
msg := <-ch
fmt.Println(msg.Err, msg.Message)
// Output: <nil> subscribe
// <nil> hello
You can also write custom commands:
func Get(client *redis.Client, key string) *redis.StringReq {
req := redis.NewStringReq("GET", key)
client.Process(req)
return req
}
get := Get(client, "key_does_not_exist")
fmt.Println(get.Err(), get.Val())
// Output: (nil)
Client uses connection pool to send commands. You can change maximum number of connections with:
client.ConnPool.(*redis.MultiConnPool).MaxCap = 1
*/ */
package redis package redis

View File

@ -4,132 +4,155 @@ import (
"fmt" "fmt"
"strconv" "strconv"
"github.com/vmihailenco/redis" "gopkg.in/redis.v1"
) )
func ExampleTCPClient() { var client *redis.Client
password := "" // no password set
db := int64(-1) // use default DB
client := redis.NewTCPClient("localhost:6379", password, db)
defer client.Close()
ping := client.Ping() func init() {
fmt.Println(ping.Err(), ping.Val()) client = redis.NewTCPClient(&redis.Options{
// Output: <nil> PONG Addr: ":6379",
})
} }
func ExampleUnixClient() { func ExampleNewTCPClient() {
client := redis.NewUnixClient("/tmp/redis.sock", "", -1) client := redis.NewTCPClient(&redis.Options{
Addr: "localhost:6379",
Password: "", // no password set
DB: 0, // use default DB
})
defer client.Close() defer client.Close()
ping := client.Ping() pong, err := client.Ping().Result()
fmt.Println(ping.Err(), ping.Val()) fmt.Println(pong, err)
// Output: <nil> PONG // Output: PONG <nil>
} }
func ExampleSetGet() { func ExampleClient() {
client := redis.NewTCPClient(":6379", "", -1)
defer client.Close()
set := client.Set("foo", "bar") set := client.Set("foo", "bar")
fmt.Println(set.Err(), set.Val()) fmt.Println(set.Err())
get := client.Get("foo") v, err := client.Get("hello").Result()
fmt.Println(get.Err(), get.Val()) fmt.Printf("%q %s %v", v, err, err == redis.Nil)
// Output: <nil> OK // Output: <nil>
// <nil> bar // "" redis: nil true
}
func ExampleClient_Pipelined() {
cmds, err := client.Pipelined(func(c *redis.Pipeline) {
c.Set("key1", "hello1")
c.Get("key1")
})
fmt.Println(err)
set := cmds[0].(*redis.StatusCmd)
fmt.Println(set)
get := cmds[1].(*redis.StringCmd)
fmt.Println(get)
// Output: <nil>
// SET key1 hello1: OK
// GET key1: hello1
} }
func ExamplePipeline() { func ExamplePipeline() {
client := redis.NewTCPClient(":6379", "", -1) pipeline := client.Pipeline()
defer client.Close() set := pipeline.Set("key1", "hello1")
get := pipeline.Get("key1")
var set *redis.StatusReq cmds, err := pipeline.Exec()
var get *redis.StringReq fmt.Println(cmds, err)
reqs, err := client.Pipelined(func(c *redis.PipelineClient) {
set = c.Set("key1", "hello1")
get = c.Get("key2")
})
fmt.Println(err, reqs)
fmt.Println(set) fmt.Println(set)
fmt.Println(get) fmt.Println(get)
// Output: <nil> [SET key1 hello1: OK GET key2: (nil)] // Output: [SET key1 hello1: OK GET key1: hello1] <nil>
// SET key1 hello1: OK // SET key1 hello1: OK
// GET key2: (nil) // GET key1: hello1
} }
func transaction(multi *redis.MultiClient) ([]redis.Req, error) { func ExampleMulti() {
get := multi.Get("key") incr := func(tx *redis.Multi) ([]redis.Cmder, error) {
if err := get.Err(); err != nil && err != redis.Nil { s, err := tx.Get("key").Result()
return nil, err if err != nil && err != redis.Nil {
return nil, err
}
n, _ := strconv.ParseInt(s, 10, 64)
return tx.Exec(func() {
tx.Set("key", strconv.FormatInt(n+1, 10))
})
} }
val, _ := strconv.ParseInt(get.Val(), 10, 64)
reqs, err := multi.Exec(func() {
multi.Set("key", strconv.FormatInt(val+1, 10))
})
// Transaction failed. Repeat.
if err == redis.Nil {
return transaction(multi)
}
return reqs, err
}
func ExampleTransaction() {
client := redis.NewTCPClient(":6379", "", -1)
defer client.Close()
client.Del("key") client.Del("key")
multi, err := client.MultiClient() tx := client.Multi()
_ = err defer tx.Close()
defer multi.Close()
watch := multi.Watch("key") watch := tx.Watch("key")
_ = watch.Err() _ = watch.Err()
reqs, err := transaction(multi) for {
fmt.Println(err, reqs) cmds, err := incr(tx)
if err == redis.TxFailedErr {
continue
} else if err != nil {
panic(err)
}
fmt.Println(cmds, err)
break
}
// Output: <nil> [SET key 1: OK] // Output: [SET key 1: OK] <nil>
} }
func ExamplePubSub() { func ExamplePubSub() {
client := redis.NewTCPClient(":6379", "", -1) pubsub := client.PubSub()
defer client.Close()
pubsub, err := client.PubSubClient()
defer pubsub.Close() defer pubsub.Close()
ch, err := pubsub.Subscribe("mychannel") err := pubsub.Subscribe("mychannel")
_ = err _ = err
subscribeMsg := <-ch msg, err := pubsub.Receive()
fmt.Println(subscribeMsg.Err, subscribeMsg.Name) fmt.Println(msg, err)
pub := client.Publish("mychannel", "hello") pub := client.Publish("mychannel", "hello")
_ = pub.Err() _ = pub.Err()
msg := <-ch msg, err = pubsub.Receive()
fmt.Println(msg.Err, msg.Message) fmt.Println(msg, err)
// Output: <nil> subscribe // Output: &{subscribe mychannel 1} <nil>
// <nil> hello // &{mychannel hello} <nil>
} }
func Get(client *redis.Client, key string) *redis.StringReq { func ExampleScript() {
req := redis.NewStringReq("GET", key) setnx := redis.NewScript(`
client.Process(req) if redis.call("get", KEYS[1]) == false then
return req redis.call("set", KEYS[1], ARGV[1])
return 1
end
return 0
`)
v1, err := setnx.Run(client, []string{"keynx"}, []string{"foo"}).Result()
fmt.Println(v1.(int64), err)
v2, err := setnx.Run(client, []string{"keynx"}, []string{"bar"}).Result()
fmt.Println(v2.(int64), err)
get := client.Get("keynx")
fmt.Println(get)
// Output: 1 <nil>
// 0 <nil>
// GET keynx: foo
} }
func ExampleCustomCommand() { func Example_customCommand() {
client := redis.NewTCPClient(":6379", "", -1) Get := func(client *redis.Client, key string) *redis.StringCmd {
defer client.Close() cmd := redis.NewStringCmd("GET", key)
client.Process(cmd)
return cmd
}
get := Get(client, "key_does_not_exist") v, err := Get(client, "key_does_not_exist").Result()
fmt.Println(get.Err(), get.Val()) fmt.Printf("%q %s", v, err)
// Output: (nil) // Output: "" redis: nil
} }

138
multi.go
View File

@ -1,132 +1,134 @@
package redis package redis
import ( import (
"errors"
"fmt" "fmt"
"sync"
) )
type MultiClient struct { var errDiscard = errors.New("redis: Discard can be used only inside Exec")
// Not thread-safe.
type Multi struct {
*Client *Client
execMtx sync.Mutex
} }
func (c *Client) MultiClient() (*MultiClient, error) { func (c *Client) Multi() *Multi {
return &MultiClient{ return &Multi{
Client: &Client{ Client: &Client{
BaseClient: &BaseClient{ baseClient: &baseClient{
ConnPool: NewSingleConnPool(c.ConnPool, true), opt: c.opt,
InitConn: c.InitConn, connPool: newSingleConnPool(c.connPool, nil, true),
}, },
}, },
}, nil }
} }
func (c *MultiClient) Close() error { func (c *Multi) Close() error {
c.Unwatch() c.Unwatch()
return c.Client.Close() return c.Client.Close()
} }
func (c *MultiClient) Watch(keys ...string) *StatusReq { func (c *Multi) Watch(keys ...string) *StatusCmd {
args := append([]string{"WATCH"}, keys...) args := append([]string{"WATCH"}, keys...)
req := NewStatusReq(args...) cmd := NewStatusCmd(args...)
c.Process(req) c.Process(cmd)
return req return cmd
} }
func (c *MultiClient) Unwatch(keys ...string) *StatusReq { func (c *Multi) Unwatch(keys ...string) *StatusCmd {
args := append([]string{"UNWATCH"}, keys...) args := append([]string{"UNWATCH"}, keys...)
req := NewStatusReq(args...) cmd := NewStatusCmd(args...)
c.Process(req) c.Process(cmd)
return req return cmd
} }
func (c *MultiClient) Discard() { func (c *Multi) Discard() error {
c.reqsMtx.Lock() if c.cmds == nil {
if c.reqs == nil { return errDiscard
panic("Discard can be used only inside Exec")
} }
c.reqs = c.reqs[:1] c.cmds = c.cmds[:1]
c.reqsMtx.Unlock() return nil
} }
func (c *MultiClient) Exec(do func()) ([]Req, error) { // Exec always returns list of commands. If transaction fails
c.reqsMtx.Lock() // TxFailedErr is returned. Otherwise Exec returns error of the first
c.reqs = []Req{NewStatusReq("MULTI")} // failed command or nil.
c.reqsMtx.Unlock() func (c *Multi) Exec(f func()) ([]Cmder, error) {
c.cmds = []Cmder{NewStatusCmd("MULTI")}
f()
c.cmds = append(c.cmds, NewSliceCmd("EXEC"))
do() cmds := c.cmds
c.cmds = nil
c.Queue(NewIfaceSliceReq("EXEC")) if len(cmds) == 2 {
return []Cmder{}, nil
c.reqsMtx.Lock()
reqs := c.reqs
c.reqs = nil
c.reqsMtx.Unlock()
if len(reqs) == 2 {
return []Req{}, nil
} }
conn, err := c.conn() cn, err := c.conn()
if err != nil { if err != nil {
return nil, err setCmdsErr(cmds[1:len(cmds)-1], err)
return cmds[1 : len(cmds)-1], err
} }
// Synchronize writes and reads to the connection using mutex. err = c.execCmds(cn, cmds)
c.execMtx.Lock()
err = c.ExecReqs(reqs, conn)
c.execMtx.Unlock()
if err != nil { if err != nil {
c.ConnPool.Remove(conn) c.freeConn(cn, err)
return nil, err return cmds[1 : len(cmds)-1], err
} }
c.ConnPool.Add(conn) c.putConn(cn)
return reqs[1 : len(reqs)-1], nil return cmds[1 : len(cmds)-1], nil
} }
func (c *MultiClient) ExecReqs(reqs []Req, conn *Conn) error { func (c *Multi) execCmds(cn *conn, cmds []Cmder) error {
err := c.WriteReq(conn, reqs...) err := c.writeCmd(cn, cmds...)
if err != nil { if err != nil {
setCmdsErr(cmds[1:len(cmds)-1], err)
return err return err
} }
statusReq := NewStatusReq() statusCmd := NewStatusCmd()
// Omit last request (EXEC). // Omit last command (EXEC).
reqsLen := len(reqs) - 1 cmdsLen := len(cmds) - 1
// Parse queued replies. // Parse queued replies.
for i := 0; i < reqsLen; i++ { for i := 0; i < cmdsLen; i++ {
_, err = statusReq.ParseReply(conn.Rd) if err := statusCmd.parseReply(cn.rd); err != nil {
if err != nil { setCmdsErr(cmds[1:len(cmds)-1], err)
return err return err
} }
} }
// Parse number of replies. // Parse number of replies.
line, err := readLine(conn.Rd) line, err := readLine(cn.rd)
if err != nil { if err != nil {
setCmdsErr(cmds[1:len(cmds)-1], err)
return err return err
} }
if line[0] != '*' { if line[0] != '*' {
return fmt.Errorf("Expected '*', but got line %q", line) err := fmt.Errorf("redis: expected '*', but got line %q", line)
setCmdsErr(cmds[1:len(cmds)-1], err)
return err
} }
if len(line) == 3 && line[1] == '-' && line[2] == '1' { if len(line) == 3 && line[1] == '-' && line[2] == '1' {
return Nil setCmdsErr(cmds[1:len(cmds)-1], TxFailedErr)
return TxFailedErr
} }
var firstCmdErr error
// Parse replies. // Parse replies.
// Loop starts from 1 to omit first request (MULTI). // Loop starts from 1 to omit MULTI cmd.
for i := 1; i < reqsLen; i++ { for i := 1; i < cmdsLen; i++ {
req := reqs[i] cmd := cmds[i]
val, err := req.ParseReply(conn.Rd) if err := cmd.parseReply(cn.rd); err != nil {
if err != nil { if firstCmdErr == nil {
req.SetErr(err) firstCmdErr = err
} else { }
req.SetVal(val)
} }
} }
return nil return firstCmdErr
} }

272
parser.go
View File

@ -8,38 +8,22 @@ import (
"github.com/vmihailenco/bufio" "github.com/vmihailenco/bufio"
) )
type replyType int type multiBulkParser func(rd reader, n int64) (interface{}, error)
const ( // Redis nil reply.
ifaceSlice replyType = iota var Nil = errors.New("redis: nil")
stringSlice
boolSlice
stringStringMap
stringFloatMap
)
// Represents Redis nil reply. // Redis transaction failed.
var Nil = errors.New("(nil)") var TxFailedErr = errors.New("redis: transaction failed")
var ( var (
errReaderTooSmall = errors.New("redis: reader is too small") errReaderTooSmall = errors.New("redis: reader is too small")
errValNotSet = errors.New("redis: value is not set") errInvalidReplyType = errors.New("redis: invalid reply type")
errInvalidType = errors.New("redis: invalid reply type")
) )
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
type parserError struct { func appendCmd(buf []byte, args []string) []byte {
err error
}
func (e *parserError) Error() string {
return e.err.Error()
}
//------------------------------------------------------------------------------
func appendReq(buf []byte, args []string) []byte {
buf = append(buf, '*') buf = append(buf, '*')
buf = strconv.AppendUint(buf, uint64(len(args)), 10) buf = strconv.AppendUint(buf, uint64(len(args)), 10)
buf = append(buf, '\r', '\n') buf = append(buf, '\r', '\n')
@ -60,6 +44,7 @@ type reader interface {
Read([]byte) (int, error) Read([]byte) (int, error)
ReadN(n int) ([]byte, error) ReadN(n int) ([]byte, error)
Buffered() int Buffered() int
Peek(int) ([]byte, error)
} }
func readLine(rd reader) ([]byte, error) { func readLine(rd reader) ([]byte, error) {
@ -99,7 +84,7 @@ func readN(rd reader, n int) ([]byte, error) {
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
func ParseReq(rd reader) ([]string, error) { func parseReq(rd reader) ([]string, error) {
line, err := readLine(rd) line, err := readLine(rd)
if err != nil { if err != nil {
return nil, err return nil, err
@ -139,30 +124,10 @@ func ParseReq(rd reader) ([]string, error) {
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
func parseReply(rd reader) (interface{}, error) { func parseReply(rd reader, p multiBulkParser) (interface{}, error) {
return _parseReply(rd, ifaceSlice)
}
func parseStringSliceReply(rd reader) (interface{}, error) {
return _parseReply(rd, stringSlice)
}
func parseBoolSliceReply(rd reader) (interface{}, error) {
return _parseReply(rd, boolSlice)
}
func parseStringStringMapReply(rd reader) (interface{}, error) {
return _parseReply(rd, stringStringMap)
}
func parseStringFloatMapReply(rd reader) (interface{}, error) {
return _parseReply(rd, stringFloatMap)
}
func _parseReply(rd reader, typ replyType) (interface{}, error) {
line, err := readLine(rd) line, err := readLine(rd)
if err != nil { if err != nil {
return 0, &parserError{err} return nil, err
} }
switch line[0] { switch line[0] {
@ -173,23 +138,23 @@ func _parseReply(rd reader, typ replyType) (interface{}, error) {
case ':': case ':':
v, err := strconv.ParseInt(string(line[1:]), 10, 64) v, err := strconv.ParseInt(string(line[1:]), 10, 64)
if err != nil { if err != nil {
return 0, &parserError{err} return nil, err
} }
return v, nil return v, nil
case '$': case '$':
if len(line) == 3 && line[1] == '-' && line[2] == '1' { if len(line) == 3 && line[1] == '-' && line[2] == '1' {
return "", Nil return nil, Nil
} }
replyLenInt32, err := strconv.ParseInt(string(line[1:]), 10, 32) replyLenInt32, err := strconv.ParseInt(string(line[1:]), 10, 32)
if err != nil { if err != nil {
return "", &parserError{err} return nil, err
} }
replyLen := int(replyLenInt32) + 2 replyLen := int(replyLenInt32) + 2
line, err = readN(rd, replyLen) line, err = readN(rd, replyLen)
if err != nil { if err != nil {
return "", &parserError{err} return nil, err
} }
return string(line[:len(line)-2]), nil return string(line[:len(line)-2]), nil
case '*': case '*':
@ -199,106 +164,113 @@ func _parseReply(rd reader, typ replyType) (interface{}, error) {
repliesNum, err := strconv.ParseInt(string(line[1:]), 10, 64) repliesNum, err := strconv.ParseInt(string(line[1:]), 10, 64)
if err != nil { if err != nil {
return nil, &parserError{err} return nil, err
} }
switch typ { return p(rd, repliesNum)
case stringSlice:
vals := make([]string, 0, repliesNum)
for i := int64(0); i < repliesNum; i++ {
vi, err := parseReply(rd)
if err != nil {
return nil, err
}
if v, ok := vi.(string); ok {
vals = append(vals, v)
} else {
return nil, errInvalidType
}
}
return vals, nil
case boolSlice:
vals := make([]bool, 0, repliesNum)
for i := int64(0); i < repliesNum; i++ {
vi, err := parseReply(rd)
if err != nil {
return nil, err
}
if v, ok := vi.(int64); ok {
vals = append(vals, v == 1)
} else {
return nil, errInvalidType
}
}
return vals, nil
case stringStringMap: // TODO: Consider handling Nil values.
m := make(map[string]string, repliesNum/2)
for i := int64(0); i < repliesNum; i += 2 {
keyI, err := parseReply(rd)
if err != nil {
return nil, err
}
key, ok := keyI.(string)
if !ok {
return nil, errInvalidType
}
valueI, err := parseReply(rd)
if err != nil {
return nil, err
}
value, ok := valueI.(string)
if !ok {
return nil, errInvalidType
}
m[key] = value
}
return m, nil
case stringFloatMap: // TODO: Consider handling Nil values.
m := make(map[string]float64, repliesNum/2)
for i := int64(0); i < repliesNum; i += 2 {
keyI, err := parseReply(rd)
if err != nil {
return nil, err
}
key, ok := keyI.(string)
if !ok {
return nil, errInvalidType
}
valueI, err := parseReply(rd)
if err != nil {
return nil, err
}
valueS, ok := valueI.(string)
if !ok {
return nil, errInvalidType
}
value, err := strconv.ParseFloat(valueS, 64)
if err != nil {
return nil, &parserError{err}
}
m[key] = value
}
return m, nil
default:
vals := make([]interface{}, 0, repliesNum)
for i := int64(0); i < repliesNum; i++ {
v, err := parseReply(rd)
if err == Nil {
vals = append(vals, nil)
} else if err != nil {
return nil, err
} else {
vals = append(vals, v)
}
}
return vals, nil
}
default:
return nil, &parserError{fmt.Errorf("redis: can't parse %q", line)}
} }
panic("not reachable") return nil, fmt.Errorf("redis: can't parse %q", line)
}
func parseSlice(rd reader, n int64) (interface{}, error) {
vals := make([]interface{}, 0, n)
for i := int64(0); i < n; i++ {
v, err := parseReply(rd, parseSlice)
if err == Nil {
vals = append(vals, nil)
} else if err != nil {
return nil, err
} else {
vals = append(vals, v)
}
}
return vals, nil
}
func parseStringSlice(rd reader, n int64) (interface{}, error) {
vals := make([]string, 0, n)
for i := int64(0); i < n; i++ {
vi, err := parseReply(rd, nil)
if err != nil {
return nil, err
}
if v, ok := vi.(string); ok {
vals = append(vals, v)
} else {
return nil, errInvalidReplyType
}
}
return vals, nil
}
func parseBoolSlice(rd reader, n int64) (interface{}, error) {
vals := make([]bool, 0, n)
for i := int64(0); i < n; i++ {
vi, err := parseReply(rd, nil)
if err != nil {
return nil, err
}
if v, ok := vi.(int64); ok {
vals = append(vals, v == 1)
} else {
return nil, errInvalidReplyType
}
}
return vals, nil
}
func parseStringStringMap(rd reader, n int64) (interface{}, error) {
m := make(map[string]string, n/2)
for i := int64(0); i < n; i += 2 {
keyI, err := parseReply(rd, nil)
if err != nil {
return nil, err
}
key, ok := keyI.(string)
if !ok {
return nil, errInvalidReplyType
}
valueI, err := parseReply(rd, nil)
if err != nil {
return nil, err
}
value, ok := valueI.(string)
if !ok {
return nil, errInvalidReplyType
}
m[key] = value
}
return m, nil
}
func parseStringFloatMap(rd reader, n int64) (interface{}, error) {
m := make(map[string]float64, n/2)
for i := int64(0); i < n; i += 2 {
keyI, err := parseReply(rd, nil)
if err != nil {
return nil, err
}
key, ok := keyI.(string)
if !ok {
return nil, errInvalidReplyType
}
valueI, err := parseReply(rd, nil)
if err != nil {
return nil, err
}
valueS, ok := valueI.(string)
if !ok {
return nil, errInvalidReplyType
}
value, err := strconv.ParseFloat(valueS, 64)
if err != nil {
return nil, err
}
m[key] = value
}
return m, nil
} }

View File

@ -1,23 +0,0 @@
package redis_test
import (
"bytes"
"github.com/vmihailenco/bufio"
. "launchpad.net/gocheck"
"github.com/vmihailenco/redis"
)
type ParserTest struct{}
var _ = Suite(&ParserTest{})
func (t *ParserTest) TestParseReq(c *C) {
buf := bytes.NewBufferString("*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nhello\r\n")
rd := bufio.NewReaderSize(buf, 1024)
args, err := redis.ParseReq(rd)
c.Check(err, IsNil)
c.Check(args, DeepEquals, []string{"SET", "key", "hello"})
}

View File

@ -1,84 +1,90 @@
package redis package redis
type PipelineClient struct { // Not thread-safe.
type Pipeline struct {
*Client *Client
closed bool
} }
func (c *Client) PipelineClient() (*PipelineClient, error) { func (c *Client) Pipeline() *Pipeline {
return &PipelineClient{ return &Pipeline{
Client: &Client{ Client: &Client{
BaseClient: &BaseClient{ baseClient: &baseClient{
ConnPool: c.ConnPool, opt: c.opt,
InitConn: c.InitConn, connPool: c.connPool,
reqs: make([]Req, 0),
cmds: make([]Cmder, 0),
}, },
}, },
}, nil
}
func (c *Client) Pipelined(do func(*PipelineClient)) ([]Req, error) {
pc, err := c.PipelineClient()
if err != nil {
return nil, err
} }
defer pc.Close()
do(pc)
return pc.RunQueued()
} }
func (c *PipelineClient) Close() error { func (c *Client) Pipelined(f func(*Pipeline)) ([]Cmder, error) {
pc := c.Pipeline()
f(pc)
cmds, err := pc.Exec()
pc.Close()
return cmds, err
}
func (c *Pipeline) Close() error {
c.closed = true
return nil return nil
} }
func (c *PipelineClient) DiscardQueued() { func (c *Pipeline) Discard() error {
c.reqsMtx.Lock() if c.closed {
c.reqs = c.reqs[:0] return errClosed
c.reqsMtx.Unlock() }
c.cmds = c.cmds[:0]
return nil
} }
func (c *PipelineClient) RunQueued() ([]Req, error) { // Exec always returns list of commands and error of the first failed
c.reqsMtx.Lock() // command if any.
reqs := c.reqs func (c *Pipeline) Exec() ([]Cmder, error) {
c.reqs = make([]Req, 0) if c.closed {
c.reqsMtx.Unlock() return nil, errClosed
if len(reqs) == 0 {
return []Req{}, nil
} }
conn, err := c.conn() cmds := c.cmds
c.cmds = make([]Cmder, 0)
if len(cmds) == 0 {
return []Cmder{}, nil
}
cn, err := c.conn()
if err != nil { if err != nil {
return nil, err setCmdsErr(cmds, err)
return cmds, err
} }
err = c.RunReqs(reqs, conn) if err := c.execCmds(cn, cmds); err != nil {
if err != nil { c.freeConn(cn, err)
c.ConnPool.Remove(conn) return cmds, err
return nil, err
} }
c.ConnPool.Add(conn) c.putConn(cn)
return reqs, nil return cmds, nil
} }
func (c *PipelineClient) RunReqs(reqs []Req, conn *Conn) error { func (c *Pipeline) execCmds(cn *conn, cmds []Cmder) error {
err := c.WriteReq(conn, reqs...) err := c.writeCmd(cn, cmds...)
if err != nil { if err != nil {
setCmdsErr(cmds, err)
return err return err
} }
reqsLen := len(reqs) var firstCmdErr error
for i := 0; i < reqsLen; i++ { for _, cmd := range cmds {
req := reqs[i] if err := cmd.parseReply(cn.rd); err != nil {
val, err := req.ParseReply(conn.Rd) if firstCmdErr == nil {
if err != nil { firstCmdErr = err
req.SetErr(err) }
} else {
req.SetVal(val)
} }
} }
return nil return firstCmdErr
} }

View File

162
pubsub.go
View File

@ -2,123 +2,121 @@ package redis
import ( import (
"fmt" "fmt"
"sync" "time"
) )
type PubSubClient struct { // Not thread-safe.
*BaseClient type PubSub struct {
ch chan *Message *baseClient
once sync.Once
} }
func (c *Client) PubSubClient() (*PubSubClient, error) { func (c *Client) PubSub() *PubSub {
return &PubSubClient{ return &PubSub{
BaseClient: &BaseClient{ baseClient: &baseClient{
ConnPool: NewSingleConnPool(c.ConnPool, false), opt: c.opt,
InitConn: c.InitConn, connPool: newSingleConnPool(c.connPool, nil, false),
}, },
ch: make(chan *Message), }
}, nil
} }
func (c *Client) Publish(channel, message string) *IntReq { func (c *Client) Publish(channel, message string) *IntCmd {
req := NewIntReq("PUBLISH", channel, message) req := NewIntCmd("PUBLISH", channel, message)
c.Process(req) c.Process(req)
return req return req
} }
type Message struct { type Message struct {
Name, Channel, ChannelPattern, Message string Channel string
Number int64 Payload string
Err error
} }
func (c *PubSubClient) consumeMessages(conn *Conn) { type PMessage struct {
req := NewIfaceSliceReq() Channel string
Pattern string
for { Payload string
msg := &Message{}
replyIface, err := req.ParseReply(conn.Rd)
if err != nil {
msg.Err = err
c.ch <- msg
return
}
reply, ok := replyIface.([]interface{})
if !ok {
msg.Err = fmt.Errorf("redis: unexpected reply type %T", replyIface)
c.ch <- msg
return
}
msgName := reply[0].(string)
switch msgName {
case "subscribe", "unsubscribe", "psubscribe", "punsubscribe":
msg.Name = msgName
msg.Channel = reply[1].(string)
msg.Number = reply[2].(int64)
case "message":
msg.Name = msgName
msg.Channel = reply[1].(string)
msg.Message = reply[2].(string)
case "pmessage":
msg.Name = msgName
msg.ChannelPattern = reply[1].(string)
msg.Channel = reply[2].(string)
msg.Message = reply[3].(string)
default:
msg.Err = fmt.Errorf("Unsupported message name: %q.", msgName)
}
c.ch <- msg
}
} }
func (c *PubSubClient) subscribe(cmd string, channels ...string) (chan *Message, error) { type Subscription struct {
args := append([]string{cmd}, channels...) Kind string
req := NewIfaceSliceReq(args...) Channel string
Count int
}
conn, err := c.conn() func (c *PubSub) Receive() (interface{}, error) {
return c.ReceiveTimeout(0)
}
func (c *PubSub) ReceiveTimeout(timeout time.Duration) (interface{}, error) {
cn, err := c.conn()
if err != nil { if err != nil {
return nil, err return nil, err
} }
cn.readTimeout = timeout
if err := c.WriteReq(conn, req); err != nil { cmd := NewSliceCmd()
if err := cmd.parseReply(cn.rd); err != nil {
return nil, err return nil, err
} }
c.once.Do(func() { reply := cmd.Val()
go c.consumeMessages(conn)
})
return c.ch, nil msgName := reply[0].(string)
switch msgName {
case "subscribe", "unsubscribe", "psubscribe", "punsubscribe":
return &Subscription{
Kind: msgName,
Channel: reply[1].(string),
Count: int(reply[2].(int64)),
}, nil
case "message":
return &Message{
Channel: reply[1].(string),
Payload: reply[2].(string),
}, nil
case "pmessage":
return &PMessage{
Pattern: reply[1].(string),
Channel: reply[2].(string),
Payload: reply[3].(string),
}, nil
}
return nil, fmt.Errorf("redis: unsupported message name: %q", msgName)
} }
func (c *PubSubClient) Subscribe(channels ...string) (chan *Message, error) { func (c *PubSub) subscribe(cmd string, channels ...string) error {
return c.subscribe("SUBSCRIBE", channels...) cn, err := c.conn()
}
func (c *PubSubClient) PSubscribe(patterns ...string) (chan *Message, error) {
return c.subscribe("PSUBSCRIBE", patterns...)
}
func (c *PubSubClient) unsubscribe(cmd string, channels ...string) error {
args := append([]string{cmd}, channels...)
req := NewIfaceSliceReq(args...)
conn, err := c.conn()
if err != nil { if err != nil {
return err return err
} }
return c.WriteReq(conn, req) args := append([]string{cmd}, channels...)
req := NewSliceCmd(args...)
return c.writeCmd(cn, req)
} }
func (c *PubSubClient) Unsubscribe(channels ...string) error { func (c *PubSub) Subscribe(channels ...string) error {
return c.subscribe("SUBSCRIBE", channels...)
}
func (c *PubSub) PSubscribe(patterns ...string) error {
return c.subscribe("PSUBSCRIBE", patterns...)
}
func (c *PubSub) unsubscribe(cmd string, channels ...string) error {
cn, err := c.conn()
if err != nil {
return err
}
args := append([]string{cmd}, channels...)
req := NewSliceCmd(args...)
return c.writeCmd(cn, req)
}
func (c *PubSub) Unsubscribe(channels ...string) error {
return c.unsubscribe("UNSUBSCRIBE", channels...) return c.unsubscribe("UNSUBSCRIBE", channels...)
} }
func (c *PubSubClient) PUnsubscribe(patterns ...string) error { func (c *PubSub) PUnsubscribe(patterns ...string) error {
return c.unsubscribe("PUNSUBSCRIBE", patterns...) return c.unsubscribe("PUNSUBSCRIBE", patterns...)
} }

283
redis.go
View File

@ -1,192 +1,191 @@
package redis package redis
import ( import (
"crypto/tls"
"log"
"net" "net"
"os"
"sync"
"time" "time"
"github.com/golang/glog"
) )
// Package logger. type baseClient struct {
var Logger = log.New(os.Stdout, "redis: ", log.Ldate|log.Ltime) connPool pool
type OpenConnFunc func() (net.Conn, error) opt *Options
type CloseConnFunc func(net.Conn) error
type InitConnFunc func(*Client) error
func TCPConnector(addr string) OpenConnFunc { cmds []Cmder
return func() (net.Conn, error) {
return net.DialTimeout("tcp", addr, 3*time.Second)
}
} }
func TLSConnector(addr string, tlsConfig *tls.Config) OpenConnFunc { func (c *baseClient) writeCmd(cn *conn, cmds ...Cmder) error {
return func() (net.Conn, error) {
conn, err := net.DialTimeout("tcp", addr, 3*time.Second)
if err != nil {
return nil, err
}
return tls.Client(conn, tlsConfig), nil
}
}
func UnixConnector(addr string) OpenConnFunc {
return func() (net.Conn, error) {
return net.DialTimeout("unix", addr, 3*time.Second)
}
}
func AuthSelectFunc(password string, db int64) InitConnFunc {
if password == "" && db < 0 {
return nil
}
return func(client *Client) error {
if password != "" {
auth := client.Auth(password)
if auth.Err() != nil {
return auth.Err()
}
}
if db >= 0 {
sel := client.Select(db)
if sel.Err() != nil {
return sel.Err()
}
}
return nil
}
}
//------------------------------------------------------------------------------
type BaseClient struct {
ConnPool ConnPool
InitConn InitConnFunc
reqs []Req
reqsMtx sync.Mutex
}
func (c *BaseClient) WriteReq(conn *Conn, reqs ...Req) error {
buf := make([]byte, 0, 1000) buf := make([]byte, 0, 1000)
for _, req := range reqs { for _, cmd := range cmds {
buf = appendReq(buf, req.Args()) buf = appendCmd(buf, cmd.args())
} }
_, err := conn.RW.Write(buf) _, err := cn.Write(buf)
return err return err
} }
func (c *BaseClient) conn() (*Conn, error) { func (c *baseClient) conn() (*conn, error) {
conn, isNew, err := c.ConnPool.Get() cn, isNew, err := c.connPool.Get()
if err != nil { if err != nil {
return nil, err return nil, err
} }
if isNew && c.InitConn != nil { if isNew && (c.opt.Password != "" || c.opt.DB > 0) {
client := &Client{ if err = c.init(cn, c.opt.Password, c.opt.DB); err != nil {
BaseClient: &BaseClient{ c.removeConn(cn)
ConnPool: NewSingleConnPoolConn(c.ConnPool, conn, true),
},
}
err = c.InitConn(client)
if err != nil {
if err := c.ConnPool.Remove(conn); err != nil {
Logger.Printf("ConnPool.Remove error: %v", err)
}
return nil, err return nil, err
} }
} }
return conn, nil
return cn, nil
} }
func (c *BaseClient) Process(req Req) { func (c *baseClient) init(cn *conn, password string, db int64) error {
if c.reqs == nil { // Client is not closed on purpose.
c.Run(req) client := &Client{
baseClient: &baseClient{
opt: c.opt,
connPool: newSingleConnPool(c.connPool, cn, false),
},
}
if password != "" {
auth := client.Auth(password)
if auth.Err() != nil {
return auth.Err()
}
}
if db > 0 {
sel := client.Select(db)
if sel.Err() != nil {
return sel.Err()
}
}
return nil
}
func (c *baseClient) freeConn(cn *conn, err error) {
if err == Nil || err == TxFailedErr {
c.putConn(cn)
} else { } else {
c.Queue(req) c.removeConn(cn)
} }
} }
func (c *BaseClient) Run(req Req) { func (c *baseClient) removeConn(cn *conn) {
conn, err := c.conn() if err := c.connPool.Remove(cn); err != nil {
if err != nil { glog.Errorf("pool.Remove failed: %s", err)
req.SetErr(err)
return
} }
err = c.WriteReq(conn, req)
if err != nil {
if err := c.ConnPool.Remove(conn); err != nil {
Logger.Printf("ConnPool.Remove error: %v", err)
}
req.SetErr(err)
return
}
val, err := req.ParseReply(conn.Rd)
if err != nil {
if _, ok := err.(*parserError); ok {
if err := c.ConnPool.Remove(conn); err != nil {
Logger.Printf("ConnPool.Remove error: %v", err)
}
} else {
if err := c.ConnPool.Add(conn); err != nil {
Logger.Printf("ConnPool.Add error: %v", err)
}
}
req.SetErr(err)
return
}
if err := c.ConnPool.Add(conn); err != nil {
Logger.Printf("ConnPool.Add error: %v", err)
}
req.SetVal(val)
} }
// Queues request to be executed later. func (c *baseClient) putConn(cn *conn) {
func (c *BaseClient) Queue(req Req) { if err := c.connPool.Put(cn); err != nil {
c.reqsMtx.Lock() glog.Errorf("pool.Put failed: %s", err)
c.reqs = append(c.reqs, req) }
c.reqsMtx.Unlock()
} }
func (c *BaseClient) Close() error { func (c *baseClient) Process(cmd Cmder) {
return c.ConnPool.Close() if c.cmds == nil {
c.run(cmd)
} else {
c.cmds = append(c.cmds, cmd)
}
}
func (c *baseClient) run(cmd Cmder) {
cn, err := c.conn()
if err != nil {
cmd.setErr(err)
return
}
cn.writeTimeout = c.opt.WriteTimeout
if timeout := cmd.writeTimeout(); timeout != nil {
cn.writeTimeout = *timeout
}
cn.readTimeout = c.opt.ReadTimeout
if timeout := cmd.readTimeout(); timeout != nil {
cn.readTimeout = *timeout
}
if err := c.writeCmd(cn, cmd); err != nil {
c.freeConn(cn, err)
cmd.setErr(err)
return
}
if err := cmd.parseReply(cn.rd); err != nil {
c.freeConn(cn, err)
return
}
c.putConn(cn)
}
// Close closes the client, releasing any open resources.
func (c *baseClient) Close() error {
return c.connPool.Close()
}
//------------------------------------------------------------------------------
type Options struct {
Addr string
Password string
DB int64
PoolSize int
DialTimeout time.Duration
ReadTimeout time.Duration
WriteTimeout time.Duration
IdleTimeout time.Duration
}
func (opt *Options) getPoolSize() int {
if opt.PoolSize == 0 {
return 10
}
return opt.PoolSize
}
func (opt *Options) getDialTimeout() time.Duration {
if opt.DialTimeout == 0 {
return 5 * time.Second
}
return opt.DialTimeout
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
type Client struct { type Client struct {
*BaseClient *baseClient
} }
func NewClient(openConn OpenConnFunc, closeConn CloseConnFunc, initConn InitConnFunc) *Client { func newClient(opt *Options, dial func() (net.Conn, error)) *Client {
return &Client{ return &Client{
BaseClient: &BaseClient{ baseClient: &baseClient{
ConnPool: NewMultiConnPool(openConn, closeConn, 10), opt: opt,
InitConn: initConn,
connPool: newConnPool(newConnFunc(dial), opt.getPoolSize(), opt.IdleTimeout),
}, },
} }
} }
func NewTCPClient(addr string, password string, db int64) *Client { func NewTCPClient(opt *Options) *Client {
return NewClient(TCPConnector(addr), nil, AuthSelectFunc(password, db)) dial := func() (net.Conn, error) {
return net.DialTimeout("tcp", opt.Addr, opt.getDialTimeout())
}
return newClient(opt, dial)
} }
func NewTLSClient(addr string, tlsConfig *tls.Config, password string, db int64) *Client { func NewUnixClient(opt *Options) *Client {
return NewClient( dial := func() (net.Conn, error) {
TLSConnector(addr, tlsConfig), return net.DialTimeout("unix", opt.Addr, opt.getDialTimeout())
nil, }
AuthSelectFunc(password, db), return newClient(opt, dial)
)
}
func NewUnixClient(addr string, password string, db int64) *Client {
return NewClient(UnixConnector(addr), nil, AuthSelectFunc(password, db))
} }

File diff suppressed because it is too large Load Diff

315
req.go
View File

@ -1,315 +0,0 @@
package redis
import (
"fmt"
"strconv"
"strings"
)
type Req interface {
Args() []string
ParseReply(reader) (interface{}, error)
SetErr(error)
Err() error
SetVal(interface{})
IfaceVal() interface{}
}
//------------------------------------------------------------------------------
type BaseReq struct {
args []string
val interface{}
err error
}
func NewBaseReq(args ...string) *BaseReq {
return &BaseReq{
args: args,
}
}
func (r *BaseReq) Args() []string {
return r.args
}
func (r *BaseReq) SetErr(err error) {
if err == nil {
panic("non-nil value expected")
}
r.err = err
}
func (r *BaseReq) Err() error {
if r.err != nil {
return r.err
}
if r.val == nil {
return errValNotSet
}
return nil
}
func (r *BaseReq) SetVal(val interface{}) {
if val == nil {
panic("non-nil value expected")
}
r.val = val
}
func (r *BaseReq) IfaceVal() interface{} {
return r.val
}
func (r *BaseReq) ParseReply(rd reader) (interface{}, error) {
return parseReply(rd)
}
func (r *BaseReq) String() string {
args := strings.Join(r.args, " ")
if r.err != nil {
return args + ": " + r.err.Error()
} else if r.val != nil {
return args + ": " + fmt.Sprint(r.val)
}
return args
}
//------------------------------------------------------------------------------
type IfaceReq struct {
*BaseReq
}
func NewIfaceReq(args ...string) *IfaceReq {
return &IfaceReq{
BaseReq: NewBaseReq(args...),
}
}
func (r *IfaceReq) Val() interface{} {
return r.val
}
//------------------------------------------------------------------------------
type StatusReq struct {
*BaseReq
}
func NewStatusReq(args ...string) *StatusReq {
return &StatusReq{
BaseReq: NewBaseReq(args...),
}
}
func (r *StatusReq) Val() string {
if r.val == nil {
return ""
}
return r.val.(string)
}
//------------------------------------------------------------------------------
type IntReq struct {
*BaseReq
}
func NewIntReq(args ...string) *IntReq {
return &IntReq{
BaseReq: NewBaseReq(args...),
}
}
func (r *IntReq) Val() int64 {
if r.val == nil {
return 0
}
return r.val.(int64)
}
//------------------------------------------------------------------------------
type BoolReq struct {
*BaseReq
}
func NewBoolReq(args ...string) *BoolReq {
return &BoolReq{
BaseReq: NewBaseReq(args...),
}
}
func (r *BoolReq) ParseReply(rd reader) (interface{}, error) {
v, err := parseReply(rd)
if err != nil {
return nil, err
}
return v.(int64) == 1, nil
}
func (r *BoolReq) Val() bool {
if r.val == nil {
return false
}
return r.val.(bool)
}
//------------------------------------------------------------------------------
type StringReq struct {
*BaseReq
}
func NewStringReq(args ...string) *StringReq {
return &StringReq{
BaseReq: NewBaseReq(args...),
}
}
func (r *StringReq) Val() string {
if r.val == nil {
return ""
}
return r.val.(string)
}
//------------------------------------------------------------------------------
type FloatReq struct {
*BaseReq
}
func NewFloatReq(args ...string) *FloatReq {
return &FloatReq{
BaseReq: NewBaseReq(args...),
}
}
func (r *FloatReq) ParseReply(rd reader) (interface{}, error) {
v, err := parseReply(rd)
if err != nil {
return nil, err
}
return strconv.ParseFloat(v.(string), 64)
}
func (r *FloatReq) Val() float64 {
if r.val == nil {
return 0
}
return r.val.(float64)
}
//------------------------------------------------------------------------------
type IfaceSliceReq struct {
*BaseReq
}
func NewIfaceSliceReq(args ...string) *IfaceSliceReq {
return &IfaceSliceReq{
BaseReq: NewBaseReq(args...),
}
}
func (r *IfaceSliceReq) Val() []interface{} {
if r.val == nil {
return nil
}
return r.val.([]interface{})
}
//------------------------------------------------------------------------------
type StringSliceReq struct {
*BaseReq
}
func NewStringSliceReq(args ...string) *StringSliceReq {
return &StringSliceReq{
BaseReq: NewBaseReq(args...),
}
}
func (r *StringSliceReq) ParseReply(rd reader) (interface{}, error) {
return parseStringSliceReply(rd)
}
func (r *StringSliceReq) Val() []string {
if r.val == nil {
return nil
}
return r.val.([]string)
}
//------------------------------------------------------------------------------
type BoolSliceReq struct {
*BaseReq
}
func NewBoolSliceReq(args ...string) *BoolSliceReq {
return &BoolSliceReq{
BaseReq: NewBaseReq(args...),
}
}
func (r *BoolSliceReq) ParseReply(rd reader) (interface{}, error) {
return parseBoolSliceReply(rd)
}
func (r *BoolSliceReq) Val() []bool {
if r.val == nil {
return nil
}
return r.val.([]bool)
}
//------------------------------------------------------------------------------
type StringStringMapReq struct {
*BaseReq
}
func NewStringStringMapReq(args ...string) *StringStringMapReq {
return &StringStringMapReq{
BaseReq: NewBaseReq(args...),
}
}
func (r *StringStringMapReq) ParseReply(rd reader) (interface{}, error) {
return parseStringStringMapReply(rd)
}
func (r *StringStringMapReq) Val() map[string]string {
if r.val == nil {
return nil
}
return r.val.(map[string]string)
}
//------------------------------------------------------------------------------
type StringFloatMapReq struct {
*BaseReq
}
func NewStringFloatMapReq(args ...string) *StringFloatMapReq {
return &StringFloatMapReq{
BaseReq: NewBaseReq(args...),
}
}
func (r *StringFloatMapReq) ParseReply(rd reader) (interface{}, error) {
return parseStringFloatMapReply(rd)
}
func (r *StringFloatMapReq) Val() map[string]float64 {
if r.val == nil {
return nil
}
return r.val.(map[string]float64)
}

View File

@ -1,93 +0,0 @@
package redis_test
import (
"github.com/vmihailenco/bufio"
. "launchpad.net/gocheck"
"github.com/vmihailenco/redis"
)
//------------------------------------------------------------------------------
type LineReader struct {
line []byte
}
func NewLineReader(line []byte) *LineReader {
return &LineReader{line: line}
}
func (r *LineReader) Read(buf []byte) (int, error) {
return copy(buf, r.line), nil
}
//------------------------------------------------------------------------------
type RequestTest struct{}
var _ = Suite(&RequestTest{})
//------------------------------------------------------------------------------
func (t *RequestTest) SetUpTest(c *C) {}
func (t *RequestTest) TearDownTest(c *C) {}
//------------------------------------------------------------------------------
func (t *RequestTest) benchmarkReq(c *C, reqString string, req redis.Req, checker Checker, expected interface{}) {
c.StopTimer()
lineReader := NewLineReader([]byte(reqString))
rd := bufio.NewReaderSize(lineReader, 1024)
for i := 0; i < 10; i++ {
vIface, err := req.ParseReply(rd)
c.Check(err, IsNil)
c.Check(vIface, checker, expected)
req.SetVal(vIface)
}
c.StartTimer()
for i := 0; i < c.N; i++ {
v, _ := req.ParseReply(rd)
req.SetVal(v)
}
}
func (t *RequestTest) BenchmarkStatusReq(c *C) {
t.benchmarkReq(c, "+OK\r\n", redis.NewStatusReq(), Equals, "OK")
}
func (t *RequestTest) BenchmarkIntReq(c *C) {
t.benchmarkReq(c, ":1\r\n", redis.NewIntReq(), Equals, int64(1))
}
func (t *RequestTest) BenchmarkStringReq(c *C) {
t.benchmarkReq(c, "$5\r\nhello\r\n", redis.NewStringReq(), Equals, "hello")
}
func (t *RequestTest) BenchmarkFloatReq(c *C) {
t.benchmarkReq(c, "$5\r\n1.111\r\n", redis.NewFloatReq(), Equals, 1.111)
}
func (t *RequestTest) BenchmarkStringSliceReq(c *C) {
t.benchmarkReq(
c,
"*2\r\n$5\r\nhello\r\n$5\r\nhello\r\n",
redis.NewStringSliceReq(),
DeepEquals,
[]string{"hello", "hello"},
)
}
func (t *RequestTest) BenchmarkIfaceSliceReq(c *C) {
t.benchmarkReq(
c,
"*2\r\n$5\r\nhello\r\n$5\r\nhello\r\n",
redis.NewIfaceSliceReq(),
DeepEquals,
[]interface{}{"hello", "hello"},
)
}

View File

@ -7,6 +7,13 @@ import (
"strings" "strings"
) )
type scripter interface {
Eval(script string, keys []string, args []string) *Cmd
EvalSha(sha1 string, keys []string, args []string) *Cmd
ScriptExists(scripts ...string) *BoolSliceCmd
ScriptLoad(script string) *StringCmd
}
type Script struct { type Script struct {
src, hash string src, hash string
} }
@ -20,23 +27,23 @@ func NewScript(src string) *Script {
} }
} }
func (s *Script) Load(c *Client) *StringReq { func (s *Script) Load(c scripter) *StringCmd {
return c.ScriptLoad(s.src) return c.ScriptLoad(s.src)
} }
func (s *Script) Exists(c *Client) *BoolSliceReq { func (s *Script) Exists(c scripter) *BoolSliceCmd {
return c.ScriptExists(s.src) return c.ScriptExists(s.src)
} }
func (s *Script) Eval(c *Client, keys []string, args []string) *IfaceReq { func (s *Script) Eval(c scripter, keys []string, args []string) *Cmd {
return c.Eval(s.src, keys, args) return c.Eval(s.src, keys, args)
} }
func (s *Script) EvalSha(c *Client, keys []string, args []string) *IfaceReq { func (s *Script) EvalSha(c scripter, keys []string, args []string) *Cmd {
return c.EvalSha(s.hash, keys, args) return c.EvalSha(s.hash, keys, args)
} }
func (s *Script) Run(c *Client, keys []string, args []string) *IfaceReq { func (s *Script) Run(c *Client, keys []string, args []string) *Cmd {
r := s.EvalSha(c, keys, args) r := s.EvalSha(c, keys, args)
if err := r.Err(); err != nil && strings.HasPrefix(err.Error(), "NOSCRIPT ") { if err := r.Err(); err != nil && strings.HasPrefix(err.Error(), "NOSCRIPT ") {
return s.Eval(c, keys, args) return s.Eval(c, keys, args)

View File

@ -1,50 +0,0 @@
Redis client for Golang [![Build Status](https://travis-ci.org/vmihailenco/redis.png?branch=master)](https://travis-ci.org/vmihailenco/redis)
=======================
Supports:
- Redis 2.8 commands except QUIT, MONITOR, SLOWLOG and SYNC.
- Pub/sub.
- Transactions.
- Pipelining.
- Connection pool.
- TLS connections.
- Thread safety.
- Timeouts.
API docs: http://godoc.org/github.com/vmihailenco/redis/v2.
Examples: http://godoc.org/github.com/vmihailenco/redis/v2#pkg-examples.
Installation
------------
Install:
go get github.com/vmihailenco/redis/v2
Upgrading from previous version
-------------------------------
Type system should catch most changes. But you have to manually change `SetEx`, `PSetEx`, `Expire` and `PExpire` to use `time.Duration` instead of `int64`.
Look and feel
-------------
Some corner cases:
SORT list LIMIT 0 2 ASC
vals, err := client.Sort("list", redis.Sort{Offset: 0, Count: 2, Order: "ASC"}).Result()
ZRANGEBYSCORE zset -inf +inf WITHSCORES LIMIT 0 2
vals, err := client.ZRangeByScoreWithScores("zset", redis.ZRangeByScore{
Min: "-inf",
Max: "+inf",
Offset: 0,
Count: 2,
}).Result()
ZINTERSTORE out 2 zset1 zset2 WEIGHTS 2 3 AGGREGATE SUM
vals, err := client.ZInterStore("out", redis.ZStore{Weights: []int64{2, 3}}, "zset1", "zset2").Result()
EVAL "return {KEYS[1],ARGV[1]}" 1 "key" "hello"
vals, err := client.Eval("return {KEYS[1],ARGV[1]}", []string{"key"}, []string{"hello"}).Result()

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +0,0 @@
/*
Package github.com/vmihailenco/redis/v2 implements a Redis client.
*/
package redis

View File

@ -1,158 +0,0 @@
package redis_test
import (
"fmt"
"strconv"
"github.com/vmihailenco/redis/v2"
)
var client *redis.Client
func init() {
client = redis.NewTCPClient(&redis.Options{
Addr: ":6379",
})
}
func ExampleNewTCPClient() {
client := redis.NewTCPClient(&redis.Options{
Addr: "localhost:6379",
Password: "", // no password set
DB: 0, // use default DB
})
defer client.Close()
pong, err := client.Ping().Result()
fmt.Println(pong, err)
// Output: PONG <nil>
}
func ExampleClient() {
set := client.Set("foo", "bar")
fmt.Println(set.Err())
v, err := client.Get("hello").Result()
fmt.Printf("%q %s %v", v, err, err == redis.Nil)
// Output: <nil>
// "" redis: nil true
}
func ExampleClient_Pipelined() {
cmds, err := client.Pipelined(func(c *redis.Pipeline) {
c.Set("key1", "hello1")
c.Get("key1")
})
fmt.Println(err)
set := cmds[0].(*redis.StatusCmd)
fmt.Println(set)
get := cmds[1].(*redis.StringCmd)
fmt.Println(get)
// Output: <nil>
// SET key1 hello1: OK
// GET key1: hello1
}
func ExamplePipeline() {
pipeline := client.Pipeline()
set := pipeline.Set("key1", "hello1")
get := pipeline.Get("key1")
cmds, err := pipeline.Exec()
fmt.Println(cmds, err)
fmt.Println(set)
fmt.Println(get)
// Output: [SET key1 hello1: OK GET key1: hello1] <nil>
// SET key1 hello1: OK
// GET key1: hello1
}
func ExampleMulti() {
incr := func(tx *redis.Multi) ([]redis.Cmder, error) {
s, err := tx.Get("key").Result()
if err != nil && err != redis.Nil {
return nil, err
}
n, _ := strconv.ParseInt(s, 10, 64)
return tx.Exec(func() {
tx.Set("key", strconv.FormatInt(n+1, 10))
})
}
client.Del("key")
tx := client.Multi()
defer tx.Close()
watch := tx.Watch("key")
_ = watch.Err()
for {
cmds, err := incr(tx)
if err == redis.TxFailedErr {
continue
} else if err != nil {
panic(err)
}
fmt.Println(cmds, err)
break
}
// Output: [SET key 1: OK] <nil>
}
func ExamplePubSub() {
pubsub := client.PubSub()
defer pubsub.Close()
err := pubsub.Subscribe("mychannel")
_ = err
msg, err := pubsub.Receive()
fmt.Println(msg, err)
pub := client.Publish("mychannel", "hello")
_ = pub.Err()
msg, err = pubsub.Receive()
fmt.Println(msg, err)
// Output: &{subscribe mychannel 1} <nil>
// &{mychannel hello} <nil>
}
func ExampleScript() {
setnx := redis.NewScript(`
if redis.call("get", KEYS[1]) == false then
redis.call("set", KEYS[1], ARGV[1])
return 1
end
return 0
`)
v1, err := setnx.Run(client, []string{"keynx"}, []string{"foo"}).Result()
fmt.Println(v1.(int64), err)
v2, err := setnx.Run(client, []string{"keynx"}, []string{"bar"}).Result()
fmt.Println(v2.(int64), err)
get := client.Get("keynx")
fmt.Println(get)
// Output: 1 <nil>
// 0 <nil>
// GET keynx: foo
}
func Example_customCommand() {
Get := func(client *redis.Client, key string) *redis.StringCmd {
cmd := redis.NewStringCmd("GET", key)
client.Process(cmd)
return cmd
}
v, err := Get(client, "key_does_not_exist").Result()
fmt.Printf("%q %s", v, err)
// Output: "" redis: nil
}

View File

@ -1,134 +0,0 @@
package redis
import (
"errors"
"fmt"
)
var errDiscard = errors.New("redis: Discard can be used only inside Exec")
// Not thread-safe.
type Multi struct {
*Client
}
func (c *Client) Multi() *Multi {
return &Multi{
Client: &Client{
baseClient: &baseClient{
opt: c.opt,
connPool: newSingleConnPool(c.connPool, nil, true),
},
},
}
}
func (c *Multi) Close() error {
c.Unwatch()
return c.Client.Close()
}
func (c *Multi) Watch(keys ...string) *StatusCmd {
args := append([]string{"WATCH"}, keys...)
cmd := NewStatusCmd(args...)
c.Process(cmd)
return cmd
}
func (c *Multi) Unwatch(keys ...string) *StatusCmd {
args := append([]string{"UNWATCH"}, keys...)
cmd := NewStatusCmd(args...)
c.Process(cmd)
return cmd
}
func (c *Multi) Discard() error {
if c.cmds == nil {
return errDiscard
}
c.cmds = c.cmds[:1]
return nil
}
// Exec always returns list of commands. If transaction fails
// TxFailedErr is returned. Otherwise Exec returns error of the first
// failed command or nil.
func (c *Multi) Exec(f func()) ([]Cmder, error) {
c.cmds = []Cmder{NewStatusCmd("MULTI")}
f()
c.cmds = append(c.cmds, NewSliceCmd("EXEC"))
cmds := c.cmds
c.cmds = nil
if len(cmds) == 2 {
return []Cmder{}, nil
}
cn, err := c.conn()
if err != nil {
setCmdsErr(cmds[1:len(cmds)-1], err)
return cmds[1 : len(cmds)-1], err
}
err = c.execCmds(cn, cmds)
if err != nil {
c.freeConn(cn, err)
return cmds[1 : len(cmds)-1], err
}
c.putConn(cn)
return cmds[1 : len(cmds)-1], nil
}
func (c *Multi) execCmds(cn *conn, cmds []Cmder) error {
err := c.writeCmd(cn, cmds...)
if err != nil {
setCmdsErr(cmds[1:len(cmds)-1], err)
return err
}
statusCmd := NewStatusCmd()
// Omit last command (EXEC).
cmdsLen := len(cmds) - 1
// Parse queued replies.
for i := 0; i < cmdsLen; i++ {
if err := statusCmd.parseReply(cn.rd); err != nil {
setCmdsErr(cmds[1:len(cmds)-1], err)
return err
}
}
// Parse number of replies.
line, err := readLine(cn.rd)
if err != nil {
setCmdsErr(cmds[1:len(cmds)-1], err)
return err
}
if line[0] != '*' {
err := fmt.Errorf("redis: expected '*', but got line %q", line)
setCmdsErr(cmds[1:len(cmds)-1], err)
return err
}
if len(line) == 3 && line[1] == '-' && line[2] == '1' {
setCmdsErr(cmds[1:len(cmds)-1], TxFailedErr)
return TxFailedErr
}
var firstCmdErr error
// Parse replies.
// Loop starts from 1 to omit MULTI cmd.
for i := 1; i < cmdsLen; i++ {
cmd := cmds[i]
if err := cmd.parseReply(cn.rd); err != nil {
if firstCmdErr == nil {
firstCmdErr = err
}
}
}
return firstCmdErr
}

View File

@ -1,276 +0,0 @@
package redis
import (
"errors"
"fmt"
"strconv"
"github.com/vmihailenco/bufio"
)
type multiBulkParser func(rd reader, n int64) (interface{}, error)
// Redis nil reply.
var Nil = errors.New("redis: nil")
// Redis transaction failed.
var TxFailedErr = errors.New("redis: transaction failed")
var (
errReaderTooSmall = errors.New("redis: reader is too small")
errInvalidReplyType = errors.New("redis: invalid reply type")
)
//------------------------------------------------------------------------------
func appendCmd(buf []byte, args []string) []byte {
buf = append(buf, '*')
buf = strconv.AppendUint(buf, uint64(len(args)), 10)
buf = append(buf, '\r', '\n')
for _, arg := range args {
buf = append(buf, '$')
buf = strconv.AppendUint(buf, uint64(len(arg)), 10)
buf = append(buf, '\r', '\n')
buf = append(buf, arg...)
buf = append(buf, '\r', '\n')
}
return buf
}
//------------------------------------------------------------------------------
type reader interface {
ReadLine() ([]byte, bool, error)
Read([]byte) (int, error)
ReadN(n int) ([]byte, error)
Buffered() int
Peek(int) ([]byte, error)
}
func readLine(rd reader) ([]byte, error) {
line, isPrefix, err := rd.ReadLine()
if err != nil {
return line, err
}
if isPrefix {
return line, errReaderTooSmall
}
return line, nil
}
func readN(rd reader, n int) ([]byte, error) {
b, err := rd.ReadN(n)
if err == bufio.ErrBufferFull {
newB := make([]byte, n)
r := copy(newB, b)
b = newB
for {
nn, err := rd.Read(b[r:])
r += nn
if r >= n {
// Ignore error if we read enough.
break
}
if err != nil {
return nil, err
}
}
} else if err != nil {
return nil, err
}
return b, nil
}
//------------------------------------------------------------------------------
func parseReq(rd reader) ([]string, error) {
line, err := readLine(rd)
if err != nil {
return nil, err
}
if line[0] != '*' {
return []string{string(line)}, nil
}
numReplies, err := strconv.ParseInt(string(line[1:]), 10, 64)
if err != nil {
return nil, err
}
args := make([]string, 0, numReplies)
for i := int64(0); i < numReplies; i++ {
line, err = readLine(rd)
if err != nil {
return nil, err
}
if line[0] != '$' {
return nil, fmt.Errorf("redis: expected '$', but got %q", line)
}
argLen, err := strconv.ParseInt(string(line[1:]), 10, 32)
if err != nil {
return nil, err
}
arg, err := readN(rd, int(argLen)+2)
if err != nil {
return nil, err
}
args = append(args, string(arg[:argLen]))
}
return args, nil
}
//------------------------------------------------------------------------------
func parseReply(rd reader, p multiBulkParser) (interface{}, error) {
line, err := readLine(rd)
if err != nil {
return nil, err
}
switch line[0] {
case '-':
return nil, errors.New(string(line[1:]))
case '+':
return string(line[1:]), nil
case ':':
v, err := strconv.ParseInt(string(line[1:]), 10, 64)
if err != nil {
return nil, err
}
return v, nil
case '$':
if len(line) == 3 && line[1] == '-' && line[2] == '1' {
return nil, Nil
}
replyLenInt32, err := strconv.ParseInt(string(line[1:]), 10, 32)
if err != nil {
return nil, err
}
replyLen := int(replyLenInt32) + 2
line, err = readN(rd, replyLen)
if err != nil {
return nil, err
}
return string(line[:len(line)-2]), nil
case '*':
if len(line) == 3 && line[1] == '-' && line[2] == '1' {
return nil, Nil
}
repliesNum, err := strconv.ParseInt(string(line[1:]), 10, 64)
if err != nil {
return nil, err
}
return p(rd, repliesNum)
}
return nil, fmt.Errorf("redis: can't parse %q", line)
}
func parseSlice(rd reader, n int64) (interface{}, error) {
vals := make([]interface{}, 0, n)
for i := int64(0); i < n; i++ {
v, err := parseReply(rd, parseSlice)
if err == Nil {
vals = append(vals, nil)
} else if err != nil {
return nil, err
} else {
vals = append(vals, v)
}
}
return vals, nil
}
func parseStringSlice(rd reader, n int64) (interface{}, error) {
vals := make([]string, 0, n)
for i := int64(0); i < n; i++ {
vi, err := parseReply(rd, nil)
if err != nil {
return nil, err
}
if v, ok := vi.(string); ok {
vals = append(vals, v)
} else {
return nil, errInvalidReplyType
}
}
return vals, nil
}
func parseBoolSlice(rd reader, n int64) (interface{}, error) {
vals := make([]bool, 0, n)
for i := int64(0); i < n; i++ {
vi, err := parseReply(rd, nil)
if err != nil {
return nil, err
}
if v, ok := vi.(int64); ok {
vals = append(vals, v == 1)
} else {
return nil, errInvalidReplyType
}
}
return vals, nil
}
func parseStringStringMap(rd reader, n int64) (interface{}, error) {
m := make(map[string]string, n/2)
for i := int64(0); i < n; i += 2 {
keyI, err := parseReply(rd, nil)
if err != nil {
return nil, err
}
key, ok := keyI.(string)
if !ok {
return nil, errInvalidReplyType
}
valueI, err := parseReply(rd, nil)
if err != nil {
return nil, err
}
value, ok := valueI.(string)
if !ok {
return nil, errInvalidReplyType
}
m[key] = value
}
return m, nil
}
func parseStringFloatMap(rd reader, n int64) (interface{}, error) {
m := make(map[string]float64, n/2)
for i := int64(0); i < n; i += 2 {
keyI, err := parseReply(rd, nil)
if err != nil {
return nil, err
}
key, ok := keyI.(string)
if !ok {
return nil, errInvalidReplyType
}
valueI, err := parseReply(rd, nil)
if err != nil {
return nil, err
}
valueS, ok := valueI.(string)
if !ok {
return nil, errInvalidReplyType
}
value, err := strconv.ParseFloat(valueS, 64)
if err != nil {
return nil, err
}
m[key] = value
}
return m, nil
}

View File

@ -1,90 +0,0 @@
package redis
// Not thread-safe.
type Pipeline struct {
*Client
closed bool
}
func (c *Client) Pipeline() *Pipeline {
return &Pipeline{
Client: &Client{
baseClient: &baseClient{
opt: c.opt,
connPool: c.connPool,
cmds: make([]Cmder, 0),
},
},
}
}
func (c *Client) Pipelined(f func(*Pipeline)) ([]Cmder, error) {
pc := c.Pipeline()
f(pc)
cmds, err := pc.Exec()
pc.Close()
return cmds, err
}
func (c *Pipeline) Close() error {
c.closed = true
return nil
}
func (c *Pipeline) Discard() error {
if c.closed {
return errClosed
}
c.cmds = c.cmds[:0]
return nil
}
// Exec always returns list of commands and error of the first failed
// command if any.
func (c *Pipeline) Exec() ([]Cmder, error) {
if c.closed {
return nil, errClosed
}
cmds := c.cmds
c.cmds = make([]Cmder, 0)
if len(cmds) == 0 {
return []Cmder{}, nil
}
cn, err := c.conn()
if err != nil {
setCmdsErr(cmds, err)
return cmds, err
}
if err := c.execCmds(cn, cmds); err != nil {
c.freeConn(cn, err)
return cmds, err
}
c.putConn(cn)
return cmds, nil
}
func (c *Pipeline) execCmds(cn *conn, cmds []Cmder) error {
err := c.writeCmd(cn, cmds...)
if err != nil {
setCmdsErr(cmds, err)
return err
}
var firstCmdErr error
for _, cmd := range cmds {
if err := cmd.parseReply(cn.rd); err != nil {
if firstCmdErr == nil {
firstCmdErr = err
}
}
}
return firstCmdErr
}

View File

@ -1,122 +0,0 @@
package redis
import (
"fmt"
"time"
)
// Not thread-safe.
type PubSub struct {
*baseClient
}
func (c *Client) PubSub() *PubSub {
return &PubSub{
baseClient: &baseClient{
opt: c.opt,
connPool: newSingleConnPool(c.connPool, nil, false),
},
}
}
func (c *Client) Publish(channel, message string) *IntCmd {
req := NewIntCmd("PUBLISH", channel, message)
c.Process(req)
return req
}
type Message struct {
Channel string
Payload string
}
type PMessage struct {
Channel string
Pattern string
Payload string
}
type Subscription struct {
Kind string
Channel string
Count int
}
func (c *PubSub) Receive() (interface{}, error) {
return c.ReceiveTimeout(0)
}
func (c *PubSub) ReceiveTimeout(timeout time.Duration) (interface{}, error) {
cn, err := c.conn()
if err != nil {
return nil, err
}
cn.readTimeout = timeout
cmd := NewSliceCmd()
if err := cmd.parseReply(cn.rd); err != nil {
return nil, err
}
reply := cmd.Val()
msgName := reply[0].(string)
switch msgName {
case "subscribe", "unsubscribe", "psubscribe", "punsubscribe":
return &Subscription{
Kind: msgName,
Channel: reply[1].(string),
Count: int(reply[2].(int64)),
}, nil
case "message":
return &Message{
Channel: reply[1].(string),
Payload: reply[2].(string),
}, nil
case "pmessage":
return &PMessage{
Pattern: reply[1].(string),
Channel: reply[2].(string),
Payload: reply[3].(string),
}, nil
}
return nil, fmt.Errorf("redis: unsupported message name: %q", msgName)
}
func (c *PubSub) subscribe(cmd string, channels ...string) error {
cn, err := c.conn()
if err != nil {
return err
}
args := append([]string{cmd}, channels...)
req := NewSliceCmd(args...)
return c.writeCmd(cn, req)
}
func (c *PubSub) Subscribe(channels ...string) error {
return c.subscribe("SUBSCRIBE", channels...)
}
func (c *PubSub) PSubscribe(patterns ...string) error {
return c.subscribe("PSUBSCRIBE", patterns...)
}
func (c *PubSub) unsubscribe(cmd string, channels ...string) error {
cn, err := c.conn()
if err != nil {
return err
}
args := append([]string{cmd}, channels...)
req := NewSliceCmd(args...)
return c.writeCmd(cn, req)
}
func (c *PubSub) Unsubscribe(channels ...string) error {
return c.unsubscribe("UNSUBSCRIBE", channels...)
}
func (c *PubSub) PUnsubscribe(patterns ...string) error {
return c.unsubscribe("PUNSUBSCRIBE", patterns...)
}

View File

@ -1,191 +0,0 @@
package redis
import (
"net"
"time"
"github.com/golang/glog"
)
type baseClient struct {
connPool pool
opt *Options
cmds []Cmder
}
func (c *baseClient) writeCmd(cn *conn, cmds ...Cmder) error {
buf := make([]byte, 0, 1000)
for _, cmd := range cmds {
buf = appendCmd(buf, cmd.args())
}
_, err := cn.Write(buf)
return err
}
func (c *baseClient) conn() (*conn, error) {
cn, isNew, err := c.connPool.Get()
if err != nil {
return nil, err
}
if isNew && (c.opt.Password != "" || c.opt.DB > 0) {
if err = c.init(cn, c.opt.Password, c.opt.DB); err != nil {
c.removeConn(cn)
return nil, err
}
}
return cn, nil
}
func (c *baseClient) init(cn *conn, password string, db int64) error {
// Client is not closed on purpose.
client := &Client{
baseClient: &baseClient{
opt: c.opt,
connPool: newSingleConnPool(c.connPool, cn, false),
},
}
if password != "" {
auth := client.Auth(password)
if auth.Err() != nil {
return auth.Err()
}
}
if db > 0 {
sel := client.Select(db)
if sel.Err() != nil {
return sel.Err()
}
}
return nil
}
func (c *baseClient) freeConn(cn *conn, err error) {
if err == Nil || err == TxFailedErr {
c.putConn(cn)
} else {
c.removeConn(cn)
}
}
func (c *baseClient) removeConn(cn *conn) {
if err := c.connPool.Remove(cn); err != nil {
glog.Errorf("pool.Remove failed: %s", err)
}
}
func (c *baseClient) putConn(cn *conn) {
if err := c.connPool.Put(cn); err != nil {
glog.Errorf("pool.Put failed: %s", err)
}
}
func (c *baseClient) Process(cmd Cmder) {
if c.cmds == nil {
c.run(cmd)
} else {
c.cmds = append(c.cmds, cmd)
}
}
func (c *baseClient) run(cmd Cmder) {
cn, err := c.conn()
if err != nil {
cmd.setErr(err)
return
}
cn.writeTimeout = c.opt.WriteTimeout
if timeout := cmd.writeTimeout(); timeout != nil {
cn.writeTimeout = *timeout
}
cn.readTimeout = c.opt.ReadTimeout
if timeout := cmd.readTimeout(); timeout != nil {
cn.readTimeout = *timeout
}
if err := c.writeCmd(cn, cmd); err != nil {
c.freeConn(cn, err)
cmd.setErr(err)
return
}
if err := cmd.parseReply(cn.rd); err != nil {
c.freeConn(cn, err)
return
}
c.putConn(cn)
}
// Close closes the client, releasing any open resources.
func (c *baseClient) Close() error {
return c.connPool.Close()
}
//------------------------------------------------------------------------------
type Options struct {
Addr string
Password string
DB int64
PoolSize int
DialTimeout time.Duration
ReadTimeout time.Duration
WriteTimeout time.Duration
IdleTimeout time.Duration
}
func (opt *Options) getPoolSize() int {
if opt.PoolSize == 0 {
return 10
}
return opt.PoolSize
}
func (opt *Options) getDialTimeout() time.Duration {
if opt.DialTimeout == 0 {
return 5 * time.Second
}
return opt.DialTimeout
}
//------------------------------------------------------------------------------
type Client struct {
*baseClient
}
func newClient(opt *Options, dial func() (net.Conn, error)) *Client {
return &Client{
baseClient: &baseClient{
opt: opt,
connPool: newConnPool(newConnFunc(dial), opt.getPoolSize(), opt.IdleTimeout),
},
}
}
func NewTCPClient(opt *Options) *Client {
dial := func() (net.Conn, error) {
return net.DialTimeout("tcp", opt.Addr, opt.getDialTimeout())
}
return newClient(opt, dial)
}
func NewUnixClient(opt *Options) *Client {
dial := func() (net.Conn, error) {
return net.DialTimeout("unix", opt.Addr, opt.getDialTimeout())
}
return newClient(opt, dial)
}

File diff suppressed because it is too large Load Diff

View File

@ -1,52 +0,0 @@
package redis
import (
"crypto/sha1"
"encoding/hex"
"io"
"strings"
)
type scripter interface {
Eval(script string, keys []string, args []string) *Cmd
EvalSha(sha1 string, keys []string, args []string) *Cmd
ScriptExists(scripts ...string) *BoolSliceCmd
ScriptLoad(script string) *StringCmd
}
type Script struct {
src, hash string
}
func NewScript(src string) *Script {
h := sha1.New()
io.WriteString(h, src)
return &Script{
src: src,
hash: hex.EncodeToString(h.Sum(nil)),
}
}
func (s *Script) Load(c scripter) *StringCmd {
return c.ScriptLoad(s.src)
}
func (s *Script) Exists(c scripter) *BoolSliceCmd {
return c.ScriptExists(s.src)
}
func (s *Script) Eval(c scripter, keys []string, args []string) *Cmd {
return c.Eval(s.src, keys, args)
}
func (s *Script) EvalSha(c scripter, keys []string, args []string) *Cmd {
return c.EvalSha(s.hash, keys, args)
}
func (s *Script) Run(c *Client, keys []string, args []string) *Cmd {
r := s.EvalSha(c, keys, args)
if err := r.Err(); err != nil && strings.HasPrefix(err.Error(), "NOSCRIPT ") {
return s.Eval(c, keys, args)
}
return r
}