diff --git a/bench_test.go b/bench_test.go index 0b57699..2b4d45c 100644 --- a/bench_test.go +++ b/bench_test.go @@ -37,7 +37,7 @@ func BenchmarkRedisPing(b *testing.B) { }) } -func BenchmarkRedisSet(b *testing.B) { +func BenchmarkRedisSetString(b *testing.B) { client := benchmarkRedisClient(10) defer client.Close() diff --git a/cluster.go b/cluster.go index 76899ea..f2832a4 100644 --- a/cluster.go +++ b/cluster.go @@ -378,7 +378,7 @@ func (c *ClusterClient) cmdSlotAndNode(state *clusterState, cmd Cmder) (int, *cl return 0, node, err } - cmdInfo := c.cmds[cmd.arg(0)] + cmdInfo := c.cmds[cmd.name()] firstKey := cmd.arg(cmdFirstKeyPos(cmd, cmdInfo)) slot := hashtag.Slot(firstKey) diff --git a/command.go b/command.go index f841707..2adb629 100644 --- a/command.go +++ b/command.go @@ -33,6 +33,7 @@ var ( type Cmder interface { args() []interface{} arg(int) string + name() string readReply(*pool.Conn) error setErr(error) @@ -83,7 +84,7 @@ func cmdString(cmd Cmder, val interface{}) string { } func cmdFirstKeyPos(cmd Cmder, info *CommandInfo) int { - switch cmd.arg(0) { + switch cmd.name() { case "eval", "evalsha": if cmd.arg(2) != "0" { return 3 @@ -92,7 +93,7 @@ func cmdFirstKeyPos(cmd Cmder, info *CommandInfo) int { } } if info == nil { - internal.Logf("info for cmd=%s not found", cmd.arg(0)) + internal.Logf("info for cmd=%s not found", cmd.name()) return -1 } return int(info.FirstKeyPos) @@ -126,6 +127,16 @@ func (cmd *baseCmd) arg(pos int) string { return s } +func (cmd *baseCmd) name() string { + if len(cmd._args) > 0 { + // Cmd name must be lower cased. + s := internal.ToLower(cmd.arg(0)) + cmd._args[0] = s + return s + } + return "" +} + func (cmd *baseCmd) readTimeout() *time.Duration { return cmd._readTimeout } @@ -156,7 +167,7 @@ type Cmd struct { func NewCmd(args ...interface{}) *Cmd { return &Cmd{ - baseCmd: newBaseCmd(args), + baseCmd: baseCmd{_args: args}, } } @@ -193,8 +204,9 @@ type SliceCmd struct { } func NewSliceCmd(args ...interface{}) *SliceCmd { - cmd := newBaseCmd(args) - return &SliceCmd{baseCmd: cmd} + return &SliceCmd{ + baseCmd: baseCmd{_args: args}, + } } func (cmd *SliceCmd) Val() []interface{} { @@ -228,8 +240,9 @@ type StatusCmd struct { } func NewStatusCmd(args ...interface{}) *StatusCmd { - cmd := newBaseCmd(args) - return &StatusCmd{baseCmd: cmd} + return &StatusCmd{ + baseCmd: baseCmd{_args: args}, + } } func (cmd *StatusCmd) Val() string { @@ -258,8 +271,9 @@ type IntCmd struct { } func NewIntCmd(args ...interface{}) *IntCmd { - cmd := newBaseCmd(args) - return &IntCmd{baseCmd: cmd} + return &IntCmd{ + baseCmd: baseCmd{_args: args}, + } } func (cmd *IntCmd) Val() int64 { @@ -289,10 +303,9 @@ type DurationCmd struct { } func NewDurationCmd(precision time.Duration, args ...interface{}) *DurationCmd { - cmd := newBaseCmd(args) return &DurationCmd{ + baseCmd: baseCmd{_args: args}, precision: precision, - baseCmd: cmd, } } @@ -327,9 +340,8 @@ type TimeCmd struct { } func NewTimeCmd(args ...interface{}) *TimeCmd { - cmd := newBaseCmd(args) return &TimeCmd{ - baseCmd: cmd, + baseCmd: baseCmd{_args: args}, } } @@ -364,8 +376,9 @@ type BoolCmd struct { } func NewBoolCmd(args ...interface{}) *BoolCmd { - cmd := newBaseCmd(args) - return &BoolCmd{baseCmd: cmd} + return &BoolCmd{ + baseCmd: baseCmd{_args: args}, + } } func (cmd *BoolCmd) Val() bool { @@ -414,16 +427,17 @@ func (cmd *BoolCmd) readReply(cn *pool.Conn) error { type StringCmd struct { baseCmd - val string + val []byte } func NewStringCmd(args ...interface{}) *StringCmd { - cmd := newBaseCmd(args) - return &StringCmd{baseCmd: cmd} + return &StringCmd{ + baseCmd: baseCmd{_args: args}, + } } func (cmd *StringCmd) Val() string { - return cmd.val + return internal.BytesToString(cmd.val) } func (cmd *StringCmd) Result() (string, error) { @@ -467,7 +481,7 @@ func (cmd *StringCmd) String() string { } func (cmd *StringCmd) readReply(cn *pool.Conn) error { - cmd.val, cmd.err = cn.Rd.ReadStringReply() + cmd.val, cmd.err = cn.Rd.ReadBytesReply() return cmd.err } @@ -480,8 +494,9 @@ type FloatCmd struct { } func NewFloatCmd(args ...interface{}) *FloatCmd { - cmd := newBaseCmd(args) - return &FloatCmd{baseCmd: cmd} + return &FloatCmd{ + baseCmd: baseCmd{_args: args}, + } } func (cmd *FloatCmd) Val() float64 { @@ -510,8 +525,9 @@ type StringSliceCmd struct { } func NewStringSliceCmd(args ...interface{}) *StringSliceCmd { - cmd := newBaseCmd(args) - return &StringSliceCmd{baseCmd: cmd} + return &StringSliceCmd{ + baseCmd: baseCmd{_args: args}, + } } func (cmd *StringSliceCmd) Val() []string { @@ -545,8 +561,9 @@ type BoolSliceCmd struct { } func NewBoolSliceCmd(args ...interface{}) *BoolSliceCmd { - cmd := newBaseCmd(args) - return &BoolSliceCmd{baseCmd: cmd} + return &BoolSliceCmd{ + baseCmd: baseCmd{_args: args}, + } } func (cmd *BoolSliceCmd) Val() []bool { @@ -580,8 +597,9 @@ type StringStringMapCmd struct { } func NewStringStringMapCmd(args ...interface{}) *StringStringMapCmd { - cmd := newBaseCmd(args) - return &StringStringMapCmd{baseCmd: cmd} + return &StringStringMapCmd{ + baseCmd: baseCmd{_args: args}, + } } func (cmd *StringStringMapCmd) Val() map[string]string { @@ -615,8 +633,9 @@ type StringIntMapCmd struct { } func NewStringIntMapCmd(args ...interface{}) *StringIntMapCmd { - cmd := newBaseCmd(args) - return &StringIntMapCmd{baseCmd: cmd} + return &StringIntMapCmd{ + baseCmd: baseCmd{_args: args}, + } } func (cmd *StringIntMapCmd) Val() map[string]int64 { @@ -650,8 +669,9 @@ type ZSliceCmd struct { } func NewZSliceCmd(args ...interface{}) *ZSliceCmd { - cmd := newBaseCmd(args) - return &ZSliceCmd{baseCmd: cmd} + return &ZSliceCmd{ + baseCmd: baseCmd{_args: args}, + } } func (cmd *ZSliceCmd) Val() []Z { @@ -689,7 +709,7 @@ type ScanCmd struct { func NewScanCmd(process func(cmd Cmder) error, args ...interface{}) *ScanCmd { return &ScanCmd{ - baseCmd: newBaseCmd(args), + baseCmd: baseCmd{_args: args}, process: process, } } @@ -738,8 +758,9 @@ type ClusterSlotsCmd struct { } func NewClusterSlotsCmd(args ...interface{}) *ClusterSlotsCmd { - cmd := newBaseCmd(args) - return &ClusterSlotsCmd{baseCmd: cmd} + return &ClusterSlotsCmd{ + baseCmd: baseCmd{_args: args}, + } } func (cmd *ClusterSlotsCmd) Val() []ClusterSlot { @@ -857,8 +878,9 @@ type GeoPosCmd struct { } func NewGeoPosCmd(args ...interface{}) *GeoPosCmd { - cmd := newBaseCmd(args) - return &GeoPosCmd{baseCmd: cmd} + return &GeoPosCmd{ + baseCmd: baseCmd{_args: args}, + } } func (cmd *GeoPosCmd) Val() []*GeoPos { @@ -902,8 +924,9 @@ type CommandsInfoCmd struct { } func NewCommandsInfoCmd(args ...interface{}) *CommandsInfoCmd { - cmd := newBaseCmd(args) - return &CommandsInfoCmd{baseCmd: cmd} + return &CommandsInfoCmd{ + baseCmd: baseCmd{_args: args}, + } } func (cmd *CommandsInfoCmd) Val() map[string]*CommandInfo { diff --git a/internal/proto/reader.go b/internal/proto/reader.go index 9aed3de..ee811c8 100644 --- a/internal/proto/reader.go +++ b/internal/proto/reader.go @@ -74,7 +74,7 @@ func (p *Reader) ReadReply(m MultiBulkParse) (interface{}, error) { case StatusReply: return parseStatusValue(line) case IntReply: - return parseIntValue(line) + return parseInt(line[1:], 10, 64) case StringReply: return p.readBytesValue(line) case ArrayReply: @@ -96,13 +96,13 @@ func (p *Reader) ReadIntReply() (int64, error) { case ErrorReply: return 0, ParseErrorReply(line) case IntReply: - return parseIntValue(line) + return parseInt(line[1:], 10, 64) default: return 0, fmt.Errorf("redis: can't parse int reply: %.100q", line) } } -func (p *Reader) ReadBytesReply() ([]byte, error) { +func (p *Reader) ReadTmpBytesReply() ([]byte, error) { line, err := p.ReadLine() if err != nil { return nil, err @@ -119,8 +119,18 @@ func (p *Reader) ReadBytesReply() ([]byte, error) { } } +func (r *Reader) ReadBytesReply() ([]byte, error) { + b, err := r.ReadTmpBytesReply() + if err != nil { + return nil, err + } + cp := make([]byte, len(b)) + copy(cp, b) + return cp, nil +} + func (p *Reader) ReadStringReply() (string, error) { - b, err := p.ReadBytesReply() + b, err := p.ReadTmpBytesReply() if err != nil { return "", err } @@ -128,11 +138,11 @@ func (p *Reader) ReadStringReply() (string, error) { } func (p *Reader) ReadFloatReply() (float64, error) { - s, err := p.ReadStringReply() + b, err := p.ReadTmpBytesReply() if err != nil { return 0, err } - return strconv.ParseFloat(s, 64) + return parseFloat(b, 64) } func (p *Reader) ReadArrayReply(m MultiBulkParse) (interface{}, error) { @@ -178,12 +188,7 @@ func (p *Reader) ReadScanReply() ([]string, uint64, error) { return nil, 0, fmt.Errorf("redis: got %d elements in scan reply, expected 2", n) } - s, err := p.ReadStringReply() - if err != nil { - return nil, 0, err - } - - cursor, err := strconv.ParseUint(s, 10, 64) + cursor, err := p.ReadUint() if err != nil { return nil, 0, err } @@ -222,6 +227,22 @@ func (p *Reader) readBytesValue(line []byte) ([]byte, error) { return b[:replyLen], nil } +func (r *Reader) ReadInt() (int64, error) { + b, err := r.ReadTmpBytesReply() + if err != nil { + return 0, err + } + return parseInt(b, 10, 64) +} + +func (r *Reader) ReadUint() (uint64, error) { + b, err := r.ReadTmpBytesReply() + if err != nil { + return 0, err + } + return parseUint(b, 10, 64) +} + // -------------------------------------------------------------------- func readN(r io.Reader, b []byte, n int) ([]byte, error) { @@ -280,13 +301,25 @@ func parseStatusValue(line []byte) ([]byte, error) { return line[1:], nil } -func parseIntValue(line []byte) (int64, error) { - return strconv.ParseInt(string(line[1:]), 10, 64) -} - func parseArrayLen(line []byte) (int64, error) { if isNilReply(line) { return 0, internal.Nil } - return parseIntValue(line) + return parseInt(line[1:], 10, 64) +} + +func atoi(b []byte) (int, error) { + return strconv.Atoi(internal.BytesToString(b)) +} + +func parseInt(b []byte, base int, bitSize int) (int64, error) { + return strconv.ParseInt(internal.BytesToString(b), base, bitSize) +} + +func parseUint(b []byte, base int, bitSize int) (uint64, error) { + return strconv.ParseUint(internal.BytesToString(b), base, bitSize) +} + +func parseFloat(b []byte, bitSize int) (float64, error) { + return strconv.ParseFloat(internal.BytesToString(b), bitSize) } diff --git a/internal/proto/scan.go b/internal/proto/scan.go index b51e4e8..67ea521 100644 --- a/internal/proto/scan.go +++ b/internal/proto/scan.go @@ -3,90 +3,89 @@ package proto import ( "encoding" "fmt" - "strconv" "gopkg.in/redis.v5/internal" ) -func Scan(s string, v interface{}) error { +func Scan(b []byte, v interface{}) error { switch v := v.(type) { case nil: return internal.RedisError("redis: Scan(nil)") case *string: - *v = s + *v = internal.BytesToString(b) return nil case *[]byte: - *v = []byte(s) + *v = b return nil case *int: var err error - *v, err = strconv.Atoi(s) + *v, err = atoi(b) return err case *int8: - n, err := strconv.ParseInt(s, 10, 8) + n, err := parseInt(b, 10, 8) if err != nil { return err } *v = int8(n) return nil case *int16: - n, err := strconv.ParseInt(s, 10, 16) + n, err := parseInt(b, 10, 16) if err != nil { return err } *v = int16(n) return nil case *int32: - n, err := strconv.ParseInt(s, 10, 32) + n, err := parseInt(b, 10, 32) if err != nil { return err } *v = int32(n) return nil case *int64: - n, err := strconv.ParseInt(s, 10, 64) + n, err := parseInt(b, 10, 64) if err != nil { return err } *v = n return nil case *uint: - n, err := strconv.ParseUint(s, 10, 64) + n, err := parseUint(b, 10, 64) if err != nil { return err } *v = uint(n) return nil case *uint8: - n, err := strconv.ParseUint(s, 10, 8) + n, err := parseUint(b, 10, 8) if err != nil { return err } *v = uint8(n) return nil case *uint16: - n, err := strconv.ParseUint(s, 10, 16) + n, err := parseUint(b, 10, 16) if err != nil { return err } *v = uint16(n) return nil case *uint32: - n, err := strconv.ParseUint(s, 10, 32) + n, err := parseUint(b, 10, 32) if err != nil { return err } *v = uint32(n) return nil case *uint64: - n, err := strconv.ParseUint(s, 10, 64) + n, err := parseUint(b, 10, 64) if err != nil { return err } *v = n return nil case *float32: - n, err := strconv.ParseFloat(s, 32) + n, err := parseFloat(b, 32) if err != nil { return err } @@ -94,13 +93,13 @@ func Scan(s string, v interface{}) error { return err case *float64: var err error - *v, err = strconv.ParseFloat(s, 64) + *v, err = parseFloat(b, 64) return err case *bool: - *v = len(s) == 1 && s[0] == '1' + *v = len(b) == 1 && b[0] == '1' return nil case encoding.BinaryUnmarshaler: - return v.UnmarshalBinary([]byte(s)) + return v.UnmarshalBinary(b) default: return fmt.Errorf( "redis: can't unmarshal %T (consider implementing BinaryUnmarshaler)", v) diff --git a/internal/safe.go b/internal/safe.go new file mode 100644 index 0000000..dc5f4cc --- /dev/null +++ b/internal/safe.go @@ -0,0 +1,7 @@ +// +build appengine + +package internal + +func BytesToString(b []byte) string { + return string(b) +} diff --git a/internal/unsafe.go b/internal/unsafe.go new file mode 100644 index 0000000..94e4a9d --- /dev/null +++ b/internal/unsafe.go @@ -0,0 +1,14 @@ +// +build !appengine + +package internal + +import ( + "reflect" + "unsafe" +) + +func BytesToString(b []byte) string { + bytesHeader := (*reflect.SliceHeader)(unsafe.Pointer(&b)) + strHeader := reflect.StringHeader{bytesHeader.Data, bytesHeader.Len} + return *(*string)(unsafe.Pointer(&strHeader)) +} diff --git a/internal/util.go b/internal/util.go index 5986cc5..0662338 100644 --- a/internal/util.go +++ b/internal/util.go @@ -13,7 +13,7 @@ func ToLower(s string) string { } b[i] = c } - return string(b) + return BytesToString(b) } func isLower(s string) bool { diff --git a/parser.go b/parser.go index 0971412..bba6096 100644 --- a/parser.go +++ b/parser.go @@ -386,22 +386,12 @@ func timeParser(rd *proto.Reader, n int64) (interface{}, error) { return nil, fmt.Errorf("got %d elements, expected 2", n) } - secStr, err := rd.ReadStringReply() + sec, err := rd.ReadInt() if err != nil { return nil, err } - microsecStr, err := rd.ReadStringReply() - if err != nil { - return nil, err - } - - sec, err := strconv.ParseInt(secStr, 10, 64) - if err != nil { - return nil, err - } - - microsec, err := strconv.ParseInt(microsecStr, 10, 64) + microsec, err := rd.ReadInt() if err != nil { return nil, err } diff --git a/result.go b/result.go index f5c7084..28cea5c 100644 --- a/result.go +++ b/result.go @@ -53,7 +53,7 @@ func NewBoolResult(val bool, err error) *BoolCmd { // NewStringResult returns a StringCmd initalised with val and err for testing func NewStringResult(val string, err error) *StringCmd { var cmd StringCmd - cmd.val = val + cmd.val = []byte(val) cmd.setErr(err) return &cmd } diff --git a/ring.go b/ring.go index aff019d..4eb57c1 100644 --- a/ring.go +++ b/ring.go @@ -265,7 +265,7 @@ func (c *Ring) shardByName(name string) (*ringShard, error) { } func (c *Ring) cmdShard(cmd Cmder) (*ringShard, error) { - cmdInfo := c.cmdInfo(cmd.arg(0)) + cmdInfo := c.cmdInfo(cmd.name()) firstKey := cmd.arg(cmdFirstKeyPos(cmd, cmdInfo)) return c.shardByKey(firstKey) } @@ -364,7 +364,7 @@ func (c *Ring) Pipelined(fn func(*Pipeline) error) ([]Cmder, error) { func (c *Ring) pipelineExec(cmds []Cmder) (firstErr error) { cmdsMap := make(map[string][]Cmder) for _, cmd := range cmds { - cmdInfo := c.cmdInfo(cmd.arg(0)) + cmdInfo := c.cmdInfo(cmd.name()) name := cmd.arg(cmdFirstKeyPos(cmd, cmdInfo)) if name != "" { name = c.hash.Get(hashtag.Key(name))