From 62e78627c138e723597049d60375f3157a67f5c8 Mon Sep 17 00:00:00 2001 From: Vladimir Mihailenco Date: Thu, 9 Jan 2014 10:17:38 +0200 Subject: [PATCH] Support SCAN and friends. --- v2/command.go | 430 ++++++++++++++++++++++++++++++++++------------- v2/commands.go | 63 +++++-- v2/parser.go | 1 - v2/redis_test.go | 51 +++++- 4 files changed, 416 insertions(+), 129 deletions(-) diff --git a/v2/command.go b/v2/command.go index 7f6baa0..aad58ed 100644 --- a/v2/command.go +++ b/v2/command.go @@ -7,16 +7,32 @@ import ( "time" ) +var ( + _ Cmder = (*Cmd)(nil) + _ Cmder = (*SliceCmd)(nil) + _ Cmder = (*StatusCmd)(nil) + _ Cmder = (*IntCmd)(nil) + _ Cmder = (*DurationCmd)(nil) + _ Cmder = (*BoolCmd)(nil) + _ Cmder = (*StringCmd)(nil) + _ Cmder = (*FloatCmd)(nil) + _ Cmder = (*StringSliceCmd)(nil) + _ Cmder = (*BoolSliceCmd)(nil) + _ Cmder = (*StringStringMapCmd)(nil) + _ Cmder = (*StringFloatMapCmd)(nil) + _ Cmder = (*ScanCmd)(nil) +) + type Cmder interface { args() []string parseReply(reader) error setErr(error) - setVal(interface{}) writeTimeout() *time.Duration readTimeout() *time.Duration Err() error + String() string } func setCmdsErr(cmds []Cmder, e error) { @@ -25,12 +41,23 @@ func setCmdsErr(cmds []Cmder, e error) { } } +func cmdString(cmd Cmder, val interface{}) string { + s := strings.Join(cmd.args(), " ") + if err := cmd.Err(); err != nil { + return s + ": " + err.Error() + } + if val != nil { + return s + ": " + fmt.Sprint(val) + } + return s + +} + //------------------------------------------------------------------------------ type baseCmd struct { _args []string - val interface{} err error _writeTimeout, _readTimeout *time.Duration @@ -42,23 +69,10 @@ func newBaseCmd(args ...string) *baseCmd { } } -func (cmd *baseCmd) String() string { - args := strings.Join(cmd._args, " ") - if cmd.err != nil { - return args + ": " + cmd.err.Error() - } else if cmd.val != nil { - return args + ": " + fmt.Sprint(cmd.val) - } - return args -} - func (cmd *baseCmd) Err() error { if cmd.err != nil { return cmd.err } - if cmd.val == nil { - return errValNotSet - } return nil } @@ -66,25 +80,6 @@ func (cmd *baseCmd) args() []string { return cmd._args } -func (cmd *baseCmd) setErr(err error) { - if err == nil { - panic("non-nil value expected") - } - cmd.err = err -} - -func (cmd *baseCmd) setVal(val interface{}) { - if val == nil { - panic("non-nil value expected") - } - cmd.val = val -} - -func (cmd *baseCmd) parseReply(rd reader) error { - cmd.val, cmd.err = parseReply(rd, parseSlice) - return cmd.err -} - func (cmd *baseCmd) readTimeout() *time.Duration { return cmd._readTimeout } @@ -101,10 +96,16 @@ func (cmd *baseCmd) setWriteTimeout(d time.Duration) { cmd._writeTimeout = &d } +func (cmd *baseCmd) setErr(e error) { + cmd.err = e +} + //------------------------------------------------------------------------------ type Cmd struct { *baseCmd + + val interface{} } func NewCmd(args ...string) *Cmd { @@ -117,10 +118,61 @@ func (cmd *Cmd) Val() interface{} { return cmd.val } +func (cmd *Cmd) Result() (interface{}, error) { + return cmd.val, cmd.err +} + +func (cmd *Cmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *Cmd) parseReply(rd reader) error { + cmd.val, cmd.err = parseReply(rd, parseSlice) + return cmd.err +} + +//------------------------------------------------------------------------------ + +type SliceCmd struct { + *baseCmd + + val []interface{} +} + +func NewSliceCmd(args ...string) *SliceCmd { + return &SliceCmd{ + baseCmd: newBaseCmd(args...), + } +} + +func (cmd *SliceCmd) Val() []interface{} { + return cmd.val +} + +func (cmd *SliceCmd) Result() ([]interface{}, error) { + return cmd.val, cmd.err +} + +func (cmd *SliceCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *SliceCmd) parseReply(rd reader) error { + v, err := parseReply(rd, parseSlice) + if err != nil { + cmd.err = err + return err + } + cmd.val = v.([]interface{}) + return nil +} + //------------------------------------------------------------------------------ type StatusCmd struct { *baseCmd + + val string } func NewStatusCmd(args ...string) *StatusCmd { @@ -130,16 +182,33 @@ func NewStatusCmd(args ...string) *StatusCmd { } func (cmd *StatusCmd) Val() string { - if cmd.val == nil { - return "" + return cmd.val +} + +func (cmd *StatusCmd) Result() (string, error) { + return cmd.val, cmd.err +} + +func (cmd *StatusCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *StatusCmd) parseReply(rd reader) error { + v, err := parseReply(rd, nil) + if err != nil { + cmd.err = err + return err } - return cmd.val.(string) + cmd.val = v.(string) + return nil } //------------------------------------------------------------------------------ type IntCmd struct { *baseCmd + + val int64 } func NewIntCmd(args ...string) *IntCmd { @@ -149,16 +218,33 @@ func NewIntCmd(args ...string) *IntCmd { } func (cmd *IntCmd) Val() int64 { - if cmd.val == nil { - return 0 + return cmd.val +} + +func (cmd *IntCmd) Result() (int64, error) { + return cmd.val, cmd.err +} + +func (cmd *IntCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *IntCmd) parseReply(rd reader) error { + v, err := parseReply(rd, nil) + if err != nil { + cmd.err = err + return err } - return cmd.val.(int64) + cmd.val = v.(int64) + return nil } //------------------------------------------------------------------------------ type DurationCmd struct { *baseCmd + + val time.Duration precision time.Duration } @@ -169,23 +255,34 @@ func NewDurationCmd(precision time.Duration, args ...string) *DurationCmd { } } -func (cmd *DurationCmd) parseReply(rd reader) error { - cmd.val, cmd.err = parseReply(rd, nil) - cmd.val = time.Duration(cmd.val.(int64)) * cmd.precision - return cmd.err +func (cmd *DurationCmd) Val() time.Duration { + return cmd.val } -func (cmd *DurationCmd) Val() time.Duration { - if cmd.val == nil { - return 0 +func (cmd *DurationCmd) Result() (time.Duration, error) { + return cmd.val, cmd.err +} + +func (cmd *DurationCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *DurationCmd) parseReply(rd reader) error { + v, err := parseReply(rd, nil) + if err != nil { + cmd.err = err + return err } - return cmd.val.(time.Duration) + cmd.val = time.Duration(v.(int64)) * cmd.precision + return nil } //------------------------------------------------------------------------------ type BoolCmd struct { *baseCmd + + val bool } func NewBoolCmd(args ...string) *BoolCmd { @@ -194,23 +291,34 @@ func NewBoolCmd(args ...string) *BoolCmd { } } -func (cmd *BoolCmd) parseReply(rd reader) error { - cmd.val, cmd.err = parseReply(rd, nil) - cmd.val = cmd.val.(int64) == 1 - return cmd.err +func (cmd *BoolCmd) Val() bool { + return cmd.val } -func (cmd *BoolCmd) Val() bool { - if cmd.val == nil { - return false +func (cmd *BoolCmd) Result() (bool, error) { + return cmd.val, cmd.err +} + +func (cmd *BoolCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *BoolCmd) parseReply(rd reader) error { + v, err := parseReply(rd, nil) + if err != nil { + cmd.err = err + return err } - return cmd.val.(bool) + cmd.val = v.(int64) == 1 + return nil } //------------------------------------------------------------------------------ type StringCmd struct { *baseCmd + + val string } func NewStringCmd(args ...string) *StringCmd { @@ -220,16 +328,33 @@ func NewStringCmd(args ...string) *StringCmd { } func (cmd *StringCmd) Val() string { - if cmd.val == nil { - return "" + return cmd.val +} + +func (cmd *StringCmd) Result() (string, error) { + return cmd.val, cmd.err +} + +func (cmd *StringCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *StringCmd) parseReply(rd reader) error { + v, err := parseReply(rd, nil) + if err != nil { + cmd.err = err + return err } - return cmd.val.(string) + cmd.val = v.(string) + return nil } //------------------------------------------------------------------------------ type FloatCmd struct { *baseCmd + + val float64 } func NewFloatCmd(args ...string) *FloatCmd { @@ -238,45 +363,30 @@ func NewFloatCmd(args ...string) *FloatCmd { } } -func (cmd *FloatCmd) parseReply(rd reader) error { - cmd.val, cmd.err = parseReply(rd, nil) - if cmd.err != nil { - return cmd.err - } - cmd.val, cmd.err = strconv.ParseFloat(cmd.val.(string), 64) - return cmd.err -} - func (cmd *FloatCmd) Val() float64 { - if cmd.val == nil { - return 0 - } - return cmd.val.(float64) + return cmd.val } -//------------------------------------------------------------------------------ - -type SliceCmd struct { - *baseCmd +func (cmd *FloatCmd) String() string { + return cmdString(cmd, cmd.val) } -func NewSliceCmd(args ...string) *SliceCmd { - return &SliceCmd{ - baseCmd: newBaseCmd(args...), +func (cmd *FloatCmd) parseReply(rd reader) error { + v, err := parseReply(rd, nil) + if err != nil { + cmd.err = err + return err } -} - -func (cmd *SliceCmd) Val() []interface{} { - if cmd.val == nil { - return nil - } - return cmd.val.([]interface{}) + cmd.val, cmd.err = strconv.ParseFloat(v.(string), 64) + return cmd.err } //------------------------------------------------------------------------------ type StringSliceCmd struct { *baseCmd + + val []string } func NewStringSliceCmd(args ...string) *StringSliceCmd { @@ -285,22 +395,30 @@ func NewStringSliceCmd(args ...string) *StringSliceCmd { } } -func (cmd *StringSliceCmd) parseReply(rd reader) error { - cmd.val, cmd.err = parseReply(rd, parseStringSlice) - return cmd.err +func (cmd *StringSliceCmd) Val() []string { + return cmd.val } -func (cmd *StringSliceCmd) Val() []string { - if cmd.val == nil { - return nil +func (cmd *StringSliceCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *StringSliceCmd) parseReply(rd reader) error { + v, err := parseReply(rd, parseStringSlice) + if err != nil { + cmd.err = err + return err } - return cmd.val.([]string) + cmd.val = v.([]string) + return nil } //------------------------------------------------------------------------------ type BoolSliceCmd struct { *baseCmd + + val []bool } func NewBoolSliceCmd(args ...string) *BoolSliceCmd { @@ -309,22 +427,34 @@ func NewBoolSliceCmd(args ...string) *BoolSliceCmd { } } -func (cmd *BoolSliceCmd) parseReply(rd reader) error { - cmd.val, cmd.err = parseReply(rd, parseBoolSlice) - return cmd.err +func (cmd *BoolSliceCmd) Val() []bool { + return cmd.val } -func (cmd *BoolSliceCmd) Val() []bool { - if cmd.val == nil { - return nil +func (cmd *BoolSliceCmd) Result() ([]bool, error) { + return cmd.val, cmd.err +} + +func (cmd *BoolSliceCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *BoolSliceCmd) parseReply(rd reader) error { + v, err := parseReply(rd, parseBoolSlice) + if err != nil { + cmd.err = err + return err } - return cmd.val.([]bool) + cmd.val = v.([]bool) + return nil } //------------------------------------------------------------------------------ type StringStringMapCmd struct { *baseCmd + + val map[string]string } func NewStringStringMapCmd(args ...string) *StringStringMapCmd { @@ -333,22 +463,34 @@ func NewStringStringMapCmd(args ...string) *StringStringMapCmd { } } -func (cmd *StringStringMapCmd) parseReply(rd reader) error { - cmd.val, cmd.err = parseReply(rd, parseStringStringMap) - return cmd.err +func (cmd *StringStringMapCmd) Val() map[string]string { + return cmd.val } -func (cmd *StringStringMapCmd) Val() map[string]string { - if cmd.val == nil { - return nil +func (cmd *StringStringMapCmd) Result() (map[string]string, error) { + return cmd.val, cmd.err +} + +func (cmd *StringStringMapCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *StringStringMapCmd) parseReply(rd reader) error { + v, err := parseReply(rd, parseStringStringMap) + if err != nil { + cmd.err = err + return err } - return cmd.val.(map[string]string) + cmd.val = v.(map[string]string) + return nil } //------------------------------------------------------------------------------ type StringFloatMapCmd struct { *baseCmd + + val map[string]float64 } func NewStringFloatMapCmd(args ...string) *StringFloatMapCmd { @@ -357,14 +499,72 @@ func NewStringFloatMapCmd(args ...string) *StringFloatMapCmd { } } -func (cmd *StringFloatMapCmd) parseReply(rd reader) error { - cmd.val, cmd.err = parseReply(rd, parseStringFloatMap) - return cmd.err +func (cmd *StringFloatMapCmd) Val() map[string]float64 { + return cmd.val } -func (cmd *StringFloatMapCmd) Val() map[string]float64 { - if cmd.val == nil { - return nil - } - return cmd.val.(map[string]float64) +func (cmd *StringFloatMapCmd) Result() (map[string]float64, error) { + return cmd.val, cmd.err +} + +func (cmd *StringFloatMapCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *StringFloatMapCmd) parseReply(rd reader) error { + v, err := parseReply(rd, parseStringFloatMap) + if err != nil { + cmd.err = err + return err + } + cmd.val = v.(map[string]float64) + return nil +} + +//------------------------------------------------------------------------------ + +type ScanCmd struct { + *baseCmd + + cursor int64 + keys []string +} + +func NewScanCmd(args ...string) *ScanCmd { + return &ScanCmd{ + baseCmd: newBaseCmd(args...), + } +} + +func (cmd *ScanCmd) Val() (int64, []string) { + return cmd.cursor, cmd.keys +} + +func (cmd *ScanCmd) Result() (int64, []string, error) { + return cmd.cursor, cmd.keys, cmd.err +} + +func (cmd *ScanCmd) String() string { + return cmdString(cmd, cmd.keys) +} + +func (cmd *ScanCmd) parseReply(rd reader) error { + vi, err := parseReply(rd, parseSlice) + if err != nil { + cmd.err = err + return cmd.err + } + v := vi.([]interface{}) + + cmd.cursor, cmd.err = strconv.ParseInt(v[0].(string), 10, 64) + if cmd.err != nil { + return cmd.err + } + + keys := v[1].([]interface{}) + for _, keyi := range keys { + cmd.keys = append(cmd.keys, keyi.(string)) + } + + return nil } diff --git a/v2/commands.go b/v2/commands.go index 27c3207..62ab8bb 100644 --- a/v2/commands.go +++ b/v2/commands.go @@ -229,18 +229,57 @@ func (c *Client) Type(key string) *StatusCmd { return req } -// func (c *Client) Scan(cursor, match string, count int64) *ScanCmd { -// args := []string{"SCAN", cursor} -// if match != "" { -// args = append(args, "MATCH", match) -// } -// if count > 0 { -// args = append(args, "COUNT", strconv.FormatInt(count, 10)) -// } -// req := NewScanCmd(args...) -// c.Process(req) -// return req -// } +func (c *Client) Scan(cursor int64, match string, count int64) *ScanCmd { + args := []string{"SCAN", strconv.FormatInt(cursor, 10)} + if match != "" { + args = append(args, "MATCH", match) + } + if count > 0 { + args = append(args, "COUNT", strconv.FormatInt(count, 10)) + } + req := NewScanCmd(args...) + c.Process(req) + return req +} + +func (c *Client) SScan(key string, cursor int64, match string, count int64) *ScanCmd { + args := []string{"SSCAN", key, strconv.FormatInt(cursor, 10)} + if match != "" { + args = append(args, "MATCH", match) + } + if count > 0 { + args = append(args, "COUNT", strconv.FormatInt(count, 10)) + } + req := NewScanCmd(args...) + c.Process(req) + return req +} + +func (c *Client) HScan(key string, cursor int64, match string, count int64) *ScanCmd { + args := []string{"HSCAN", key, strconv.FormatInt(cursor, 10)} + if match != "" { + args = append(args, "MATCH", match) + } + if count > 0 { + args = append(args, "COUNT", strconv.FormatInt(count, 10)) + } + req := NewScanCmd(args...) + c.Process(req) + return req +} + +func (c *Client) ZScan(key string, cursor int64, match string, count int64) *ScanCmd { + args := []string{"ZSCAN", key, strconv.FormatInt(cursor, 10)} + if match != "" { + args = append(args, "MATCH", match) + } + if count > 0 { + args = append(args, "COUNT", strconv.FormatInt(count, 10)) + } + req := NewScanCmd(args...) + c.Process(req) + return req +} //------------------------------------------------------------------------------ diff --git a/v2/parser.go b/v2/parser.go index 735de7a..729946d 100644 --- a/v2/parser.go +++ b/v2/parser.go @@ -18,7 +18,6 @@ var TxFailedErr = errors.New("redis: transaction failed") var ( errReaderTooSmall = errors.New("redis: reader is too small") - errValNotSet = errors.New("redis: value is not set") errInvalidReplyType = errors.New("redis: invalid reply type") ) diff --git a/v2/redis_test.go b/v2/redis_test.go index 3193c62..1892a43 100644 --- a/v2/redis_test.go +++ b/v2/redis_test.go @@ -760,6 +760,54 @@ func (t *RedisTest) TestCmdKeysType(c *C) { c.Assert(type_.Val(), Equals, "string") } +func (t *RedisTest) TestCmdScan(c *C) { + for i := 0; i < 1000; i++ { + set := t.client.Set(fmt.Sprintf("key%d", i), "hello") + c.Assert(set.Err(), IsNil) + } + + cursor, keys, err := t.client.Scan(0, "", 0).Result() + c.Assert(err, IsNil) + c.Assert(cursor > 0, Equals, true) + c.Assert(len(keys) > 0, Equals, true) +} + +func (t *RedisTest) TestCmdSScan(c *C) { + for i := 0; i < 1000; i++ { + sadd := t.client.SAdd("myset", fmt.Sprintf("member%d", i)) + c.Assert(sadd.Err(), IsNil) + } + + cursor, keys, err := t.client.SScan("myset", 0, "", 0).Result() + c.Assert(err, IsNil) + c.Assert(cursor > 0, Equals, true) + c.Assert(len(keys) > 0, Equals, true) +} + +func (t *RedisTest) TestCmdHScan(c *C) { + for i := 0; i < 1000; i++ { + sadd := t.client.HSet("myhash", fmt.Sprintf("key%d", i), "hello") + c.Assert(sadd.Err(), IsNil) + } + + cursor, keys, err := t.client.HScan("myhash", 0, "", 0).Result() + c.Assert(err, IsNil) + c.Assert(cursor > 0, Equals, true) + c.Assert(len(keys) > 0, Equals, true) +} + +func (t *RedisTest) TestCmdZScan(c *C) { + for i := 0; i < 1000; i++ { + sadd := t.client.ZAdd("myset", redis.Z{float64(i), fmt.Sprintf("member%d", i)}) + c.Assert(sadd.Err(), IsNil) + } + + cursor, keys, err := t.client.ZScan("myset", 0, "", 0).Result() + c.Assert(err, IsNil) + c.Assert(cursor > 0, Equals, true) + c.Assert(len(keys) > 0, Equals, true) +} + //------------------------------------------------------------------------------ func (t *RedisTest) TestStringsAppend(c *C) { @@ -2508,7 +2556,8 @@ func (t *RedisTest) TestPipelineErrValNotSet(c *C) { }() get := pipeline.Get("key") - c.Assert(get.Err(), ErrorMatches, "redis: value is not set") + c.Assert(get.Err(), IsNil) + c.Assert(get.Val(), Equals, "") } func (t *RedisTest) TestPipelineRunQueuedOnEmptyQueue(c *C) {