diff --git a/v2/command.go b/v2/command.go index ca16da7..7f6baa0 100644 --- a/v2/command.go +++ b/v2/command.go @@ -9,7 +9,7 @@ import ( type Cmder interface { args() []string - parseReply(reader) (interface{}, error) + parseReply(reader) error setErr(error) setVal(interface{}) @@ -80,8 +80,9 @@ func (cmd *baseCmd) setVal(val interface{}) { cmd.val = val } -func (cmd *baseCmd) parseReply(rd reader) (interface{}, error) { - return parseReply(rd) +func (cmd *baseCmd) parseReply(rd reader) error { + cmd.val, cmd.err = parseReply(rd, parseSlice) + return cmd.err } func (cmd *baseCmd) readTimeout() *time.Duration { @@ -168,13 +169,10 @@ func NewDurationCmd(precision time.Duration, args ...string) *DurationCmd { } } -func (cmd *DurationCmd) parseReply(rd reader) (interface{}, error) { - v, err := parseReply(rd) - if err != nil { - return 0, err - } - vv := time.Duration(v.(int64)) - return vv * cmd.precision, nil +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 { @@ -196,12 +194,10 @@ func NewBoolCmd(args ...string) *BoolCmd { } } -func (cmd *BoolCmd) parseReply(rd reader) (interface{}, error) { - v, err := parseReply(rd) - if err != nil { - return nil, err - } - return v.(int64) == 1, nil +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 { @@ -242,12 +238,13 @@ func NewFloatCmd(args ...string) *FloatCmd { } } -func (cmd *FloatCmd) parseReply(rd reader) (interface{}, error) { - v, err := parseReply(rd) - if err != nil { - return nil, err +func (cmd *FloatCmd) parseReply(rd reader) error { + cmd.val, cmd.err = parseReply(rd, nil) + if cmd.err != nil { + return cmd.err } - return strconv.ParseFloat(v.(string), 64) + cmd.val, cmd.err = strconv.ParseFloat(cmd.val.(string), 64) + return cmd.err } func (cmd *FloatCmd) Val() float64 { @@ -288,8 +285,9 @@ func NewStringSliceCmd(args ...string) *StringSliceCmd { } } -func (cmd *StringSliceCmd) parseReply(rd reader) (interface{}, error) { - return parseStringSliceReply(rd) +func (cmd *StringSliceCmd) parseReply(rd reader) error { + cmd.val, cmd.err = parseReply(rd, parseStringSlice) + return cmd.err } func (cmd *StringSliceCmd) Val() []string { @@ -311,8 +309,9 @@ func NewBoolSliceCmd(args ...string) *BoolSliceCmd { } } -func (cmd *BoolSliceCmd) parseReply(rd reader) (interface{}, error) { - return parseBoolSliceReply(rd) +func (cmd *BoolSliceCmd) parseReply(rd reader) error { + cmd.val, cmd.err = parseReply(rd, parseBoolSlice) + return cmd.err } func (cmd *BoolSliceCmd) Val() []bool { @@ -334,8 +333,9 @@ func NewStringStringMapCmd(args ...string) *StringStringMapCmd { } } -func (cmd *StringStringMapCmd) parseReply(rd reader) (interface{}, error) { - return parseStringStringMapReply(rd) +func (cmd *StringStringMapCmd) parseReply(rd reader) error { + cmd.val, cmd.err = parseReply(rd, parseStringStringMap) + return cmd.err } func (cmd *StringStringMapCmd) Val() map[string]string { @@ -357,8 +357,9 @@ func NewStringFloatMapCmd(args ...string) *StringFloatMapCmd { } } -func (cmd *StringFloatMapCmd) parseReply(rd reader) (interface{}, error) { - return parseStringFloatMapReply(rd) +func (cmd *StringFloatMapCmd) parseReply(rd reader) error { + cmd.val, cmd.err = parseReply(rd, parseStringFloatMap) + return cmd.err } func (cmd *StringFloatMapCmd) Val() map[string]float64 { diff --git a/v2/commands.go b/v2/commands.go index 76c13d1..27c3207 100644 --- a/v2/commands.go +++ b/v2/commands.go @@ -229,6 +229,19 @@ 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) Append(key, value string) *IntCmd { diff --git a/v2/multi.go b/v2/multi.go index 197b81e..0360a76 100644 --- a/v2/multi.go +++ b/v2/multi.go @@ -95,8 +95,7 @@ func (c *Multi) execCmds(cn *conn, cmds []Cmder) error { // Parse queued replies. for i := 0; i < cmdsLen; i++ { - _, err = statusCmd.parseReply(cn.rd) - if err != nil { + if err := statusCmd.parseReply(cn.rd); err != nil { setCmdsErr(cmds[1:len(cmds)-1], err) return err } @@ -124,14 +123,10 @@ func (c *Multi) execCmds(cn *conn, cmds []Cmder) error { // Loop starts from 1 to omit MULTI cmd. for i := 1; i < cmdsLen; i++ { cmd := cmds[i] - val, err := cmd.parseReply(cn.rd) - if err != nil { - cmd.setErr(err) + if err := cmd.parseReply(cn.rd); err != nil { if firstCmdErr == nil { firstCmdErr = err } - } else { - cmd.setVal(val) } } diff --git a/v2/parser.go b/v2/parser.go index 6c618dd..735de7a 100644 --- a/v2/parser.go +++ b/v2/parser.go @@ -8,15 +8,7 @@ import ( "github.com/vmihailenco/bufio" ) -type replyType int - -const ( - ifaceSlice replyType = iota - stringSlice - boolSlice - stringStringMap - stringFloatMap -) +type multiBulkParser func(rd reader, n int64) (interface{}, error) // Redis nil reply. var Nil = errors.New("redis: nil") @@ -133,30 +125,10 @@ func parseReq(rd reader) ([]string, error) { //------------------------------------------------------------------------------ -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) { +func parseReply(rd reader, p multiBulkParser) (interface{}, error) { line, err := readLine(rd) if err != nil { - return 0, err + return nil, err } switch line[0] { @@ -167,23 +139,23 @@ func _parseReply(rd reader, typ replyType) (interface{}, error) { case ':': v, err := strconv.ParseInt(string(line[1:]), 10, 64) if err != nil { - return 0, err + return nil, err } return v, nil case '$': if len(line) == 3 && line[1] == '-' && line[2] == '1' { - return "", Nil + return nil, Nil } replyLenInt32, err := strconv.ParseInt(string(line[1:]), 10, 32) if err != nil { - return "", err + return nil, err } replyLen := int(replyLenInt32) + 2 line, err = readN(rd, replyLen) if err != nil { - return "", err + return nil, err } return string(line[:len(line)-2]), nil case '*': @@ -196,101 +168,110 @@ func _parseReply(rd reader, typ replyType) (interface{}, error) { return nil, 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, 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 - } + return p(rd, repliesNum) } return nil, fmt.Errorf("redis: can't parse %q", line) } + +func parseSlice(rd reader, n int64) (interface{}, error) { + vals := make([]interface{}, 0, n) + for i := int64(0); i < n; i++ { + v, err := parseReply(rd, parseSlice) + if err == Nil { + vals = append(vals, nil) + } else if err != nil { + return nil, err + } else { + vals = append(vals, v) + } + } + return vals, nil +} + +func parseStringSlice(rd reader, n int64) (interface{}, error) { + vals := make([]string, 0, n) + for i := int64(0); i < n; i++ { + vi, err := parseReply(rd, nil) + if err != nil { + return nil, err + } + if v, ok := vi.(string); ok { + vals = append(vals, v) + } else { + return nil, errInvalidReplyType + } + } + return vals, nil +} + +func parseBoolSlice(rd reader, n int64) (interface{}, error) { + vals := make([]bool, 0, n) + for i := int64(0); i < n; i++ { + vi, err := parseReply(rd, nil) + if err != nil { + return nil, err + } + if v, ok := vi.(int64); ok { + vals = append(vals, v == 1) + } else { + return nil, errInvalidReplyType + } + } + return vals, nil +} + +func parseStringStringMap(rd reader, n int64) (interface{}, error) { + m := make(map[string]string, n/2) + for i := int64(0); i < n; i += 2 { + keyI, err := parseReply(rd, nil) + if err != nil { + return nil, err + } + key, ok := keyI.(string) + if !ok { + return nil, errInvalidReplyType + } + + valueI, err := parseReply(rd, nil) + if err != nil { + return nil, err + } + value, ok := valueI.(string) + if !ok { + return nil, errInvalidReplyType + } + + m[key] = value + } + return m, nil +} + +func parseStringFloatMap(rd reader, n int64) (interface{}, error) { + m := make(map[string]float64, n/2) + for i := int64(0); i < n; i += 2 { + keyI, err := parseReply(rd, nil) + if err != nil { + return nil, err + } + key, ok := keyI.(string) + if !ok { + return nil, errInvalidReplyType + } + + valueI, err := parseReply(rd, nil) + 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, err + } + + m[key] = value + } + return m, nil +} diff --git a/v2/pipeline.go b/v2/pipeline.go index e021d1f..6d436d2 100644 --- a/v2/pipeline.go +++ b/v2/pipeline.go @@ -79,14 +79,10 @@ func (c *Pipeline) execCmds(cn *conn, cmds []Cmder) error { var firstCmdErr error for _, cmd := range cmds { - val, err := cmd.parseReply(cn.rd) - if err != nil { - cmd.setErr(err) + if err := cmd.parseReply(cn.rd); err != nil { if firstCmdErr == nil { firstCmdErr = err } - } else { - cmd.setVal(val) } } diff --git a/v2/pubsub.go b/v2/pubsub.go index 99be619..cc8d7e0 100644 --- a/v2/pubsub.go +++ b/v2/pubsub.go @@ -53,14 +53,12 @@ func (c *PubSub) ReceiveTimeout(timeout time.Duration) (interface{}, error) { } cn.readTimeout = timeout - replyIface, err := NewSliceCmd().parseReply(cn.rd) - if err != nil { + cmd := NewSliceCmd() + if err := cmd.parseReply(cn.rd); err != nil { return nil, err } - reply, ok := replyIface.([]interface{}) - if !ok { - return nil, fmt.Errorf("redis: unexpected reply type %T", replyIface) - } + + reply := cmd.Val() msgName := reply[0].(string) switch msgName { diff --git a/v2/redis.go b/v2/redis.go index ae74596..6e945da 100644 --- a/v2/redis.go +++ b/v2/redis.go @@ -118,15 +118,12 @@ func (c *baseClient) run(cmd Cmder) { return } - val, err := cmd.parseReply(cn.rd) - if err != nil { + if err := cmd.parseReply(cn.rd); err != nil { c.freeConn(cn, err) - cmd.setErr(err) return } c.putConn(cn) - cmd.setVal(val) } // Close closes the client, releasing any open resources.