diff --git a/commands.go b/commands.go index b350f9a..0375b2d 100644 --- a/commands.go +++ b/commands.go @@ -425,6 +425,12 @@ func (c *Client) HGetAll(key string) *StringSliceReq { return req } +func (c *Client) HGetAllMap(key string) *StringStringMapReq { + req := NewStringStringMapReq("HGETALL", key) + c.Process(req) + return req +} + func (c *Client) HIncrBy(key, field string, incr int64) *IntReq { req := NewIntReq("HINCRBY", key, field, strconv.FormatInt(incr, 10)) c.Process(req) @@ -784,6 +790,19 @@ func (c *Client) ZRangeWithScores(key string, start, stop int64) *StringSliceReq return c.zRange(key, start, stop, true) } +func (c *Client) ZRangeWithScoresMap(key string, start, stop int64) *StringFloatMapReq { + args := []string{ + "ZRANGE", + key, + strconv.FormatInt(start, 10), + strconv.FormatInt(stop, 10), + "WITHSCORES", + } + req := NewStringFloatMapReq(args...) + c.Process(req) + return req +} + func (c *Client) zRangeByScore( key string, min, max string, @@ -815,6 +834,22 @@ func (c *Client) ZRangeByScoreWithScores(key string, min, max string, offset, co return c.zRangeByScore(key, min, max, true, offset, count) } +func (c *Client) ZRangeByScoreWithScoresMap( + key string, min, max string, offset, count int64) *StringFloatMapReq { + args := []string{"ZRANGEBYSCORE", key, min, max, "WITHSCORES"} + if offset != 0 || count != 0 { + args = append( + args, + "LIMIT", + strconv.FormatInt(offset, 10), + strconv.FormatInt(count, 10), + ) + } + req := NewStringFloatMapReq(args...) + c.Process(req) + return req +} + func (c *Client) ZRank(key, member string) *IntReq { req := NewIntReq("ZRANK", key, member) c.Process(req) @@ -863,6 +898,13 @@ func (c *Client) ZRevRangeWithScores(key, start, stop string) *StringSliceReq { return c.zRevRange(key, start, stop, true) } +func (c *Client) ZRevRangeWithScoresMap(key, start, stop string) *StringFloatMapReq { + args := []string{"ZREVRANGE", key, start, stop, "WITHSCORES"} + req := NewStringFloatMapReq(args...) + c.Process(req) + return req +} + func (c *Client) zRevRangeByScore(key, start, stop string, withScores bool, offset, count int64) *StringSliceReq { args := []string{"ZREVRANGEBYSCORE", key, start, stop} if withScores { @@ -889,6 +931,22 @@ func (c *Client) ZRevRangeByScoreWithScores(key, start, stop string, offset, cou return c.zRevRangeByScore(key, start, stop, false, offset, count) } +func (c *Client) ZRevRangeByScoreWithScoresMap( + key, start, stop string, offset, count int64) *StringFloatMapReq { + args := []string{"ZREVRANGEBYSCORE", key, start, stop, "WITHSCORES"} + if offset != 0 || count != 0 { + args = append( + args, + "LIMIT", + strconv.FormatInt(offset, 10), + strconv.FormatInt(count, 10), + ) + } + req := NewStringFloatMapReq(args...) + c.Process(req) + return req +} + func (c *Client) ZRevRank(key, member string) *IntReq { req := NewIntReq("ZREVRANK", key, member) c.Process(req) diff --git a/parser.go b/parser.go index 336853b..fa73265 100644 --- a/parser.go +++ b/parser.go @@ -8,16 +8,23 @@ import ( "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") + errInvalidType = errors.New("redis: invalid reply type") ) //------------------------------------------------------------------------------ @@ -113,7 +120,7 @@ func ParseReq(rd reader) ([]string, error) { return nil, err } if line[0] != '$' { - return nil, fmt.Errorf("Expected '$', but got %q", line) + return nil, fmt.Errorf("redis: expected '$', but got %q", line) } argLen, err := strconv.ParseInt(string(line[1:]), 10, 32) @@ -132,12 +139,6 @@ func ParseReq(rd reader) ([]string, error) { //------------------------------------------------------------------------------ -const ( - ifaceSlice = iota - stringSlice - boolSlice -) - func parseReply(rd reader) (interface{}, error) { return _parseReply(rd, ifaceSlice) } @@ -150,7 +151,15 @@ func parseBoolSliceReply(rd reader) (interface{}, error) { return _parseReply(rd, boolSlice) } -func _parseReply(rd reader, multiBulkType int) (interface{}, error) { +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} @@ -188,39 +197,95 @@ func _parseReply(rd reader, multiBulkType int) (interface{}, error) { return nil, Nil } - numReplies, err := strconv.ParseInt(string(line[1:]), 10, 64) + repliesNum, err := strconv.ParseInt(string(line[1:]), 10, 64) if err != nil { return nil, &parserError{err} } - switch multiBulkType { + switch typ { case stringSlice: - vals := make([]string, 0, numReplies) - for i := int64(0); i < numReplies; i++ { - v, err := parseReply(rd) + 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 { - vals = append(vals, v.(string)) + return nil, errInvalidType } } - return vals, nil case boolSlice: - vals := make([]bool, 0, numReplies) - for i := int64(0); i < numReplies; i++ { - v, err := parseReply(rd) + 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 { - vals = append(vals, v.(int64) == 1) + return nil, errInvalidType } } - return vals, nil + case stringStringMap: + 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: + 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, numReplies) - for i := int64(0); i < numReplies; i++ { + vals := make([]interface{}, 0, repliesNum) + for i := int64(0); i < repliesNum; i++ { v, err := parseReply(rd) if err == Nil { vals = append(vals, nil) @@ -230,7 +295,6 @@ func _parseReply(rd reader, multiBulkType int) (interface{}, error) { vals = append(vals, v) } } - return vals, nil } default: diff --git a/redis_test.go b/redis_test.go index 1cf0acf..2afccfc 100644 --- a/redis_test.go +++ b/redis_test.go @@ -1088,6 +1088,17 @@ func (t *RedisTest) TestCmdHGetAll(c *C) { c.Assert(hGetAll.Val(), DeepEquals, []string{"key1", "hello1", "key2", "hello2"}) } +func (t *RedisTest) TestCmdHGetAllMap(c *C) { + hSet := t.client.HSet("hash", "key1", "hello1") + c.Assert(hSet.Err(), IsNil) + hSet = t.client.HSet("hash", "key2", "hello2") + c.Assert(hSet.Err(), IsNil) + + hGetAll := t.client.HGetAllMap("hash") + c.Assert(hGetAll.Err(), IsNil) + c.Assert(hGetAll.Val(), DeepEquals, map[string]string{"key1": "hello1", "key2": "hello2"}) +} + func (t *RedisTest) TestCmdHIncrBy(c *C) { hSet := t.client.HSet("hash", "key", "5") c.Assert(hSet.Err(), IsNil) @@ -1928,6 +1939,27 @@ func (t *RedisTest) TestZRange(c *C) { c.Assert(zRange.Val(), DeepEquals, []string{"two", "three"}) } +func (t *RedisTest) TestZRangeWithScoresMap(c *C) { + zAdd := t.client.ZAdd("zset", redis.Z{1, "one"}) + c.Assert(zAdd.Err(), IsNil) + zAdd = t.client.ZAdd("zset", redis.Z{2, "two"}) + c.Assert(zAdd.Err(), IsNil) + zAdd = t.client.ZAdd("zset", redis.Z{3, "three"}) + c.Assert(zAdd.Err(), IsNil) + + zRange := t.client.ZRangeWithScoresMap("zset", 0, -1) + c.Assert(zRange.Err(), IsNil) + c.Assert(zRange.Val(), DeepEquals, map[string]float64{"one": 1, "two": 2, "three": 3}) + + zRange = t.client.ZRangeWithScoresMap("zset", 2, 3) + c.Assert(zRange.Err(), IsNil) + c.Assert(zRange.Val(), DeepEquals, map[string]float64{"three": 3}) + + zRange = t.client.ZRangeWithScoresMap("zset", -2, -1) + c.Assert(zRange.Err(), IsNil) + c.Assert(zRange.Val(), DeepEquals, map[string]float64{"two": 2, "three": 3}) +} + func (t *RedisTest) TestZRangeByScore(c *C) { zAdd := t.client.ZAdd("zset", redis.Z{1, "one"}) c.Assert(zAdd.Err(), IsNil) @@ -1953,6 +1985,31 @@ func (t *RedisTest) TestZRangeByScore(c *C) { c.Assert(zRangeByScore.Val(), DeepEquals, []string{}) } +func (t *RedisTest) TestZRangeByScoreWithScoresMap(c *C) { + zAdd := t.client.ZAdd("zset", redis.Z{1, "one"}) + c.Assert(zAdd.Err(), IsNil) + zAdd = t.client.ZAdd("zset", redis.Z{2, "two"}) + c.Assert(zAdd.Err(), IsNil) + zAdd = t.client.ZAdd("zset", redis.Z{3, "three"}) + c.Assert(zAdd.Err(), IsNil) + + zRangeByScore := t.client.ZRangeByScoreWithScoresMap("zset", "-inf", "+inf", 0, 0) + c.Assert(zRangeByScore.Err(), IsNil) + c.Assert(zRangeByScore.Val(), DeepEquals, map[string]float64{"one": 1, "two": 2, "three": 3}) + + zRangeByScore = t.client.ZRangeByScoreWithScoresMap("zset", "1", "2", 0, 0) + c.Assert(zRangeByScore.Err(), IsNil) + c.Assert(zRangeByScore.Val(), DeepEquals, map[string]float64{"one": 1, "two": 2}) + + zRangeByScore = t.client.ZRangeByScoreWithScoresMap("zset", "(1", "2", 0, 0) + c.Assert(zRangeByScore.Err(), IsNil) + c.Assert(zRangeByScore.Val(), DeepEquals, map[string]float64{"two": 2}) + + zRangeByScore = t.client.ZRangeByScoreWithScoresMap("zset", "(1", "(2", 0, 0) + c.Assert(zRangeByScore.Err(), IsNil) + c.Assert(zRangeByScore.Val(), DeepEquals, map[string]float64{}) +} + func (t *RedisTest) TestZRank(c *C) { zAdd := t.client.ZAdd("zset", redis.Z{1, "one"}) c.Assert(zAdd.Err(), IsNil) @@ -2042,6 +2099,27 @@ func (t *RedisTest) TestZRevRange(c *C) { c.Assert(zRevRange.Val(), DeepEquals, []string{"two", "one"}) } +func (t *RedisTest) TestZRevRangeWithScoresMap(c *C) { + zAdd := t.client.ZAdd("zset", redis.Z{1, "one"}) + c.Assert(zAdd.Err(), IsNil) + zAdd = t.client.ZAdd("zset", redis.Z{2, "two"}) + c.Assert(zAdd.Err(), IsNil) + zAdd = t.client.ZAdd("zset", redis.Z{3, "three"}) + c.Assert(zAdd.Err(), IsNil) + + zRevRange := t.client.ZRevRangeWithScoresMap("zset", "0", "-1") + c.Assert(zRevRange.Err(), IsNil) + c.Assert(zRevRange.Val(), DeepEquals, map[string]float64{"three": 3, "two": 2, "one": 1}) + + zRevRange = t.client.ZRevRangeWithScoresMap("zset", "2", "3") + c.Assert(zRevRange.Err(), IsNil) + c.Assert(zRevRange.Val(), DeepEquals, map[string]float64{"one": 1}) + + zRevRange = t.client.ZRevRangeWithScoresMap("zset", "-2", "-1") + c.Assert(zRevRange.Err(), IsNil) + c.Assert(zRevRange.Val(), DeepEquals, map[string]float64{"two": 2, "one": 1}) +} + func (t *RedisTest) TestZRevRangeByScore(c *C) { zAdd := t.client.ZAdd("zset", redis.Z{1, "one"}) c.Assert(zAdd.Err(), IsNil) @@ -2063,6 +2141,27 @@ func (t *RedisTest) TestZRevRangeByScore(c *C) { c.Assert(zRevRangeByScore.Val(), DeepEquals, []string{}) } +func (t *RedisTest) TestZRevRangeByScoreWithScoresMap(c *C) { + zAdd := t.client.ZAdd("zset", redis.Z{1, "one"}) + c.Assert(zAdd.Err(), IsNil) + zAdd = t.client.ZAdd("zset", redis.Z{2, "two"}) + c.Assert(zAdd.Err(), IsNil) + zAdd = t.client.ZAdd("zset", redis.Z{3, "three"}) + c.Assert(zAdd.Err(), IsNil) + + zRevRangeByScore := t.client.ZRevRangeByScoreWithScoresMap("zset", "+inf", "-inf", 0, 0) + c.Assert(zRevRangeByScore.Err(), IsNil) + c.Assert(zRevRangeByScore.Val(), DeepEquals, map[string]float64{"three": 3, "two": 2, "one": 1}) + + zRevRangeByScore = t.client.ZRevRangeByScoreWithScoresMap("zset", "2", "(1", 0, 0) + c.Assert(zRevRangeByScore.Err(), IsNil) + c.Assert(zRevRangeByScore.Val(), DeepEquals, map[string]float64{"two": 2}) + + zRevRangeByScore = t.client.ZRevRangeByScoreWithScoresMap("zset", "(2", "(1", 0, 0) + c.Assert(zRevRangeByScore.Err(), IsNil) + c.Assert(zRevRangeByScore.Val(), DeepEquals, map[string]float64{}) +} + func (t *RedisTest) TestZRevRank(c *C) { zAdd := t.client.ZAdd("zset", redis.Z{1, "one"}) c.Assert(zAdd.Err(), IsNil) diff --git a/req.go b/req.go index 014b8db..b5940d7 100644 --- a/req.go +++ b/req.go @@ -255,3 +255,49 @@ func (r *BoolSliceReq) Val() []bool { } 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) +}