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.
|
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
|
||||||
|
|
189
example_test.go
189
example_test.go
|
@ -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
138
multi.go
|
@ -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
272
parser.go
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"})
|
|
||||||
}
|
|
108
pipeline.go
108
pipeline.go
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
162
pubsub.go
162
pubsub.go
|
@ -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
283
redis.go
|
@ -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))
|
|
||||||
}
|
}
|
||||||
|
|
1089
redis_test.go
1089
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"
|
"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)
|
||||||
|
|
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