mirror of https://github.com/go-redis/redis.git
Add support for scripting commands.
This commit is contained in:
parent
303687e438
commit
9ad848d04b
|
@ -242,7 +242,10 @@ Some corner cases:
|
||||||
client.ZRangeByScoreWithScores("zset", "-inf", "+inf", 0, 2)
|
client.ZRangeByScoreWithScores("zset", "-inf", "+inf", 0, 2)
|
||||||
|
|
||||||
ZINTERSTORE out 2 zset1 zset2 WEIGHTS 2 3 AGGREGATE SUM
|
ZINTERSTORE out 2 zset1 zset2 WEIGHTS 2 3 AGGREGATE SUM
|
||||||
client.ZInterStore("out", 2, redis.ZStore{Weights: []int64{2, 3}}, "zset1", "zset2")
|
client.ZInterStore("out", redis.ZStore{Weights: []int64{2, 3}}, "zset1", "zset2")
|
||||||
|
|
||||||
|
EVAL "return {KEYS[1],ARGV[1]}" 1 "key" "hello"
|
||||||
|
client.Eval("return {KEYS[1],ARGV[1]}", []string{"key"}, []string{"hello"})
|
||||||
|
|
||||||
Contributing
|
Contributing
|
||||||
------------
|
------------
|
||||||
|
|
51
commands.go
51
commands.go
|
@ -742,11 +742,10 @@ func (c *Client) ZIncrBy(key string, increment float64, member string) *FloatReq
|
||||||
|
|
||||||
func (c *Client) ZInterStore(
|
func (c *Client) ZInterStore(
|
||||||
destination string,
|
destination string,
|
||||||
numkeys int64,
|
|
||||||
store ZStore,
|
store ZStore,
|
||||||
keys ...string,
|
keys ...string,
|
||||||
) *IntReq {
|
) *IntReq {
|
||||||
args := []string{"ZINTERSTORE", destination, strconv.FormatInt(numkeys, 10)}
|
args := []string{"ZINTERSTORE", destination, strconv.FormatInt(int64(len(keys)), 10)}
|
||||||
args = append(args, keys...)
|
args = append(args, keys...)
|
||||||
if len(store.Weights) > 0 {
|
if len(store.Weights) > 0 {
|
||||||
args = append(args, "WEIGHTS")
|
args = append(args, "WEIGHTS")
|
||||||
|
@ -904,11 +903,10 @@ func (c *Client) ZScore(key, member string) *FloatReq {
|
||||||
|
|
||||||
func (c *Client) ZUnionStore(
|
func (c *Client) ZUnionStore(
|
||||||
destination string,
|
destination string,
|
||||||
numkeys int64,
|
|
||||||
store ZStore,
|
store ZStore,
|
||||||
keys ...string,
|
keys ...string,
|
||||||
) *IntReq {
|
) *IntReq {
|
||||||
args := []string{"ZUNIONSTORE", destination, strconv.FormatInt(numkeys, 10)}
|
args := []string{"ZUNIONSTORE", destination, strconv.FormatInt(int64(len(keys)), 10)}
|
||||||
args = append(args, keys...)
|
args = append(args, keys...)
|
||||||
if len(store.Weights) > 0 {
|
if len(store.Weights) > 0 {
|
||||||
args = append(args, "WEIGHTS")
|
args = append(args, "WEIGHTS")
|
||||||
|
@ -1031,3 +1029,48 @@ func (c *Client) Time() *StringSliceReq {
|
||||||
c.Process(req)
|
c.Process(req)
|
||||||
return req
|
return req
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func (c *Client) Eval(script string, keys []string, args []string) *IfaceReq {
|
||||||
|
reqArgs := []string{"EVAL", script, strconv.FormatInt(int64(len(keys)), 10)}
|
||||||
|
reqArgs = append(reqArgs, keys...)
|
||||||
|
reqArgs = append(reqArgs, args...)
|
||||||
|
req := NewIfaceReq(reqArgs...)
|
||||||
|
c.Process(req)
|
||||||
|
return req
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) EvalSha(sha1 string, keys []string, args []string) *IfaceReq {
|
||||||
|
reqArgs := []string{"EVALSHA", sha1, strconv.FormatInt(int64(len(keys)), 10)}
|
||||||
|
reqArgs = append(reqArgs, keys...)
|
||||||
|
reqArgs = append(reqArgs, args...)
|
||||||
|
req := NewIfaceReq(reqArgs...)
|
||||||
|
c.Process(req)
|
||||||
|
return req
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) ScriptExists(scripts ...string) *BoolSliceReq {
|
||||||
|
args := append([]string{"SCRIPT", "EXISTS"}, scripts...)
|
||||||
|
req := NewBoolSliceReq(args...)
|
||||||
|
c.Process(req)
|
||||||
|
return req
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) ScriptFlush() *StatusReq {
|
||||||
|
req := NewStatusReq("SCRIPT", "FLUSH")
|
||||||
|
c.Process(req)
|
||||||
|
return req
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) ScriptKill() *StatusReq {
|
||||||
|
req := NewStatusReq("SCRIPT", "KILL")
|
||||||
|
c.Process(req)
|
||||||
|
return req
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) ScriptLoad(script string) *StringReq {
|
||||||
|
req := NewStringReq("SCRIPT", "LOAD", script)
|
||||||
|
c.Process(req)
|
||||||
|
return req
|
||||||
|
}
|
||||||
|
|
72
parser.go
72
parser.go
|
@ -57,15 +57,25 @@ func readLine(rd reader) ([]byte, error) {
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
const (
|
||||||
|
ifaceSlice = iota
|
||||||
|
stringSlice
|
||||||
|
boolSlice
|
||||||
|
)
|
||||||
|
|
||||||
func parseReply(rd reader) (interface{}, error) {
|
func parseReply(rd reader) (interface{}, error) {
|
||||||
return _parseReply(rd, false)
|
return _parseReply(rd, ifaceSlice)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseIfaceSliceReply(rd reader) (interface{}, error) {
|
func parseStringSliceReply(rd reader) (interface{}, error) {
|
||||||
return _parseReply(rd, true)
|
return _parseReply(rd, stringSlice)
|
||||||
}
|
}
|
||||||
|
|
||||||
func _parseReply(rd reader, useIfaceSlice bool) (interface{}, error) {
|
func parseBoolSliceReply(rd reader) (interface{}, error) {
|
||||||
|
return _parseReply(rd, boolSlice)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _parseReply(rd reader, multiBulkType int) (interface{}, error) {
|
||||||
line, err := readLine(rd)
|
line, err := readLine(rd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
|
@ -113,7 +123,42 @@ func _parseReply(rd reader, useIfaceSlice bool) (interface{}, error) {
|
||||||
return nil, Nil
|
return nil, Nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if useIfaceSlice {
|
switch multiBulkType {
|
||||||
|
case stringSlice:
|
||||||
|
numReplies, err := strconv.ParseInt(string(line[1:]), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
vals := make([]string, 0, numReplies)
|
||||||
|
for i := int64(0); i < numReplies; i++ {
|
||||||
|
v, err := parseReply(rd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
vals = append(vals, v.(string))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return vals, nil
|
||||||
|
case boolSlice:
|
||||||
|
numReplies, err := strconv.ParseInt(string(line[1:]), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
vals := make([]bool, 0, numReplies)
|
||||||
|
for i := int64(0); i < numReplies; i++ {
|
||||||
|
v, err := parseReply(rd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
vals = append(vals, v.(int64) == 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return vals, nil
|
||||||
|
default:
|
||||||
numReplies, err := strconv.ParseInt(string(line[1:]), 10, 64)
|
numReplies, err := strconv.ParseInt(string(line[1:]), 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -131,23 +176,6 @@ func _parseReply(rd reader, useIfaceSlice bool) (interface{}, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return vals, nil
|
|
||||||
} else {
|
|
||||||
numReplies, err := strconv.ParseInt(string(line[1:]), 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
vals := make([]string, 0, numReplies)
|
|
||||||
for i := int64(0); i < numReplies; i++ {
|
|
||||||
v, err := parseReply(rd)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
vals = append(vals, v.(string))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return vals, nil
|
return vals, nil
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
|
2
redis.go
2
redis.go
|
@ -137,6 +137,7 @@ func (c *BaseClient) Close() error {
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
*BaseClient
|
*BaseClient
|
||||||
|
TryEvalShaMinLen int
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClient(openConn OpenConnFunc, closeConn CloseConnFunc, initConn InitConnFunc) *Client {
|
func NewClient(openConn OpenConnFunc, closeConn CloseConnFunc, initConn InitConnFunc) *Client {
|
||||||
|
@ -145,6 +146,7 @@ func NewClient(openConn OpenConnFunc, closeConn CloseConnFunc, initConn InitConn
|
||||||
ConnPool: NewMultiConnPool(openConn, closeConn, 10),
|
ConnPool: NewMultiConnPool(openConn, closeConn, 10),
|
||||||
InitConn: initConn,
|
InitConn: initConn,
|
||||||
},
|
},
|
||||||
|
TryEvalShaMinLen: 80,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ package redis_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
@ -1818,7 +1819,7 @@ func (t *RedisTest) TestZInterStore(c *C) {
|
||||||
c.Assert(zAdd.Err(), IsNil)
|
c.Assert(zAdd.Err(), IsNil)
|
||||||
|
|
||||||
zInterStore := t.client.ZInterStore(
|
zInterStore := t.client.ZInterStore(
|
||||||
"out", 2, redis.ZStore{Weights: []int64{2, 3}}, "zset1", "zset2")
|
"out", redis.ZStore{Weights: []int64{2, 3}}, "zset1", "zset2")
|
||||||
c.Assert(zInterStore.Err(), IsNil)
|
c.Assert(zInterStore.Err(), IsNil)
|
||||||
c.Assert(zInterStore.Val(), Equals, int64(2))
|
c.Assert(zInterStore.Val(), Equals, int64(2))
|
||||||
|
|
||||||
|
@ -2023,7 +2024,7 @@ func (t *RedisTest) TestZUnionStore(c *C) {
|
||||||
c.Assert(zAdd.Err(), IsNil)
|
c.Assert(zAdd.Err(), IsNil)
|
||||||
|
|
||||||
zUnionStore := t.client.ZUnionStore(
|
zUnionStore := t.client.ZUnionStore(
|
||||||
"out", 2, redis.ZStore{Weights: []int64{2, 3}}, "zset1", "zset2")
|
"out", redis.ZStore{Weights: []int64{2, 3}}, "zset1", "zset2")
|
||||||
c.Assert(zUnionStore.Err(), IsNil)
|
c.Assert(zUnionStore.Err(), IsNil)
|
||||||
c.Assert(zUnionStore.Val(), Equals, int64(3))
|
c.Assert(zUnionStore.Val(), Equals, int64(3))
|
||||||
|
|
||||||
|
@ -2599,6 +2600,88 @@ func (t *RedisTest) TestTime(c *C) {
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func (t *RedisTest) TestScriptingEval(c *C) {
|
||||||
|
eval := t.client.Eval(
|
||||||
|
"return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}",
|
||||||
|
[]string{"key1", "key2"},
|
||||||
|
[]string{"first", "second"},
|
||||||
|
)
|
||||||
|
c.Assert(eval.Err(), IsNil)
|
||||||
|
c.Assert(eval.Val(), DeepEquals, []interface{}{"key1", "key2", "first", "second"})
|
||||||
|
|
||||||
|
eval = t.client.Eval(
|
||||||
|
"return redis.call('set',KEYS[1],'bar')",
|
||||||
|
[]string{"foo"},
|
||||||
|
[]string{},
|
||||||
|
)
|
||||||
|
c.Assert(eval.Err(), IsNil)
|
||||||
|
c.Assert(eval.Val(), Equals, "OK")
|
||||||
|
|
||||||
|
eval = t.client.Eval("return 10", []string{}, []string{})
|
||||||
|
c.Assert(eval.Err(), IsNil)
|
||||||
|
c.Assert(eval.Val(), Equals, int64(10))
|
||||||
|
|
||||||
|
eval = t.client.Eval("return {1,2,{3,'Hello World!'}}", []string{}, []string{})
|
||||||
|
c.Assert(eval.Err(), IsNil)
|
||||||
|
// DeepEquals can't compare nested slices.
|
||||||
|
c.Assert(
|
||||||
|
fmt.Sprintf("%#v", eval.Val()),
|
||||||
|
Equals,
|
||||||
|
`[]interface {}{1, 2, []interface {}{3, "Hello World!"}}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *RedisTest) TestScriptingEvalSha(c *C) {
|
||||||
|
set := t.client.Set("foo", "bar")
|
||||||
|
c.Assert(set.Err(), IsNil)
|
||||||
|
c.Assert(set.Val(), Equals, "OK")
|
||||||
|
|
||||||
|
eval := t.client.Eval("return redis.call('get','foo')", []string{}, []string{})
|
||||||
|
c.Assert(eval.Err(), IsNil)
|
||||||
|
c.Assert(eval.Val(), Equals, "bar")
|
||||||
|
|
||||||
|
evalSha := t.client.EvalSha("6b1bf486c81ceb7edf3c093f4c48582e38c0e791", []string{}, []string{})
|
||||||
|
c.Assert(evalSha.Err(), IsNil)
|
||||||
|
c.Assert(evalSha.Val(), Equals, "bar")
|
||||||
|
|
||||||
|
evalSha = t.client.EvalSha("ffffffffffffffffffffffffffffffffffffffff", []string{}, []string{})
|
||||||
|
c.Assert(evalSha.Err(), ErrorMatches, "NOSCRIPT No matching script. Please use EVAL.")
|
||||||
|
c.Assert(evalSha.Val(), Equals, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *RedisTest) TestScriptingScriptExists(c *C) {
|
||||||
|
scriptLoad := t.client.ScriptLoad("return 1")
|
||||||
|
c.Assert(scriptLoad.Err(), IsNil)
|
||||||
|
c.Assert(scriptLoad.Val(), Equals, "e0e1f9fabfc9d4800c877a703b823ac0578ff8db")
|
||||||
|
|
||||||
|
scriptExists := t.client.ScriptExists(
|
||||||
|
"e0e1f9fabfc9d4800c877a703b823ac0578ff8db",
|
||||||
|
"ffffffffffffffffffffffffffffffffffffffff",
|
||||||
|
)
|
||||||
|
c.Assert(scriptExists.Err(), IsNil)
|
||||||
|
c.Assert(scriptExists.Val(), DeepEquals, []bool{true, false})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *RedisTest) TestScriptingScriptFlush(c *C) {
|
||||||
|
scriptFlush := t.client.ScriptFlush()
|
||||||
|
c.Assert(scriptFlush.Err(), IsNil)
|
||||||
|
c.Assert(scriptFlush.Val(), Equals, "OK")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *RedisTest) TestScriptingScriptKill(c *C) {
|
||||||
|
scriptKill := t.client.ScriptKill()
|
||||||
|
c.Assert(scriptKill.Err(), ErrorMatches, "ERR No scripts in execution right now.")
|
||||||
|
c.Assert(scriptKill.Val(), Equals, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *RedisTest) TestScriptingScriptLoad(c *C) {
|
||||||
|
scriptLoad := t.client.ScriptLoad("return redis.call('get','foo')")
|
||||||
|
c.Assert(scriptLoad.Err(), IsNil)
|
||||||
|
c.Assert(scriptLoad.Val(), Equals, "6b1bf486c81ceb7edf3c093f4c48582e38c0e791")
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
func (t *RedisTest) BenchmarkRedisPing(c *C) {
|
func (t *RedisTest) BenchmarkRedisPing(c *C) {
|
||||||
c.StopTimer()
|
c.StopTimer()
|
||||||
|
|
||||||
|
|
47
req.go
47
req.go
|
@ -66,6 +66,22 @@ func (r *BaseReq) ParseReply(rd reader) (interface{}, error) {
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
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 {
|
type StatusReq struct {
|
||||||
*BaseReq
|
*BaseReq
|
||||||
}
|
}
|
||||||
|
@ -187,10 +203,6 @@ func NewIfaceSliceReq(args ...string) *IfaceSliceReq {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *IfaceSliceReq) ParseReply(rd reader) (interface{}, error) {
|
|
||||||
return parseIfaceSliceReply(rd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *IfaceSliceReq) Val() []interface{} {
|
func (r *IfaceSliceReq) Val() []interface{} {
|
||||||
if r.val == nil {
|
if r.val == nil {
|
||||||
return nil
|
return nil
|
||||||
|
@ -210,9 +222,36 @@ func NewStringSliceReq(args ...string) *StringSliceReq {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *StringSliceReq) ParseReply(rd reader) (interface{}, error) {
|
||||||
|
return parseStringSliceReply(rd)
|
||||||
|
}
|
||||||
|
|
||||||
func (r *StringSliceReq) Val() []string {
|
func (r *StringSliceReq) Val() []string {
|
||||||
if r.val == nil {
|
if r.val == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return r.val.([]string)
|
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)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue