diff --git a/commands.go b/commands.go index 508290e3..03ad9eff 100644 --- a/commands.go +++ b/commands.go @@ -6,6 +6,16 @@ import ( //------------------------------------------------------------------------------ +type Limit struct { + Offset, Count int64 +} + +func NewLimit(offset, count int64) *Limit { + return &Limit{offset, count} +} + +//------------------------------------------------------------------------------ + func (c *Client) Auth(password string) *StatusReq { req := NewStatusReq("AUTH", password) c.Run(req) @@ -641,6 +651,207 @@ func (c *Client) SUnionStore(destination string, keys ...string) *IntReq { //------------------------------------------------------------------------------ +type ZMember struct { + Score float64 + Member string +} + +func NewZMember(score float64, member string) *ZMember { + return &ZMember{score, member} +} + +func (m *ZMember) ScoreString() string { + return strconv.FormatFloat(m.Score, 'f', -1, 32) +} + +func (c *Client) ZAdd(key string, members ...*ZMember) *IntReq { + args := []string{"ZADD", key} + for _, m := range members { + args = append(args, m.ScoreString(), m.Member) + } + req := NewIntReq(args...) + c.Run(req) + return req +} + +func (c *Client) ZCard(key string) *IntReq { + req := NewIntReq("ZCARD", key) + c.Run(req) + return req +} + +func (c *Client) ZCount(key, min, max string) *IntReq { + req := NewIntReq("ZCOUNT", key, min, max) + c.Run(req) + return req +} + +func (c *Client) ZIncrBy(key string, increment int64, member string) *IntReq { + req := NewIntReq("ZINCRBY", key, strconv.FormatInt(increment, 10), member) + c.Run(req) + return req +} + +func (c *Client) ZInterStore( + destination string, + numkeys int64, + keys []string, + weights []int64, + aggregate string, +) *IntReq { + args := []string{"ZINTERSTORE", destination, strconv.FormatInt(numkeys, 10)} + args = append(args, keys...) + if weights != nil { + args = append(args, "WEIGHTS") + for _, w := range weights { + args = append(args, strconv.FormatInt(w, 10)) + } + } + if aggregate != "" { + args = append(args, "AGGREGATE", aggregate) + } + req := NewIntReq(args...) + c.Run(req) + return req +} + +func (c *Client) ZRange(key string, start, stop int64, withScores bool) *MultiBulkReq { + args := []string{ + "ZRANGE", + key, + strconv.FormatInt(start, 10), + strconv.FormatInt(stop, 10), + } + if withScores { + args = append(args, "WITHSCORES") + } + req := NewMultiBulkReq(args...) + c.Run(req) + return req +} + +func (c *Client) ZRangeByScore( + key string, + min, max string, + withScores bool, + limit *Limit, +) *MultiBulkReq { + args := []string{"ZRANGEBYSCORE", key, min, max} + if withScores { + args = append(args, "WITHSCORES") + } + if limit != nil { + args = append( + args, + "LIMIT", + strconv.FormatInt(limit.Offset, 10), + strconv.FormatInt(limit.Count, 10), + ) + } + req := NewMultiBulkReq(args...) + c.Run(req) + return req +} + +func (c *Client) ZRank(key, member string) *IntNilReq { + req := NewIntNilReq("ZRANK", key, member) + c.Run(req) + return req +} + +func (c *Client) ZRem(key string, members ...string) *IntReq { + args := append([]string{"ZREM", key}, members...) + req := NewIntReq(args...) + c.Run(req) + return req +} + +func (c *Client) ZRemRangeByRank(key string, start, stop int64) *IntReq { + req := NewIntReq( + "ZREMRANGEBYRANK", + key, + strconv.FormatInt(start, 10), + strconv.FormatInt(stop, 10), + ) + c.Run(req) + return req +} + +func (c *Client) ZRemRangeByScore(key, min, max string) *IntReq { + req := NewIntReq("ZREMRANGEBYSCORE", key, min, max) + c.Run(req) + return req +} + +func (c *Client) ZRevRange(key, start, stop string, withScores bool) *MultiBulkReq { + args := []string{"ZREVRANGE", key, start, stop} + if withScores { + args = append(args, "WITHSCORES") + } + req := NewMultiBulkReq(args...) + c.Run(req) + return req +} + +func (c *Client) ZRevRangeByScore( + key, start, stop string, + withScores bool, + limit *Limit, +) *MultiBulkReq { + args := []string{"ZREVRANGEBYSCORE", key, start, stop} + if withScores { + args = append(args, "WITHSCORES") + } + if limit != nil { + args = append( + args, + "LIMIT", + strconv.FormatInt(limit.Offset, 10), + strconv.FormatInt(limit.Count, 10), + ) + } + req := NewMultiBulkReq(args...) + c.Run(req) + return req +} + +func (c *Client) ZRevRank(key, member string) *IntNilReq { + req := NewIntNilReq("ZREVRANK", key, member) + c.Run(req) + return req +} + +func (c *Client) ZScore(key, member string) *FloatReq { + req := NewFloatReq("ZSCORE", key, member) + c.Run(req) + return req +} + +func (c *Client) ZUnionStore( + destination string, + numkeys int64, + keys []string, + weights []int64, + aggregate string, +) *IntReq { + args := []string{"ZUNIONSTORE", destination, strconv.FormatInt(numkeys, 10)} + args = append(args, keys...) + if weights != nil { + args = append(args, "WEIGHTS") + for _, w := range weights { + args = append(args, strconv.FormatInt(w, 10)) + } + } + if aggregate != "" { + args = append(args, "AGGREGATE", aggregate) + } + req := NewIntReq(args...) + c.Run(req) + return req +} + +//------------------------------------------------------------------------------ + func (c *Client) PubSubClient() *PubSubClient { return NewPubSubClient(c.connect, c.disconnect) } diff --git a/redis_test.go b/redis_test.go index 1ed4af4f..5f6cc1fb 100644 --- a/redis_test.go +++ b/redis_test.go @@ -108,9 +108,7 @@ func (t *RedisTest) TestKeys(c *C) { keys, err := t.redisC.Keys("*").Reply() c.Check(err, IsNil) - c.Check(keys, HasLen, 2) - c.Check(keys[0], Equals, "foo1") - c.Check(keys[1], Equals, "foo2") + c.Check(keys, DeepEquals, []interface{}{"foo1", "foo2"}) } func (t *RedisTest) TestMigrate(c *C) { @@ -450,7 +448,6 @@ func (t *RedisTest) TestHGetAll(c *C) { values, err := t.redisC.HGetAll("myhash").Reply() c.Check(err, IsNil) - c.Check(values, HasLen, 4) c.Check(values, DeepEquals, []interface{}{"foo1", "bar1", "foo2", "bar2"}) } @@ -478,10 +475,9 @@ func (t *RedisTest) TestHKeys(c *C) { t.redisC.HSet("myhash", "foo1", "bar1").Reply() t.redisC.HSet("myhash", "foo2", "bar2").Reply() - values, err := t.redisC.HKeys("myhash").Reply() + keys, err := t.redisC.HKeys("myhash").Reply() c.Check(err, IsNil) - c.Check(values, HasLen, 2) - c.Check(values, DeepEquals, []interface{}{"foo1", "foo2"}) + c.Check(keys, DeepEquals, []interface{}{"foo1", "foo2"}) } func (t *RedisTest) TestHLen(c *C) { @@ -499,7 +495,6 @@ func (t *RedisTest) TestHMGet(c *C) { values, err := t.redisC.HMGet("myhash", "foo1", "foo2", "_").Reply() c.Check(err, IsNil) - c.Check(values, HasLen, 3) c.Check(values, DeepEquals, []interface{}{"bar1", "bar2", nil}) } @@ -545,10 +540,9 @@ func (t *RedisTest) TestHVals(c *C) { t.redisC.HSet("myhash", "foo1", "bar1").Reply() t.redisC.HSet("myhash", "foo2", "bar2").Reply() - values, err := t.redisC.HVals("myhash").Reply() + vals, err := t.redisC.HVals("myhash").Reply() c.Check(err, IsNil) - c.Check(values, HasLen, 2) - c.Check(values, DeepEquals, []interface{}{"bar1", "bar2"}) + c.Check(vals, DeepEquals, []interface{}{"bar1", "bar2"}) } //------------------------------------------------------------------------------ @@ -558,7 +552,6 @@ func (t *RedisTest) TestBLPop(c *C) { values, err := t.redisC.BLPop(0, "list1", "list2").Reply() c.Check(err, IsNil) - c.Check(values, HasLen, 2) c.Check(values, DeepEquals, []interface{}{"list1", "a"}) } @@ -567,7 +560,6 @@ func (t *RedisTest) TestBrPop(c *C) { values, err := t.redisC.BRPop(0, "list1", "list2").Reply() c.Check(err, IsNil) - c.Check(values, HasLen, 2) c.Check(values, DeepEquals, []interface{}{"list1", "c"}) } @@ -606,7 +598,6 @@ func (t *RedisTest) TestLInsert(c *C) { values, err := t.redisC.LRange("list", 0, -1).Reply() c.Check(err, IsNil) - c.Check(values, HasLen, 3) c.Check(values, DeepEquals, []interface{}{"Hello", "There", "World"}) } @@ -630,7 +621,6 @@ func (t *RedisTest) TestLPop(c *C) { values, err := t.redisC.LRange("list", 0, -1).Reply() c.Check(err, IsNil) - c.Check(values, HasLen, 2) c.Check(values, DeepEquals, []interface{}{"two", "three"}) } @@ -640,7 +630,6 @@ func (t *RedisTest) TestLPush(c *C) { values, err := t.redisC.LRange("list", 0, -1).Reply() c.Check(err, IsNil) - c.Check(values, HasLen, 2) c.Check(values, DeepEquals, []interface{}{"Hello", "World"}) } @@ -661,7 +650,7 @@ func (t *RedisTest) TestLPushX(c *C) { values, err = t.redisC.LRange("list2", 0, -1).Reply() c.Check(err, IsNil) - c.Check(values, HasLen, 0) + c.Check(values, DeepEquals, []interface{}{}) } func (t *RedisTest) TestLRange(c *C) { @@ -671,22 +660,19 @@ func (t *RedisTest) TestLRange(c *C) { values, err := t.redisC.LRange("list", 0, 0).Reply() c.Check(err, IsNil) - c.Check(values, HasLen, 1) c.Check(values, DeepEquals, []interface{}{"one"}) values, err = t.redisC.LRange("list", -3, 2).Reply() c.Check(err, IsNil) - c.Check(values, HasLen, 3) c.Check(values, DeepEquals, []interface{}{"one", "two", "three"}) values, err = t.redisC.LRange("list", -100, 100).Reply() c.Check(err, IsNil) - c.Check(values, HasLen, 3) c.Check(values, DeepEquals, []interface{}{"one", "two", "three"}) values, err = t.redisC.LRange("list", 5, 10).Reply() c.Check(err, IsNil) - c.Check(values, HasLen, 0) + c.Check(values, DeepEquals, []interface{}{}) } func (t *RedisTest) TestLRem(c *C) { @@ -701,7 +687,6 @@ func (t *RedisTest) TestLRem(c *C) { values, err := t.redisC.LRange("list", 0, -1).Reply() c.Check(err, IsNil) - c.Check(values, HasLen, 2) c.Check(values, DeepEquals, []interface{}{"hello", "foo"}) } @@ -720,7 +705,6 @@ func (t *RedisTest) TestLSet(c *C) { values, err := t.redisC.LRange("list", 0, -1).Reply() c.Check(err, IsNil) - c.Check(values, HasLen, 3) c.Check(values, DeepEquals, []interface{}{"four", "five", "three"}) } @@ -735,7 +719,6 @@ func (t *RedisTest) TestLTrim(c *C) { values, err := t.redisC.LRange("list", 0, -1).Reply() c.Check(err, IsNil) - c.Check(values, HasLen, 2) c.Check(values, DeepEquals, []interface{}{"two", "three"}) } @@ -750,7 +733,6 @@ func (t *RedisTest) TestRPop(c *C) { values, err := t.redisC.LRange("list", 0, -1).Reply() c.Check(err, IsNil) - c.Check(values, HasLen, 2) c.Check(values, DeepEquals, []interface{}{"one", "two"}) } @@ -765,12 +747,10 @@ func (t *RedisTest) TestRPopLPush(c *C) { values, err := t.redisC.LRange("list", 0, -1).Reply() c.Check(err, IsNil) - c.Check(values, HasLen, 2) c.Check(values, DeepEquals, []interface{}{"one", "two"}) values, err = t.redisC.LRange("list2", 0, -1).Reply() c.Check(err, IsNil) - c.Check(values, HasLen, 1) c.Check(values, DeepEquals, []interface{}{"three"}) } @@ -785,7 +765,6 @@ func (t *RedisTest) TestRPush(c *C) { values, err := t.redisC.LRange("list", 0, -1).Reply() c.Check(err, IsNil) - c.Check(values, HasLen, 2) c.Check(values, DeepEquals, []interface{}{"Hello", "World"}) } @@ -804,12 +783,11 @@ func (t *RedisTest) TestRPushX(c *C) { values, err := t.redisC.LRange("list", 0, -1).Reply() c.Check(err, IsNil) - c.Check(values, HasLen, 2) c.Check(values, DeepEquals, []interface{}{"Hello", "World"}) values, err = t.redisC.LRange("list2", 0, -1).Reply() c.Check(err, IsNil) - c.Check(values, HasLen, 0) + c.Check(values, DeepEquals, []interface{}{}) } //------------------------------------------------------------------------------ @@ -829,9 +807,7 @@ func (t *RedisTest) TestSAdd(c *C) { members, err := t.redisC.SMembers("set").Reply() c.Check(err, IsNil) - c.Check(members, HasLen, 2) - c.Check(members[0], Equals, "World") - c.Check(members[1], Equals, "Hello") + c.Check(members, DeepEquals, []interface{}{"World", "Hello"}) } func (t *RedisTest) TestSCard(c *C) { @@ -859,7 +835,6 @@ func (t *RedisTest) TestSDiff(c *C) { values, err := t.redisC.SDiff("set1", "set2").Reply() c.Check(err, IsNil) - c.Check(values, HasLen, 2) c.Check(values, DeepEquals, []interface{}{"a", "b"}) } @@ -878,7 +853,6 @@ func (t *RedisTest) TestSDiffStore(c *C) { values, err := t.redisC.SMembers("set").Reply() c.Check(err, IsNil) - c.Check(values, HasLen, 2) c.Check(values, DeepEquals, []interface{}{"a", "b"}) } @@ -893,7 +867,6 @@ func (t *RedisTest) TestSInter(c *C) { values, err := t.redisC.SInter("set1", "set2").Reply() c.Check(err, IsNil) - c.Check(values, HasLen, 1) c.Check(values, DeepEquals, []interface{}{"c"}) } @@ -912,7 +885,6 @@ func (t *RedisTest) TestSInterStore(c *C) { values, err := t.redisC.SMembers("set").Reply() c.Check(err, IsNil) - c.Check(values, HasLen, 1) c.Check(values, DeepEquals, []interface{}{"c"}) } @@ -934,7 +906,6 @@ func (t *RedisTest) TestSMembers(c *C) { values, err := t.redisC.SMembers("set").Reply() c.Check(err, IsNil) - c.Check(values, HasLen, 2) c.Check(values, DeepEquals, []interface{}{"World", "Hello"}) } @@ -950,12 +921,10 @@ func (t *RedisTest) TestSMove(c *C) { values, err := t.redisC.SMembers("set1").Reply() c.Check(err, IsNil) - c.Check(values, HasLen, 1) c.Check(values, DeepEquals, []interface{}{"one"}) values, err = t.redisC.SMembers("set2").Reply() c.Check(err, IsNil) - c.Check(values, HasLen, 2) c.Check(values, DeepEquals, []interface{}{"three", "two"}) } @@ -1002,7 +971,6 @@ func (t *RedisTest) TestSRem(c *C) { values, err := t.redisC.SMembers("set").Reply() c.Check(err, IsNil) - c.Check(values, HasLen, 2) c.Check(values, DeepEquals, []interface{}{"three", "two"}) } @@ -1040,6 +1008,263 @@ func (t *RedisTest) TestSUnionStore(c *C) { //------------------------------------------------------------------------------ +func (t *RedisTest) TestZAdd(c *C) { + n, err := t.redisC.ZAdd("zset", redis.NewZMember(1, "one")).Reply() + c.Check(err, IsNil) + c.Check(n, Equals, int64(1)) + + n, err = t.redisC.ZAdd("zset", redis.NewZMember(1, "uno")).Reply() + c.Check(err, IsNil) + c.Check(n, Equals, int64(1)) + + n, err = t.redisC.ZAdd("zset", redis.NewZMember(2, "two")).Reply() + c.Check(err, IsNil) + c.Check(n, Equals, int64(1)) + + n, err = t.redisC.ZAdd("zset", redis.NewZMember(3, "two")).Reply() + c.Check(err, IsNil) + c.Check(n, Equals, int64(0)) + + values, err := t.redisC.ZRange("zset", 0, -1, true).Reply() + c.Check(err, IsNil) + c.Check(values, DeepEquals, []interface{}{"one", "1", "uno", "1", "two", "3"}) +} + +func (t *RedisTest) TestZCard(c *C) { + t.redisC.ZAdd("zset", redis.NewZMember(1, "one")).Reply() + t.redisC.ZAdd("zset", redis.NewZMember(2, "two")).Reply() + + n, err := t.redisC.ZCard("zset").Reply() + c.Check(err, IsNil) + c.Check(n, Equals, int64(2)) +} + +func (t *RedisTest) TestZCount(c *C) { + t.redisC.ZAdd("zset", redis.NewZMember(1, "one")).Reply() + t.redisC.ZAdd("zset", redis.NewZMember(2, "two")).Reply() + t.redisC.ZAdd("zset", redis.NewZMember(3, "three")).Reply() + + n, err := t.redisC.ZCount("zset", "-inf", "+inf").Reply() + c.Check(err, IsNil) + c.Check(n, Equals, int64(3)) + + n, err = t.redisC.ZCount("zset", "(1", "3").Reply() + c.Check(err, IsNil) + c.Check(n, Equals, int64(2)) +} + +func (t *RedisTest) TestZIncrBy(c *C) { + t.redisC.ZAdd("zset", redis.NewZMember(1, "one")).Reply() + t.redisC.ZAdd("zset", redis.NewZMember(2, "two")).Reply() + t.redisC.ZIncrBy("zset", 2, "one").Reply() + + values, err := t.redisC.ZRange("zset", 0, -1, true).Reply() + c.Check(err, IsNil) + c.Check(values, DeepEquals, []interface{}{"two", "2", "one", "3"}) +} + +func (t *RedisTest) TestZInterStore(c *C) { + t.redisC.ZAdd("zset1", redis.NewZMember(1, "one")).Reply() + t.redisC.ZAdd("zset1", redis.NewZMember(2, "two")).Reply() + + t.redisC.ZAdd("zset2", redis.NewZMember(1, "one")).Reply() + t.redisC.ZAdd("zset2", redis.NewZMember(2, "two")).Reply() + t.redisC.ZAdd("zset3", redis.NewZMember(3, "two")).Reply() + + n, err := t.redisC.ZInterStore( + "out", + 2, + []string{"zset1", "zset2"}, + []int64{2, 3}, + "", + ).Reply() + c.Check(err, IsNil) + c.Check(n, Equals, int64(2)) + + values, err := t.redisC.ZRange("out", 0, -1, true).Reply() + c.Check(err, IsNil) + c.Check(values, DeepEquals, []interface{}{"one", "5", "two", "10"}) +} + +func (t *RedisTest) TestZRange(c *C) { + t.redisC.ZAdd("zset", redis.NewZMember(1, "one")).Reply() + t.redisC.ZAdd("zset", redis.NewZMember(2, "two")).Reply() + t.redisC.ZAdd("zset", redis.NewZMember(3, "three")).Reply() + + values, err := t.redisC.ZRange("zset", 0, -1, false).Reply() + c.Check(err, IsNil) + c.Check(values, DeepEquals, []interface{}{"one", "two", "three"}) + + values, err = t.redisC.ZRange("zset", 2, 3, false).Reply() + c.Check(err, IsNil) + c.Check(values, DeepEquals, []interface{}{"three"}) + + values, err = t.redisC.ZRange("zset", -2, -1, false).Reply() + c.Check(err, IsNil) + c.Check(values, DeepEquals, []interface{}{"two", "three"}) +} + +func (t *RedisTest) TestZRangeByScore(c *C) { + t.redisC.ZAdd("zset", redis.NewZMember(1, "one")).Reply() + t.redisC.ZAdd("zset", redis.NewZMember(2, "two")).Reply() + t.redisC.ZAdd("zset", redis.NewZMember(3, "three")).Reply() + + values, err := t.redisC.ZRangeByScore("zset", "-inf", "+inf", false, nil).Reply() + c.Check(err, IsNil) + c.Check(values, DeepEquals, []interface{}{"one", "two", "three"}) + + values, err = t.redisC.ZRangeByScore("zset", "1", "2", false, nil).Reply() + c.Check(err, IsNil) + c.Check(values, DeepEquals, []interface{}{"one", "two"}) + + values, err = t.redisC.ZRangeByScore("zset", "(1", "2", false, nil).Reply() + c.Check(err, IsNil) + c.Check(values, DeepEquals, []interface{}{"two"}) + + values, err = t.redisC.ZRangeByScore("zset", "(1", "(2", false, nil).Reply() + c.Check(err, IsNil) + c.Check(values, DeepEquals, []interface{}{}) +} + +func (t *RedisTest) TestZRank(c *C) { + t.redisC.ZAdd("zset", redis.NewZMember(1, "one")).Reply() + t.redisC.ZAdd("zset", redis.NewZMember(2, "two")).Reply() + t.redisC.ZAdd("zset", redis.NewZMember(3, "three")).Reply() + + n, err := t.redisC.ZRank("zset", "three").Reply() + c.Check(err, IsNil) + c.Check(n, Equals, int64(2)) + + n, err = t.redisC.ZRank("zset", "four").Reply() + c.Check(err, Equals, redis.Nil) + c.Check(n, Equals, int64(0)) +} + +func (t *RedisTest) TestZRem(c *C) { + t.redisC.ZAdd("zset", redis.NewZMember(1, "one")).Reply() + t.redisC.ZAdd("zset", redis.NewZMember(2, "two")).Reply() + t.redisC.ZAdd("zset", redis.NewZMember(3, "three")).Reply() + + n, err := t.redisC.ZRem("zset", "two").Reply() + c.Check(err, IsNil) + c.Check(n, Equals, int64(1)) + + values, err := t.redisC.ZRange("zset", 0, -1, true).Reply() + c.Check(err, IsNil) + c.Check(values, DeepEquals, []interface{}{"one", "1", "three", "3"}) +} + +func (t *RedisTest) TestZRemRangeByRank(c *C) { + t.redisC.ZAdd("zset", redis.NewZMember(1, "one")).Reply() + t.redisC.ZAdd("zset", redis.NewZMember(2, "two")).Reply() + t.redisC.ZAdd("zset", redis.NewZMember(3, "three")).Reply() + + n, err := t.redisC.ZRemRangeByRank("zset", 0, 1).Reply() + c.Check(err, IsNil) + c.Check(n, Equals, int64(2)) + + values, err := t.redisC.ZRange("zset", 0, -1, true).Reply() + c.Check(err, IsNil) + c.Check(values, DeepEquals, []interface{}{"three", "3"}) +} + +func (t *RedisTest) TestZRemRangeByScore(c *C) { + t.redisC.ZAdd("zset", redis.NewZMember(1, "one")).Reply() + t.redisC.ZAdd("zset", redis.NewZMember(2, "two")).Reply() + t.redisC.ZAdd("zset", redis.NewZMember(3, "three")).Reply() + + n, err := t.redisC.ZRemRangeByScore("zset", "-inf", "(2").Reply() + c.Check(err, IsNil) + c.Check(n, Equals, int64(1)) + + values, err := t.redisC.ZRange("zset", 0, -1, true).Reply() + c.Check(err, IsNil) + c.Check(values, DeepEquals, []interface{}{"two", "2", "three", "3"}) +} + +func (t *RedisTest) TestZRevRange(c *C) { + t.redisC.ZAdd("zset", redis.NewZMember(1, "one")).Reply() + t.redisC.ZAdd("zset", redis.NewZMember(2, "two")).Reply() + t.redisC.ZAdd("zset", redis.NewZMember(3, "three")).Reply() + + values, err := t.redisC.ZRevRange("zset", "0", "-1", false).Reply() + c.Check(err, IsNil) + c.Check(values, DeepEquals, []interface{}{"three", "two", "one"}) + + values, err = t.redisC.ZRevRange("zset", "2", "3", false).Reply() + c.Check(err, IsNil) + c.Check(values, DeepEquals, []interface{}{"one"}) + + values, err = t.redisC.ZRevRange("zset", "-2", "-1", false).Reply() + c.Check(err, IsNil) + c.Check(values, DeepEquals, []interface{}{"two", "one"}) +} + +func (t *RedisTest) TestZRevRangeByScore(c *C) { + t.redisC.ZAdd("zset", redis.NewZMember(1, "one")).Reply() + t.redisC.ZAdd("zset", redis.NewZMember(2, "two")).Reply() + t.redisC.ZAdd("zset", redis.NewZMember(3, "three")).Reply() + + values, err := t.redisC.ZRevRangeByScore("zset", "+inf", "-inf", false, nil).Reply() + c.Check(err, IsNil) + c.Check(values, DeepEquals, []interface{}{"three", "two", "one"}) + + values, err = t.redisC.ZRevRangeByScore("zset", "2", "(1", false, nil).Reply() + c.Check(err, IsNil) + c.Check(values, DeepEquals, []interface{}{"two"}) + + values, err = t.redisC.ZRevRangeByScore("zset", "(2", "(1", false, nil).Reply() + c.Check(err, IsNil) + c.Check(values, DeepEquals, []interface{}{}) +} + +func (t *RedisTest) TestZRevRank(c *C) { + t.redisC.ZAdd("zset", redis.NewZMember(1, "one")).Reply() + t.redisC.ZAdd("zset", redis.NewZMember(2, "two")).Reply() + t.redisC.ZAdd("zset", redis.NewZMember(3, "three")).Reply() + + n, err := t.redisC.ZRevRank("zset", "one").Reply() + c.Check(err, IsNil) + c.Check(n, Equals, int64(2)) + + n, err = t.redisC.ZRevRank("zset", "four").Reply() + c.Check(err, Equals, redis.Nil) + c.Check(n, Equals, int64(0)) +} + +func (t *RedisTest) TestZScore(c *C) { + t.redisC.ZAdd("zset", redis.NewZMember(1.001, "one")).Reply() + + score, err := t.redisC.ZScore("zset", "one").Reply() + c.Check(err, IsNil) + c.Check(score, Equals, float64(1.001)) +} + +func (t *RedisTest) TestZUnionStore(c *C) { + t.redisC.ZAdd("zset1", redis.NewZMember(1, "one")).Reply() + t.redisC.ZAdd("zset1", redis.NewZMember(2, "two")).Reply() + + t.redisC.ZAdd("zset2", redis.NewZMember(1, "one")).Reply() + t.redisC.ZAdd("zset2", redis.NewZMember(2, "two")).Reply() + t.redisC.ZAdd("zset2", redis.NewZMember(3, "three")).Reply() + + n, err := t.redisC.ZUnionStore( + "out", + 2, + []string{"zset1", "zset2"}, + []int64{2, 3}, + "", + ).Reply() + c.Check(err, IsNil) + c.Check(n, Equals, int64(3)) + + values, err := t.redisC.ZRange("out", 0, -1, true).Reply() + c.Check(err, IsNil) + c.Check(values, DeepEquals, []interface{}{"one", "5", "three", "9", "two", "10"}) +} + +//------------------------------------------------------------------------------ + func (t *RedisTest) TestPubSub(c *C) { pubsub := t.redisC.PubSubClient() diff --git a/request.go b/request.go index c9af41ea..d76858ca 100644 --- a/request.go +++ b/request.go @@ -13,6 +13,16 @@ var Nil = errors.New("(nil)") //------------------------------------------------------------------------------ +func isNil(buf []byte) bool { + return len(buf) == 3 && buf[0] == '$' && buf[1] == '-' && buf[2] == '1' +} + +func isEmpty(buf []byte) bool { + return len(buf) == 2 && buf[0] == '$' && buf[1] == '0' +} + +//------------------------------------------------------------------------------ + func ParseReq(rd *bufreader.Reader) ([]string, error) { line, err := rd.ReadLine('\n') if err != nil { @@ -173,6 +183,42 @@ func (r *IntReq) Reply() (int64, error) { //------------------------------------------------------------------------------ +type IntNilReq struct { + *BaseReq +} + +func NewIntNilReq(args ...string) *IntNilReq { + return &IntNilReq{ + BaseReq: NewBaseReq(args...), + } +} + +func (r *IntNilReq) ParseReply(rd *bufreader.Reader) (interface{}, error) { + line, err := rd.ReadLine('\n') + if err != nil { + return nil, err + } + + if line[0] == '-' { + return nil, errors.New(string(line[1:])) + } else if line[0] == ':' { + return strconv.ParseInt(string(line[1:]), 10, 64) + } else if isNil(line) { + return nil, Nil + } + + return nil, fmt.Errorf("Expected ':', but got %q of %q.", line, rd.Bytes()) +} + +func (r *IntNilReq) Reply() (int64, error) { + if r.err != nil { + return 0, r.err + } + return r.val.(int64), nil +} + +//------------------------------------------------------------------------------ + type BoolReq struct { *BaseReq } @@ -229,7 +275,7 @@ func (r *BulkReq) ParseReply(rd *bufreader.Reader) (interface{}, error) { return nil, fmt.Errorf("Expected '$', but got %q of %q.", line, rd.Bytes()) } - if len(line) >= 3 && line[1] == '-' && line[2] == '1' { + if isNil(line) { return nil, Nil } @@ -250,6 +296,49 @@ func (r *BulkReq) Reply() (string, error) { //------------------------------------------------------------------------------ +type FloatReq struct { + *BaseReq +} + +func NewFloatReq(args ...string) *FloatReq { + return &FloatReq{ + BaseReq: NewBaseReq(args...), + } +} + +func (r *FloatReq) ParseReply(rd *bufreader.Reader) (interface{}, error) { + line, err := rd.ReadLine('\n') + if err != nil { + return nil, err + } + + if line[0] == '-' { + return nil, errors.New(string(line[1:])) + } else if line[0] != '$' { + return nil, fmt.Errorf("Expected '$', but got %q of %q.", line, rd.Bytes()) + } + + if isNil(line) { + return nil, Nil + } + + line, err = rd.ReadLine('\n') + if err != nil { + return nil, err + } + + return strconv.ParseFloat(string(line), 64) +} + +func (r *FloatReq) Reply() (float64, error) { + if r.err != nil { + return 0, r.err + } + return r.val.(float64), nil +} + +//------------------------------------------------------------------------------ + type MultiBulkReq struct { *BaseReq } @@ -276,7 +365,7 @@ func (r *MultiBulkReq) ParseReply(rd *bufreader.Reader) (interface{}, error) { if len(line) >= 2 && line[1] == '0' { return val, nil - } else if len(line) >= 3 && line[1] == '-' && line[2] == '1' { + } else if isNil(line) { return nil, Nil } @@ -293,9 +382,9 @@ func (r *MultiBulkReq) ParseReply(rd *bufreader.Reader) (interface{}, error) { } val = append(val, n) } else if line[0] == '$' { - if len(line) >= 2 && line[1] == '0' { + if isEmpty(line) { val = append(val, "") - } else if len(line) >= 3 && line[1] == '-' && line[2] == '1' { + } else if isNil(line) { val = append(val, nil) } else { line, err = rd.ReadLine('\n')