mirror of https://github.com/go-redis/redis.git
all: switch to gopkg.in.
This commit is contained in:
parent
df12cdcfaf
commit
3bea997988
47
README.md
47
README.md
|
@ -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()
|
||||
|
|
707
commands.go
707
commands.go
File diff suppressed because it is too large
Load Diff
197
connpool.go
197
connpool.go
|
@ -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
131
doc.go
|
@ -1,133 +1,4 @@
|
|||
/*
|
||||
Package github.com/vmihailenco/redis 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 gopkg.in/redis.v1 implements a Redis client.
|
||||
*/
|
||||
package redis
|
||||
|
|
183
example_test.go
183
example_test.go
|
@ -4,132 +4,155 @@ import (
|
|||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/vmihailenco/redis"
|
||||
"gopkg.in/redis.v1"
|
||||
)
|
||||
|
||||
func ExampleTCPClient() {
|
||||
password := "" // no password set
|
||||
db := int64(-1) // use default DB
|
||||
client := redis.NewTCPClient("localhost:6379", password, db)
|
||||
defer client.Close()
|
||||
var client *redis.Client
|
||||
|
||||
ping := client.Ping()
|
||||
fmt.Println(ping.Err(), ping.Val())
|
||||
// Output: <nil> PONG
|
||||
func init() {
|
||||
client = redis.NewTCPClient(&redis.Options{
|
||||
Addr: ":6379",
|
||||
})
|
||||
}
|
||||
|
||||
func ExampleUnixClient() {
|
||||
client := redis.NewUnixClient("/tmp/redis.sock", "", -1)
|
||||
func ExampleNewTCPClient() {
|
||||
client := redis.NewTCPClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password set
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
defer client.Close()
|
||||
|
||||
ping := client.Ping()
|
||||
fmt.Println(ping.Err(), ping.Val())
|
||||
// Output: <nil> PONG
|
||||
pong, err := client.Ping().Result()
|
||||
fmt.Println(pong, err)
|
||||
// Output: PONG <nil>
|
||||
}
|
||||
|
||||
func ExampleSetGet() {
|
||||
client := redis.NewTCPClient(":6379", "", -1)
|
||||
defer client.Close()
|
||||
|
||||
func ExampleClient() {
|
||||
set := client.Set("foo", "bar")
|
||||
fmt.Println(set.Err(), set.Val())
|
||||
fmt.Println(set.Err())
|
||||
|
||||
get := client.Get("foo")
|
||||
fmt.Println(get.Err(), get.Val())
|
||||
v, err := client.Get("hello").Result()
|
||||
fmt.Printf("%q %s %v", v, err, err == redis.Nil)
|
||||
|
||||
// Output: <nil> OK
|
||||
// <nil> bar
|
||||
// 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() {
|
||||
client := redis.NewTCPClient(":6379", "", -1)
|
||||
defer client.Close()
|
||||
|
||||
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)
|
||||
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: <nil> [SET key1 hello1: OK GET key2: (nil)]
|
||||
// Output: [SET key1 hello1: OK GET key1: hello1] <nil>
|
||||
// SET key1 hello1: OK
|
||||
// GET key2: (nil)
|
||||
// GET key1: hello1
|
||||
}
|
||||
|
||||
func transaction(multi *redis.MultiClient) ([]redis.Req, error) {
|
||||
get := multi.Get("key")
|
||||
if err := get.Err(); err != nil && err != redis.Nil {
|
||||
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)
|
||||
|
||||
val, _ := strconv.ParseInt(get.Val(), 10, 64)
|
||||
|
||||
reqs, err := multi.Exec(func() {
|
||||
multi.Set("key", strconv.FormatInt(val+1, 10))
|
||||
return tx.Exec(func() {
|
||||
tx.Set("key", strconv.FormatInt(n+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")
|
||||
|
||||
multi, err := client.MultiClient()
|
||||
_ = err
|
||||
defer multi.Close()
|
||||
tx := client.Multi()
|
||||
defer tx.Close()
|
||||
|
||||
watch := multi.Watch("key")
|
||||
watch := tx.Watch("key")
|
||||
_ = watch.Err()
|
||||
|
||||
reqs, err := transaction(multi)
|
||||
fmt.Println(err, reqs)
|
||||
for {
|
||||
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() {
|
||||
client := redis.NewTCPClient(":6379", "", -1)
|
||||
defer client.Close()
|
||||
|
||||
pubsub, err := client.PubSubClient()
|
||||
pubsub := client.PubSub()
|
||||
defer pubsub.Close()
|
||||
|
||||
ch, err := pubsub.Subscribe("mychannel")
|
||||
err := pubsub.Subscribe("mychannel")
|
||||
_ = err
|
||||
|
||||
subscribeMsg := <-ch
|
||||
fmt.Println(subscribeMsg.Err, subscribeMsg.Name)
|
||||
msg, err := pubsub.Receive()
|
||||
fmt.Println(msg, err)
|
||||
|
||||
pub := client.Publish("mychannel", "hello")
|
||||
_ = pub.Err()
|
||||
|
||||
msg := <-ch
|
||||
fmt.Println(msg.Err, msg.Message)
|
||||
msg, err = pubsub.Receive()
|
||||
fmt.Println(msg, err)
|
||||
|
||||
// Output: <nil> subscribe
|
||||
// <nil> hello
|
||||
// Output: &{subscribe mychannel 1} <nil>
|
||||
// &{mychannel hello} <nil>
|
||||
}
|
||||
|
||||
func Get(client *redis.Client, key string) *redis.StringReq {
|
||||
req := redis.NewStringReq("GET", key)
|
||||
client.Process(req)
|
||||
return req
|
||||
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 ExampleCustomCommand() {
|
||||
client := redis.NewTCPClient(":6379", "", -1)
|
||||
defer client.Close()
|
||||
|
||||
get := Get(client, "key_does_not_exist")
|
||||
fmt.Println(get.Err(), get.Val())
|
||||
// Output: (nil)
|
||||
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
|
||||
}
|
||||
|
|
138
multi.go
138
multi.go
|
@ -1,132 +1,134 @@
|
|||
package redis
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type MultiClient struct {
|
||||
var errDiscard = errors.New("redis: Discard can be used only inside Exec")
|
||||
|
||||
// Not thread-safe.
|
||||
type Multi struct {
|
||||
*Client
|
||||
execMtx sync.Mutex
|
||||
}
|
||||
|
||||
func (c *Client) MultiClient() (*MultiClient, error) {
|
||||
return &MultiClient{
|
||||
func (c *Client) Multi() *Multi {
|
||||
return &Multi{
|
||||
Client: &Client{
|
||||
BaseClient: &BaseClient{
|
||||
ConnPool: NewSingleConnPool(c.ConnPool, true),
|
||||
InitConn: c.InitConn,
|
||||
baseClient: &baseClient{
|
||||
opt: c.opt,
|
||||
connPool: newSingleConnPool(c.connPool, nil, true),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *MultiClient) Close() error {
|
||||
func (c *Multi) Close() error {
|
||||
c.Unwatch()
|
||||
return c.Client.Close()
|
||||
}
|
||||
|
||||
func (c *MultiClient) Watch(keys ...string) *StatusReq {
|
||||
func (c *Multi) Watch(keys ...string) *StatusCmd {
|
||||
args := append([]string{"WATCH"}, keys...)
|
||||
req := NewStatusReq(args...)
|
||||
c.Process(req)
|
||||
return req
|
||||
cmd := NewStatusCmd(args...)
|
||||
c.Process(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c *MultiClient) Unwatch(keys ...string) *StatusReq {
|
||||
func (c *Multi) Unwatch(keys ...string) *StatusCmd {
|
||||
args := append([]string{"UNWATCH"}, keys...)
|
||||
req := NewStatusReq(args...)
|
||||
c.Process(req)
|
||||
return req
|
||||
cmd := NewStatusCmd(args...)
|
||||
c.Process(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c *MultiClient) Discard() {
|
||||
c.reqsMtx.Lock()
|
||||
if c.reqs == nil {
|
||||
panic("Discard can be used only inside Exec")
|
||||
func (c *Multi) Discard() error {
|
||||
if c.cmds == nil {
|
||||
return errDiscard
|
||||
}
|
||||
c.reqs = c.reqs[:1]
|
||||
c.reqsMtx.Unlock()
|
||||
c.cmds = c.cmds[:1]
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *MultiClient) Exec(do func()) ([]Req, error) {
|
||||
c.reqsMtx.Lock()
|
||||
c.reqs = []Req{NewStatusReq("MULTI")}
|
||||
c.reqsMtx.Unlock()
|
||||
// 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"))
|
||||
|
||||
do()
|
||||
cmds := c.cmds
|
||||
c.cmds = nil
|
||||
|
||||
c.Queue(NewIfaceSliceReq("EXEC"))
|
||||
|
||||
c.reqsMtx.Lock()
|
||||
reqs := c.reqs
|
||||
c.reqs = nil
|
||||
c.reqsMtx.Unlock()
|
||||
|
||||
if len(reqs) == 2 {
|
||||
return []Req{}, nil
|
||||
if len(cmds) == 2 {
|
||||
return []Cmder{}, nil
|
||||
}
|
||||
|
||||
conn, err := c.conn()
|
||||
cn, err := c.conn()
|
||||
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.
|
||||
c.execMtx.Lock()
|
||||
err = c.ExecReqs(reqs, conn)
|
||||
c.execMtx.Unlock()
|
||||
err = c.execCmds(cn, cmds)
|
||||
if err != nil {
|
||||
c.ConnPool.Remove(conn)
|
||||
return nil, err
|
||||
c.freeConn(cn, err)
|
||||
return cmds[1 : len(cmds)-1], err
|
||||
}
|
||||
|
||||
c.ConnPool.Add(conn)
|
||||
return reqs[1 : len(reqs)-1], nil
|
||||
c.putConn(cn)
|
||||
return cmds[1 : len(cmds)-1], nil
|
||||
}
|
||||
|
||||
func (c *MultiClient) ExecReqs(reqs []Req, conn *Conn) error {
|
||||
err := c.WriteReq(conn, reqs...)
|
||||
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
|
||||
}
|
||||
|
||||
statusReq := NewStatusReq()
|
||||
statusCmd := NewStatusCmd()
|
||||
|
||||
// Omit last request (EXEC).
|
||||
reqsLen := len(reqs) - 1
|
||||
// Omit last command (EXEC).
|
||||
cmdsLen := len(cmds) - 1
|
||||
|
||||
// Parse queued replies.
|
||||
for i := 0; i < reqsLen; i++ {
|
||||
_, err = statusReq.ParseReply(conn.Rd)
|
||||
if err != nil {
|
||||
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(conn.Rd)
|
||||
line, err := readLine(cn.rd)
|
||||
if err != nil {
|
||||
setCmdsErr(cmds[1:len(cmds)-1], err)
|
||||
return err
|
||||
}
|
||||
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' {
|
||||
return Nil
|
||||
setCmdsErr(cmds[1:len(cmds)-1], TxFailedErr)
|
||||
return TxFailedErr
|
||||
}
|
||||
|
||||
var firstCmdErr error
|
||||
|
||||
// Parse replies.
|
||||
// Loop starts from 1 to omit first request (MULTI).
|
||||
for i := 1; i < reqsLen; i++ {
|
||||
req := reqs[i]
|
||||
val, err := req.ParseReply(conn.Rd)
|
||||
if err != nil {
|
||||
req.SetErr(err)
|
||||
} else {
|
||||
req.SetVal(val)
|
||||
// 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 nil
|
||||
return firstCmdErr
|
||||
}
|
||||
|
|
244
parser.go
244
parser.go
|
@ -8,38 +8,22 @@ import (
|
|||
"github.com/vmihailenco/bufio"
|
||||
)
|
||||
|
||||
type replyType int
|
||||
type multiBulkParser func(rd reader, n int64) (interface{}, error)
|
||||
|
||||
const (
|
||||
ifaceSlice replyType = iota
|
||||
stringSlice
|
||||
boolSlice
|
||||
stringStringMap
|
||||
stringFloatMap
|
||||
)
|
||||
// Redis nil reply.
|
||||
var Nil = errors.New("redis: nil")
|
||||
|
||||
// Represents Redis nil reply.
|
||||
var Nil = errors.New("(nil)")
|
||||
// Redis transaction failed.
|
||||
var TxFailedErr = errors.New("redis: transaction failed")
|
||||
|
||||
var (
|
||||
errReaderTooSmall = errors.New("redis: reader is too small")
|
||||
errValNotSet = errors.New("redis: value is not set")
|
||||
errInvalidType = errors.New("redis: invalid reply type")
|
||||
errInvalidReplyType = errors.New("redis: invalid reply type")
|
||||
)
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
type parserError struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (e *parserError) Error() string {
|
||||
return e.err.Error()
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
func appendReq(buf []byte, args []string) []byte {
|
||||
func appendCmd(buf []byte, args []string) []byte {
|
||||
buf = append(buf, '*')
|
||||
buf = strconv.AppendUint(buf, uint64(len(args)), 10)
|
||||
buf = append(buf, '\r', '\n')
|
||||
|
@ -60,6 +44,7 @@ type reader interface {
|
|||
Read([]byte) (int, error)
|
||||
ReadN(n int) ([]byte, error)
|
||||
Buffered() int
|
||||
Peek(int) ([]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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -139,30 +124,10 @@ func ParseReq(rd reader) ([]string, error) {
|
|||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
func parseReply(rd reader) (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) {
|
||||
func parseReply(rd reader, p multiBulkParser) (interface{}, error) {
|
||||
line, err := readLine(rd)
|
||||
if err != nil {
|
||||
return 0, &parserError{err}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch line[0] {
|
||||
|
@ -173,23 +138,23 @@ func _parseReply(rd reader, typ replyType) (interface{}, error) {
|
|||
case ':':
|
||||
v, err := strconv.ParseInt(string(line[1:]), 10, 64)
|
||||
if err != nil {
|
||||
return 0, &parserError{err}
|
||||
return nil, err
|
||||
}
|
||||
return v, nil
|
||||
case '$':
|
||||
if len(line) == 3 && line[1] == '-' && line[2] == '1' {
|
||||
return "", Nil
|
||||
return nil, Nil
|
||||
}
|
||||
|
||||
replyLenInt32, err := strconv.ParseInt(string(line[1:]), 10, 32)
|
||||
if err != nil {
|
||||
return "", &parserError{err}
|
||||
return nil, err
|
||||
}
|
||||
replyLen := int(replyLenInt32) + 2
|
||||
|
||||
line, err = readN(rd, replyLen)
|
||||
if err != nil {
|
||||
return "", &parserError{err}
|
||||
return nil, err
|
||||
}
|
||||
return string(line[:len(line)-2]), nil
|
||||
case '*':
|
||||
|
@ -199,94 +164,18 @@ func _parseReply(rd reader, typ replyType) (interface{}, error) {
|
|||
|
||||
repliesNum, err := strconv.ParseInt(string(line[1:]), 10, 64)
|
||||
if err != nil {
|
||||
return nil, &parserError{err}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch typ {
|
||||
case stringSlice:
|
||||
vals := make([]string, 0, repliesNum)
|
||||
for i := int64(0); i < repliesNum; i++ {
|
||||
vi, err := parseReply(rd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return p(rd, repliesNum)
|
||||
}
|
||||
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
|
||||
return nil, fmt.Errorf("redis: can't parse %q", line)
|
||||
}
|
||||
|
||||
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)
|
||||
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 {
|
||||
|
@ -297,8 +186,91 @@ func _parseReply(rd reader, typ replyType) (interface{}, error) {
|
|||
}
|
||||
return vals, nil
|
||||
}
|
||||
default:
|
||||
return nil, &parserError{fmt.Errorf("redis: can't parse %q", line)}
|
||||
|
||||
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
|
||||
}
|
||||
panic("not reachable")
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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"})
|
||||
}
|
114
pipeline.go
114
pipeline.go
|
@ -1,84 +1,90 @@
|
|||
package redis
|
||||
|
||||
type PipelineClient struct {
|
||||
// Not thread-safe.
|
||||
type Pipeline struct {
|
||||
*Client
|
||||
|
||||
closed bool
|
||||
}
|
||||
|
||||
func (c *Client) PipelineClient() (*PipelineClient, error) {
|
||||
return &PipelineClient{
|
||||
func (c *Client) Pipeline() *Pipeline {
|
||||
return &Pipeline{
|
||||
Client: &Client{
|
||||
BaseClient: &BaseClient{
|
||||
ConnPool: c.ConnPool,
|
||||
InitConn: c.InitConn,
|
||||
reqs: make([]Req, 0),
|
||||
baseClient: &baseClient{
|
||||
opt: c.opt,
|
||||
connPool: c.connPool,
|
||||
|
||||
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 *Client) Pipelined(f func(*Pipeline)) ([]Cmder, error) {
|
||||
pc := c.Pipeline()
|
||||
f(pc)
|
||||
cmds, err := pc.Exec()
|
||||
pc.Close()
|
||||
return cmds, err
|
||||
}
|
||||
|
||||
func (c *PipelineClient) Close() error {
|
||||
func (c *Pipeline) Close() error {
|
||||
c.closed = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *PipelineClient) DiscardQueued() {
|
||||
c.reqsMtx.Lock()
|
||||
c.reqs = c.reqs[:0]
|
||||
c.reqsMtx.Unlock()
|
||||
func (c *Pipeline) Discard() error {
|
||||
if c.closed {
|
||||
return errClosed
|
||||
}
|
||||
c.cmds = c.cmds[:0]
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *PipelineClient) RunQueued() ([]Req, error) {
|
||||
c.reqsMtx.Lock()
|
||||
reqs := c.reqs
|
||||
c.reqs = make([]Req, 0)
|
||||
c.reqsMtx.Unlock()
|
||||
|
||||
if len(reqs) == 0 {
|
||||
return []Req{}, 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
|
||||
}
|
||||
|
||||
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 {
|
||||
return nil, err
|
||||
setCmdsErr(cmds, err)
|
||||
return cmds, err
|
||||
}
|
||||
|
||||
err = c.RunReqs(reqs, conn)
|
||||
if err != nil {
|
||||
c.ConnPool.Remove(conn)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.ConnPool.Add(conn)
|
||||
return reqs, nil
|
||||
}
|
||||
|
||||
func (c *PipelineClient) RunReqs(reqs []Req, conn *Conn) error {
|
||||
err := c.WriteReq(conn, reqs...)
|
||||
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
|
||||
}
|
||||
|
||||
reqsLen := len(reqs)
|
||||
for i := 0; i < reqsLen; i++ {
|
||||
req := reqs[i]
|
||||
val, err := req.ParseReply(conn.Rd)
|
||||
if err != nil {
|
||||
req.SetErr(err)
|
||||
} else {
|
||||
req.SetVal(val)
|
||||
var firstCmdErr error
|
||||
for _, cmd := range cmds {
|
||||
if err := cmd.parseReply(cn.rd); err != nil {
|
||||
if firstCmdErr == nil {
|
||||
firstCmdErr = err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return firstCmdErr
|
||||
}
|
||||
|
|
160
pubsub.go
160
pubsub.go
|
@ -2,123 +2,121 @@ package redis
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type PubSubClient struct {
|
||||
*BaseClient
|
||||
ch chan *Message
|
||||
once sync.Once
|
||||
// Not thread-safe.
|
||||
type PubSub struct {
|
||||
*baseClient
|
||||
}
|
||||
|
||||
func (c *Client) PubSubClient() (*PubSubClient, error) {
|
||||
return &PubSubClient{
|
||||
BaseClient: &BaseClient{
|
||||
ConnPool: NewSingleConnPool(c.ConnPool, false),
|
||||
InitConn: c.InitConn,
|
||||
func (c *Client) PubSub() *PubSub {
|
||||
return &PubSub{
|
||||
baseClient: &baseClient{
|
||||
opt: c.opt,
|
||||
connPool: newSingleConnPool(c.connPool, nil, false),
|
||||
},
|
||||
ch: make(chan *Message),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) Publish(channel, message string) *IntReq {
|
||||
req := NewIntReq("PUBLISH", channel, message)
|
||||
func (c *Client) Publish(channel, message string) *IntCmd {
|
||||
req := NewIntCmd("PUBLISH", channel, message)
|
||||
c.Process(req)
|
||||
return req
|
||||
}
|
||||
|
||||
type Message struct {
|
||||
Name, Channel, ChannelPattern, Message string
|
||||
Number int64
|
||||
|
||||
Err error
|
||||
Channel string
|
||||
Payload string
|
||||
}
|
||||
|
||||
func (c *PubSubClient) consumeMessages(conn *Conn) {
|
||||
req := NewIfaceSliceReq()
|
||||
type PMessage struct {
|
||||
Channel string
|
||||
Pattern string
|
||||
Payload string
|
||||
}
|
||||
|
||||
for {
|
||||
msg := &Message{}
|
||||
type Subscription struct {
|
||||
Kind string
|
||||
Channel string
|
||||
Count int
|
||||
}
|
||||
|
||||
replyIface, err := req.ParseReply(conn.Rd)
|
||||
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 {
|
||||
msg.Err = err
|
||||
c.ch <- msg
|
||||
return
|
||||
return nil, err
|
||||
}
|
||||
reply, ok := replyIface.([]interface{})
|
||||
if !ok {
|
||||
msg.Err = fmt.Errorf("redis: unexpected reply type %T", replyIface)
|
||||
c.ch <- msg
|
||||
return
|
||||
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":
|
||||
msg.Name = msgName
|
||||
msg.Channel = reply[1].(string)
|
||||
msg.Number = reply[2].(int64)
|
||||
return &Subscription{
|
||||
Kind: msgName,
|
||||
Channel: reply[1].(string),
|
||||
Count: int(reply[2].(int64)),
|
||||
}, nil
|
||||
case "message":
|
||||
msg.Name = msgName
|
||||
msg.Channel = reply[1].(string)
|
||||
msg.Message = reply[2].(string)
|
||||
return &Message{
|
||||
Channel: reply[1].(string),
|
||||
Payload: reply[2].(string),
|
||||
}, nil
|
||||
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
|
||||
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(cmd string, channels ...string) (chan *Message, error) {
|
||||
args := append([]string{cmd}, channels...)
|
||||
req := NewIfaceSliceReq(args...)
|
||||
|
||||
conn, err := c.conn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := c.WriteReq(conn, req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.once.Do(func() {
|
||||
go c.consumeMessages(conn)
|
||||
})
|
||||
|
||||
return c.ch, nil
|
||||
}
|
||||
|
||||
func (c *PubSubClient) Subscribe(channels ...string) (chan *Message, error) {
|
||||
return c.subscribe("SUBSCRIBE", channels...)
|
||||
}
|
||||
|
||||
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()
|
||||
func (c *PubSub) subscribe(cmd string, channels ...string) error {
|
||||
cn, err := c.conn()
|
||||
if err != nil {
|
||||
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...)
|
||||
}
|
||||
|
||||
func (c *PubSubClient) PUnsubscribe(patterns ...string) error {
|
||||
func (c *PubSub) PUnsubscribe(patterns ...string) error {
|
||||
return c.unsubscribe("PUNSUBSCRIBE", patterns...)
|
||||
}
|
||||
|
|
261
redis.go
261
redis.go
|
@ -1,49 +1,55 @@
|
|||
package redis
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
// Package logger.
|
||||
var Logger = log.New(os.Stdout, "redis: ", log.Ldate|log.Ltime)
|
||||
type baseClient struct {
|
||||
connPool pool
|
||||
|
||||
type OpenConnFunc func() (net.Conn, error)
|
||||
type CloseConnFunc func(net.Conn) error
|
||||
type InitConnFunc func(*Client) error
|
||||
opt *Options
|
||||
|
||||
func TCPConnector(addr string) OpenConnFunc {
|
||||
return func() (net.Conn, error) {
|
||||
return net.DialTimeout("tcp", addr, 3*time.Second)
|
||||
}
|
||||
cmds []Cmder
|
||||
}
|
||||
|
||||
func TLSConnector(addr string, tlsConfig *tls.Config) OpenConnFunc {
|
||||
return func() (net.Conn, error) {
|
||||
conn, err := net.DialTimeout("tcp", addr, 3*time.Second)
|
||||
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
|
||||
}
|
||||
return tls.Client(conn, tlsConfig), nil
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
func UnixConnector(addr string) OpenConnFunc {
|
||||
return func() (net.Conn, error) {
|
||||
return net.DialTimeout("unix", addr, 3*time.Second)
|
||||
}
|
||||
return cn, nil
|
||||
}
|
||||
|
||||
func AuthSelectFunc(password string, db int64) InitConnFunc {
|
||||
if password == "" && db < 0 {
|
||||
return 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),
|
||||
},
|
||||
}
|
||||
|
||||
return func(client *Client) error {
|
||||
if password != "" {
|
||||
auth := client.Auth(password)
|
||||
if auth.Err() != nil {
|
||||
|
@ -51,7 +57,7 @@ func AuthSelectFunc(password string, db int64) InitConnFunc {
|
|||
}
|
||||
}
|
||||
|
||||
if db >= 0 {
|
||||
if db > 0 {
|
||||
sel := client.Select(db)
|
||||
if sel.Err() != nil {
|
||||
return sel.Err()
|
||||
|
@ -60,133 +66,126 @@ func AuthSelectFunc(password string, db int64) InitConnFunc {
|
|||
|
||||
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 BaseClient struct {
|
||||
ConnPool ConnPool
|
||||
InitConn InitConnFunc
|
||||
reqs []Req
|
||||
reqsMtx sync.Mutex
|
||||
type Options struct {
|
||||
Addr string
|
||||
Password string
|
||||
DB int64
|
||||
|
||||
PoolSize int
|
||||
|
||||
DialTimeout time.Duration
|
||||
ReadTimeout time.Duration
|
||||
WriteTimeout time.Duration
|
||||
IdleTimeout time.Duration
|
||||
}
|
||||
|
||||
func (c *BaseClient) WriteReq(conn *Conn, reqs ...Req) error {
|
||||
buf := make([]byte, 0, 1000)
|
||||
for _, req := range reqs {
|
||||
buf = appendReq(buf, req.Args())
|
||||
func (opt *Options) getPoolSize() int {
|
||||
if opt.PoolSize == 0 {
|
||||
return 10
|
||||
}
|
||||
return opt.PoolSize
|
||||
}
|
||||
|
||||
_, err := conn.RW.Write(buf)
|
||||
return err
|
||||
func (opt *Options) getDialTimeout() time.Duration {
|
||||
if opt.DialTimeout == 0 {
|
||||
return 5 * time.Second
|
||||
}
|
||||
|
||||
func (c *BaseClient) conn() (*Conn, error) {
|
||||
conn, isNew, err := c.ConnPool.Get()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if isNew && c.InitConn != nil {
|
||||
client := &Client{
|
||||
BaseClient: &BaseClient{
|
||||
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 conn, nil
|
||||
}
|
||||
|
||||
func (c *BaseClient) Process(req Req) {
|
||||
if c.reqs == nil {
|
||||
c.Run(req)
|
||||
} else {
|
||||
c.Queue(req)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *BaseClient) Run(req Req) {
|
||||
conn, err := c.conn()
|
||||
if err != nil {
|
||||
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) Queue(req Req) {
|
||||
c.reqsMtx.Lock()
|
||||
c.reqs = append(c.reqs, req)
|
||||
c.reqsMtx.Unlock()
|
||||
}
|
||||
|
||||
func (c *BaseClient) Close() error {
|
||||
return c.ConnPool.Close()
|
||||
return opt.DialTimeout
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
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{
|
||||
BaseClient: &BaseClient{
|
||||
ConnPool: NewMultiConnPool(openConn, closeConn, 10),
|
||||
InitConn: initConn,
|
||||
baseClient: &baseClient{
|
||||
opt: opt,
|
||||
|
||||
connPool: newConnPool(newConnFunc(dial), opt.getPoolSize(), opt.IdleTimeout),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func NewTCPClient(addr string, password string, db int64) *Client {
|
||||
return NewClient(TCPConnector(addr), nil, AuthSelectFunc(password, db))
|
||||
func NewTCPClient(opt *Options) *Client {
|
||||
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 {
|
||||
return NewClient(
|
||||
TLSConnector(addr, tlsConfig),
|
||||
nil,
|
||||
AuthSelectFunc(password, db),
|
||||
)
|
||||
func NewUnixClient(opt *Options) *Client {
|
||||
dial := func() (net.Conn, error) {
|
||||
return net.DialTimeout("unix", opt.Addr, opt.getDialTimeout())
|
||||
}
|
||||
|
||||
func NewUnixClient(addr string, password string, db int64) *Client {
|
||||
return NewClient(UnixConnector(addr), nil, AuthSelectFunc(password, db))
|
||||
return newClient(opt, dial)
|
||||
}
|
||||
|
|
1079
redis_test.go
1079
redis_test.go
File diff suppressed because it is too large
Load Diff
315
req.go
315
req.go
|
@ -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)
|
||||
}
|
93
req_test.go
93
req_test.go
|
@ -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"},
|
||||
)
|
||||
}
|
17
script.go
17
script.go
|
@ -7,6 +7,13 @@ import (
|
|||
"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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
func (s *Script) Exists(c *Client) *BoolSliceReq {
|
||||
func (s *Script) Exists(c scripter) *BoolSliceCmd {
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
if err := r.Err(); err != nil && strings.HasPrefix(err.Error(), "NOSCRIPT ") {
|
||||
return s.Eval(c, keys, args)
|
||||
|
|
50
v2/README.md
50
v2/README.md
|
@ -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()
|
1226
v2/commands.go
1226
v2/commands.go
File diff suppressed because it is too large
Load Diff
|
@ -1,4 +0,0 @@
|
|||
/*
|
||||
Package github.com/vmihailenco/redis/v2 implements a Redis client.
|
||||
*/
|
||||
package redis
|
|
@ -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
|
||||
}
|
134
v2/multi.go
134
v2/multi.go
|
@ -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
|
||||
}
|
276
v2/parser.go
276
v2/parser.go
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
122
v2/pubsub.go
122
v2/pubsub.go
|
@ -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...)
|
||||
}
|
191
v2/redis.go
191
v2/redis.go
|
@ -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)
|
||||
}
|
3136
v2/redis_test.go
3136
v2/redis_test.go
File diff suppressed because it is too large
Load Diff
52
v2/script.go
52
v2/script.go
|
@ -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
|
||||
}
|
Loading…
Reference in New Issue