mirror of https://github.com/go-redis/redis.git
Add experimental V2 client.
This commit is contained in:
parent
45e45f8422
commit
49c91385e0
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,133 @@
|
|||
/*
|
||||
Package github.com/vmihailenco/redis implements a Redis client.
|
||||
|
||||
Let's start with connecting to Redis using TCP:
|
||||
|
||||
password := "" // no password set
|
||||
db := int64(-1) // use default DB
|
||||
client := redis.NewTCPClient("localhost:6379", password, db)
|
||||
defer client.Close()
|
||||
|
||||
ping := client.Ping()
|
||||
fmt.Println(ping.Err(), ping.Val())
|
||||
// Output: <nil> PONG
|
||||
|
||||
or using Unix socket:
|
||||
|
||||
client := redis.NewUnixClient("/tmp/redis.sock", "", -1)
|
||||
defer client.Close()
|
||||
|
||||
ping := client.Ping()
|
||||
fmt.Println(ping.Err(), ping.Val())
|
||||
// Output: <nil> PONG
|
||||
|
||||
Then we can start sending commands:
|
||||
|
||||
set := client.Set("foo", "bar")
|
||||
fmt.Println(set.Err(), set.Val())
|
||||
|
||||
get := client.Get("foo")
|
||||
fmt.Println(get.Err(), get.Val())
|
||||
|
||||
// Output: <nil> OK
|
||||
// <nil> bar
|
||||
|
||||
We can also pipeline two commands together:
|
||||
|
||||
var set *redis.StatusReq
|
||||
var get *redis.StringReq
|
||||
reqs, err := client.Pipelined(func(c *redis.PipelineClient) {
|
||||
set = c.Set("key1", "hello1")
|
||||
get = c.Get("key2")
|
||||
})
|
||||
fmt.Println(err, reqs)
|
||||
fmt.Println(set)
|
||||
fmt.Println(get)
|
||||
// Output: <nil> [SET key1 hello1: OK GET key2: (nil)]
|
||||
// SET key1 hello1: OK
|
||||
// GET key2: (nil)
|
||||
|
||||
or:
|
||||
|
||||
var set *redis.StatusReq
|
||||
var get *redis.StringReq
|
||||
reqs, err := client.Pipelined(func(c *redis.PipelineClient) {
|
||||
set = c.Set("key1", "hello1")
|
||||
get = c.Get("key2")
|
||||
})
|
||||
fmt.Println(err, reqs)
|
||||
fmt.Println(set)
|
||||
fmt.Println(get)
|
||||
// Output: <nil> [SET key1 hello1 GET key2]
|
||||
// SET key1 hello1
|
||||
// GET key2
|
||||
|
||||
We can also send several commands in transaction:
|
||||
|
||||
func transaction(multi *redis.MultiClient) ([]redis.Req, error) {
|
||||
get := multi.Get("key")
|
||||
if err := get.Err(); err != nil && err != redis.Nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
val, _ := strconv.ParseInt(get.Val(), 10, 64)
|
||||
|
||||
reqs, err := multi.Exec(func() {
|
||||
multi.Set("key", strconv.FormatInt(val+1, 10))
|
||||
})
|
||||
// Transaction failed. Repeat.
|
||||
if err == redis.Nil {
|
||||
return transaction(multi)
|
||||
}
|
||||
return reqs, err
|
||||
}
|
||||
|
||||
multi, err := client.MultiClient()
|
||||
_ = err
|
||||
defer multi.Close()
|
||||
|
||||
watch := multi.Watch("key")
|
||||
_ = watch.Err()
|
||||
|
||||
reqs, err := transaction(multi)
|
||||
fmt.Println(err, reqs)
|
||||
|
||||
// Output: <nil> [SET key 1: OK]
|
||||
|
||||
To subscribe to the channel:
|
||||
|
||||
pubsub, err := client.PubSubClient()
|
||||
defer pubsub.Close()
|
||||
|
||||
ch, err := pubsub.Subscribe("mychannel")
|
||||
_ = err
|
||||
|
||||
subscribeMsg := <-ch
|
||||
fmt.Println(subscribeMsg.Err, subscribeMsg.Name)
|
||||
|
||||
pub := client.Publish("mychannel", "hello")
|
||||
_ = pub.Err()
|
||||
|
||||
msg := <-ch
|
||||
fmt.Println(msg.Err, msg.Message)
|
||||
|
||||
// Output: <nil> subscribe
|
||||
// <nil> hello
|
||||
|
||||
You can also write custom commands:
|
||||
|
||||
func Get(client *redis.Client, key string) *redis.StringReq {
|
||||
req := redis.NewStringReq("GET", key)
|
||||
client.Process(req)
|
||||
return req
|
||||
}
|
||||
|
||||
get := Get(client, "key_does_not_exist")
|
||||
fmt.Println(get.Err(), get.Val())
|
||||
// Output: (nil)
|
||||
|
||||
Client uses connection pool to send commands. You can change maximum number of connections with:
|
||||
|
||||
client.ConnPool.(*redis.MultiConnPool).MaxCap = 1
|
||||
*/
|
||||
package redis
|
|
@ -0,0 +1,135 @@
|
|||
package redis_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/vmihailenco/redis/v2"
|
||||
)
|
||||
|
||||
func ExampleTCPClient() {
|
||||
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
|
||||
}
|
||||
|
||||
func ExampleUnixClient() {
|
||||
client := redis.NewUnixClient("/tmp/redis.sock", "", -1)
|
||||
defer client.Close()
|
||||
|
||||
ping := client.Ping()
|
||||
fmt.Println(ping.Err(), ping.Val())
|
||||
// Output: <nil> PONG
|
||||
}
|
||||
|
||||
func ExampleSetGet() {
|
||||
client := redis.NewTCPClient(":6379", "", -1)
|
||||
defer client.Close()
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func ExamplePipeline() {
|
||||
client := redis.NewTCPClient(":6379", "", -1)
|
||||
defer client.Close()
|
||||
|
||||
var set *redis.StatusReq
|
||||
var get *redis.StringReq
|
||||
reqs, err := client.Pipelined(func(c *redis.PipelineClient) {
|
||||
set = c.Set("key1", "hello1")
|
||||
get = c.Get("key2")
|
||||
})
|
||||
fmt.Println(err, reqs)
|
||||
fmt.Println(set)
|
||||
fmt.Println(get)
|
||||
// Output: <nil> [SET key1 hello1: OK GET key2: (nil)]
|
||||
// SET key1 hello1: OK
|
||||
// GET key2: (nil)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func ExampleTransaction() {
|
||||
client := redis.NewTCPClient(":6379", "", -1)
|
||||
defer client.Close()
|
||||
|
||||
client.Del("key")
|
||||
|
||||
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]
|
||||
}
|
||||
|
||||
func ExamplePubSub() {
|
||||
client := redis.NewTCPClient(":6379", "", -1)
|
||||
defer client.Close()
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func Get(client *redis.Client, key string) *redis.StringReq {
|
||||
req := redis.NewStringReq("GET", key)
|
||||
client.Process(req)
|
||||
return req
|
||||
}
|
||||
|
||||
func ExampleCustomCommand() {
|
||||
client := redis.NewTCPClient(":6379", "", -1)
|
||||
defer client.Close()
|
||||
|
||||
get := Get(client, "key_does_not_exist")
|
||||
fmt.Println(get.Err(), get.Val())
|
||||
// Output: (nil)
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
package redis
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type MultiClient struct {
|
||||
*Client
|
||||
execMtx sync.Mutex
|
||||
}
|
||||
|
||||
func (c *Client) MultiClient() (*MultiClient, error) {
|
||||
return &MultiClient{
|
||||
Client: &Client{
|
||||
baseClient: &baseClient{
|
||||
connPool: newSingleConnPool(c.connPool, nil, true),
|
||||
|
||||
password: c.password,
|
||||
db: c.db,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *MultiClient) Close() error {
|
||||
c.Unwatch()
|
||||
return c.Client.Close()
|
||||
}
|
||||
|
||||
func (c *MultiClient) Watch(keys ...string) *StatusReq {
|
||||
args := append([]string{"WATCH"}, keys...)
|
||||
req := NewStatusReq(args...)
|
||||
c.Process(req)
|
||||
return req
|
||||
}
|
||||
|
||||
func (c *MultiClient) Unwatch(keys ...string) *StatusReq {
|
||||
args := append([]string{"UNWATCH"}, keys...)
|
||||
req := NewStatusReq(args...)
|
||||
c.Process(req)
|
||||
return req
|
||||
}
|
||||
|
||||
func (c *MultiClient) Discard() {
|
||||
c.reqsMtx.Lock()
|
||||
if c.reqs == nil {
|
||||
panic("Discard can be used only inside Exec")
|
||||
}
|
||||
c.reqs = c.reqs[:1]
|
||||
c.reqsMtx.Unlock()
|
||||
}
|
||||
|
||||
func (c *MultiClient) Exec(do func()) ([]Req, error) {
|
||||
c.reqsMtx.Lock()
|
||||
c.reqs = []Req{NewStatusReq("MULTI")}
|
||||
c.reqsMtx.Unlock()
|
||||
|
||||
do()
|
||||
|
||||
c.queue(NewIfaceSliceReq("EXEC"))
|
||||
|
||||
c.reqsMtx.Lock()
|
||||
reqs := c.reqs
|
||||
c.reqs = nil
|
||||
c.reqsMtx.Unlock()
|
||||
|
||||
if len(reqs) == 2 {
|
||||
return []Req{}, nil
|
||||
}
|
||||
|
||||
cn, err := c.conn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Synchronize writes and reads to the connection using mutex.
|
||||
c.execMtx.Lock()
|
||||
err = c.execReqs(reqs, cn)
|
||||
c.execMtx.Unlock()
|
||||
if err != nil {
|
||||
c.removeConn(cn)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.putConn(cn)
|
||||
return reqs[1 : len(reqs)-1], nil
|
||||
}
|
||||
|
||||
func (c *MultiClient) execReqs(reqs []Req, cn *conn) error {
|
||||
err := c.writeReq(cn, reqs...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
statusReq := NewStatusReq()
|
||||
|
||||
// Omit last request (EXEC).
|
||||
reqsLen := len(reqs) - 1
|
||||
|
||||
// Parse queued replies.
|
||||
for i := 0; i < reqsLen; i++ {
|
||||
_, err = statusReq.ParseReply(cn.Rd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Parse number of replies.
|
||||
line, err := readLine(cn.Rd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if line[0] != '*' {
|
||||
return fmt.Errorf("Expected '*', but got line %q", line)
|
||||
}
|
||||
if len(line) == 3 && line[1] == '-' && line[2] == '1' {
|
||||
return Nil
|
||||
}
|
||||
|
||||
// Parse replies.
|
||||
// Loop starts from 1 to omit first request (MULTI).
|
||||
for i := 1; i < reqsLen; i++ {
|
||||
req := reqs[i]
|
||||
val, err := req.ParseReply(cn.Rd)
|
||||
if err != nil {
|
||||
req.SetErr(err)
|
||||
} else {
|
||||
req.SetVal(val)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,304 @@
|
|||
package redis
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/vmihailenco/bufio"
|
||||
)
|
||||
|
||||
type replyType int
|
||||
|
||||
const (
|
||||
ifaceSlice replyType = iota
|
||||
stringSlice
|
||||
boolSlice
|
||||
stringStringMap
|
||||
stringFloatMap
|
||||
)
|
||||
|
||||
// Represents Redis nil reply.
|
||||
var Nil = errors.New("(nil)")
|
||||
|
||||
var (
|
||||
errReaderTooSmall = errors.New("redis: reader is too small")
|
||||
errValNotSet = errors.New("redis: value is not set")
|
||||
errInvalidReplyType = errors.New("redis: invalid reply type")
|
||||
)
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
type parserError struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (e *parserError) Error() string {
|
||||
return e.err.Error()
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
func appendReq(buf []byte, args []string) []byte {
|
||||
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) (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)
|
||||
if err != nil {
|
||||
return 0, &parserError{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 0, &parserError{err}
|
||||
}
|
||||
return v, nil
|
||||
case '$':
|
||||
if len(line) == 3 && line[1] == '-' && line[2] == '1' {
|
||||
return "", Nil
|
||||
}
|
||||
|
||||
replyLenInt32, err := strconv.ParseInt(string(line[1:]), 10, 32)
|
||||
if err != nil {
|
||||
return "", &parserError{err}
|
||||
}
|
||||
replyLen := int(replyLenInt32) + 2
|
||||
|
||||
line, err = readN(rd, replyLen)
|
||||
if err != nil {
|
||||
return "", &parserError{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, &parserError{err}
|
||||
}
|
||||
|
||||
switch typ {
|
||||
case stringSlice:
|
||||
vals := make([]string, 0, repliesNum)
|
||||
for i := int64(0); i < repliesNum; i++ {
|
||||
vi, err := parseReply(rd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if v, ok := vi.(string); ok {
|
||||
vals = append(vals, v)
|
||||
} else {
|
||||
return nil, errInvalidReplyType
|
||||
}
|
||||
}
|
||||
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, errInvalidReplyType
|
||||
}
|
||||
}
|
||||
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, errInvalidReplyType
|
||||
}
|
||||
|
||||
valueI, err := parseReply(rd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
value, ok := valueI.(string)
|
||||
if !ok {
|
||||
return nil, errInvalidReplyType
|
||||
}
|
||||
|
||||
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, errInvalidReplyType
|
||||
}
|
||||
|
||||
valueI, err := parseReply(rd)
|
||||
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, &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)}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package redis_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/vmihailenco/bufio"
|
||||
. "launchpad.net/gocheck"
|
||||
|
||||
"github.com/vmihailenco/redis/v2"
|
||||
)
|
||||
|
||||
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"})
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
package redis
|
||||
|
||||
type PipelineClient struct {
|
||||
*Client
|
||||
}
|
||||
|
||||
func (c *Client) PipelineClient() (*PipelineClient, error) {
|
||||
return &PipelineClient{
|
||||
Client: &Client{
|
||||
baseClient: &baseClient{
|
||||
connPool: c.connPool,
|
||||
|
||||
password: c.password,
|
||||
db: c.db,
|
||||
|
||||
reqs: make([]Req, 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 {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *PipelineClient) DiscardQueued() {
|
||||
c.reqsMtx.Lock()
|
||||
c.reqs = c.reqs[:0]
|
||||
c.reqsMtx.Unlock()
|
||||
}
|
||||
|
||||
func (c *PipelineClient) RunQueued() ([]Req, error) {
|
||||
c.reqsMtx.Lock()
|
||||
reqs := c.reqs
|
||||
c.reqs = make([]Req, 0)
|
||||
c.reqsMtx.Unlock()
|
||||
|
||||
if len(reqs) == 0 {
|
||||
return []Req{}, nil
|
||||
}
|
||||
|
||||
conn, err := c.conn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = c.runReqs(reqs, conn)
|
||||
if err != nil {
|
||||
c.removeConn(conn)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.putConn(conn)
|
||||
return reqs, nil
|
||||
}
|
||||
|
||||
func (c *PipelineClient) runReqs(reqs []Req, cn *conn) error {
|
||||
err := c.writeReq(cn, reqs...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reqsLen := len(reqs)
|
||||
for i := 0; i < reqsLen; i++ {
|
||||
req := reqs[i]
|
||||
val, err := req.ParseReply(cn.Rd)
|
||||
if err != nil {
|
||||
req.SetErr(err)
|
||||
} else {
|
||||
req.SetVal(val)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,257 @@
|
|||
package redis
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/vmihailenco/bufio"
|
||||
)
|
||||
|
||||
type pool interface {
|
||||
Get() (*conn, bool, error)
|
||||
Put(*conn) error
|
||||
Remove(*conn) error
|
||||
Len() int
|
||||
Close() error
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
type conn struct {
|
||||
Cn net.Conn
|
||||
Rd reader
|
||||
|
||||
UsedAt time.Time
|
||||
|
||||
readTimeout, writeTimeout time.Duration
|
||||
}
|
||||
|
||||
func newConn(netcn net.Conn, readTimeout, writeTimeout time.Duration) *conn {
|
||||
cn := &conn{
|
||||
Cn: netcn,
|
||||
|
||||
readTimeout: readTimeout,
|
||||
writeTimeout: writeTimeout,
|
||||
}
|
||||
cn.Rd = bufio.NewReaderSize(netcn, 1024)
|
||||
return cn
|
||||
}
|
||||
|
||||
func (cn *conn) Read(b []byte) (int, error) {
|
||||
if cn.readTimeout > 0 {
|
||||
cn.Cn.SetReadDeadline(time.Now().Add(cn.readTimeout))
|
||||
}
|
||||
return cn.Cn.Read(b)
|
||||
}
|
||||
|
||||
func (cn *conn) Write(b []byte) (int, error) {
|
||||
if cn.writeTimeout > 0 {
|
||||
cn.Cn.SetWriteDeadline(time.Now().Add(cn.writeTimeout))
|
||||
}
|
||||
return cn.Cn.Write(b)
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
type connPool struct {
|
||||
dial func() (net.Conn, error)
|
||||
close func(net.Conn) error
|
||||
|
||||
cond *sync.Cond
|
||||
conns *list.List
|
||||
|
||||
readTimeout, writeTimeout time.Duration
|
||||
|
||||
size, maxSize int
|
||||
idleTimeout time.Duration
|
||||
}
|
||||
|
||||
func newConnPool(
|
||||
dial func() (net.Conn, error),
|
||||
close func(net.Conn) error,
|
||||
maxSize int,
|
||||
readTimeout, writeTimeout, idleTimeout time.Duration,
|
||||
) *connPool {
|
||||
return &connPool{
|
||||
dial: dial,
|
||||
close: close,
|
||||
|
||||
cond: sync.NewCond(&sync.Mutex{}),
|
||||
conns: list.New(),
|
||||
|
||||
maxSize: maxSize,
|
||||
|
||||
readTimeout: readTimeout,
|
||||
writeTimeout: writeTimeout,
|
||||
idleTimeout: idleTimeout,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *connPool) Get() (*conn, bool, error) {
|
||||
defer p.cond.L.Unlock()
|
||||
p.cond.L.Lock()
|
||||
|
||||
for p.conns.Len() == 0 && p.size >= p.maxSize {
|
||||
p.cond.Wait()
|
||||
}
|
||||
|
||||
if p.idleTimeout > 0 {
|
||||
for e := p.conns.Front(); e != nil; e = e.Next() {
|
||||
cn := e.Value.(*conn)
|
||||
if time.Since(cn.UsedAt) > p.idleTimeout {
|
||||
p.conns.Remove(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if p.conns.Len() == 0 {
|
||||
rw, err := p.dial()
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
p.size++
|
||||
return newConn(rw, p.readTimeout, p.writeTimeout), true, nil
|
||||
}
|
||||
|
||||
elem := p.conns.Front()
|
||||
p.conns.Remove(elem)
|
||||
return elem.Value.(*conn), false, nil
|
||||
}
|
||||
|
||||
func (p *connPool) Put(cn *conn) error {
|
||||
p.cond.L.Lock()
|
||||
cn.UsedAt = time.Now()
|
||||
p.conns.PushFront(cn)
|
||||
p.cond.Signal()
|
||||
p.cond.L.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *connPool) Remove(cn *conn) error {
|
||||
var err error
|
||||
if cn != nil {
|
||||
err = p.closeConn(cn)
|
||||
}
|
||||
p.cond.L.Lock()
|
||||
p.size--
|
||||
p.cond.Signal()
|
||||
p.cond.L.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *connPool) Len() int {
|
||||
return p.conns.Len()
|
||||
}
|
||||
|
||||
func (p *connPool) Size() int {
|
||||
return p.size
|
||||
}
|
||||
|
||||
func (p *connPool) Close() error {
|
||||
defer p.cond.L.Unlock()
|
||||
p.cond.L.Lock()
|
||||
|
||||
for e := p.conns.Front(); e != nil; e = e.Next() {
|
||||
if err := p.closeConn(e.Value.(*conn)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
p.conns.Init()
|
||||
p.size = 0
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *connPool) closeConn(cn *conn) error {
|
||||
if p.close != nil {
|
||||
return p.close(cn.Cn)
|
||||
} else {
|
||||
return cn.Cn.Close()
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
type singleConnPool struct {
|
||||
pool pool
|
||||
|
||||
l sync.RWMutex
|
||||
cn *conn
|
||||
reusable bool
|
||||
}
|
||||
|
||||
func newSingleConnPool(pool pool, cn *conn, reusable bool) *singleConnPool {
|
||||
return &singleConnPool{
|
||||
pool: pool,
|
||||
cn: cn,
|
||||
reusable: reusable,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *singleConnPool) Get() (*conn, bool, error) {
|
||||
p.l.RLock()
|
||||
if p.cn != nil {
|
||||
p.l.RUnlock()
|
||||
return p.cn, false, nil
|
||||
}
|
||||
p.l.RUnlock()
|
||||
|
||||
defer p.l.Unlock()
|
||||
p.l.Lock()
|
||||
|
||||
cn, isNew, err := p.pool.Get()
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
p.cn = cn
|
||||
|
||||
return cn, isNew, nil
|
||||
}
|
||||
|
||||
func (p *singleConnPool) Put(cn *conn) error {
|
||||
defer p.l.Unlock()
|
||||
p.l.Lock()
|
||||
if p.cn != cn {
|
||||
panic("p.cn != cn")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *singleConnPool) Remove(cn *conn) error {
|
||||
defer p.l.Unlock()
|
||||
p.l.Lock()
|
||||
if p.cn != cn {
|
||||
panic("p.cn != cn")
|
||||
}
|
||||
p.cn = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *singleConnPool) Len() int {
|
||||
defer p.l.Unlock()
|
||||
p.l.Lock()
|
||||
if p.cn == nil {
|
||||
return 0
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
func (p *singleConnPool) Close() error {
|
||||
defer p.l.Unlock()
|
||||
p.l.Lock()
|
||||
|
||||
var err error
|
||||
if p.cn != nil {
|
||||
if p.reusable {
|
||||
err = p.pool.Put(p.cn)
|
||||
} else {
|
||||
err = p.pool.Remove(p.cn)
|
||||
}
|
||||
}
|
||||
p.cn = nil
|
||||
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
package redis
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type PubSubClient struct {
|
||||
*baseClient
|
||||
|
||||
ch chan *Message
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
func (c *Client) PubSubClient() (*PubSubClient, error) {
|
||||
return &PubSubClient{
|
||||
baseClient: &baseClient{
|
||||
connPool: newSingleConnPool(c.connPool, nil, false),
|
||||
|
||||
password: c.password,
|
||||
db: c.db,
|
||||
},
|
||||
|
||||
ch: make(chan *Message),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Client) Publish(channel, message string) *IntReq {
|
||||
req := NewIntReq("PUBLISH", channel, message)
|
||||
c.Process(req)
|
||||
return req
|
||||
}
|
||||
|
||||
type Message struct {
|
||||
Name, Channel, ChannelPattern, Message string
|
||||
Number int64
|
||||
|
||||
Err error
|
||||
}
|
||||
|
||||
func (c *PubSubClient) consumeMessages(cn *conn) {
|
||||
req := NewIfaceSliceReq()
|
||||
|
||||
for {
|
||||
msg := &Message{}
|
||||
|
||||
replyIface, err := req.ParseReply(cn.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("redis: unsupported message name: %q", msgName)
|
||||
}
|
||||
c.ch <- msg
|
||||
}
|
||||
}
|
||||
|
||||
func (c *PubSubClient) subscribe(cmd string, channels ...string) (chan *Message, error) {
|
||||
args := append([]string{cmd}, channels...)
|
||||
req := NewIfaceSliceReq(args...)
|
||||
|
||||
cn, err := c.conn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := c.writeReq(cn, req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.once.Do(func() {
|
||||
go c.consumeMessages(cn)
|
||||
})
|
||||
|
||||
return c.ch, nil
|
||||
}
|
||||
|
||||
func (c *PubSubClient) Subscribe(channels ...string) (chan *Message, error) {
|
||||
return c.subscribe("SUBSCRIBE", channels...)
|
||||
}
|
||||
|
||||
func (c *PubSubClient) PSubscribe(patterns ...string) (chan *Message, error) {
|
||||
return c.subscribe("PSUBSCRIBE", patterns...)
|
||||
}
|
||||
|
||||
func (c *PubSubClient) unsubscribe(cmd string, channels ...string) error {
|
||||
args := append([]string{cmd}, channels...)
|
||||
req := NewIfaceSliceReq(args...)
|
||||
|
||||
cn, err := c.conn()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.writeReq(cn, req)
|
||||
}
|
||||
|
||||
func (c *PubSubClient) Unsubscribe(channels ...string) error {
|
||||
return c.unsubscribe("UNSUBSCRIBE", channels...)
|
||||
}
|
||||
|
||||
func (c *PubSubClient) PUnsubscribe(patterns ...string) error {
|
||||
return c.unsubscribe("PUNSUBSCRIBE", patterns...)
|
||||
}
|
|
@ -0,0 +1,226 @@
|
|||
package redis
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Package logger.
|
||||
var Logger = log.New(os.Stdout, "redis: ", log.Ldate|log.Ltime)
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
type baseClient struct {
|
||||
connPool pool
|
||||
|
||||
password string
|
||||
db int64
|
||||
|
||||
reqs []Req
|
||||
reqsMtx sync.Mutex
|
||||
}
|
||||
|
||||
func (c *baseClient) writeReq(cn *conn, reqs ...Req) error {
|
||||
buf := make([]byte, 0, 1000)
|
||||
for _, req := range reqs {
|
||||
buf = appendReq(buf, req.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.password != "" || c.db > 0) {
|
||||
if err = c.init(cn, c.password, c.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{
|
||||
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) removeConn(cn *conn) {
|
||||
if err := c.connPool.Remove(cn); err != nil {
|
||||
Logger.Printf("connPool.Remove error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *baseClient) putConn(cn *conn) {
|
||||
if err := c.connPool.Put(cn); err != nil {
|
||||
Logger.Printf("connPool.Add error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *baseClient) Process(req Req) {
|
||||
if c.reqs == nil {
|
||||
c.run(req)
|
||||
} else {
|
||||
c.queue(req)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *baseClient) run(req Req) {
|
||||
cn, err := c.conn()
|
||||
if err != nil {
|
||||
req.SetErr(err)
|
||||
return
|
||||
}
|
||||
|
||||
err = c.writeReq(cn, req)
|
||||
if err != nil {
|
||||
c.removeConn(cn)
|
||||
req.SetErr(err)
|
||||
return
|
||||
}
|
||||
|
||||
val, err := req.ParseReply(cn.Rd)
|
||||
if err != nil {
|
||||
if _, ok := err.(*parserError); ok {
|
||||
c.removeConn(cn)
|
||||
} else {
|
||||
c.putConn(cn)
|
||||
}
|
||||
req.SetErr(err)
|
||||
return
|
||||
}
|
||||
|
||||
c.putConn(cn)
|
||||
req.SetVal(val)
|
||||
}
|
||||
|
||||
// Queues request to be executed later.
|
||||
func (c *baseClient) queue(req Req) {
|
||||
c.reqsMtx.Lock()
|
||||
c.reqs = append(c.reqs, req)
|
||||
c.reqsMtx.Unlock()
|
||||
}
|
||||
|
||||
func (c *baseClient) Close() error {
|
||||
return c.connPool.Close()
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
type ClientFactory struct {
|
||||
Dial func() (net.Conn, error)
|
||||
Close func(net.Conn) error
|
||||
|
||||
Password string
|
||||
DB int64
|
||||
|
||||
PoolSize int
|
||||
|
||||
ReadTimeout, WriteTimeout, IdleTimeout time.Duration
|
||||
}
|
||||
|
||||
func (f *ClientFactory) New() *Client {
|
||||
return &Client{
|
||||
baseClient: &baseClient{
|
||||
password: f.Password,
|
||||
db: f.DB,
|
||||
|
||||
connPool: newConnPool(
|
||||
f.Dial, f.getClose(), f.getPoolSize(),
|
||||
f.ReadTimeout, f.WriteTimeout, f.IdleTimeout,
|
||||
),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (f *ClientFactory) getClose() func(net.Conn) error {
|
||||
if f.Close == nil {
|
||||
return func(conn net.Conn) error {
|
||||
return conn.Close()
|
||||
}
|
||||
}
|
||||
return f.Close
|
||||
}
|
||||
|
||||
func (f *ClientFactory) getPoolSize() int {
|
||||
if f.PoolSize == 0 {
|
||||
return 10
|
||||
}
|
||||
return f.PoolSize
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
type Client struct {
|
||||
*baseClient
|
||||
}
|
||||
|
||||
func NewTCPClient(addr string, password string, db int64) *Client {
|
||||
dial := func() (net.Conn, error) {
|
||||
return net.DialTimeout("tcp", addr, 3*time.Second)
|
||||
}
|
||||
return (&ClientFactory{
|
||||
Dial: dial,
|
||||
|
||||
Password: password,
|
||||
DB: db,
|
||||
}).New()
|
||||
}
|
||||
|
||||
func NewTLSClient(addr string, tlsConfig *tls.Config, password string, db int64) *Client {
|
||||
dial := 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
|
||||
}
|
||||
return (&ClientFactory{
|
||||
Dial: dial,
|
||||
|
||||
Password: password,
|
||||
DB: db,
|
||||
}).New()
|
||||
}
|
||||
|
||||
func NewUnixClient(addr string, password string, db int64) *Client {
|
||||
dial := func() (net.Conn, error) {
|
||||
return net.DialTimeout("unix", addr, 3*time.Second)
|
||||
}
|
||||
return (&ClientFactory{
|
||||
Dial: dial,
|
||||
|
||||
Password: password,
|
||||
DB: db,
|
||||
}).New()
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,315 @@
|
|||
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)
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
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"},
|
||||
)
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
package redis
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
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 *Client) *StringReq {
|
||||
return c.ScriptLoad(s.src)
|
||||
}
|
||||
|
||||
func (s *Script) Exists(c *Client) *BoolSliceReq {
|
||||
return c.ScriptExists(s.src)
|
||||
}
|
||||
|
||||
func (s *Script) Eval(c *Client, keys []string, args []string) *IfaceReq {
|
||||
return c.Eval(s.src, keys, args)
|
||||
}
|
||||
|
||||
func (s *Script) EvalSha(c *Client, keys []string, args []string) *IfaceReq {
|
||||
return c.EvalSha(s.hash, keys, args)
|
||||
}
|
||||
|
||||
func (s *Script) Run(c *Client, keys []string, args []string) *IfaceReq {
|
||||
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