diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8369e141..e0ae9434 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,9 +2,9 @@ name: Go on: push: - branches: [master] + branches: [master, v9] pull_request: - branches: [master] + branches: [master, v9] jobs: build: diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 7f000466..3be3b383 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -7,6 +7,7 @@ on: branches: - master - main + - v9 pull_request: jobs: diff --git a/CHANGELOG.md b/CHANGELOG.md index 195e5193..ab9e7d85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,177 +1,6 @@ -## [8.11.5](https://github.com/go-redis/redis/compare/v8.11.4...v8.11.5) (2022-03-17) +## v9 UNRELEASED - -### Bug Fixes - -* add missing Expire methods to Cmdable ([17e3b43](https://github.com/go-redis/redis/commit/17e3b43879d516437ada71cf9c0deac6a382ed9a)) -* add whitespace for avoid unlikely colisions ([7f7c181](https://github.com/go-redis/redis/commit/7f7c1817617cfec909efb13d14ad22ef05a6ad4c)) -* example/otel compile error ([#2028](https://github.com/go-redis/redis/issues/2028)) ([187c07c](https://github.com/go-redis/redis/commit/187c07c41bf68dc3ab280bc3a925e960bbef6475)) -* **extra/redisotel:** set span.kind attribute to client ([065b200](https://github.com/go-redis/redis/commit/065b200070b41e6e949710b4f9e01b50ccc60ab2)) -* format ([96f53a0](https://github.com/go-redis/redis/commit/96f53a0159a28affa94beec1543a62234e7f8b32)) -* invalid type assert in stringArg ([de6c131](https://github.com/go-redis/redis/commit/de6c131865b8263400c8491777b295035f2408e4)) -* rename Golang to Go ([#2030](https://github.com/go-redis/redis/issues/2030)) ([b82a2d9](https://github.com/go-redis/redis/commit/b82a2d9d4d2de7b7cbe8fcd4895be62dbcacacbc)) -* set timeout for WAIT command. Fixes [#1963](https://github.com/go-redis/redis/issues/1963) ([333fee1](https://github.com/go-redis/redis/commit/333fee1a8fd98a2fbff1ab187c1b03246a7eb01f)) -* update some argument counts in pre-allocs ([f6974eb](https://github.com/go-redis/redis/commit/f6974ebb5c40a8adf90d2cacab6dc297f4eba4c2)) - - -### Features - -* Add redis v7's NX, XX, GT, LT expire variants ([e19bbb2](https://github.com/go-redis/redis/commit/e19bbb26e2e395c6e077b48d80d79e99f729a8b8)) -* add support for acl sentinel auth in universal client ([ab0ccc4](https://github.com/go-redis/redis/commit/ab0ccc47413f9b2a6eabc852fed5005a3ee1af6e)) -* add support for COPY command ([#2016](https://github.com/go-redis/redis/issues/2016)) ([730afbc](https://github.com/go-redis/redis/commit/730afbcffb93760e8a36cc06cfe55ab102b693a7)) -* add support for passing extra attributes added to spans ([39faaa1](https://github.com/go-redis/redis/commit/39faaa171523834ba527c9789710c4fde87f5a2e)) -* add support for time.Duration write and scan ([2f1b74e](https://github.com/go-redis/redis/commit/2f1b74e20cdd7719b2aecf0768d3e3ae7c3e781b)) -* **redisotel:** ability to override TracerProvider ([#1998](https://github.com/go-redis/redis/issues/1998)) ([bf8d4aa](https://github.com/go-redis/redis/commit/bf8d4aa60c00366cda2e98c3ddddc8cf68507417)) -* set net.peer.name and net.peer.port in otel example ([69bf454](https://github.com/go-redis/redis/commit/69bf454f706204211cd34835f76b2e8192d3766d)) - - - -## [8.11.4](https://github.com/go-redis/redis/compare/v8.11.3...v8.11.4) (2021-10-04) - - -### Features - -* add acl auth support for sentinels ([f66582f](https://github.com/go-redis/redis/commit/f66582f44f3dc3a4705a5260f982043fde4aa634)) -* add Cmd.{String,Int,Float,Bool}Slice helpers and an example ([5d3d293](https://github.com/go-redis/redis/commit/5d3d293cc9c60b90871e2420602001463708ce24)) -* add SetVal method for each command ([168981d](https://github.com/go-redis/redis/commit/168981da2d84ee9e07d15d3e74d738c162e264c4)) - - - -## v8.11 - -- Remove OpenTelemetry metrics. -- Supports more redis commands and options. - -## v8.10 - -- Removed extra OpenTelemetry spans from go-redis core. Now go-redis instrumentation only adds a - single span with a Redis command (instead of 4 spans). There are multiple reasons behind this - decision: - - - Traces become smaller and less noisy. - - It may be costly to process those 3 extra spans for each query. - - go-redis no longer depends on OpenTelemetry. - - Eventually we hope to replace the information that we no longer collect with OpenTelemetry - Metrics. - -## v8.9 - -- Changed `PubSub.Channel` to only rely on `Ping` result. You can now use `WithChannelSize`, - `WithChannelHealthCheckInterval`, and `WithChannelSendTimeout` to override default settings. - -## v8.8 - -- To make updating easier, extra modules now have the same version as go-redis does. That means that - you need to update your imports: - -``` -github.com/go-redis/redis/extra/redisotel -> github.com/go-redis/redis/extra/redisotel/v8 -github.com/go-redis/redis/extra/rediscensus -> github.com/go-redis/redis/extra/rediscensus/v8 -``` - -## v8.5 - -- [knadh](https://github.com/knadh) contributed long-awaited ability to scan Redis Hash into a - struct: - -```go -err := rdb.HGetAll(ctx, "hash").Scan(&data) - -err := rdb.MGet(ctx, "key1", "key2").Scan(&data) -``` - -- Please check [redismock](https://github.com/go-redis/redismock) by - [monkey92t](https://github.com/monkey92t) if you are looking for mocking Redis Client. - -## v8 - -- All commands require `context.Context` as a first argument, e.g. `rdb.Ping(ctx)`. If you are not - using `context.Context` yet, the simplest option is to define global package variable - `var ctx = context.TODO()` and use it when `ctx` is required. - -- Full support for `context.Context` canceling. - -- Added `redis.NewFailoverClusterClient` that supports routing read-only commands to a slave node. - -- Added `redisext.OpenTemetryHook` that adds - [Redis OpenTelemetry instrumentation](https://redis.uptrace.dev/tracing/). - -- Redis slow log support. - -- Ring uses Rendezvous Hashing by default which provides better distribution. You need to move - existing keys to a new location or keys will be inaccessible / lost. To use old hashing scheme: - -```go -import "github.com/golang/groupcache/consistenthash" - -ring := redis.NewRing(&redis.RingOptions{ - NewConsistentHash: func() { - return consistenthash.New(100, crc32.ChecksumIEEE) - }, -}) -``` - -- `ClusterOptions.MaxRedirects` default value is changed from 8 to 3. -- `Options.MaxRetries` default value is changed from 0 to 3. - -- `Cluster.ForEachNode` is renamed to `ForEachShard` for consistency with `Ring`. - -## v7.3 - -- New option `Options.Username` which causes client to use `AuthACL`. Be aware if your connection - URL contains username. - -## v7.2 - -- Existing `HMSet` is renamed to `HSet` and old deprecated `HMSet` is restored for Redis 3 users. - -## v7.1 - -- Existing `Cmd.String` is renamed to `Cmd.Text`. New `Cmd.String` implements `fmt.Stringer` - interface. - -## v7 - -- _Important_. Tx.Pipeline now returns a non-transactional pipeline. Use Tx.TxPipeline for a - transactional pipeline. -- WrapProcess is replaced with more convenient AddHook that has access to context.Context. -- WithContext now can not be used to create a shallow copy of the client. -- New methods ProcessContext, DoContext, and ExecContext. -- Client respects Context.Deadline when setting net.Conn deadline. -- Client listens on Context.Done while waiting for a connection from the pool and returns an error - when context context is cancelled. -- Add PubSub.ChannelWithSubscriptions that sends `*Subscription` in addition to `*Message` to allow - detecting reconnections. -- `time.Time` is now marshalled in RFC3339 format. `rdb.Get("foo").Time()` helper is added to parse - the time. -- `SetLimiter` is removed and added `Options.Limiter` instead. -- `HMSet` is deprecated as of Redis v4. - -## v6.15 - -- Cluster and Ring pipelines process commands for each node in its own goroutine. - -## 6.14 - -- Added Options.MinIdleConns. -- Added Options.MaxConnAge. -- PoolStats.FreeConns is renamed to PoolStats.IdleConns. -- Add Client.Do to simplify creating custom commands. -- Add Cmd.String, Cmd.Int, Cmd.Int64, Cmd.Uint64, Cmd.Float64, and Cmd.Bool helpers. -- Lower memory usage. - -## v6.13 - -- Ring got new options called `HashReplicas` and `Hash`. It is recommended to set - `HashReplicas = 1000` for better keys distribution between shards. -- Cluster client was optimized to use much less memory when reloading cluster state. -- PubSub.ReceiveMessage is re-worked to not use ReceiveTimeout so it does not lose data when timeout - occurres. In most cases it is recommended to use PubSub.Channel instead. -- Dialer.KeepAlive is set to 5 minutes by default. - -## v6.12 - -- ClusterClient got new option called `ClusterSlots` which allows to build cluster of normal Redis - Servers that don't have cluster mode enabled. See - https://godoc.org/github.com/go-redis/redis#example-NewClusterClient--ManualSetup +- Added support for [RESP3](https://github.com/antirez/RESP3/blob/master/spec.md) protocol. +- Removed `Pipeline.Close` since there is no real need to explicitly manage pipeline resources. + `Pipeline.Discard` is still available if you want to reset commands for some reason. +- Replaced `*redis.Z` with `redis.Z` since it is small enough to be passed as value. diff --git a/bench_decode_test.go b/bench_decode_test.go index 83828064..b07ad4ed 100644 --- a/bench_decode_test.go +++ b/bench_decode_test.go @@ -18,14 +18,17 @@ type ClientStub struct { resp []byte } +var initHello = []byte("%1\r\n+proto\r\n:3\r\n") + func NewClientStub(resp []byte) *ClientStub { stub := &ClientStub{ resp: resp, } + stub.Cmdable = NewClient(&Options{ PoolSize: 128, Dialer: func(ctx context.Context, network, addr string) (net.Conn, error) { - return stub.stubConn(), nil + return stub.stubConn(initHello), nil }, }) return stub @@ -40,7 +43,7 @@ func NewClusterClientStub(resp []byte) *ClientStub { PoolSize: 128, Addrs: []string{"127.0.0.1:6379"}, Dialer: func(ctx context.Context, network, addr string) (net.Conn, error) { - return stub.stubConn(), nil + return stub.stubConn(initHello), nil }, ClusterSlots: func(_ context.Context) ([]ClusterSlot, error) { return []ClusterSlot{ @@ -65,18 +68,27 @@ func NewClusterClientStub(resp []byte) *ClientStub { return stub } -func (c *ClientStub) stubConn() *ConnStub { +func (c *ClientStub) stubConn(init []byte) *ConnStub { return &ConnStub{ + init: init, resp: c.resp, } } type ConnStub struct { + init []byte resp []byte pos int } func (c *ConnStub) Read(b []byte) (n int, err error) { + // Return conn.init() + if len(c.init) > 0 { + n = copy(b, c.init) + c.init = c.init[n:] + return n, nil + } + if len(c.resp) == 0 { return 0, io.EOF } diff --git a/bench_test.go b/bench_test.go index ba81ce84..df43d890 100644 --- a/bench_test.go +++ b/bench_test.go @@ -223,7 +223,7 @@ func BenchmarkZAdd(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { - err := client.ZAdd(ctx, "key", &redis.Z{ + err := client.ZAdd(ctx, "key", redis.Z{ Score: float64(1), Member: "hello", }).Err() diff --git a/cluster.go b/cluster.go index a54f2f37..27dd31b3 100644 --- a/cluster.go +++ b/cluster.go @@ -795,7 +795,6 @@ func (c *ClusterClient) process(ctx context.Context, cmd Cmder) error { _ = pipe.Process(ctx, NewCmd(ctx, "asking")) _ = pipe.Process(ctx, cmd) _, lastErr = pipe.Exec(ctx) - _ = pipe.Close() ask = false } else { lastErr = node.Client.Process(ctx, cmd) @@ -1406,12 +1405,7 @@ func (c *ClusterClient) txPipelineReadQueued( return err } - switch line[0] { - case proto.ErrorReply: - return proto.ParseErrorReply(line) - case proto.ArrayReply: - // ok - default: + if line[0] != proto.RespArray { return fmt.Errorf("redis: expected '*', but got line %q", line) } diff --git a/cluster_test.go b/cluster_test.go index 6ee7364e..2d2021cf 100644 --- a/cluster_test.go +++ b/cluster_test.go @@ -515,9 +515,7 @@ var _ = Describe("ClusterClient", func() { pipe = client.Pipeline().(*redis.Pipeline) }) - AfterEach(func() { - Expect(pipe.Close()).NotTo(HaveOccurred()) - }) + AfterEach(func() {}) assertPipeline() }) @@ -527,9 +525,7 @@ var _ = Describe("ClusterClient", func() { pipe = client.TxPipeline().(*redis.Pipeline) }) - AfterEach(func() { - Expect(pipe.Close()).NotTo(HaveOccurred()) - }) + AfterEach(func() {}) assertPipeline() }) @@ -1182,16 +1178,17 @@ var _ = Describe("ClusterClient with unavailable Cluster", func() { var client *redis.ClusterClient BeforeEach(func() { - for _, node := range cluster.clients { - err := node.ClientPause(ctx, 5*time.Second).Err() - Expect(err).NotTo(HaveOccurred()) - } - opt := redisClusterOptions() opt.ReadTimeout = 250 * time.Millisecond opt.WriteTimeout = 250 * time.Millisecond opt.MaxRedirects = 1 client = cluster.newClusterClientUnstable(opt) + Expect(client.Ping(ctx).Err()).NotTo(HaveOccurred()) + + for _, node := range cluster.clients { + err := node.ClientPause(ctx, 5*time.Second).Err() + Expect(err).NotTo(HaveOccurred()) + } }) AfterEach(func() { diff --git a/command.go b/command.go index a97f9e97..a6beea66 100644 --- a/command.go +++ b/command.go @@ -464,31 +464,10 @@ func (cmd *Cmd) BoolSlice() ([]bool, error) { } func (cmd *Cmd) readReply(rd *proto.Reader) (err error) { - cmd.val, err = rd.ReadReply(sliceParser) + cmd.val, err = rd.ReadReply() return err } -// sliceParser implements proto.MultiBulkParse. -func sliceParser(rd *proto.Reader, n int64) (interface{}, error) { - vals := make([]interface{}, n) - for i := 0; i < len(vals); i++ { - v, err := rd.ReadReply(sliceParser) - if err != nil { - if err == Nil { - vals[i] = nil - continue - } - if err, ok := err.(proto.RedisError); ok { - vals[i] = err - continue - } - return nil, err - } - vals[i] = v - } - return vals, nil -} - //------------------------------------------------------------------------------ type SliceCmd struct { @@ -544,13 +523,9 @@ func (cmd *SliceCmd) Scan(dst interface{}) error { return hscan.Scan(dst, args, cmd.val) } -func (cmd *SliceCmd) readReply(rd *proto.Reader) error { - v, err := rd.ReadArrayReply(sliceParser) - if err != nil { - return err - } - cmd.val = v.([]interface{}) - return nil +func (cmd *SliceCmd) readReply(rd *proto.Reader) (err error) { + cmd.val, err = rd.ReadSlice() + return err } //------------------------------------------------------------------------------ @@ -633,7 +608,7 @@ func (cmd *IntCmd) String() string { } func (cmd *IntCmd) readReply(rd *proto.Reader) (err error) { - cmd.val, err = rd.ReadIntReply() + cmd.val, err = rd.ReadInt() return err } @@ -673,18 +648,17 @@ func (cmd *IntSliceCmd) String() string { } func (cmd *IntSliceCmd) readReply(rd *proto.Reader) error { - _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { - cmd.val = make([]int64, n) - for i := 0; i < len(cmd.val); i++ { - num, err := rd.ReadIntReply() - if err != nil { - return nil, err - } - cmd.val[i] = num + n, err := rd.ReadArrayLen() + if err != nil { + return err + } + cmd.val = make([]int64, n) + for i := 0; i < len(cmd.val); i++ { + if cmd.val[i], err = rd.ReadInt(); err != nil { + return err } - return nil, nil - }) - return err + } + return nil } //------------------------------------------------------------------------------ @@ -725,7 +699,7 @@ func (cmd *DurationCmd) String() string { } func (cmd *DurationCmd) readReply(rd *proto.Reader) error { - n, err := rd.ReadIntReply() + n, err := rd.ReadInt() if err != nil { return err } @@ -776,25 +750,19 @@ func (cmd *TimeCmd) String() string { } func (cmd *TimeCmd) readReply(rd *proto.Reader) error { - _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { - if n != 2 { - return nil, fmt.Errorf("got %d elements, expected 2", n) - } - - sec, err := rd.ReadInt() - if err != nil { - return nil, err - } - - microsec, err := rd.ReadInt() - if err != nil { - return nil, err - } - - cmd.val = time.Unix(sec, microsec*1000) - return nil, nil - }) - return err + if err := rd.ReadFixedArrayLen(2); err != nil { + return err + } + second, err := rd.ReadInt() + if err != nil { + return err + } + microsecond, err := rd.ReadInt() + if err != nil { + return err + } + cmd.val = time.Unix(second, microsecond*1000) + return nil } //------------------------------------------------------------------------------ @@ -832,27 +800,16 @@ func (cmd *BoolCmd) String() string { return cmdString(cmd, cmd.val) } -func (cmd *BoolCmd) readReply(rd *proto.Reader) error { - v, err := rd.ReadReply(nil) +func (cmd *BoolCmd) readReply(rd *proto.Reader) (err error) { + cmd.val, err = rd.ReadBool() + // `SET key value NX` returns nil when key already exists. But // `SETNX key value` returns bool (0/1). So convert nil to bool. if err == Nil { cmd.val = false - return nil - } - if err != nil { - return err - } - switch v := v.(type) { - case int64: - cmd.val = v == 1 - return nil - case string: - cmd.val = v == "OK" - return nil - default: - return fmt.Errorf("got %T, wanted int64 or string", v) + err = nil } + return err } //------------------------------------------------------------------------------ @@ -995,7 +952,7 @@ func (cmd *FloatCmd) String() string { } func (cmd *FloatCmd) readReply(rd *proto.Reader) (err error) { - cmd.val, err = rd.ReadFloatReply() + cmd.val, err = rd.ReadFloat() return err } @@ -1035,21 +992,23 @@ func (cmd *FloatSliceCmd) String() string { } func (cmd *FloatSliceCmd) readReply(rd *proto.Reader) error { - _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { - cmd.val = make([]float64, n) - for i := 0; i < len(cmd.val); i++ { - switch num, err := rd.ReadFloatReply(); { - case err == Nil: - cmd.val[i] = 0 - case err != nil: - return nil, err - default: - cmd.val[i] = num - } + n, err := rd.ReadArrayLen() + if err != nil { + return err + } + + cmd.val = make([]float64, n) + for i := 0; i < len(cmd.val); i++ { + switch num, err := rd.ReadFloat(); { + case err == Nil: + cmd.val[i] = 0 + case err != nil: + return err + default: + cmd.val[i] = num } - return nil, nil - }) - return err + } + return nil } //------------------------------------------------------------------------------ @@ -1092,21 +1051,111 @@ func (cmd *StringSliceCmd) ScanSlice(container interface{}) error { } func (cmd *StringSliceCmd) readReply(rd *proto.Reader) error { - _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { - cmd.val = make([]string, n) - for i := 0; i < len(cmd.val); i++ { - switch s, err := rd.ReadString(); { - case err == Nil: - cmd.val[i] = "" - case err != nil: - return nil, err - default: - cmd.val[i] = s + n, err := rd.ReadArrayLen() + if err != nil { + return err + } + cmd.val = make([]string, n) + for i := 0; i < len(cmd.val); i++ { + switch s, err := rd.ReadString(); { + case err == Nil: + cmd.val[i] = "" + case err != nil: + return err + default: + cmd.val[i] = s + } + } + return nil +} + +//------------------------------------------------------------------------------ + +type KeyValue struct { + Key string + Value string +} + +type KeyValueSliceCmd struct { + baseCmd + + val []KeyValue +} + +var _ Cmder = (*KeyValueSliceCmd)(nil) + +func NewKeyValueSliceCmd(ctx context.Context, args ...interface{}) *KeyValueSliceCmd { + return &KeyValueSliceCmd{ + baseCmd: baseCmd{ + ctx: ctx, + args: args, + }, + } +} + +func (cmd *KeyValueSliceCmd) Val() []KeyValue { + return cmd.val +} + +func (cmd *KeyValueSliceCmd) Result() ([]KeyValue, error) { + return cmd.val, cmd.err +} + +func (cmd *KeyValueSliceCmd) String() string { + return cmdString(cmd, cmd.val) +} + +// Many commands will respond to two formats: +// 1) 1) "one" +// 2) (double) 1 +// 2) 1) "two" +// 2) (double) 2 +// OR: +// 1) "two" +// 2) (double) 2 +// 3) "one" +// 4) (double) 1 +func (cmd *KeyValueSliceCmd) readReply(rd *proto.Reader) error { // nolint:dupl + n, err := rd.ReadArrayLen() + if err != nil { + return err + } + + // If the n is 0, can't continue reading. + if n == 0 { + cmd.val = make([]KeyValue, 0) + return nil + } + + typ, err := rd.PeekReplyType() + if err != nil { + return err + } + array := typ == proto.RespArray + + if array { + cmd.val = make([]KeyValue, n) + } else { + cmd.val = make([]KeyValue, n/2) + } + + for i := 0; i < len(cmd.val); i++ { + if array { + if err = rd.ReadFixedArrayLen(2); err != nil { + return err } } - return nil, nil - }) - return err + + if cmd.val[i].Key, err = rd.ReadString(); err != nil { + return err + } + + if cmd.val[i].Value, err = rd.ReadString(); err != nil { + return err + } + } + + return nil } //------------------------------------------------------------------------------ @@ -1145,32 +1194,31 @@ func (cmd *BoolSliceCmd) String() string { } func (cmd *BoolSliceCmd) readReply(rd *proto.Reader) error { - _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { - cmd.val = make([]bool, n) - for i := 0; i < len(cmd.val); i++ { - n, err := rd.ReadIntReply() - if err != nil { - return nil, err - } - cmd.val[i] = n == 1 + n, err := rd.ReadArrayLen() + if err != nil { + return err + } + cmd.val = make([]bool, n) + for i := 0; i < len(cmd.val); i++ { + if cmd.val[i], err = rd.ReadBool(); err != nil { + return err } - return nil, nil - }) - return err + } + return nil } //------------------------------------------------------------------------------ -type StringStringMapCmd struct { +type MapStringStringCmd struct { baseCmd val map[string]string } -var _ Cmder = (*StringStringMapCmd)(nil) +var _ Cmder = (*MapStringStringCmd)(nil) -func NewStringStringMapCmd(ctx context.Context, args ...interface{}) *StringStringMapCmd { - return &StringStringMapCmd{ +func NewMapStringStringCmd(ctx context.Context, args ...interface{}) *MapStringStringCmd { + return &MapStringStringCmd{ baseCmd: baseCmd{ ctx: ctx, args: args, @@ -1178,25 +1226,25 @@ func NewStringStringMapCmd(ctx context.Context, args ...interface{}) *StringStri } } -func (cmd *StringStringMapCmd) SetVal(val map[string]string) { - cmd.val = val -} - -func (cmd *StringStringMapCmd) Val() map[string]string { +func (cmd *MapStringStringCmd) Val() map[string]string { return cmd.val } -func (cmd *StringStringMapCmd) Result() (map[string]string, error) { +func (cmd *MapStringStringCmd) SetVal(val map[string]string) { + cmd.val = val +} + +func (cmd *MapStringStringCmd) Result() (map[string]string, error) { return cmd.val, cmd.err } -func (cmd *StringStringMapCmd) String() string { +func (cmd *MapStringStringCmd) String() string { return cmdString(cmd, cmd.val) } // Scan scans the results from the map into a destination struct. The map keys // are matched in the Redis struct fields by the `redis:"field"` tag. -func (cmd *StringStringMapCmd) Scan(dest interface{}) error { +func (cmd *MapStringStringCmd) Scan(dest interface{}) error { if cmd.err != nil { return cmd.err } @@ -1215,25 +1263,27 @@ func (cmd *StringStringMapCmd) Scan(dest interface{}) error { return nil } -func (cmd *StringStringMapCmd) readReply(rd *proto.Reader) error { - _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { - cmd.val = make(map[string]string, n/2) - for i := int64(0); i < n; i += 2 { - key, err := rd.ReadString() - if err != nil { - return nil, err - } +func (cmd *MapStringStringCmd) readReply(rd *proto.Reader) error { + n, err := rd.ReadMapLen() + if err != nil { + return err + } - value, err := rd.ReadString() - if err != nil { - return nil, err - } - - cmd.val[key] = value + cmd.val = make(map[string]string, n) + for i := 0; i < n; i++ { + key, err := rd.ReadString() + if err != nil { + return err } - return nil, nil - }) - return err + + value, err := rd.ReadString() + if err != nil { + return err + } + + cmd.val[key] = value + } + return nil } //------------------------------------------------------------------------------ @@ -1272,24 +1322,25 @@ func (cmd *StringIntMapCmd) String() string { } func (cmd *StringIntMapCmd) readReply(rd *proto.Reader) error { - _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { - cmd.val = make(map[string]int64, n/2) - for i := int64(0); i < n; i += 2 { - key, err := rd.ReadString() - if err != nil { - return nil, err - } + n, err := rd.ReadMapLen() + if err != nil { + return err + } - n, err := rd.ReadIntReply() - if err != nil { - return nil, err - } - - cmd.val[key] = n + cmd.val = make(map[string]int64, n) + for i := 0; i < n; i++ { + key, err := rd.ReadString() + if err != nil { + return err } - return nil, nil - }) - return err + + nn, err := rd.ReadInt() + if err != nil { + return err + } + cmd.val[key] = nn + } + return nil } //------------------------------------------------------------------------------ @@ -1328,18 +1379,20 @@ func (cmd *StringStructMapCmd) String() string { } func (cmd *StringStructMapCmd) readReply(rd *proto.Reader) error { - _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { - cmd.val = make(map[string]struct{}, n) - for i := int64(0); i < n; i++ { - key, err := rd.ReadString() - if err != nil { - return nil, err - } - cmd.val[key] = struct{}{} + n, err := rd.ReadArrayLen() + if err != nil { + return err + } + + cmd.val = make(map[string]struct{}, n) + for i := 0; i < n; i++ { + key, err := rd.ReadString() + if err != nil { + return err } - return nil, nil - }) - return err + cmd.val[key] = struct{}{} + } + return nil } //------------------------------------------------------------------------------ @@ -1382,8 +1435,7 @@ func (cmd *XMessageSliceCmd) String() string { return cmdString(cmd, cmd.val) } -func (cmd *XMessageSliceCmd) readReply(rd *proto.Reader) error { - var err error +func (cmd *XMessageSliceCmd) readReply(rd *proto.Reader) (err error) { cmd.val, err = readXMessageSlice(rd) return err } @@ -1395,10 +1447,8 @@ func readXMessageSlice(rd *proto.Reader) ([]XMessage, error) { } msgs := make([]XMessage, n) - for i := 0; i < n; i++ { - var err error - msgs[i], err = readXMessage(rd) - if err != nil { + for i := 0; i < len(msgs); i++ { + if msgs[i], err = readXMessage(rd); err != nil { return nil, err } } @@ -1406,40 +1456,36 @@ func readXMessageSlice(rd *proto.Reader) ([]XMessage, error) { } func readXMessage(rd *proto.Reader) (XMessage, error) { - n, err := rd.ReadArrayLen() - if err != nil { + if err := rd.ReadFixedArrayLen(2); err != nil { return XMessage{}, err } - if n != 2 { - return XMessage{}, fmt.Errorf("got %d, wanted 2", n) - } id, err := rd.ReadString() if err != nil { return XMessage{}, err } - var values map[string]interface{} - - v, err := rd.ReadArrayReply(stringInterfaceMapParser) + v, err := stringInterfaceMapParser(rd) if err != nil { if err != proto.Nil { return XMessage{}, err } - } else { - values = v.(map[string]interface{}) } return XMessage{ ID: id, - Values: values, + Values: v, }, nil } -// stringInterfaceMapParser implements proto.MultiBulkParse. -func stringInterfaceMapParser(rd *proto.Reader, n int64) (interface{}, error) { - m := make(map[string]interface{}, n/2) - for i := int64(0); i < n; i += 2 { +func stringInterfaceMapParser(rd *proto.Reader) (map[string]interface{}, error) { + n, err := rd.ReadMapLen() + if err != nil { + return nil, err + } + + m := make(map[string]interface{}, n) + for i := 0; i < n; i++ { key, err := rd.ReadString() if err != nil { return nil, err @@ -1496,38 +1542,35 @@ func (cmd *XStreamSliceCmd) String() string { } func (cmd *XStreamSliceCmd) readReply(rd *proto.Reader) error { - _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { - cmd.val = make([]XStream, n) - for i := 0; i < len(cmd.val); i++ { - i := i - _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { - if n != 2 { - return nil, fmt.Errorf("got %d, wanted 2", n) - } + typ, err := rd.PeekReplyType() + if err != nil { + return err + } - stream, err := rd.ReadString() - if err != nil { - return nil, err - } - - msgs, err := readXMessageSlice(rd) - if err != nil { - return nil, err - } - - cmd.val[i] = XStream{ - Stream: stream, - Messages: msgs, - } - return nil, nil - }) - if err != nil { - return nil, err + var n int + if typ == proto.RespMap { + n, err = rd.ReadMapLen() + } else { + n, err = rd.ReadArrayLen() + } + if err != nil { + return err + } + cmd.val = make([]XStream, n) + for i := 0; i < len(cmd.val); i++ { + if typ != proto.RespMap { + if err = rd.ReadFixedArrayLen(2); err != nil { + return err } } - return nil, nil - }) - return err + if cmd.val[i].Stream, err = rd.ReadString(); err != nil { + return err + } + if cmd.val[i].Messages, err = readXMessageSlice(rd); err != nil { + return err + } + } + return nil } //------------------------------------------------------------------------------ @@ -1572,68 +1615,45 @@ func (cmd *XPendingCmd) String() string { } func (cmd *XPendingCmd) readReply(rd *proto.Reader) error { - _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { - if n != 4 { - return nil, fmt.Errorf("got %d, wanted 4", n) + var err error + if err = rd.ReadFixedArrayLen(4); err != nil { + return err + } + cmd.val = &XPending{} + + if cmd.val.Count, err = rd.ReadInt(); err != nil { + return err + } + + if cmd.val.Lower, err = rd.ReadString(); err != nil && err != Nil { + return err + } + + if cmd.val.Higher, err = rd.ReadString(); err != nil && err != Nil { + return err + } + + n, err := rd.ReadArrayLen() + if err != nil && err != Nil { + return err + } + cmd.val.Consumers = make(map[string]int64, n) + for i := 0; i < n; i++ { + if err = rd.ReadFixedArrayLen(2); err != nil { + return err } - count, err := rd.ReadIntReply() + consumerName, err := rd.ReadString() if err != nil { - return nil, err + return err } - - lower, err := rd.ReadString() - if err != nil && err != Nil { - return nil, err + consumerPending, err := rd.ReadInt() + if err != nil { + return err } - - higher, err := rd.ReadString() - if err != nil && err != Nil { - return nil, err - } - - cmd.val = &XPending{ - Count: count, - Lower: lower, - Higher: higher, - } - _, err = rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { - for i := int64(0); i < n; i++ { - _, err = rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { - if n != 2 { - return nil, fmt.Errorf("got %d, wanted 2", n) - } - - consumerName, err := rd.ReadString() - if err != nil { - return nil, err - } - - consumerPending, err := rd.ReadInt() - if err != nil { - return nil, err - } - - if cmd.val.Consumers == nil { - cmd.val.Consumers = make(map[string]int64) - } - cmd.val.Consumers[consumerName] = consumerPending - - return nil, nil - }) - if err != nil { - return nil, err - } - } - return nil, nil - }) - if err != nil && err != Nil { - return nil, err - } - - return nil, nil - }) - return err + cmd.val.Consumers[consumerName] = consumerPending + } + return nil } //------------------------------------------------------------------------------ @@ -1678,49 +1698,37 @@ func (cmd *XPendingExtCmd) String() string { } func (cmd *XPendingExtCmd) readReply(rd *proto.Reader) error { - _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { - cmd.val = make([]XPendingExt, 0, n) - for i := int64(0); i < n; i++ { - _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { - if n != 4 { - return nil, fmt.Errorf("got %d, wanted 4", n) - } + n, err := rd.ReadArrayLen() + if err != nil { + return err + } + cmd.val = make([]XPendingExt, n) - id, err := rd.ReadString() - if err != nil { - return nil, err - } - - consumer, err := rd.ReadString() - if err != nil && err != Nil { - return nil, err - } - - idle, err := rd.ReadIntReply() - if err != nil && err != Nil { - return nil, err - } - - retryCount, err := rd.ReadIntReply() - if err != nil && err != Nil { - return nil, err - } - - cmd.val = append(cmd.val, XPendingExt{ - ID: id, - Consumer: consumer, - Idle: time.Duration(idle) * time.Millisecond, - RetryCount: retryCount, - }) - return nil, nil - }) - if err != nil { - return nil, err - } + for i := 0; i < len(cmd.val); i++ { + if err = rd.ReadFixedArrayLen(4); err != nil { + return err } - return nil, nil - }) - return err + + if cmd.val[i].ID, err = rd.ReadString(); err != nil { + return err + } + + if cmd.val[i].Consumer, err = rd.ReadString(); err != nil && err != Nil { + return err + } + + idle, err := rd.ReadInt() + if err != nil && err != Nil { + return err + } + cmd.val[i].Idle = time.Duration(idle) * time.Millisecond + + if cmd.val[i].RetryCount, err = rd.ReadInt(); err != nil && err != Nil { + return err + } + } + + return nil } //------------------------------------------------------------------------------ @@ -1761,25 +1769,20 @@ func (cmd *XAutoClaimCmd) String() string { } func (cmd *XAutoClaimCmd) readReply(rd *proto.Reader) error { - _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { - if n != 2 { - return nil, fmt.Errorf("got %d, wanted 2", n) - } - var err error + var err error + if err = rd.ReadFixedArrayLen(2); err != nil { + return err + } - cmd.start, err = rd.ReadString() - if err != nil { - return nil, err - } - - cmd.val, err = readXMessageSlice(rd) - if err != nil { - return nil, err - } - - return nil, nil - }) - return err + cmd.start, err = rd.ReadString() + if err != nil { + return err + } + cmd.val, err = readXMessageSlice(rd) + if err != nil { + return err + } + return nil } //------------------------------------------------------------------------------ @@ -1820,33 +1823,28 @@ func (cmd *XAutoClaimJustIDCmd) String() string { } func (cmd *XAutoClaimJustIDCmd) readReply(rd *proto.Reader) error { - _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { - if n != 2 { - return nil, fmt.Errorf("got %d, wanted 2", n) - } - var err error + var err error + if err = rd.ReadFixedArrayLen(2); err != nil { + return err + } - cmd.start, err = rd.ReadString() + cmd.start, err = rd.ReadString() + if err != nil { + return err + } + n, err := rd.ReadArrayLen() + if err != nil { + return err + } + + cmd.val = make([]string, n) + for i := 0; i < n; i++ { + cmd.val[i], err = rd.ReadString() if err != nil { - return nil, err + return err } - - nn, err := rd.ReadArrayLen() - if err != nil { - return nil, err - } - - cmd.val = make([]string, nn) - for i := 0; i < nn; i++ { - cmd.val[i], err = rd.ReadString() - if err != nil { - return nil, err - } - } - - return nil, nil - }) - return err + } + return nil } //------------------------------------------------------------------------------ @@ -1859,7 +1857,7 @@ type XInfoConsumersCmd struct { type XInfoConsumer struct { Name string Pending int64 - Idle int64 + Idle time.Duration } var _ Cmder = (*XInfoConsumersCmd)(nil) @@ -1894,62 +1892,41 @@ func (cmd *XInfoConsumersCmd) readReply(rd *proto.Reader) error { if err != nil { return err } - cmd.val = make([]XInfoConsumer, n) - for i := 0; i < n; i++ { - cmd.val[i], err = readXConsumerInfo(rd) - if err != nil { + for i := 0; i < len(cmd.val); i++ { + if err = rd.ReadFixedMapLen(3); err != nil { return err } + + var key string + for f := 0; f < 3; f++ { + key, err = rd.ReadString() + if err != nil { + return err + } + + switch key { + case "name": + cmd.val[i].Name, err = rd.ReadString() + case "pending": + cmd.val[i].Pending, err = rd.ReadInt() + case "idle": + var idle int64 + idle, err = rd.ReadInt() + cmd.val[i].Idle = time.Duration(idle) * time.Millisecond + default: + return fmt.Errorf("redis: unexpected content %s in XINFO CONSUMERS reply", key) + } + if err != nil { + return err + } + } } return nil } -func readXConsumerInfo(rd *proto.Reader) (XInfoConsumer, error) { - var consumer XInfoConsumer - - n, err := rd.ReadArrayLen() - if err != nil { - return consumer, err - } - if n != 6 { - return consumer, fmt.Errorf("redis: got %d elements in XINFO CONSUMERS reply, wanted 6", n) - } - - for i := 0; i < 3; i++ { - key, err := rd.ReadString() - if err != nil { - return consumer, err - } - - val, err := rd.ReadString() - if err != nil { - return consumer, err - } - - switch key { - case "name": - consumer.Name = val - case "pending": - consumer.Pending, err = strconv.ParseInt(val, 0, 64) - if err != nil { - return consumer, err - } - case "idle": - consumer.Idle, err = strconv.ParseInt(val, 0, 64) - if err != nil { - return consumer, err - } - default: - return consumer, fmt.Errorf("redis: unexpected content %s in XINFO CONSUMERS reply", key) - } - } - - return consumer, nil -} - //------------------------------------------------------------------------------ type XInfoGroupsCmd struct { @@ -1996,64 +1973,41 @@ func (cmd *XInfoGroupsCmd) readReply(rd *proto.Reader) error { if err != nil { return err } - cmd.val = make([]XInfoGroup, n) - for i := 0; i < n; i++ { - cmd.val[i], err = readXGroupInfo(rd) - if err != nil { + for i := 0; i < len(cmd.val); i++ { + if err = rd.ReadFixedMapLen(4); err != nil { return err } + + var key string + for f := 0; f < 4; f++ { + key, err = rd.ReadString() + if err != nil { + return err + } + + switch key { + case "name": + cmd.val[i].Name, err = rd.ReadString() + case "consumers": + cmd.val[i].Consumers, err = rd.ReadInt() + case "pending": + cmd.val[i].Pending, err = rd.ReadInt() + case "last-delivered-id": + cmd.val[i].LastDeliveredID, err = rd.ReadString() + default: + return fmt.Errorf("redis: unexpected content %s in XINFO GROUPS reply", key) + } + if err != nil { + return err + } + } } return nil } -func readXGroupInfo(rd *proto.Reader) (XInfoGroup, error) { - var group XInfoGroup - - n, err := rd.ReadArrayLen() - if err != nil { - return group, err - } - if n != 8 { - return group, fmt.Errorf("redis: got %d elements in XINFO GROUPS reply, wanted 8", n) - } - - for i := 0; i < 4; i++ { - key, err := rd.ReadString() - if err != nil { - return group, err - } - - val, err := rd.ReadString() - if err != nil { - return group, err - } - - switch key { - case "name": - group.Name = val - case "consumers": - group.Consumers, err = strconv.ParseInt(val, 0, 64) - if err != nil { - return group, err - } - case "pending": - group.Pending, err = strconv.ParseInt(val, 0, 64) - if err != nil { - return group, err - } - case "last-delivered-id": - group.LastDeliveredID = val - default: - return group, fmt.Errorf("redis: unexpected content %s in XINFO GROUPS reply", key) - } - } - - return group, nil -} - //------------------------------------------------------------------------------ type XInfoStreamCmd struct { @@ -2099,55 +2053,46 @@ func (cmd *XInfoStreamCmd) String() string { } func (cmd *XInfoStreamCmd) readReply(rd *proto.Reader) error { - v, err := rd.ReadReply(xStreamInfoParser) - if err != nil { + if err := rd.ReadFixedMapLen(7); err != nil { return err } - cmd.val = v.(*XInfoStream) - return nil -} + cmd.val = &XInfoStream{} -func xStreamInfoParser(rd *proto.Reader, n int64) (interface{}, error) { - if n != 14 { - return nil, fmt.Errorf("redis: got %d elements in XINFO STREAM reply,"+ - "wanted 14", n) - } - var info XInfoStream for i := 0; i < 7; i++ { key, err := rd.ReadString() if err != nil { - return nil, err + return err } switch key { case "length": - info.Length, err = rd.ReadIntReply() + cmd.val.Length, err = rd.ReadInt() case "radix-tree-keys": - info.RadixTreeKeys, err = rd.ReadIntReply() + cmd.val.RadixTreeKeys, err = rd.ReadInt() case "radix-tree-nodes": - info.RadixTreeNodes, err = rd.ReadIntReply() + cmd.val.RadixTreeNodes, err = rd.ReadInt() case "groups": - info.Groups, err = rd.ReadIntReply() + cmd.val.Groups, err = rd.ReadInt() case "last-generated-id": - info.LastGeneratedID, err = rd.ReadString() + cmd.val.LastGeneratedID, err = rd.ReadString() case "first-entry": - info.FirstEntry, err = readXMessage(rd) + cmd.val.FirstEntry, err = readXMessage(rd) if err == Nil { err = nil } case "last-entry": - info.LastEntry, err = readXMessage(rd) + cmd.val.LastEntry, err = readXMessage(rd) if err == Nil { err = nil } default: - return nil, fmt.Errorf("redis: unexpected content %s "+ + return fmt.Errorf("redis: unexpected content %s "+ "in XINFO STREAM reply", key) } if err != nil { - return nil, err + return err } } - return &info, nil + return nil } //------------------------------------------------------------------------------ @@ -2222,14 +2167,9 @@ func (cmd *XInfoStreamFullCmd) String() string { } func (cmd *XInfoStreamFullCmd) readReply(rd *proto.Reader) error { - n, err := rd.ReadArrayLen() - if err != nil { + if err := rd.ReadFixedMapLen(6); err != nil { return err } - if n != 12 { - return fmt.Errorf("redis: got %d elements in XINFO STREAM FULL reply,"+ - "wanted 12", n) - } cmd.val = &XInfoStreamFull{} @@ -2241,11 +2181,11 @@ func (cmd *XInfoStreamFullCmd) readReply(rd *proto.Reader) error { switch key { case "length": - cmd.val.Length, err = rd.ReadIntReply() + cmd.val.Length, err = rd.ReadInt() case "radix-tree-keys": - cmd.val.RadixTreeKeys, err = rd.ReadIntReply() + cmd.val.RadixTreeKeys, err = rd.ReadInt() case "radix-tree-nodes": - cmd.val.RadixTreeNodes, err = rd.ReadIntReply() + cmd.val.RadixTreeNodes, err = rd.ReadInt() case "last-generated-id": cmd.val.LastGeneratedID, err = rd.ReadString() case "entries": @@ -2254,7 +2194,7 @@ func (cmd *XInfoStreamFullCmd) readReply(rd *proto.Reader) error { cmd.val.Groups, err = readStreamGroups(rd) default: return fmt.Errorf("redis: unexpected content %s "+ - "in XINFO STREAM reply", key) + "in XINFO STREAM FULL reply", key) } if err != nil { return err @@ -2270,14 +2210,9 @@ func readStreamGroups(rd *proto.Reader) ([]XInfoStreamGroup, error) { } groups := make([]XInfoStreamGroup, 0, n) for i := 0; i < n; i++ { - nn, err := rd.ReadArrayLen() - if err != nil { + if err = rd.ReadFixedMapLen(5); err != nil { return nil, err } - if nn != 10 { - return nil, fmt.Errorf("redis: got %d elements in XINFO STREAM FULL reply,"+ - "wanted 10", nn) - } group := XInfoStreamGroup{} @@ -2293,14 +2228,14 @@ func readStreamGroups(rd *proto.Reader) ([]XInfoStreamGroup, error) { case "last-delivered-id": group.LastDeliveredID, err = rd.ReadString() case "pel-count": - group.PelCount, err = rd.ReadIntReply() + group.PelCount, err = rd.ReadInt() case "pending": group.Pending, err = readXInfoStreamGroupPending(rd) case "consumers": group.Consumers, err = readXInfoStreamConsumers(rd) default: return nil, fmt.Errorf("redis: unexpected content %s "+ - "in XINFO STREAM reply", key) + "in XINFO STREAM FULL reply", key) } if err != nil { @@ -2323,14 +2258,9 @@ func readXInfoStreamGroupPending(rd *proto.Reader) ([]XInfoStreamGroupPending, e pending := make([]XInfoStreamGroupPending, 0, n) for i := 0; i < n; i++ { - nn, err := rd.ReadArrayLen() - if err != nil { + if err = rd.ReadFixedArrayLen(4); err != nil { return nil, err } - if nn != 4 { - return nil, fmt.Errorf("redis: got %d elements in XINFO STREAM FULL reply,"+ - "wanted 4", nn) - } p := XInfoStreamGroupPending{} @@ -2344,13 +2274,13 @@ func readXInfoStreamGroupPending(rd *proto.Reader) ([]XInfoStreamGroupPending, e return nil, err } - delivery, err := rd.ReadIntReply() + delivery, err := rd.ReadInt() if err != nil { return nil, err } p.DeliveryTime = time.Unix(delivery/1000, delivery%1000*int64(time.Millisecond)) - p.DeliveryCount, err = rd.ReadIntReply() + p.DeliveryCount, err = rd.ReadInt() if err != nil { return nil, err } @@ -2370,14 +2300,9 @@ func readXInfoStreamConsumers(rd *proto.Reader) ([]XInfoStreamConsumer, error) { consumers := make([]XInfoStreamConsumer, 0, n) for i := 0; i < n; i++ { - nn, err := rd.ReadArrayLen() - if err != nil { + if err = rd.ReadFixedMapLen(4); err != nil { return nil, err } - if nn != 8 { - return nil, fmt.Errorf("redis: got %d elements in XINFO STREAM FULL reply,"+ - "wanted 8", nn) - } c := XInfoStreamConsumer{} @@ -2391,13 +2316,13 @@ func readXInfoStreamConsumers(rd *proto.Reader) ([]XInfoStreamConsumer, error) { case "name": c.Name, err = rd.ReadString() case "seen-time": - seen, err := rd.ReadIntReply() + seen, err := rd.ReadInt() if err != nil { return nil, err } c.SeenTime = time.Unix(seen/1000, seen%1000*int64(time.Millisecond)) case "pel-count": - c.PelCount, err = rd.ReadIntReply() + c.PelCount, err = rd.ReadInt() case "pending": pendingNumber, err := rd.ReadArrayLen() if err != nil { @@ -2407,14 +2332,9 @@ func readXInfoStreamConsumers(rd *proto.Reader) ([]XInfoStreamConsumer, error) { c.Pending = make([]XInfoStreamConsumerPending, 0, pendingNumber) for pn := 0; pn < pendingNumber; pn++ { - nn, err := rd.ReadArrayLen() - if err != nil { + if err = rd.ReadFixedArrayLen(3); err != nil { return nil, err } - if nn != 3 { - return nil, fmt.Errorf("redis: got %d elements in XINFO STREAM reply,"+ - "wanted 3", nn) - } p := XInfoStreamConsumerPending{} @@ -2423,13 +2343,13 @@ func readXInfoStreamConsumers(rd *proto.Reader) ([]XInfoStreamConsumer, error) { return nil, err } - delivery, err := rd.ReadIntReply() + delivery, err := rd.ReadInt() if err != nil { return nil, err } p.DeliveryTime = time.Unix(delivery/1000, delivery%1000*int64(time.Millisecond)) - p.DeliveryCount, err = rd.ReadIntReply() + p.DeliveryCount, err = rd.ReadInt() if err != nil { return nil, err } @@ -2438,7 +2358,7 @@ func readXInfoStreamConsumers(rd *proto.Reader) ([]XInfoStreamConsumer, error) { } default: return nil, fmt.Errorf("redis: unexpected content %s "+ - "in XINFO STREAM reply", cKey) + "in XINFO STREAM FULL reply", cKey) } if err != nil { return nil, err @@ -2485,28 +2405,47 @@ func (cmd *ZSliceCmd) String() string { return cmdString(cmd, cmd.val) } -func (cmd *ZSliceCmd) readReply(rd *proto.Reader) error { - _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { +func (cmd *ZSliceCmd) readReply(rd *proto.Reader) error { // nolint:dupl + n, err := rd.ReadArrayLen() + if err != nil { + return err + } + + // If the n is 0, can't continue reading. + if n == 0 { + cmd.val = make([]Z, 0) + return nil + } + + typ, err := rd.PeekReplyType() + if err != nil { + return err + } + array := typ == proto.RespArray + + if array { + cmd.val = make([]Z, n) + } else { cmd.val = make([]Z, n/2) - for i := 0; i < len(cmd.val); i++ { - member, err := rd.ReadString() - if err != nil { - return nil, err - } + } - score, err := rd.ReadFloatReply() - if err != nil { - return nil, err - } - - cmd.val[i] = Z{ - Member: member, - Score: score, + for i := 0; i < len(cmd.val); i++ { + if array { + if err = rd.ReadFixedArrayLen(2); err != nil { + return err } } - return nil, nil - }) - return err + + if cmd.val[i].Member, err = rd.ReadString(); err != nil { + return err + } + + if cmd.val[i].Score, err = rd.ReadFloat(); err != nil { + return err + } + } + + return nil } //------------------------------------------------------------------------------ @@ -2544,33 +2483,23 @@ func (cmd *ZWithKeyCmd) String() string { return cmdString(cmd, cmd.val) } -func (cmd *ZWithKeyCmd) readReply(rd *proto.Reader) error { - _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { - if n != 3 { - return nil, fmt.Errorf("got %d elements, expected 3", n) - } +func (cmd *ZWithKeyCmd) readReply(rd *proto.Reader) (err error) { + if err = rd.ReadFixedArrayLen(3); err != nil { + return err + } + cmd.val = &ZWithKey{} - cmd.val = &ZWithKey{} - var err error + if cmd.val.Key, err = rd.ReadString(); err != nil { + return err + } + if cmd.val.Member, err = rd.ReadString(); err != nil { + return err + } + if cmd.val.Score, err = rd.ReadFloat(); err != nil { + return err + } - cmd.val.Key, err = rd.ReadString() - if err != nil { - return nil, err - } - - cmd.val.Member, err = rd.ReadString() - if err != nil { - return nil, err - } - - cmd.val.Score, err = rd.ReadFloatReply() - if err != nil { - return nil, err - } - - return nil, nil - }) - return err + return nil } //------------------------------------------------------------------------------ @@ -2613,9 +2542,29 @@ func (cmd *ScanCmd) String() string { return cmdString(cmd, cmd.page) } -func (cmd *ScanCmd) readReply(rd *proto.Reader) (err error) { - cmd.page, cmd.cursor, err = rd.ReadScanReply() - return err +func (cmd *ScanCmd) readReply(rd *proto.Reader) error { + if err := rd.ReadFixedArrayLen(2); err != nil { + return err + } + + cursor, err := rd.ReadInt() + if err != nil { + return err + } + cmd.cursor = uint64(cursor) + + n, err := rd.ReadArrayLen() + if err != nil { + return err + } + cmd.page = make([]string, n) + + for i := 0; i < len(cmd.page); i++ { + if cmd.page[i], err = rd.ReadString(); err != nil { + return err + } + } + return nil } // Iterator creates a new ScanIterator. @@ -2673,92 +2622,70 @@ func (cmd *ClusterSlotsCmd) String() string { } func (cmd *ClusterSlotsCmd) readReply(rd *proto.Reader) error { - _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { - cmd.val = make([]ClusterSlot, n) - for i := 0; i < len(cmd.val); i++ { - n, err := rd.ReadArrayLen() + n, err := rd.ReadArrayLen() + if err != nil { + return err + } + cmd.val = make([]ClusterSlot, n) + + for i := 0; i < len(cmd.val); i++ { + n, err = rd.ReadArrayLen() + if err != nil { + return err + } + if n < 2 { + return fmt.Errorf("redis: got %d elements in cluster info, expected at least 2", n) + } + + start, err := rd.ReadInt() + if err != nil { + return err + } + + end, err := rd.ReadInt() + if err != nil { + return err + } + + // subtract start and end. + nodes := make([]ClusterNode, n-2) + for j := 0; j < len(nodes); j++ { + nn, err := rd.ReadArrayLen() if err != nil { - return nil, err + return err } - if n < 2 { - err := fmt.Errorf("redis: got %d elements in cluster info, expected at least 2", n) - return nil, err + if nn != 2 && nn != 3 { + return fmt.Errorf("got %d elements in cluster info address, expected 2 or 3", nn) } - start, err := rd.ReadIntReply() + ip, err := rd.ReadString() if err != nil { - return nil, err + return err } - end, err := rd.ReadIntReply() + port, err := rd.ReadString() if err != nil { - return nil, err + return err } - nodes := make([]ClusterNode, n-2) - for j := 0; j < len(nodes); j++ { - n, err := rd.ReadArrayLen() + nodes[j].Addr = net.JoinHostPort(ip, port) + + if nn == 3 { + id, err := rd.ReadString() if err != nil { - return nil, err + return err } - if n < 2 || n > 4 { - err := fmt.Errorf("got %d elements in cluster info address, shoud be between 2 and 4", n) - return nil, err - } - - ip, err := rd.ReadString() - if err != nil { - return nil, err - } - - port, err := rd.ReadString() - if err != nil { - return nil, err - } - - nodes[j].Addr = net.JoinHostPort(ip, port) - - if n >= 3 { - id, err := rd.ReadString() - if err != nil { - return nil, err - } - nodes[j].ID = id - } - if n == 4 { - networkingMetadata := make(map[string]string) - metadataLength, err := rd.ReadArrayLen() - if err != nil { - return nil, err - } - if metadataLength%2 != 0 { - err := fmt.Errorf("the array length of metadata must be a even number, current: %d", metadataLength) - return nil, err - } - for i := 0; i < metadataLength; i = i + 2 { - key, err := rd.ReadString() - if err != nil { - return nil, err - } - value, err := rd.ReadString() - if err != nil { - return nil, err - } - networkingMetadata[key] = value - } - nodes[j].NetworkingMetadata = networkingMetadata - } - } - - cmd.val[i] = ClusterSlot{ - Start: int(start), - End: int(end), - Nodes: nodes, + nodes[j].ID = id } } - return nil, nil - }) - return err + cmd.val[i] = ClusterSlot{ + Start: int(start), + End: int(end), + Nodes: nodes, + } + } + + return nil } //------------------------------------------------------------------------------ @@ -2783,6 +2710,9 @@ type GeoRadiusQuery struct { Sort string Store string StoreDist string + + // WithCoord+WithDist+WithGeoHash + withLen int } type GeoLocationCmd struct { @@ -2813,12 +2743,15 @@ func geoLocationArgs(q *GeoRadiusQuery, args ...interface{}) []interface{} { } if q.WithCoord { args = append(args, "withcoord") + q.withLen++ } if q.WithDist { args = append(args, "withdist") + q.withLen++ } if q.WithGeoHash { args = append(args, "withhash") + q.withLen++ } if q.Count > 0 { args = append(args, "count", q.Count) @@ -2854,82 +2787,55 @@ func (cmd *GeoLocationCmd) String() string { } func (cmd *GeoLocationCmd) readReply(rd *proto.Reader) error { - v, err := rd.ReadArrayReply(newGeoLocationSliceParser(cmd.q)) + n, err := rd.ReadArrayLen() if err != nil { return err } - cmd.locations = v.([]GeoLocation) + cmd.locations = make([]GeoLocation, n) + + for i := 0; i < len(cmd.locations); i++ { + // only name + if cmd.q.withLen == 0 { + if cmd.locations[i].Name, err = rd.ReadString(); err != nil { + return err + } + continue + } + + // +name + if err = rd.ReadFixedArrayLen(cmd.q.withLen + 1); err != nil { + return err + } + + if cmd.locations[i].Name, err = rd.ReadString(); err != nil { + return err + } + if cmd.q.WithDist { + if cmd.locations[i].Dist, err = rd.ReadFloat(); err != nil { + return err + } + } + if cmd.q.WithGeoHash { + if cmd.locations[i].GeoHash, err = rd.ReadInt(); err != nil { + return err + } + } + if cmd.q.WithCoord { + if err = rd.ReadFixedArrayLen(2); err != nil { + return err + } + if cmd.locations[i].Longitude, err = rd.ReadFloat(); err != nil { + return err + } + if cmd.locations[i].Latitude, err = rd.ReadFloat(); err != nil { + return err + } + } + } + return nil } -func newGeoLocationSliceParser(q *GeoRadiusQuery) proto.MultiBulkParse { - return func(rd *proto.Reader, n int64) (interface{}, error) { - locs := make([]GeoLocation, 0, n) - for i := int64(0); i < n; i++ { - v, err := rd.ReadReply(newGeoLocationParser(q)) - if err != nil { - return nil, err - } - switch vv := v.(type) { - case string: - locs = append(locs, GeoLocation{ - Name: vv, - }) - case *GeoLocation: - // TODO: avoid copying - locs = append(locs, *vv) - default: - return nil, fmt.Errorf("got %T, expected string or *GeoLocation", v) - } - } - return locs, nil - } -} - -func newGeoLocationParser(q *GeoRadiusQuery) proto.MultiBulkParse { - return func(rd *proto.Reader, n int64) (interface{}, error) { - var loc GeoLocation - var err error - - loc.Name, err = rd.ReadString() - if err != nil { - return nil, err - } - if q.WithDist { - loc.Dist, err = rd.ReadFloatReply() - if err != nil { - return nil, err - } - } - if q.WithGeoHash { - loc.GeoHash, err = rd.ReadIntReply() - if err != nil { - return nil, err - } - } - if q.WithCoord { - n, err := rd.ReadArrayLen() - if err != nil { - return nil, err - } - if n != 2 { - return nil, fmt.Errorf("got %d coordinates, expected 2", n) - } - - loc.Longitude, err = rd.ReadFloatReply() - if err != nil { - return nil, err - } - loc.Latitude, err = rd.ReadFloatReply() - if err != nil { - return nil, err - } - } - - return &loc, nil - } -} - //------------------------------------------------------------------------------ // GeoSearchQuery is used for GEOSearch/GEOSearchStore command query. @@ -3044,10 +2950,6 @@ func NewGeoSearchLocationCmd( } } -func (cmd *GeoSearchLocationCmd) SetVal(val []GeoLocation) { - cmd.val = val -} - func (cmd *GeoSearchLocationCmd) Val() []GeoLocation { return cmd.val } @@ -3080,31 +2982,26 @@ func (cmd *GeoSearchLocationCmd) readReply(rd *proto.Reader) error { return err } if cmd.opt.WithDist { - loc.Dist, err = rd.ReadFloatReply() + loc.Dist, err = rd.ReadFloat() if err != nil { return err } } if cmd.opt.WithHash { - loc.GeoHash, err = rd.ReadIntReply() + loc.GeoHash, err = rd.ReadInt() if err != nil { return err } } if cmd.opt.WithCoord { - nn, err := rd.ReadArrayLen() + if err = rd.ReadFixedArrayLen(2); err != nil { + return err + } + loc.Longitude, err = rd.ReadFloat() if err != nil { return err } - if nn != 2 { - return fmt.Errorf("got %d coordinates, expected 2", nn) - } - - loc.Longitude, err = rd.ReadFloatReply() - if err != nil { - return err - } - loc.Latitude, err = rd.ReadFloatReply() + loc.Latitude, err = rd.ReadFloat() if err != nil { return err } @@ -3156,38 +3053,38 @@ func (cmd *GeoPosCmd) String() string { } func (cmd *GeoPosCmd) readReply(rd *proto.Reader) error { - _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { - cmd.val = make([]*GeoPos, n) - for i := 0; i < len(cmd.val); i++ { - i := i - _, err := rd.ReadReply(func(rd *proto.Reader, n int64) (interface{}, error) { - longitude, err := rd.ReadFloatReply() - if err != nil { - return nil, err - } + n, err := rd.ReadArrayLen() + if err != nil { + return err + } + cmd.val = make([]*GeoPos, n) - latitude, err := rd.ReadFloatReply() - if err != nil { - return nil, err - } - - cmd.val[i] = &GeoPos{ - Longitude: longitude, - Latitude: latitude, - } - return nil, nil - }) - if err != nil { - if err == Nil { - cmd.val[i] = nil - continue - } - return nil, err + for i := 0; i < len(cmd.val); i++ { + err = rd.ReadFixedArrayLen(2) + if err != nil { + if err == Nil { + cmd.val[i] = nil + continue } + return err } - return nil, nil - }) - return err + + longitude, err := rd.ReadFloat() + if err != nil { + return err + } + latitude, err := rd.ReadFloat() + if err != nil { + return err + } + + cmd.val[i] = &GeoPos{ + Longitude: longitude, + Latitude: latitude, + } + } + + return nil } //------------------------------------------------------------------------------ @@ -3237,112 +3134,94 @@ func (cmd *CommandsInfoCmd) String() string { } func (cmd *CommandsInfoCmd) readReply(rd *proto.Reader) error { - _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { - cmd.val = make(map[string]*CommandInfo, n) - for i := int64(0); i < n; i++ { - v, err := rd.ReadReply(commandInfoParser) - if err != nil { - return nil, err - } - vv := v.(*CommandInfo) - cmd.val[vv.Name] = vv - } - return nil, nil - }) - return err -} - -func commandInfoParser(rd *proto.Reader, n int64) (interface{}, error) { const numArgRedis5 = 6 const numArgRedis6 = 7 - switch n { - case numArgRedis5, numArgRedis6: - // continue - default: - return nil, fmt.Errorf("redis: got %d elements in COMMAND reply, wanted 7", n) - } - - var cmd CommandInfo - var err error - - cmd.Name, err = rd.ReadString() + n, err := rd.ReadArrayLen() if err != nil { - return nil, err + return err } + cmd.val = make(map[string]*CommandInfo, n) - arity, err := rd.ReadIntReply() - if err != nil { - return nil, err - } - cmd.Arity = int8(arity) + for i := 0; i < n; i++ { + nn, err := rd.ReadArrayLen() + if err != nil { + return err + } + if nn != numArgRedis5 && nn != numArgRedis6 { + return fmt.Errorf("redis: got %d elements in COMMAND reply, wanted 6/7", nn) + } - _, err = rd.ReadReply(func(rd *proto.Reader, n int64) (interface{}, error) { - cmd.Flags = make([]string, n) - for i := 0; i < len(cmd.Flags); i++ { + cmdInfo := &CommandInfo{} + if cmdInfo.Name, err = rd.ReadString(); err != nil { + return err + } + + arity, err := rd.ReadInt() + if err != nil { + return err + } + cmdInfo.Arity = int8(arity) + + flagLen, err := rd.ReadArrayLen() + if err != nil { + return err + } + cmdInfo.Flags = make([]string, flagLen) + for f := 0; f < len(cmdInfo.Flags); f++ { switch s, err := rd.ReadString(); { case err == Nil: - cmd.Flags[i] = "" + cmdInfo.Flags[f] = "" case err != nil: - return nil, err + return err default: - cmd.Flags[i] = s + if !cmdInfo.ReadOnly && s == "readonly" { + cmdInfo.ReadOnly = true + } + cmdInfo.Flags[f] = s } } - return nil, nil - }) - if err != nil { - return nil, err - } - firstKeyPos, err := rd.ReadIntReply() - if err != nil { - return nil, err - } - cmd.FirstKeyPos = int8(firstKeyPos) - - lastKeyPos, err := rd.ReadIntReply() - if err != nil { - return nil, err - } - cmd.LastKeyPos = int8(lastKeyPos) - - stepCount, err := rd.ReadIntReply() - if err != nil { - return nil, err - } - cmd.StepCount = int8(stepCount) - - for _, flag := range cmd.Flags { - if flag == "readonly" { - cmd.ReadOnly = true - break + firstKeyPos, err := rd.ReadInt() + if err != nil { + return err } - } + cmdInfo.FirstKeyPos = int8(firstKeyPos) - if n == numArgRedis5 { - return &cmd, nil - } + lastKeyPos, err := rd.ReadInt() + if err != nil { + return err + } + cmdInfo.LastKeyPos = int8(lastKeyPos) - _, err = rd.ReadReply(func(rd *proto.Reader, n int64) (interface{}, error) { - cmd.ACLFlags = make([]string, n) - for i := 0; i < len(cmd.ACLFlags); i++ { - switch s, err := rd.ReadString(); { - case err == Nil: - cmd.ACLFlags[i] = "" - case err != nil: - return nil, err - default: - cmd.ACLFlags[i] = s + stepCount, err := rd.ReadInt() + if err != nil { + return err + } + cmdInfo.StepCount = int8(stepCount) + + if nn == numArgRedis6 { + aclFlagLen, err := rd.ReadArrayLen() + if err != nil { + return err + } + cmdInfo.ACLFlags = make([]string, aclFlagLen) + for f := 0; f < len(cmdInfo.ACLFlags); f++ { + switch s, err := rd.ReadString(); { + case err == Nil: + cmdInfo.ACLFlags[f] = "" + case err != nil: + return err + default: + cmdInfo.ACLFlags[f] = s + } } } - return nil, nil - }) - if err != nil { - return nil, err + + cmd.val[cmdInfo.Name] = cmdInfo } - return &cmd, nil + return nil } //------------------------------------------------------------------------------ @@ -3428,75 +3307,185 @@ func (cmd *SlowLogCmd) String() string { } func (cmd *SlowLogCmd) readReply(rd *proto.Reader) error { - _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { - cmd.val = make([]SlowLog, n) - for i := 0; i < len(cmd.val); i++ { - n, err := rd.ReadArrayLen() + n, err := rd.ReadArrayLen() + if err != nil { + return err + } + cmd.val = make([]SlowLog, n) + + for i := 0; i < len(cmd.val); i++ { + nn, err := rd.ReadArrayLen() + if err != nil { + return err + } + if nn < 4 { + return fmt.Errorf("redis: got %d elements in slowlog get, expected at least 4", nn) + } + + if cmd.val[i].ID, err = rd.ReadInt(); err != nil { + return err + } + + createdAt, err := rd.ReadInt() + if err != nil { + return err + } + cmd.val[i].Time = time.Unix(createdAt, 0) + + costs, err := rd.ReadInt() + if err != nil { + return err + } + cmd.val[i].Duration = time.Duration(costs) * time.Microsecond + + cmdLen, err := rd.ReadArrayLen() + if err != nil { + return err + } + if cmdLen < 1 { + return fmt.Errorf("redis: got %d elements commands reply in slowlog get, expected at least 1", cmdLen) + } + + cmd.val[i].Args = make([]string, cmdLen) + for f := 0; f < len(cmd.val[i].Args); f++ { + cmd.val[i].Args[f], err = rd.ReadString() if err != nil { - return nil, err - } - if n < 4 { - err := fmt.Errorf("redis: got %d elements in slowlog get, expected at least 4", n) - return nil, err - } - - id, err := rd.ReadIntReply() - if err != nil { - return nil, err - } - - createdAt, err := rd.ReadIntReply() - if err != nil { - return nil, err - } - createdAtTime := time.Unix(createdAt, 0) - - costs, err := rd.ReadIntReply() - if err != nil { - return nil, err - } - costsDuration := time.Duration(costs) * time.Microsecond - - cmdLen, err := rd.ReadArrayLen() - if err != nil { - return nil, err - } - if cmdLen < 1 { - err := fmt.Errorf("redis: got %d elements commands reply in slowlog get, expected at least 1", cmdLen) - return nil, err - } - - cmdString := make([]string, cmdLen) - for i := 0; i < cmdLen; i++ { - cmdString[i], err = rd.ReadString() - if err != nil { - return nil, err - } - } - - var address, name string - for i := 4; i < n; i++ { - str, err := rd.ReadString() - if err != nil { - return nil, err - } - if i == 4 { - address = str - } else if i == 5 { - name = str - } - } - - cmd.val[i] = SlowLog{ - ID: id, - Time: createdAtTime, - Duration: costsDuration, - Args: cmdString, - ClientAddr: address, - ClientName: name, + return err } } - return nil, nil - }) - return err + + if nn >= 5 { + if cmd.val[i].ClientAddr, err = rd.ReadString(); err != nil { + return err + } + } + + if nn >= 6 { + if cmd.val[i].ClientName, err = rd.ReadString(); err != nil { + return err + } + } + } + + return nil +} + +//----------------------------------------------------------------------- + +type MapStringInterfaceCmd struct { + baseCmd + + val map[string]interface{} +} + +var _ Cmder = (*MapStringInterfaceCmd)(nil) + +func NewMapStringInterfaceCmd(ctx context.Context, args ...interface{}) *MapStringInterfaceCmd { + return &MapStringInterfaceCmd{ + baseCmd: baseCmd{ + ctx: ctx, + args: args, + }, + } +} + +func (cmd *MapStringInterfaceCmd) Val() map[string]interface{} { + return cmd.val +} + +func (cmd *MapStringInterfaceCmd) Result() (map[string]interface{}, error) { + return cmd.Val(), cmd.Err() +} + +func (cmd *MapStringInterfaceCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *MapStringInterfaceCmd) readReply(rd *proto.Reader) error { + n, err := rd.ReadMapLen() + if err != nil { + return err + } + + cmd.val = make(map[string]interface{}, n) + for i := 0; i < n; i++ { + k, err := rd.ReadString() + if err != nil { + return err + } + v, err := rd.ReadReply() + if err != nil { + if err == Nil { + cmd.val[k] = Nil + continue + } + if err, ok := err.(proto.RedisError); ok { + cmd.val[k] = err + continue + } + return err + } + cmd.val[k] = v + } + return nil +} + +//----------------------------------------------------------------------- + +type MapStringStringSliceCmd struct { + baseCmd + + val []map[string]string +} + +var _ Cmder = (*MapStringStringSliceCmd)(nil) + +func NewMapStringStringSliceCmd(ctx context.Context, args ...interface{}) *MapStringStringSliceCmd { + return &MapStringStringSliceCmd{ + baseCmd: baseCmd{ + ctx: ctx, + args: args, + }, + } +} + +func (cmd *MapStringStringSliceCmd) Val() []map[string]string { + return cmd.val +} + +func (cmd *MapStringStringSliceCmd) Result() ([]map[string]string, error) { + return cmd.Val(), cmd.Err() +} + +func (cmd *MapStringStringSliceCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *MapStringStringSliceCmd) readReply(rd *proto.Reader) error { + n, err := rd.ReadArrayLen() + if err != nil { + return err + } + + cmd.val = make([]map[string]string, n) + for i := 0; i < n; i++ { + nn, err := rd.ReadMapLen() + if err != nil { + return err + } + cmd.val[i] = make(map[string]string, nn) + for f := 0; f < nn; f++ { + k, err := rd.ReadString() + if err != nil { + return err + } + + v, err := rd.ReadString() + if err != nil { + return err + } + cmd.val[i][k] = v + } + } + return nil } diff --git a/command_test.go b/command_test.go index 168f9f69..775987fc 100644 --- a/command_test.go +++ b/command_test.go @@ -4,10 +4,10 @@ import ( "errors" "time" + "github.com/go-redis/redis/v8" + . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" - - redis "github.com/go-redis/redis/v8" ) var _ = Describe("Cmd", func() { diff --git a/commands.go b/commands.go index bbfe089d..eb0757d1 100644 --- a/commands.go +++ b/commands.go @@ -137,8 +137,7 @@ type Cmdable interface { MSetNX(ctx context.Context, values ...interface{}) *BoolCmd Set(ctx context.Context, key string, value interface{}, expiration time.Duration) *StatusCmd SetArgs(ctx context.Context, key string, value interface{}, a SetArgs) *StatusCmd - // TODO: rename to SetEx - SetEX(ctx context.Context, key string, value interface{}, expiration time.Duration) *StatusCmd + SetEx(ctx context.Context, key string, value interface{}, expiration time.Duration) *StatusCmd SetNX(ctx context.Context, key string, value interface{}, expiration time.Duration) *BoolCmd SetXX(ctx context.Context, key string, value interface{}, expiration time.Duration) *BoolCmd SetRange(ctx context.Context, key string, offset int64, value string) *IntCmd @@ -164,7 +163,7 @@ type Cmdable interface { HDel(ctx context.Context, key string, fields ...string) *IntCmd HExists(ctx context.Context, key, field string) *BoolCmd HGet(ctx context.Context, key, field string) *StringCmd - HGetAll(ctx context.Context, key string) *StringStringMapCmd + HGetAll(ctx context.Context, key string) *MapStringStringCmd HIncrBy(ctx context.Context, key, field string, incr int64) *IntCmd HIncrByFloat(ctx context.Context, key, field string, incr float64) *FloatCmd HKeys(ctx context.Context, key string) *StringSliceCmd @@ -174,7 +173,8 @@ type Cmdable interface { HMSet(ctx context.Context, key string, values ...interface{}) *BoolCmd HSetNX(ctx context.Context, key, field string, value interface{}) *BoolCmd HVals(ctx context.Context, key string) *StringSliceCmd - HRandField(ctx context.Context, key string, count int, withValues bool) *StringSliceCmd + HRandField(ctx context.Context, key string, count int) *StringSliceCmd + HRandFieldWithValues(ctx context.Context, key string, count int) *KeyValueSliceCmd BLPop(ctx context.Context, timeout time.Duration, keys ...string) *StringSliceCmd BRPop(ctx context.Context, timeout time.Duration, keys ...string) *StringSliceCmd @@ -244,10 +244,6 @@ type Cmdable interface { XClaimJustID(ctx context.Context, a *XClaimArgs) *StringSliceCmd XAutoClaim(ctx context.Context, a *XAutoClaimArgs) *XAutoClaimCmd XAutoClaimJustID(ctx context.Context, a *XAutoClaimArgs) *XAutoClaimJustIDCmd - - // TODO: XTrim and XTrimApprox remove in v9. - XTrim(ctx context.Context, key string, maxLen int64) *IntCmd - XTrimApprox(ctx context.Context, key string, maxLen int64) *IntCmd XTrimMaxLen(ctx context.Context, key string, maxLen int64) *IntCmd XTrimMaxLenApprox(ctx context.Context, key string, maxLen, limit int64) *IntCmd XTrimMinID(ctx context.Context, key string, minID string) *IntCmd @@ -260,27 +256,11 @@ type Cmdable interface { BZPopMax(ctx context.Context, timeout time.Duration, keys ...string) *ZWithKeyCmd BZPopMin(ctx context.Context, timeout time.Duration, keys ...string) *ZWithKeyCmd - // TODO: remove - // ZAddCh - // ZIncr - // ZAddNXCh - // ZAddXXCh - // ZIncrNX - // ZIncrXX - // in v9. - // use ZAddArgs and ZAddArgsIncr. - - ZAdd(ctx context.Context, key string, members ...*Z) *IntCmd - ZAddNX(ctx context.Context, key string, members ...*Z) *IntCmd - ZAddXX(ctx context.Context, key string, members ...*Z) *IntCmd - ZAddCh(ctx context.Context, key string, members ...*Z) *IntCmd - ZAddNXCh(ctx context.Context, key string, members ...*Z) *IntCmd - ZAddXXCh(ctx context.Context, key string, members ...*Z) *IntCmd + ZAdd(ctx context.Context, key string, members ...Z) *IntCmd + ZAddNX(ctx context.Context, key string, members ...Z) *IntCmd + ZAddXX(ctx context.Context, key string, members ...Z) *IntCmd ZAddArgs(ctx context.Context, key string, args ZAddArgs) *IntCmd ZAddArgsIncr(ctx context.Context, key string, args ZAddArgs) *FloatCmd - ZIncr(ctx context.Context, key string, member *Z) *FloatCmd - ZIncrNX(ctx context.Context, key string, member *Z) *FloatCmd - ZIncrXX(ctx context.Context, key string, member *Z) *FloatCmd ZCard(ctx context.Context, key string) *IntCmd ZCount(ctx context.Context, key, min, max string) *IntCmd ZLexCount(ctx context.Context, key, min, max string) *IntCmd @@ -312,9 +292,10 @@ type Cmdable interface { ZRevRank(ctx context.Context, key, member string) *IntCmd ZScore(ctx context.Context, key, member string) *FloatCmd ZUnionStore(ctx context.Context, dest string, store *ZStore) *IntCmd + ZRandMember(ctx context.Context, key string, count int) *StringSliceCmd + ZRandMemberWithScores(ctx context.Context, key string, count int) *ZSliceCmd ZUnion(ctx context.Context, store ZStore) *StringSliceCmd ZUnionWithScores(ctx context.Context, store ZStore) *ZSliceCmd - ZRandMember(ctx context.Context, key string, count int, withScores bool) *StringSliceCmd ZDiff(ctx context.Context, keys ...string) *StringSliceCmd ZDiffWithScores(ctx context.Context, keys ...string) *ZSliceCmd ZDiffStore(ctx context.Context, destination string, keys ...string) *IntCmd @@ -330,7 +311,9 @@ type Cmdable interface { ClientList(ctx context.Context) *StringCmd ClientPause(ctx context.Context, dur time.Duration) *BoolCmd ClientID(ctx context.Context) *IntCmd - ConfigGet(ctx context.Context, parameter string) *SliceCmd + ClientUnblock(ctx context.Context, id int64) *IntCmd + ClientUnblockWithError(ctx context.Context, id int64) *IntCmd + ConfigGet(ctx context.Context, parameter string) *MapStringStringCmd ConfigResetStat(ctx context.Context) *StatusCmd ConfigSet(ctx context.Context, parameter, value string) *StatusCmd ConfigRewrite(ctx context.Context) *StatusCmd @@ -346,6 +329,7 @@ type Cmdable interface { ShutdownSave(ctx context.Context) *StatusCmd ShutdownNoSave(ctx context.Context) *StatusCmd SlaveOf(ctx context.Context, host, port string) *StatusCmd + SlowLogGet(ctx context.Context, num int64) *SlowLogCmd Time(ctx context.Context) *TimeCmd DebugObject(ctx context.Context, key string) *StringCmd ReadOnly(ctx context.Context) *StatusCmd @@ -404,6 +388,7 @@ type StatefulCmdable interface { Select(ctx context.Context, index int) *StatusCmd SwapDB(ctx context.Context, index1, index2 int) *StatusCmd ClientSetName(ctx context.Context, name string) *BoolCmd + Hello(ctx context.Context, ver int, username, password, clientName string) *MapStringInterfaceCmd } var ( @@ -460,6 +445,26 @@ func (c statefulCmdable) ClientSetName(ctx context.Context, name string) *BoolCm return cmd } +// Hello Set the resp protocol used. +func (c statefulCmdable) Hello(ctx context.Context, + ver int, username, password, clientName string) *MapStringInterfaceCmd { + args := make([]interface{}, 0, 7) + args = append(args, "hello", ver) + if password != "" { + if username != "" { + args = append(args, "auth", username, password) + } else { + args = append(args, "auth", "default", password) + } + } + if clientName != "" { + args = append(args, "setname", clientName) + } + cmd := NewMapStringInterfaceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + //------------------------------------------------------------------------------ func (c cmdable) Command(ctx context.Context) *CommandsInfoCmd { @@ -882,7 +887,7 @@ func (c cmdable) MSetNX(ctx context.Context, values ...interface{}) *BoolCmd { } // Set Redis `SET key value [expiration]` command. -// Use expiration for `SETEX`-like behavior. +// Use expiration for `SETEx`-like behavior. // // Zero expiration means the key has no expiration time. // KeepTTL is a Redis KEEPTTL option to keep existing TTL, it requires your redis-server version >= 6.0, @@ -958,8 +963,8 @@ func (c cmdable) SetArgs(ctx context.Context, key string, value interface{}, a S return cmd } -// SetEX Redis `SETEX key expiration value` command. -func (c cmdable) SetEX(ctx context.Context, key string, value interface{}, expiration time.Duration) *StatusCmd { +// SetEx Redis `SETEx key expiration value` command. +func (c cmdable) SetEx(ctx context.Context, key string, value interface{}, expiration time.Duration) *StatusCmd { cmd := NewStatusCmd(ctx, "setex", key, formatSec(ctx, expiration), value) _ = c(ctx, cmd) return cmd @@ -1229,8 +1234,8 @@ func (c cmdable) HGet(ctx context.Context, key, field string) *StringCmd { return cmd } -func (c cmdable) HGetAll(ctx context.Context, key string) *StringStringMapCmd { - cmd := NewStringStringMapCmd(ctx, "hgetall", key) +func (c cmdable) HGetAll(ctx context.Context, key string) *MapStringStringCmd { + cmd := NewMapStringStringCmd(ctx, "hgetall", key) _ = c(ctx, cmd) return cmd } @@ -1313,16 +1318,15 @@ func (c cmdable) HVals(ctx context.Context, key string) *StringSliceCmd { } // HRandField redis-server version >= 6.2.0. -func (c cmdable) HRandField(ctx context.Context, key string, count int, withValues bool) *StringSliceCmd { - args := make([]interface{}, 0, 4) +func (c cmdable) HRandField(ctx context.Context, key string, count int) *StringSliceCmd { + cmd := NewStringSliceCmd(ctx, "hrandfield", key, count) + _ = c(ctx, cmd) + return cmd +} - // Although count=0 is meaningless, redis accepts count=0. - args = append(args, "hrandfield", key, count) - if withValues { - args = append(args, "withvalues") - } - - cmd := NewStringSliceCmd(ctx, args...) +// HRandFieldWithValues redis-server version >= 6.2.0. +func (c cmdable) HRandFieldWithValues(ctx context.Context, key string, count int) *KeyValueSliceCmd { + cmd := NewKeyValueSliceCmd(ctx, "hrandfield", key, count, "withvalues") _ = c(ctx, cmd) return cmd } @@ -1725,11 +1729,7 @@ type XAddArgs struct { Stream string NoMkStream bool MaxLen int64 // MAXLEN N - - // Deprecated: use MaxLen+Approx, remove in v9. - MaxLenApprox int64 // MAXLEN ~ N - - MinID string + MinID string // Approx causes MaxLen and MinID to use "~" matcher (instead of "="). Approx bool Limit int64 @@ -1752,9 +1752,6 @@ func (c cmdable) XAdd(ctx context.Context, a *XAddArgs) *StringCmd { } else { args = append(args, "maxlen", a.MaxLen) } - case a.MaxLenApprox > 0: - // TODO remove in v9. - args = append(args, "maxlen", "~", a.MaxLenApprox) case a.MinID != "": if a.Approx { args = append(args, "minid", "~", a.MinID) @@ -2070,16 +2067,6 @@ func (c cmdable) xTrim( return cmd } -// Deprecated: use XTrimMaxLen, remove in v9. -func (c cmdable) XTrim(ctx context.Context, key string, maxLen int64) *IntCmd { - return c.xTrim(ctx, key, "maxlen", false, maxLen, 0) -} - -// Deprecated: use XTrimMaxLenApprox, remove in v9. -func (c cmdable) XTrimApprox(ctx context.Context, key string, maxLen int64) *IntCmd { - return c.xTrim(ctx, key, "maxlen", true, maxLen, 0) -} - // XTrimMaxLen No `~` rules are used, `limit` cannot be used. // cmd: XTRIM key MAXLEN maxLen func (c cmdable) XTrimMaxLen(ctx context.Context, key string, maxLen int64) *IntCmd { @@ -2266,116 +2253,26 @@ func (c cmdable) ZAddArgsIncr(ctx context.Context, key string, args ZAddArgs) *F return cmd } -// TODO: Compatible with v8 api, will be removed in v9. -func (c cmdable) zAdd(ctx context.Context, key string, args ZAddArgs, members ...*Z) *IntCmd { - args.Members = make([]Z, len(members)) - for i, m := range members { - args.Members[i] = *m - } - cmd := NewIntCmd(ctx, c.zAddArgs(key, args, false)...) - _ = c(ctx, cmd) - return cmd -} - // ZAdd Redis `ZADD key score member [score member ...]` command. -func (c cmdable) ZAdd(ctx context.Context, key string, members ...*Z) *IntCmd { - return c.zAdd(ctx, key, ZAddArgs{}, members...) +func (c cmdable) ZAdd(ctx context.Context, key string, members ...Z) *IntCmd { + return c.ZAddArgs(ctx, key, ZAddArgs{ + Members: members, + }) } // ZAddNX Redis `ZADD key NX score member [score member ...]` command. -func (c cmdable) ZAddNX(ctx context.Context, key string, members ...*Z) *IntCmd { - return c.zAdd(ctx, key, ZAddArgs{ - NX: true, - }, members...) +func (c cmdable) ZAddNX(ctx context.Context, key string, members ...Z) *IntCmd { + return c.ZAddArgs(ctx, key, ZAddArgs{ + NX: true, + Members: members, + }) } // ZAddXX Redis `ZADD key XX score member [score member ...]` command. -func (c cmdable) ZAddXX(ctx context.Context, key string, members ...*Z) *IntCmd { - return c.zAdd(ctx, key, ZAddArgs{ - XX: true, - }, members...) -} - -// ZAddCh Redis `ZADD key CH score member [score member ...]` command. -// Deprecated: Use -// client.ZAddArgs(ctx, ZAddArgs{ -// Ch: true, -// Members: []Z, -// }) -// remove in v9. -func (c cmdable) ZAddCh(ctx context.Context, key string, members ...*Z) *IntCmd { - return c.zAdd(ctx, key, ZAddArgs{ - Ch: true, - }, members...) -} - -// ZAddNXCh Redis `ZADD key NX CH score member [score member ...]` command. -// Deprecated: Use -// client.ZAddArgs(ctx, ZAddArgs{ -// NX: true, -// Ch: true, -// Members: []Z, -// }) -// remove in v9. -func (c cmdable) ZAddNXCh(ctx context.Context, key string, members ...*Z) *IntCmd { - return c.zAdd(ctx, key, ZAddArgs{ - NX: true, - Ch: true, - }, members...) -} - -// ZAddXXCh Redis `ZADD key XX CH score member [score member ...]` command. -// Deprecated: Use -// client.ZAddArgs(ctx, ZAddArgs{ -// XX: true, -// Ch: true, -// Members: []Z, -// }) -// remove in v9. -func (c cmdable) ZAddXXCh(ctx context.Context, key string, members ...*Z) *IntCmd { - return c.zAdd(ctx, key, ZAddArgs{ - XX: true, - Ch: true, - }, members...) -} - -// ZIncr Redis `ZADD key INCR score member` command. -// Deprecated: Use -// client.ZAddArgsIncr(ctx, ZAddArgs{ -// Members: []Z, -// }) -// remove in v9. -func (c cmdable) ZIncr(ctx context.Context, key string, member *Z) *FloatCmd { - return c.ZAddArgsIncr(ctx, key, ZAddArgs{ - Members: []Z{*member}, - }) -} - -// ZIncrNX Redis `ZADD key NX INCR score member` command. -// Deprecated: Use -// client.ZAddArgsIncr(ctx, ZAddArgs{ -// NX: true, -// Members: []Z, -// }) -// remove in v9. -func (c cmdable) ZIncrNX(ctx context.Context, key string, member *Z) *FloatCmd { - return c.ZAddArgsIncr(ctx, key, ZAddArgs{ - NX: true, - Members: []Z{*member}, - }) -} - -// ZIncrXX Redis `ZADD key XX INCR score member` command. -// Deprecated: Use -// client.ZAddArgsIncr(ctx, ZAddArgs{ -// XX: true, -// Members: []Z, -// }) -// remove in v9. -func (c cmdable) ZIncrXX(ctx context.Context, key string, member *Z) *FloatCmd { - return c.ZAddArgsIncr(ctx, key, ZAddArgs{ +func (c cmdable) ZAddXX(ctx context.Context, key string, members ...Z) *IntCmd { + return c.ZAddArgs(ctx, key, ZAddArgs{ XX: true, - Members: []Z{*member}, + Members: members, }) } @@ -2783,16 +2680,15 @@ func (c cmdable) ZUnionStore(ctx context.Context, dest string, store *ZStore) *I } // ZRandMember redis-server version >= 6.2.0. -func (c cmdable) ZRandMember(ctx context.Context, key string, count int, withScores bool) *StringSliceCmd { - args := make([]interface{}, 0, 4) +func (c cmdable) ZRandMember(ctx context.Context, key string, count int) *StringSliceCmd { + cmd := NewStringSliceCmd(ctx, "zrandmember", key, count) + _ = c(ctx, cmd) + return cmd +} - // Although count=0 is meaningless, redis accepts count=0. - args = append(args, "zrandmember", key, count) - if withScores { - args = append(args, "withscores") - } - - cmd := NewStringSliceCmd(ctx, args...) +// ZRandMemberWithScores redis-server version >= 6.2.0. +func (c cmdable) ZRandMemberWithScores(ctx context.Context, key string, count int) *ZSliceCmd { + cmd := NewZSliceCmd(ctx, "zrandmember", key, count, "withscores") _ = c(ctx, cmd) return cmd } @@ -2940,8 +2836,8 @@ func (c cmdable) ClientUnblockWithError(ctx context.Context, id int64) *IntCmd { return cmd } -func (c cmdable) ConfigGet(ctx context.Context, parameter string) *SliceCmd { - cmd := NewSliceCmd(ctx, "config", "get", parameter) +func (c cmdable) ConfigGet(ctx context.Context, parameter string) *MapStringStringCmd { + cmd := NewMapStringStringCmd(ctx, "config", "get", parameter) _ = c(ctx, cmd) return cmd } diff --git a/commands_test.go b/commands_test.go index 030bdf38..81c1bdb8 100644 --- a/commands_test.go +++ b/commands_test.go @@ -47,6 +47,17 @@ var _ = Describe("Commands", func() { Expect(stats.IdleConns).To(Equal(uint32(1))) }) + It("should hello", func() { + cmds, err := client.Pipelined(ctx, func(pipe redis.Pipeliner) error { + pipe.Hello(ctx, 3, "", "", "") + return nil + }) + Expect(err).NotTo(HaveOccurred()) + m, err := cmds[0].(*redis.MapStringInterfaceCmd).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(m["proto"]).To(Equal(int64(3))) + }) + It("should Echo", func() { pipe := client.Pipeline() echo := pipe.Echo(ctx, "hello") @@ -182,10 +193,11 @@ var _ = Describe("Commands", func() { It("should ConfigSet", func() { configGet := client.ConfigGet(ctx, "maxmemory") Expect(configGet.Err()).NotTo(HaveOccurred()) - Expect(configGet.Val()).To(HaveLen(2)) - Expect(configGet.Val()[0]).To(Equal("maxmemory")) + Expect(configGet.Val()).To(HaveLen(1)) + _, ok := configGet.Val()["maxmemory"] + Expect(ok).To(BeTrue()) - configSet := client.ConfigSet(ctx, "maxmemory", configGet.Val()[1].(string)) + configSet := client.ConfigSet(ctx, "maxmemory", configGet.Val()["maxmemory"]) Expect(configSet.Err()).NotTo(HaveOccurred()) Expect(configSet.Val()).To(Equal("OK")) }) @@ -815,7 +827,7 @@ var _ = Describe("Commands", func() { It("should ZScan", func() { for i := 0; i < 1000; i++ { - err := client.ZAdd(ctx, "myset", &redis.Z{ + err := client.ZAdd(ctx, "myset", redis.Z{ Score: float64(i), Member: fmt.Sprintf("member%d", i), }).Err() @@ -835,13 +847,13 @@ var _ = Describe("Commands", func() { Expect(err).NotTo(HaveOccurred()) Expect(n).To(Equal(int64(0))) - append := client.Append(ctx, "key", "Hello") - Expect(append.Err()).NotTo(HaveOccurred()) - Expect(append.Val()).To(Equal(int64(5))) + appendRes := client.Append(ctx, "key", "Hello") + Expect(appendRes.Err()).NotTo(HaveOccurred()) + Expect(appendRes.Val()).To(Equal(int64(5))) - append = client.Append(ctx, "key", " World") - Expect(append.Err()).NotTo(HaveOccurred()) - Expect(append.Val()).To(Equal(int64(11))) + appendRes = client.Append(ctx, "key", " World") + Expect(appendRes.Err()).NotTo(HaveOccurred()) + Expect(appendRes.Val()).To(Equal(int64(11))) get := client.Get(ctx, "key") Expect(get.Err()).NotTo(HaveOccurred()) @@ -1496,7 +1508,7 @@ var _ = Describe("Commands", func() { }) It("should SetEX", func() { - err := client.SetEX(ctx, "key", "hello", 1*time.Second).Err() + err := client.SetEx(ctx, "key", "hello", 1*time.Second).Err() Expect(err).NotTo(HaveOccurred()) val, err := client.Get(ctx, "key").Result() @@ -1865,18 +1877,20 @@ var _ = Describe("Commands", func() { err = client.HSet(ctx, "hash", "key2", "hello2").Err() Expect(err).NotTo(HaveOccurred()) - v := client.HRandField(ctx, "hash", 1, false) + v := client.HRandField(ctx, "hash", 1) Expect(v.Err()).NotTo(HaveOccurred()) Expect(v.Val()).To(Or(Equal([]string{"key1"}), Equal([]string{"key2"}))) - v = client.HRandField(ctx, "hash", 0, false) + v = client.HRandField(ctx, "hash", 0) Expect(v.Err()).NotTo(HaveOccurred()) Expect(v.Val()).To(HaveLen(0)) - var slice []string - err = client.HRandField(ctx, "hash", 1, true).ScanSlice(&slice) + kv, err := client.HRandFieldWithValues(ctx, "hash", 1).Result() Expect(err).NotTo(HaveOccurred()) - Expect(slice).To(Or(Equal([]string{"key1", "hello1"}), Equal([]string{"key2", "hello2"}))) + Expect(kv).To(Or( + Equal([]redis.KeyValue{{Key: "key1", Value: "hello1"}}), + Equal([]redis.KeyValue{{Key: "key2", Value: "hello2"}}), + )) }) }) @@ -2753,17 +2767,17 @@ var _ = Describe("Commands", func() { Describe("sorted sets", func() { It("should BZPopMax", func() { - err := client.ZAdd(ctx, "zset1", &redis.Z{ + err := client.ZAdd(ctx, "zset1", redis.Z{ Score: 1, Member: "one", }).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset1", &redis.Z{ + err = client.ZAdd(ctx, "zset1", redis.Z{ Score: 2, Member: "two", }).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset1", &redis.Z{ + err = client.ZAdd(ctx, "zset1", redis.Z{ Score: 3, Member: "three", }).Err() @@ -2807,7 +2821,7 @@ var _ = Describe("Commands", func() { // ok } - zAdd := client.ZAdd(ctx, "zset", &redis.Z{ + zAdd := client.ZAdd(ctx, "zset", redis.Z{ Member: "a", Score: 1, }) @@ -2835,17 +2849,17 @@ var _ = Describe("Commands", func() { }) It("should BZPopMin", func() { - err := client.ZAdd(ctx, "zset1", &redis.Z{ + err := client.ZAdd(ctx, "zset1", redis.Z{ Score: 1, Member: "one", }).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset1", &redis.Z{ + err = client.ZAdd(ctx, "zset1", redis.Z{ Score: 2, Member: "two", }).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset1", &redis.Z{ + err = client.ZAdd(ctx, "zset1", redis.Z{ Score: 3, Member: "three", }).Err() @@ -2889,7 +2903,7 @@ var _ = Describe("Commands", func() { // ok } - zAdd := client.ZAdd(ctx, "zset", &redis.Z{ + zAdd := client.ZAdd(ctx, "zset", redis.Z{ Member: "a", Score: 1, }) @@ -2917,28 +2931,28 @@ var _ = Describe("Commands", func() { }) It("should ZAdd", func() { - added, err := client.ZAdd(ctx, "zset", &redis.Z{ + added, err := client.ZAdd(ctx, "zset", redis.Z{ Score: 1, Member: "one", }).Result() Expect(err).NotTo(HaveOccurred()) Expect(added).To(Equal(int64(1))) - added, err = client.ZAdd(ctx, "zset", &redis.Z{ + added, err = client.ZAdd(ctx, "zset", redis.Z{ Score: 1, Member: "uno", }).Result() Expect(err).NotTo(HaveOccurred()) Expect(added).To(Equal(int64(1))) - added, err = client.ZAdd(ctx, "zset", &redis.Z{ + added, err = client.ZAdd(ctx, "zset", redis.Z{ Score: 2, Member: "two", }).Result() Expect(err).NotTo(HaveOccurred()) Expect(added).To(Equal(int64(1))) - added, err = client.ZAdd(ctx, "zset", &redis.Z{ + added, err = client.ZAdd(ctx, "zset", redis.Z{ Score: 3, Member: "two", }).Result() @@ -2960,28 +2974,28 @@ var _ = Describe("Commands", func() { }) It("should ZAdd bytes", func() { - added, err := client.ZAdd(ctx, "zset", &redis.Z{ + added, err := client.ZAdd(ctx, "zset", redis.Z{ Score: 1, Member: []byte("one"), }).Result() Expect(err).NotTo(HaveOccurred()) Expect(added).To(Equal(int64(1))) - added, err = client.ZAdd(ctx, "zset", &redis.Z{ + added, err = client.ZAdd(ctx, "zset", redis.Z{ Score: 1, Member: []byte("uno"), }).Result() Expect(err).NotTo(HaveOccurred()) Expect(added).To(Equal(int64(1))) - added, err = client.ZAdd(ctx, "zset", &redis.Z{ + added, err = client.ZAdd(ctx, "zset", redis.Z{ Score: 2, Member: []byte("two"), }).Result() Expect(err).NotTo(HaveOccurred()) Expect(added).To(Equal(int64(1))) - added, err = client.ZAdd(ctx, "zset", &redis.Z{ + added, err = client.ZAdd(ctx, "zset", redis.Z{ Score: 3, Member: []byte("two"), }).Result() @@ -3002,7 +3016,7 @@ var _ = Describe("Commands", func() { }})) }) - It("should ZAddArgs", func() { + It("should ZAddArgsGTAndLT", func() { // Test only the GT+LT options. added, err := client.ZAddArgs(ctx, "zset", redis.ZAddArgs{ GT: true, @@ -3038,8 +3052,8 @@ var _ = Describe("Commands", func() { Expect(vals).To(Equal([]redis.Z{{Score: 1, Member: "one"}})) }) - It("should ZAddNX", func() { - added, err := client.ZAddNX(ctx, "zset", &redis.Z{ + It("should ZAddArgsNX", func() { + added, err := client.ZAddNX(ctx, "zset", redis.Z{ Score: 1, Member: "one", }).Result() @@ -3050,7 +3064,7 @@ var _ = Describe("Commands", func() { Expect(err).NotTo(HaveOccurred()) Expect(vals).To(Equal([]redis.Z{{Score: 1, Member: "one"}})) - added, err = client.ZAddNX(ctx, "zset", &redis.Z{ + added, err = client.ZAddNX(ctx, "zset", redis.Z{ Score: 2, Member: "one", }).Result() @@ -3062,8 +3076,8 @@ var _ = Describe("Commands", func() { Expect(vals).To(Equal([]redis.Z{{Score: 1, Member: "one"}})) }) - It("should ZAddXX", func() { - added, err := client.ZAddXX(ctx, "zset", &redis.Z{ + It("should ZAddArgsXX", func() { + added, err := client.ZAddXX(ctx, "zset", redis.Z{ Score: 1, Member: "one", }).Result() @@ -3074,14 +3088,14 @@ var _ = Describe("Commands", func() { Expect(err).NotTo(HaveOccurred()) Expect(vals).To(BeEmpty()) - added, err = client.ZAdd(ctx, "zset", &redis.Z{ + added, err = client.ZAdd(ctx, "zset", redis.Z{ Score: 1, Member: "one", }).Result() Expect(err).NotTo(HaveOccurred()) Expect(added).To(Equal(int64(1))) - added, err = client.ZAddXX(ctx, "zset", &redis.Z{ + added, err = client.ZAddXX(ctx, "zset", redis.Z{ Score: 2, Member: "one", }).Result() @@ -3093,28 +3107,33 @@ var _ = Describe("Commands", func() { Expect(vals).To(Equal([]redis.Z{{Score: 2, Member: "one"}})) }) - // TODO: remove in v9. - It("should ZAddCh", func() { - changed, err := client.ZAddCh(ctx, "zset", &redis.Z{ - Score: 1, - Member: "one", + It("should ZAddArgsCh", func() { + changed, err := client.ZAddArgs(ctx, "zset", redis.ZAddArgs{ + Ch: true, + Members: []redis.Z{ + {Score: 1, Member: "one"}, + }, }).Result() Expect(err).NotTo(HaveOccurred()) Expect(changed).To(Equal(int64(1))) - changed, err = client.ZAddCh(ctx, "zset", &redis.Z{ - Score: 1, - Member: "one", + changed, err = client.ZAddArgs(ctx, "zset", redis.ZAddArgs{ + Ch: true, + Members: []redis.Z{ + {Score: 1, Member: "one"}, + }, }).Result() Expect(err).NotTo(HaveOccurred()) Expect(changed).To(Equal(int64(0))) }) - // TODO: remove in v9. - It("should ZAddNXCh", func() { - changed, err := client.ZAddNXCh(ctx, "zset", &redis.Z{ - Score: 1, - Member: "one", + It("should ZAddArgsNXCh", func() { + changed, err := client.ZAddArgs(ctx, "zset", redis.ZAddArgs{ + NX: true, + Ch: true, + Members: []redis.Z{ + {Score: 1, Member: "one"}, + }, }).Result() Expect(err).NotTo(HaveOccurred()) Expect(changed).To(Equal(int64(1))) @@ -3123,9 +3142,12 @@ var _ = Describe("Commands", func() { Expect(err).NotTo(HaveOccurred()) Expect(vals).To(Equal([]redis.Z{{Score: 1, Member: "one"}})) - changed, err = client.ZAddNXCh(ctx, "zset", &redis.Z{ - Score: 2, - Member: "one", + changed, err = client.ZAddArgs(ctx, "zset", redis.ZAddArgs{ + NX: true, + Ch: true, + Members: []redis.Z{ + {Score: 2, Member: "one"}, + }, }).Result() Expect(err).NotTo(HaveOccurred()) Expect(changed).To(Equal(int64(0))) @@ -3138,11 +3160,13 @@ var _ = Describe("Commands", func() { }})) }) - // TODO: remove in v9. - It("should ZAddXXCh", func() { - changed, err := client.ZAddXXCh(ctx, "zset", &redis.Z{ - Score: 1, - Member: "one", + It("should ZAddArgsXXCh", func() { + changed, err := client.ZAddArgs(ctx, "zset", redis.ZAddArgs{ + XX: true, + Ch: true, + Members: []redis.Z{ + {Score: 1, Member: "one"}, + }, }).Result() Expect(err).NotTo(HaveOccurred()) Expect(changed).To(Equal(int64(0))) @@ -3151,16 +3175,19 @@ var _ = Describe("Commands", func() { Expect(err).NotTo(HaveOccurred()) Expect(vals).To(BeEmpty()) - added, err := client.ZAdd(ctx, "zset", &redis.Z{ + added, err := client.ZAdd(ctx, "zset", redis.Z{ Score: 1, Member: "one", }).Result() Expect(err).NotTo(HaveOccurred()) Expect(added).To(Equal(int64(1))) - changed, err = client.ZAddXXCh(ctx, "zset", &redis.Z{ - Score: 2, - Member: "one", + changed, err = client.ZAddArgs(ctx, "zset", redis.ZAddArgs{ + XX: true, + Ch: true, + Members: []redis.Z{ + {Score: 2, Member: "one"}, + }, }).Result() Expect(err).NotTo(HaveOccurred()) Expect(changed).To(Equal(int64(1))) @@ -3170,11 +3197,11 @@ var _ = Describe("Commands", func() { Expect(vals).To(Equal([]redis.Z{{Score: 2, Member: "one"}})) }) - // TODO: remove in v9. - It("should ZIncr", func() { - score, err := client.ZIncr(ctx, "zset", &redis.Z{ - Score: 1, - Member: "one", + It("should ZAddArgsIncr", func() { + score, err := client.ZAddArgsIncr(ctx, "zset", redis.ZAddArgs{ + Members: []redis.Z{ + {Score: 1, Member: "one"}, + }, }).Result() Expect(err).NotTo(HaveOccurred()) Expect(score).To(Equal(float64(1))) @@ -3183,9 +3210,10 @@ var _ = Describe("Commands", func() { Expect(err).NotTo(HaveOccurred()) Expect(vals).To(Equal([]redis.Z{{Score: 1, Member: "one"}})) - score, err = client.ZIncr(ctx, "zset", &redis.Z{ - Score: 1, - Member: "one", + score, err = client.ZAddArgsIncr(ctx, "zset", redis.ZAddArgs{ + Members: []redis.Z{ + {Score: 1, Member: "one"}, + }, }).Result() Expect(err).NotTo(HaveOccurred()) Expect(score).To(Equal(float64(2))) @@ -3195,11 +3223,12 @@ var _ = Describe("Commands", func() { Expect(vals).To(Equal([]redis.Z{{Score: 2, Member: "one"}})) }) - // TODO: remove in v9. - It("should ZIncrNX", func() { - score, err := client.ZIncrNX(ctx, "zset", &redis.Z{ - Score: 1, - Member: "one", + It("should ZAddArgsIncrNX", func() { + score, err := client.ZAddArgsIncr(ctx, "zset", redis.ZAddArgs{ + NX: true, + Members: []redis.Z{ + {Score: 1, Member: "one"}, + }, }).Result() Expect(err).NotTo(HaveOccurred()) Expect(score).To(Equal(float64(1))) @@ -3208,9 +3237,11 @@ var _ = Describe("Commands", func() { Expect(err).NotTo(HaveOccurred()) Expect(vals).To(Equal([]redis.Z{{Score: 1, Member: "one"}})) - score, err = client.ZIncrNX(ctx, "zset", &redis.Z{ - Score: 1, - Member: "one", + score, err = client.ZAddArgsIncr(ctx, "zset", redis.ZAddArgs{ + NX: true, + Members: []redis.Z{ + {Score: 1, Member: "one"}, + }, }).Result() Expect(err).To(Equal(redis.Nil)) Expect(score).To(Equal(float64(0))) @@ -3220,11 +3251,12 @@ var _ = Describe("Commands", func() { Expect(vals).To(Equal([]redis.Z{{Score: 1, Member: "one"}})) }) - // TODO: remove in v9. - It("should ZIncrXX", func() { - score, err := client.ZIncrXX(ctx, "zset", &redis.Z{ - Score: 1, - Member: "one", + It("should ZAddArgsIncrXX", func() { + score, err := client.ZAddArgsIncr(ctx, "zset", redis.ZAddArgs{ + XX: true, + Members: []redis.Z{ + {Score: 1, Member: "one"}, + }, }).Result() Expect(err).To(Equal(redis.Nil)) Expect(score).To(Equal(float64(0))) @@ -3233,16 +3265,18 @@ var _ = Describe("Commands", func() { Expect(err).NotTo(HaveOccurred()) Expect(vals).To(BeEmpty()) - added, err := client.ZAdd(ctx, "zset", &redis.Z{ + added, err := client.ZAdd(ctx, "zset", redis.Z{ Score: 1, Member: "one", }).Result() Expect(err).NotTo(HaveOccurred()) Expect(added).To(Equal(int64(1))) - score, err = client.ZIncrXX(ctx, "zset", &redis.Z{ - Score: 1, - Member: "one", + score, err = client.ZAddArgsIncr(ctx, "zset", redis.ZAddArgs{ + XX: true, + Members: []redis.Z{ + {Score: 1, Member: "one"}, + }, }).Result() Expect(err).NotTo(HaveOccurred()) Expect(score).To(Equal(float64(2))) @@ -3253,12 +3287,12 @@ var _ = Describe("Commands", func() { }) It("should ZCard", func() { - err := client.ZAdd(ctx, "zset", &redis.Z{ + err := client.ZAdd(ctx, "zset", redis.Z{ Score: 1, Member: "one", }).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset", &redis.Z{ + err = client.ZAdd(ctx, "zset", redis.Z{ Score: 2, Member: "two", }).Err() @@ -3270,17 +3304,17 @@ var _ = Describe("Commands", func() { }) It("should ZCount", func() { - err := client.ZAdd(ctx, "zset", &redis.Z{ + err := client.ZAdd(ctx, "zset", redis.Z{ Score: 1, Member: "one", }).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset", &redis.Z{ + err = client.ZAdd(ctx, "zset", redis.Z{ Score: 2, Member: "two", }).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset", &redis.Z{ + err = client.ZAdd(ctx, "zset", redis.Z{ Score: 3, Member: "three", }).Err() @@ -3300,12 +3334,12 @@ var _ = Describe("Commands", func() { }) It("should ZIncrBy", func() { - err := client.ZAdd(ctx, "zset", &redis.Z{ + err := client.ZAdd(ctx, "zset", redis.Z{ Score: 1, Member: "one", }).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset", &redis.Z{ + err = client.ZAdd(ctx, "zset", redis.Z{ Score: 2, Member: "two", }).Err() @@ -3327,22 +3361,22 @@ var _ = Describe("Commands", func() { }) It("should ZInterStore", func() { - err := client.ZAdd(ctx, "zset1", &redis.Z{ + err := client.ZAdd(ctx, "zset1", redis.Z{ Score: 1, Member: "one", }).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset1", &redis.Z{ + err = client.ZAdd(ctx, "zset1", redis.Z{ Score: 2, Member: "two", }).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset2", &redis.Z{Score: 1, Member: "one"}).Err() + err = client.ZAdd(ctx, "zset2", redis.Z{Score: 1, Member: "one"}).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset2", &redis.Z{Score: 2, Member: "two"}).Err() + err = client.ZAdd(ctx, "zset2", redis.Z{Score: 2, Member: "two"}).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset3", &redis.Z{Score: 3, Member: "two"}).Err() + err = client.ZAdd(ctx, "zset3", redis.Z{Score: 3, Member: "two"}).Err() Expect(err).NotTo(HaveOccurred()) n, err := client.ZInterStore(ctx, "out", &redis.ZStore{ @@ -3369,11 +3403,11 @@ var _ = Describe("Commands", func() { Expect(zmScore.Val()).To(HaveLen(2)) Expect(zmScore.Val()[0]).To(Equal(float64(0))) - err := client.ZAdd(ctx, "zset", &redis.Z{Score: 1, Member: "one"}).Err() + err := client.ZAdd(ctx, "zset", redis.Z{Score: 1, Member: "one"}).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset", &redis.Z{Score: 2, Member: "two"}).Err() + err = client.ZAdd(ctx, "zset", redis.Z{Score: 2, Member: "two"}).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset", &redis.Z{Score: 3, Member: "three"}).Err() + err = client.ZAdd(ctx, "zset", redis.Z{Score: 3, Member: "three"}).Err() Expect(err).NotTo(HaveOccurred()) zmScore = client.ZMScore(ctx, "zset", "one", "three") @@ -3391,17 +3425,17 @@ var _ = Describe("Commands", func() { }) It("should ZPopMax", func() { - err := client.ZAdd(ctx, "zset", &redis.Z{ + err := client.ZAdd(ctx, "zset", redis.Z{ Score: 1, Member: "one", }).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset", &redis.Z{ + err = client.ZAdd(ctx, "zset", redis.Z{ Score: 2, Member: "two", }).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset", &redis.Z{ + err = client.ZAdd(ctx, "zset", redis.Z{ Score: 3, Member: "three", }).Err() @@ -3415,7 +3449,7 @@ var _ = Describe("Commands", func() { }})) // adding back 3 - err = client.ZAdd(ctx, "zset", &redis.Z{ + err = client.ZAdd(ctx, "zset", redis.Z{ Score: 3, Member: "three", }).Err() @@ -3431,12 +3465,12 @@ var _ = Describe("Commands", func() { }})) // adding back 2 & 3 - err = client.ZAdd(ctx, "zset", &redis.Z{ + err = client.ZAdd(ctx, "zset", redis.Z{ Score: 3, Member: "three", }).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset", &redis.Z{ + err = client.ZAdd(ctx, "zset", redis.Z{ Score: 2, Member: "two", }).Err() @@ -3456,17 +3490,17 @@ var _ = Describe("Commands", func() { }) It("should ZPopMin", func() { - err := client.ZAdd(ctx, "zset", &redis.Z{ + err := client.ZAdd(ctx, "zset", redis.Z{ Score: 1, Member: "one", }).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset", &redis.Z{ + err = client.ZAdd(ctx, "zset", redis.Z{ Score: 2, Member: "two", }).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset", &redis.Z{ + err = client.ZAdd(ctx, "zset", redis.Z{ Score: 3, Member: "three", }).Err() @@ -3480,7 +3514,7 @@ var _ = Describe("Commands", func() { }})) // adding back 1 - err = client.ZAdd(ctx, "zset", &redis.Z{ + err = client.ZAdd(ctx, "zset", redis.Z{ Score: 1, Member: "one", }).Err() @@ -3496,13 +3530,13 @@ var _ = Describe("Commands", func() { }})) // adding back 1 & 2 - err = client.ZAdd(ctx, "zset", &redis.Z{ + err = client.ZAdd(ctx, "zset", redis.Z{ Score: 1, Member: "one", }).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset", &redis.Z{ + err = client.ZAdd(ctx, "zset", redis.Z{ Score: 2, Member: "two", }).Err() @@ -3523,11 +3557,11 @@ var _ = Describe("Commands", func() { }) It("should ZRange", func() { - err := client.ZAdd(ctx, "zset", &redis.Z{Score: 1, Member: "one"}).Err() + err := client.ZAdd(ctx, "zset", redis.Z{Score: 1, Member: "one"}).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset", &redis.Z{Score: 2, Member: "two"}).Err() + err = client.ZAdd(ctx, "zset", redis.Z{Score: 2, Member: "two"}).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset", &redis.Z{Score: 3, Member: "three"}).Err() + err = client.ZAdd(ctx, "zset", redis.Z{Score: 3, Member: "three"}).Err() Expect(err).NotTo(HaveOccurred()) zRange := client.ZRange(ctx, "zset", 0, -1) @@ -3544,11 +3578,11 @@ var _ = Describe("Commands", func() { }) It("should ZRangeWithScores", func() { - err := client.ZAdd(ctx, "zset", &redis.Z{Score: 1, Member: "one"}).Err() + err := client.ZAdd(ctx, "zset", redis.Z{Score: 1, Member: "one"}).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset", &redis.Z{Score: 2, Member: "two"}).Err() + err = client.ZAdd(ctx, "zset", redis.Z{Score: 2, Member: "two"}).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset", &redis.Z{Score: 3, Member: "three"}).Err() + err = client.ZAdd(ctx, "zset", redis.Z{Score: 3, Member: "three"}).Err() Expect(err).NotTo(HaveOccurred()) vals, err := client.ZRangeWithScores(ctx, "zset", 0, -1).Result() @@ -3642,11 +3676,11 @@ var _ = Describe("Commands", func() { }) It("should ZRangeByScore", func() { - err := client.ZAdd(ctx, "zset", &redis.Z{Score: 1, Member: "one"}).Err() + err := client.ZAdd(ctx, "zset", redis.Z{Score: 1, Member: "one"}).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset", &redis.Z{Score: 2, Member: "two"}).Err() + err = client.ZAdd(ctx, "zset", redis.Z{Score: 2, Member: "two"}).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset", &redis.Z{Score: 3, Member: "three"}).Err() + err = client.ZAdd(ctx, "zset", redis.Z{Score: 3, Member: "three"}).Err() Expect(err).NotTo(HaveOccurred()) zRangeByScore := client.ZRangeByScore(ctx, "zset", &redis.ZRangeBy{ @@ -3679,17 +3713,17 @@ var _ = Describe("Commands", func() { }) It("should ZRangeByLex", func() { - err := client.ZAdd(ctx, "zset", &redis.Z{ + err := client.ZAdd(ctx, "zset", redis.Z{ Score: 0, Member: "a", }).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset", &redis.Z{ + err = client.ZAdd(ctx, "zset", redis.Z{ Score: 0, Member: "b", }).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset", &redis.Z{ + err = client.ZAdd(ctx, "zset", redis.Z{ Score: 0, Member: "c", }).Err() @@ -3725,11 +3759,11 @@ var _ = Describe("Commands", func() { }) It("should ZRangeByScoreWithScoresMap", func() { - err := client.ZAdd(ctx, "zset", &redis.Z{Score: 1, Member: "one"}).Err() + err := client.ZAdd(ctx, "zset", redis.Z{Score: 1, Member: "one"}).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset", &redis.Z{Score: 2, Member: "two"}).Err() + err = client.ZAdd(ctx, "zset", redis.Z{Score: 2, Member: "two"}).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset", &redis.Z{Score: 3, Member: "three"}).Err() + err = client.ZAdd(ctx, "zset", redis.Z{Score: 3, Member: "three"}).Err() Expect(err).NotTo(HaveOccurred()) vals, err := client.ZRangeByScoreWithScores(ctx, "zset", &redis.ZRangeBy{ @@ -3806,11 +3840,11 @@ var _ = Describe("Commands", func() { }) It("should ZRank", func() { - err := client.ZAdd(ctx, "zset", &redis.Z{Score: 1, Member: "one"}).Err() + err := client.ZAdd(ctx, "zset", redis.Z{Score: 1, Member: "one"}).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset", &redis.Z{Score: 2, Member: "two"}).Err() + err = client.ZAdd(ctx, "zset", redis.Z{Score: 2, Member: "two"}).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset", &redis.Z{Score: 3, Member: "three"}).Err() + err = client.ZAdd(ctx, "zset", redis.Z{Score: 3, Member: "three"}).Err() Expect(err).NotTo(HaveOccurred()) zRank := client.ZRank(ctx, "zset", "three") @@ -3823,11 +3857,11 @@ var _ = Describe("Commands", func() { }) It("should ZRem", func() { - err := client.ZAdd(ctx, "zset", &redis.Z{Score: 1, Member: "one"}).Err() + err := client.ZAdd(ctx, "zset", redis.Z{Score: 1, Member: "one"}).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset", &redis.Z{Score: 2, Member: "two"}).Err() + err = client.ZAdd(ctx, "zset", redis.Z{Score: 2, Member: "two"}).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset", &redis.Z{Score: 3, Member: "three"}).Err() + err = client.ZAdd(ctx, "zset", redis.Z{Score: 3, Member: "three"}).Err() Expect(err).NotTo(HaveOccurred()) zRem := client.ZRem(ctx, "zset", "two") @@ -3846,11 +3880,11 @@ var _ = Describe("Commands", func() { }) It("should ZRemRangeByRank", func() { - err := client.ZAdd(ctx, "zset", &redis.Z{Score: 1, Member: "one"}).Err() + err := client.ZAdd(ctx, "zset", redis.Z{Score: 1, Member: "one"}).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset", &redis.Z{Score: 2, Member: "two"}).Err() + err = client.ZAdd(ctx, "zset", redis.Z{Score: 2, Member: "two"}).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset", &redis.Z{Score: 3, Member: "three"}).Err() + err = client.ZAdd(ctx, "zset", redis.Z{Score: 3, Member: "three"}).Err() Expect(err).NotTo(HaveOccurred()) zRemRangeByRank := client.ZRemRangeByRank(ctx, "zset", 0, 1) @@ -3866,11 +3900,11 @@ var _ = Describe("Commands", func() { }) It("should ZRemRangeByScore", func() { - err := client.ZAdd(ctx, "zset", &redis.Z{Score: 1, Member: "one"}).Err() + err := client.ZAdd(ctx, "zset", redis.Z{Score: 1, Member: "one"}).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset", &redis.Z{Score: 2, Member: "two"}).Err() + err = client.ZAdd(ctx, "zset", redis.Z{Score: 2, Member: "two"}).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset", &redis.Z{Score: 3, Member: "three"}).Err() + err = client.ZAdd(ctx, "zset", redis.Z{Score: 3, Member: "three"}).Err() Expect(err).NotTo(HaveOccurred()) zRemRangeByScore := client.ZRemRangeByScore(ctx, "zset", "-inf", "(2") @@ -3889,7 +3923,7 @@ var _ = Describe("Commands", func() { }) It("should ZRemRangeByLex", func() { - zz := []*redis.Z{ + zz := []redis.Z{ {Score: 0, Member: "aaaa"}, {Score: 0, Member: "b"}, {Score: 0, Member: "c"}, @@ -3916,11 +3950,11 @@ var _ = Describe("Commands", func() { }) It("should ZRevRange", func() { - err := client.ZAdd(ctx, "zset", &redis.Z{Score: 1, Member: "one"}).Err() + err := client.ZAdd(ctx, "zset", redis.Z{Score: 1, Member: "one"}).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset", &redis.Z{Score: 2, Member: "two"}).Err() + err = client.ZAdd(ctx, "zset", redis.Z{Score: 2, Member: "two"}).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset", &redis.Z{Score: 3, Member: "three"}).Err() + err = client.ZAdd(ctx, "zset", redis.Z{Score: 3, Member: "three"}).Err() Expect(err).NotTo(HaveOccurred()) zRevRange := client.ZRevRange(ctx, "zset", 0, -1) @@ -3937,11 +3971,11 @@ var _ = Describe("Commands", func() { }) It("should ZRevRangeWithScoresMap", func() { - err := client.ZAdd(ctx, "zset", &redis.Z{Score: 1, Member: "one"}).Err() + err := client.ZAdd(ctx, "zset", redis.Z{Score: 1, Member: "one"}).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset", &redis.Z{Score: 2, Member: "two"}).Err() + err = client.ZAdd(ctx, "zset", redis.Z{Score: 2, Member: "two"}).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset", &redis.Z{Score: 3, Member: "three"}).Err() + err = client.ZAdd(ctx, "zset", redis.Z{Score: 3, Member: "three"}).Err() Expect(err).NotTo(HaveOccurred()) val, err := client.ZRevRangeWithScores(ctx, "zset", 0, -1).Result() @@ -3973,11 +4007,11 @@ var _ = Describe("Commands", func() { }) It("should ZRevRangeByScore", func() { - err := client.ZAdd(ctx, "zset", &redis.Z{Score: 1, Member: "one"}).Err() + err := client.ZAdd(ctx, "zset", redis.Z{Score: 1, Member: "one"}).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset", &redis.Z{Score: 2, Member: "two"}).Err() + err = client.ZAdd(ctx, "zset", redis.Z{Score: 2, Member: "two"}).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset", &redis.Z{Score: 3, Member: "three"}).Err() + err = client.ZAdd(ctx, "zset", redis.Z{Score: 3, Member: "three"}).Err() Expect(err).NotTo(HaveOccurred()) vals, err := client.ZRevRangeByScore( @@ -3997,11 +4031,11 @@ var _ = Describe("Commands", func() { }) It("should ZRevRangeByLex", func() { - err := client.ZAdd(ctx, "zset", &redis.Z{Score: 0, Member: "a"}).Err() + err := client.ZAdd(ctx, "zset", redis.Z{Score: 0, Member: "a"}).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset", &redis.Z{Score: 0, Member: "b"}).Err() + err = client.ZAdd(ctx, "zset", redis.Z{Score: 0, Member: "b"}).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset", &redis.Z{Score: 0, Member: "c"}).Err() + err = client.ZAdd(ctx, "zset", redis.Z{Score: 0, Member: "c"}).Err() Expect(err).NotTo(HaveOccurred()) vals, err := client.ZRevRangeByLex( @@ -4021,11 +4055,11 @@ var _ = Describe("Commands", func() { }) It("should ZRevRangeByScoreWithScores", func() { - err := client.ZAdd(ctx, "zset", &redis.Z{Score: 1, Member: "one"}).Err() + err := client.ZAdd(ctx, "zset", redis.Z{Score: 1, Member: "one"}).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset", &redis.Z{Score: 2, Member: "two"}).Err() + err = client.ZAdd(ctx, "zset", redis.Z{Score: 2, Member: "two"}).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset", &redis.Z{Score: 3, Member: "three"}).Err() + err = client.ZAdd(ctx, "zset", redis.Z{Score: 3, Member: "three"}).Err() Expect(err).NotTo(HaveOccurred()) vals, err := client.ZRevRangeByScoreWithScores( @@ -4044,11 +4078,11 @@ var _ = Describe("Commands", func() { }) It("should ZRevRangeByScoreWithScoresMap", func() { - err := client.ZAdd(ctx, "zset", &redis.Z{Score: 1, Member: "one"}).Err() + err := client.ZAdd(ctx, "zset", redis.Z{Score: 1, Member: "one"}).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset", &redis.Z{Score: 2, Member: "two"}).Err() + err = client.ZAdd(ctx, "zset", redis.Z{Score: 2, Member: "two"}).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset", &redis.Z{Score: 3, Member: "three"}).Err() + err = client.ZAdd(ctx, "zset", redis.Z{Score: 3, Member: "three"}).Err() Expect(err).NotTo(HaveOccurred()) vals, err := client.ZRevRangeByScoreWithScores( @@ -4077,11 +4111,11 @@ var _ = Describe("Commands", func() { }) It("should ZRevRank", func() { - err := client.ZAdd(ctx, "zset", &redis.Z{Score: 1, Member: "one"}).Err() + err := client.ZAdd(ctx, "zset", redis.Z{Score: 1, Member: "one"}).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset", &redis.Z{Score: 2, Member: "two"}).Err() + err = client.ZAdd(ctx, "zset", redis.Z{Score: 2, Member: "two"}).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset", &redis.Z{Score: 3, Member: "three"}).Err() + err = client.ZAdd(ctx, "zset", redis.Z{Score: 3, Member: "three"}).Err() Expect(err).NotTo(HaveOccurred()) zRevRank := client.ZRevRank(ctx, "zset", "one") @@ -4094,12 +4128,12 @@ var _ = Describe("Commands", func() { }) It("should ZScore", func() { - zAdd := client.ZAdd(ctx, "zset", &redis.Z{Score: 1.001, Member: "one"}) + zAdd := client.ZAdd(ctx, "zset", redis.Z{Score: 1.001, Member: "one"}) Expect(zAdd.Err()).NotTo(HaveOccurred()) zScore := client.ZScore(ctx, "zset", "one") Expect(zScore.Err()).NotTo(HaveOccurred()) - Expect(zScore.Val()).To(Equal(float64(1.001))) + Expect(zScore.Val()).To(Equal(1.001)) }) It("should ZUnion", func() { @@ -4142,16 +4176,16 @@ var _ = Describe("Commands", func() { }) It("should ZUnionStore", func() { - err := client.ZAdd(ctx, "zset1", &redis.Z{Score: 1, Member: "one"}).Err() + err := client.ZAdd(ctx, "zset1", redis.Z{Score: 1, Member: "one"}).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset1", &redis.Z{Score: 2, Member: "two"}).Err() + err = client.ZAdd(ctx, "zset1", redis.Z{Score: 2, Member: "two"}).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset2", &redis.Z{Score: 1, Member: "one"}).Err() + err = client.ZAdd(ctx, "zset2", redis.Z{Score: 1, Member: "one"}).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset2", &redis.Z{Score: 2, Member: "two"}).Err() + err = client.ZAdd(ctx, "zset2", redis.Z{Score: 2, Member: "two"}).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset2", &redis.Z{Score: 3, Member: "three"}).Err() + err = client.ZAdd(ctx, "zset2", redis.Z{Score: 3, Member: "three"}).Err() Expect(err).NotTo(HaveOccurred()) n, err := client.ZUnionStore(ctx, "out", &redis.ZStore{ @@ -4176,33 +4210,35 @@ var _ = Describe("Commands", func() { }) It("should ZRandMember", func() { - err := client.ZAdd(ctx, "zset", &redis.Z{Score: 1, Member: "one"}).Err() + err := client.ZAdd(ctx, "zset", redis.Z{Score: 1, Member: "one"}).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset", &redis.Z{Score: 2, Member: "two"}).Err() + err = client.ZAdd(ctx, "zset", redis.Z{Score: 2, Member: "two"}).Err() Expect(err).NotTo(HaveOccurred()) - v := client.ZRandMember(ctx, "zset", 1, false) + v := client.ZRandMember(ctx, "zset", 1) Expect(v.Err()).NotTo(HaveOccurred()) Expect(v.Val()).To(Or(Equal([]string{"one"}), Equal([]string{"two"}))) - v = client.ZRandMember(ctx, "zset", 0, false) + v = client.ZRandMember(ctx, "zset", 0) Expect(v.Err()).NotTo(HaveOccurred()) Expect(v.Val()).To(HaveLen(0)) - var slice []string - err = client.ZRandMember(ctx, "zset", 1, true).ScanSlice(&slice) + kv, err := client.ZRandMemberWithScores(ctx, "zset", 1).Result() Expect(err).NotTo(HaveOccurred()) - Expect(slice).To(Or(Equal([]string{"one", "1"}), Equal([]string{"two", "2"}))) + Expect(kv).To(Or( + Equal([]redis.Z{{Member: "one", Score: 1}}), + Equal([]redis.Z{{Member: "two", Score: 2}}), + )) }) It("should ZDiff", func() { - err := client.ZAdd(ctx, "zset1", &redis.Z{Score: 1, Member: "one"}).Err() + err := client.ZAdd(ctx, "zset1", redis.Z{Score: 1, Member: "one"}).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset1", &redis.Z{Score: 2, Member: "two"}).Err() + err = client.ZAdd(ctx, "zset1", redis.Z{Score: 2, Member: "two"}).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset1", &redis.Z{Score: 3, Member: "three"}).Err() + err = client.ZAdd(ctx, "zset1", redis.Z{Score: 3, Member: "three"}).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset2", &redis.Z{Score: 1, Member: "one"}).Err() + err = client.ZAdd(ctx, "zset2", redis.Z{Score: 1, Member: "one"}).Err() Expect(err).NotTo(HaveOccurred()) v, err := client.ZDiff(ctx, "zset1", "zset2").Result() @@ -4211,13 +4247,13 @@ var _ = Describe("Commands", func() { }) It("should ZDiffWithScores", func() { - err := client.ZAdd(ctx, "zset1", &redis.Z{Score: 1, Member: "one"}).Err() + err := client.ZAdd(ctx, "zset1", redis.Z{Score: 1, Member: "one"}).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset1", &redis.Z{Score: 2, Member: "two"}).Err() + err = client.ZAdd(ctx, "zset1", redis.Z{Score: 2, Member: "two"}).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset1", &redis.Z{Score: 3, Member: "three"}).Err() + err = client.ZAdd(ctx, "zset1", redis.Z{Score: 3, Member: "three"}).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset2", &redis.Z{Score: 1, Member: "one"}).Err() + err = client.ZAdd(ctx, "zset2", redis.Z{Score: 1, Member: "one"}).Err() Expect(err).NotTo(HaveOccurred()) v, err := client.ZDiffWithScores(ctx, "zset1", "zset2").Result() @@ -4235,15 +4271,15 @@ var _ = Describe("Commands", func() { }) It("should ZInter", func() { - err := client.ZAdd(ctx, "zset1", &redis.Z{Score: 1, Member: "one"}).Err() + err := client.ZAdd(ctx, "zset1", redis.Z{Score: 1, Member: "one"}).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset1", &redis.Z{Score: 2, Member: "two"}).Err() + err = client.ZAdd(ctx, "zset1", redis.Z{Score: 2, Member: "two"}).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset2", &redis.Z{Score: 1, Member: "one"}).Err() + err = client.ZAdd(ctx, "zset2", redis.Z{Score: 1, Member: "one"}).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset2", &redis.Z{Score: 2, Member: "two"}).Err() + err = client.ZAdd(ctx, "zset2", redis.Z{Score: 2, Member: "two"}).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset2", &redis.Z{Score: 3, Member: "three"}).Err() + err = client.ZAdd(ctx, "zset2", redis.Z{Score: 3, Member: "three"}).Err() Expect(err).NotTo(HaveOccurred()) v, err := client.ZInter(ctx, &redis.ZStore{ @@ -4254,15 +4290,15 @@ var _ = Describe("Commands", func() { }) It("should ZInterWithScores", func() { - err := client.ZAdd(ctx, "zset1", &redis.Z{Score: 1, Member: "one"}).Err() + err := client.ZAdd(ctx, "zset1", redis.Z{Score: 1, Member: "one"}).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset1", &redis.Z{Score: 2, Member: "two"}).Err() + err = client.ZAdd(ctx, "zset1", redis.Z{Score: 2, Member: "two"}).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset2", &redis.Z{Score: 1, Member: "one"}).Err() + err = client.ZAdd(ctx, "zset2", redis.Z{Score: 1, Member: "one"}).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset2", &redis.Z{Score: 2, Member: "two"}).Err() + err = client.ZAdd(ctx, "zset2", redis.Z{Score: 2, Member: "two"}).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset2", &redis.Z{Score: 3, Member: "three"}).Err() + err = client.ZAdd(ctx, "zset2", redis.Z{Score: 3, Member: "three"}).Err() Expect(err).NotTo(HaveOccurred()) v, err := client.ZInterWithScores(ctx, &redis.ZStore{ @@ -4284,15 +4320,15 @@ var _ = Describe("Commands", func() { }) It("should ZDiffStore", func() { - err := client.ZAdd(ctx, "zset1", &redis.Z{Score: 1, Member: "one"}).Err() + err := client.ZAdd(ctx, "zset1", redis.Z{Score: 1, Member: "one"}).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset1", &redis.Z{Score: 2, Member: "two"}).Err() + err = client.ZAdd(ctx, "zset1", redis.Z{Score: 2, Member: "two"}).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset2", &redis.Z{Score: 1, Member: "one"}).Err() + err = client.ZAdd(ctx, "zset2", redis.Z{Score: 1, Member: "one"}).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset2", &redis.Z{Score: 2, Member: "two"}).Err() + err = client.ZAdd(ctx, "zset2", redis.Z{Score: 2, Member: "two"}).Err() Expect(err).NotTo(HaveOccurred()) - err = client.ZAdd(ctx, "zset2", &redis.Z{Score: 3, Member: "three"}).Err() + err = client.ZAdd(ctx, "zset2", redis.Z{Score: 3, Member: "three"}).Err() Expect(err).NotTo(HaveOccurred()) v, err := client.ZDiffStore(ctx, "out1", "zset1", "zset2").Result() Expect(err).NotTo(HaveOccurred()) @@ -4338,20 +4374,6 @@ var _ = Describe("Commands", func() { Expect(id).To(Equal("3-0")) }) - // TODO remove in v9. - It("should XTrim", func() { - n, err := client.XTrim(ctx, "stream", 0).Result() - Expect(err).NotTo(HaveOccurred()) - Expect(n).To(Equal(int64(3))) - }) - - // TODO remove in v9. - It("should XTrimApprox", func() { - n, err := client.XTrimApprox(ctx, "stream", 0).Result() - Expect(err).NotTo(HaveOccurred()) - Expect(n).To(Equal(int64(3))) - }) - // TODO XTrimMaxLenApprox/XTrimMinIDApprox There is a bug in the limit parameter. // TODO Don't test it for now. // TODO link: https://github.com/redis/redis/issues/9046 @@ -5401,7 +5423,7 @@ var _ = Describe("Commands", func() { {nil, "", nil}, {"hello", "hello", new(string)}, {[]byte("hello"), "hello", new([]byte)}, - {int(1), "1", new(int)}, + {1, "1", new(int)}, {int8(1), "1", new(int8)}, {int16(1), "1", new(int16)}, {int32(1), "1", new(int32)}, @@ -5412,7 +5434,7 @@ var _ = Describe("Commands", func() { {uint32(1), "1", new(uint32)}, {uint64(1), "1", new(uint64)}, {float32(1.0), "1", new(float32)}, - {float64(1.0), "1", new(float64)}, + {1.0, "1", new(float64)}, {true, "1", new(bool)}, {false, "0", new(bool)}, } @@ -5487,7 +5509,7 @@ var _ = Describe("Commands", func() { old := client.ConfigGet(ctx, key).Val() client.ConfigSet(ctx, key, "0") - defer client.ConfigSet(ctx, key, old[1].(string)) + defer client.ConfigSet(ctx, key, old[key]) err := client.Do(ctx, "slowlog", "reset").Err() Expect(err).NotTo(HaveOccurred()) diff --git a/example_test.go b/example_test.go index f0158096..75b5cbbb 100644 --- a/example_test.go +++ b/example_test.go @@ -190,8 +190,8 @@ func ExampleClient_Set() { } } -func ExampleClient_SetEX() { - err := rdb.SetEX(ctx, "key", "value", time.Hour).Err() +func ExampleClient_SetEx() { + err := rdb.SetEx(ctx, "key", "value", time.Hour).Err() if err != nil { panic(err) } @@ -278,9 +278,9 @@ func ExampleClient_ScanType() { // Output: found 33 keys } -// ExampleStringStringMapCmd_Scan shows how to scan the results of a map fetch +// ExampleMapStringStringCmd_Scan shows how to scan the results of a map fetch // into a struct. -func ExampleStringStringMapCmd_Scan() { +func ExampleMapStringStringCmd_Scan() { rdb.FlushDB(ctx) err := rdb.HMSet(ctx, "map", "name", "hello", @@ -617,7 +617,7 @@ func ExampleClient_SlowLogGet() { old := rdb.ConfigGet(ctx, key).Val() rdb.ConfigSet(ctx, key, "0") - defer rdb.ConfigSet(ctx, key, old[1].(string)) + defer rdb.ConfigSet(ctx, key, old[key]) if err := rdb.Do(ctx, "slowlog", "reset").Err(); err != nil { panic(err) diff --git a/export_test.go b/export_test.go index 49c4b94c..e243a192 100644 --- a/export_test.go +++ b/export_test.go @@ -60,21 +60,21 @@ func (c *ClusterClient) SwapNodes(ctx context.Context, key string) error { return nil } -func (state *clusterState) IsConsistent(ctx context.Context) bool { - if len(state.Masters) < 3 { +func (c *clusterState) IsConsistent(ctx context.Context) bool { + if len(c.Masters) < 3 { return false } - for _, master := range state.Masters { + for _, master := range c.Masters { s := master.Client.Info(ctx, "replication").Val() if !strings.Contains(s, "role:master") { return false } } - if len(state.Slaves) < 3 { + if len(c.Slaves) < 3 { return false } - for _, slave := range state.Slaves { + for _, slave := range c.Slaves { s := slave.Client.Info(ctx, "replication").Val() if !strings.Contains(s, "role:slave") { return false diff --git a/internal/pool/conn_check.go b/internal/pool/conn_check.go new file mode 100644 index 00000000..1afdd6fe --- /dev/null +++ b/internal/pool/conn_check.go @@ -0,0 +1,49 @@ +// +build linux darwin dragonfly freebsd netbsd openbsd solaris illumos + +package pool + +import ( + "errors" + "io" + "net" + "syscall" + "time" +) + +var errUnexpectedRead = errors.New("unexpected read from socket") + +func connCheck(conn net.Conn) error { + // Reset previous timeout. + _ = conn.SetDeadline(time.Time{}) + + sysConn, ok := conn.(syscall.Conn) + if !ok { + return nil + } + rawConn, err := sysConn.SyscallConn() + if err != nil { + return err + } + + var sysErr error + err = rawConn.Read(func(fd uintptr) bool { + var buf [1]byte + n, err := syscall.Read(int(fd), buf[:]) + switch { + case n == 0 && err == nil: + sysErr = io.EOF + case n > 0: + sysErr = errUnexpectedRead + case err == syscall.EAGAIN || err == syscall.EWOULDBLOCK: + sysErr = nil + default: + sysErr = err + } + return true + }) + if err != nil { + return err + } + + return sysErr +} \ No newline at end of file diff --git a/internal/pool/conn_check_dummy.go b/internal/pool/conn_check_dummy.go new file mode 100644 index 00000000..e7d62808 --- /dev/null +++ b/internal/pool/conn_check_dummy.go @@ -0,0 +1,9 @@ +// +build !linux,!darwin,!dragonfly,!freebsd,!netbsd,!openbsd,!solaris,!illumos + +package pool + +import "net" + +func connCheck(conn net.Conn) error { + return nil +} \ No newline at end of file diff --git a/internal/pool/conn_check_test.go b/internal/pool/conn_check_test.go new file mode 100644 index 00000000..6adfc3d0 --- /dev/null +++ b/internal/pool/conn_check_test.go @@ -0,0 +1,48 @@ +//go:build linux || darwin || dragonfly || freebsd || netbsd || openbsd || solaris || illumos +// +build linux darwin dragonfly freebsd netbsd openbsd solaris illumos + +package pool + +import ( + "net" + "net/http/httptest" + "time" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("tests conn_check with real conns", func() { + var ts *httptest.Server + var conn net.Conn + var err error + + BeforeEach(func() { + ts = httptest.NewServer(nil) + conn, err = net.DialTimeout(ts.Listener.Addr().Network(), ts.Listener.Addr().String(), time.Second) + Expect(err).NotTo(HaveOccurred()) + }) + + AfterEach(func() { + ts.Close() + }) + + It("good conn check", func() { + Expect(connCheck(conn)).NotTo(HaveOccurred()) + + Expect(conn.Close()).NotTo(HaveOccurred()) + Expect(connCheck(conn)).To(HaveOccurred()) + }) + + It("bad conn check", func() { + Expect(conn.Close()).NotTo(HaveOccurred()) + Expect(connCheck(conn)).To(HaveOccurred()) + }) + + It("check conn deadline", func() { + Expect(conn.SetDeadline(time.Now())).NotTo(HaveOccurred()) + time.Sleep(time.Millisecond * 10) + Expect(connCheck(conn)).NotTo(HaveOccurred()) + Expect(conn.Close()).NotTo(HaveOccurred()) + }) +}) diff --git a/internal/pool/export_test.go b/internal/pool/export_test.go index 75dd4ad6..f3a65f86 100644 --- a/internal/pool/export_test.go +++ b/internal/pool/export_test.go @@ -1,9 +1,14 @@ package pool import ( + "net" "time" ) func (cn *Conn) SetCreatedAt(tm time.Time) { cn.createdAt = tm } + +func (cn *Conn) NetConn() net.Conn { + return cn.netConn +} diff --git a/internal/pool/pool_test.go b/internal/pool/pool_test.go index 423a783c..d0fbfe19 100644 --- a/internal/pool/pool_test.go +++ b/internal/pool/pool_test.go @@ -323,6 +323,8 @@ var _ = Describe("conns reaper", func() { cn.SetUsedAt(time.Now().Add(-2 * idleTimeout)) case "aged": cn.SetCreatedAt(time.Now().Add(-2 * maxAge)) + case "connCheck": + _ = cn.Close() } conns = append(conns, cn) staleConns = append(staleConns, cn) @@ -409,6 +411,7 @@ var _ = Describe("conns reaper", func() { assert("idle") assert("aged") + assert("connCheck") }) var _ = Describe("race", func() { diff --git a/internal/proto/reader.go b/internal/proto/reader.go index 0e6ca779..3ceedca9 100644 --- a/internal/proto/reader.go +++ b/internal/proto/reader.go @@ -2,21 +2,40 @@ package proto import ( "bufio" + "errors" "fmt" "io" + "math" + "math/big" + "strconv" "github.com/go-redis/redis/v8/internal/util" ) // redis resp protocol data type. const ( - ErrorReply = '-' - StatusReply = '+' - IntReply = ':' - StringReply = '$' - ArrayReply = '*' + RespStatus = '+' // +\r\n + RespError = '-' // -\r\n + RespString = '$' // $\r\n\r\n + RespInt = ':' // :\r\n + RespNil = '_' // _\r\n + RespFloat = ',' // ,\r\n (golang float) + RespBool = '#' // true: #t\r\n false: #f\r\n + RespBlobError = '!' // !\r\n\r\n + RespVerbatim = '=' // =\r\nFORMAT:\r\n + RespBigInt = '(' // (\r\n + RespArray = '*' // *\r\n... (same as resp2) + RespMap = '%' // %\r\n(key)\r\n(value)\r\n... (golang map) + RespSet = '~' // ~\r\n... (same as Array) + RespAttr = '|' // |\r\n(key)\r\n(value)\r\n... + command reply + RespPush = '>' // >\r\n... (same as Array) ) +// Not used temporarily. +// Redis has not used these two data types for the time being, and will implement them later. +// Streamed = "EOF:" +// StreamedAggregated = '?' + //------------------------------------------------------------------------------ const Nil = RedisError("redis: nil") // nolint:errname @@ -27,19 +46,19 @@ func (e RedisError) Error() string { return string(e) } func (RedisError) RedisError() {} +func ParseErrorReply(line []byte) error { + return RedisError(line[1:]) +} + //------------------------------------------------------------------------------ -type MultiBulkParse func(*Reader, int64) (interface{}, error) - type Reader struct { - rd *bufio.Reader - _buf []byte + rd *bufio.Reader } func NewReader(rd io.Reader) *Reader { return &Reader{ - rd: bufio.NewReader(rd), - _buf: make([]byte, 64), + rd: bufio.NewReader(rd), } } @@ -55,14 +74,53 @@ func (r *Reader) Reset(rd io.Reader) { r.rd.Reset(rd) } +// PeekReplyType returns the data type of the next response without advancing the Reader, +// and discard the attribute type. +func (r *Reader) PeekReplyType() (byte, error) { + b, err := r.rd.Peek(1) + if err != nil { + return 0, err + } + if b[0] == RespAttr { + if err = r.DiscardNext(); err != nil { + return 0, err + } + return r.PeekReplyType() + } + return b[0], nil +} + +// ReadLine Return a valid reply, it will check the protocol or redis error, +// and discard the attribute type. func (r *Reader) ReadLine() ([]byte, error) { line, err := r.readLine() if err != nil { return nil, err } - if isNilReply(line) { + switch line[0] { + case RespError: + return nil, ParseErrorReply(line) + case RespNil: + return nil, Nil + case RespBlobError: + var blobErr string + blobErr, err = r.readStringReply(line) + if err == nil { + err = RedisError(blobErr) + } + return nil, err + case RespAttr: + if err = r.Discard(line); err != nil { + return nil, err + } + return r.ReadLine() + } + + // Compatible with RESP2 + if IsNilReply(line) { return nil, Nil } + return line, nil } @@ -93,48 +151,192 @@ func (r *Reader) readLine() ([]byte, error) { return b[:len(b)-2], nil } -func (r *Reader) ReadReply(m MultiBulkParse) (interface{}, error) { +func (r *Reader) ReadReply() (interface{}, error) { line, err := r.ReadLine() if err != nil { return nil, err } switch line[0] { - case ErrorReply: - return nil, ParseErrorReply(line) - case StatusReply: + case RespStatus: return string(line[1:]), nil - case IntReply: + case RespInt: return util.ParseInt(line[1:], 10, 64) - case StringReply: + case RespFloat: + return r.readFloat(line) + case RespBool: + return r.readBool(line) + case RespBigInt: + return r.readBigInt(line) + + case RespString: return r.readStringReply(line) - case ArrayReply: - n, err := parseArrayLen(line) - if err != nil { - return nil, err - } - if m == nil { - err := fmt.Errorf("redis: got %.100q, but multi bulk parser is nil", line) - return nil, err - } - return m(r, n) + case RespVerbatim: + return r.readVerb(line) + + case RespArray, RespSet, RespPush: + return r.readSlice(line) + case RespMap: + return r.readMap(line) } return nil, fmt.Errorf("redis: can't parse %.100q", line) } -func (r *Reader) ReadIntReply() (int64, error) { +func (r *Reader) readFloat(line []byte) (float64, error) { + v := string(line[1:]) + switch string(line[1:]) { + case "inf": + return math.Inf(1), nil + case "-inf": + return math.Inf(-1), nil + } + return strconv.ParseFloat(v, 64) +} + +func (r *Reader) readBool(line []byte) (bool, error) { + switch string(line[1:]) { + case "t": + return true, nil + case "f": + return false, nil + } + return false, fmt.Errorf("redis: can't parse bool reply: %q", line) +} + +func (r *Reader) readBigInt(line []byte) (*big.Int, error) { + i := new(big.Int) + if i, ok := i.SetString(string(line[1:]), 10); ok { + return i, nil + } + return nil, fmt.Errorf("redis: can't parse bigInt reply: %q", line) +} + +func (r *Reader) readStringReply(line []byte) (string, error) { + n, err := replyLen(line) + if err != nil { + return "", err + } + + b := make([]byte, n+2) + _, err = io.ReadFull(r.rd, b) + if err != nil { + return "", err + } + + return util.BytesToString(b[:n]), nil +} + +func (r *Reader) readVerb(line []byte) (string, error) { + s, err := r.readStringReply(line) + if err != nil { + return "", err + } + if len(s) < 4 || s[3] != ':' { + return "", fmt.Errorf("redis: can't parse verbatim string reply: %q", line) + } + return s[4:], nil +} + +func (r *Reader) readSlice(line []byte) ([]interface{}, error) { + n, err := replyLen(line) + if err != nil { + return nil, err + } + + val := make([]interface{}, n) + for i := 0; i < len(val); i++ { + v, err := r.ReadReply() + if err != nil { + if err == Nil { + val[i] = nil + continue + } + if err, ok := err.(RedisError); ok { + val[i] = err + continue + } + return nil, err + } + val[i] = v + } + return val, nil +} + +func (r *Reader) readMap(line []byte) (map[interface{}]interface{}, error) { + n, err := replyLen(line) + if err != nil { + return nil, err + } + m := make(map[interface{}]interface{}, n) + for i := 0; i < n; i++ { + k, err := r.ReadReply() + if err != nil { + return nil, err + } + v, err := r.ReadReply() + if err != nil { + if err == Nil { + m[k] = nil + continue + } + if err, ok := err.(RedisError); ok { + m[k] = err + continue + } + return nil, err + } + m[k] = v + } + return m, nil +} + +// ------------------------------- + +func (r *Reader) ReadInt() (int64, error) { line, err := r.ReadLine() if err != nil { return 0, err } switch line[0] { - case ErrorReply: - return 0, ParseErrorReply(line) - case IntReply: + case RespInt, RespStatus: return util.ParseInt(line[1:], 10, 64) - default: - return 0, fmt.Errorf("redis: can't parse int reply: %.100q", line) + case RespString: + s, err := r.readStringReply(line) + if err != nil { + return 0, err + } + return util.ParseInt([]byte(s), 10, 64) + case RespBigInt: + b, err := r.readBigInt(line) + if err != nil { + return 0, err + } + if !b.IsInt64() { + return 0, fmt.Errorf("bigInt(%s) value out of range", b.String()) + } + return b.Int64(), nil } + return 0, fmt.Errorf("redis: can't parse int reply: %.100q", line) +} + +func (r *Reader) ReadFloat() (float64, error) { + line, err := r.ReadLine() + if err != nil { + return 0, err + } + switch line[0] { + case RespFloat: + return r.readFloat(line) + case RespStatus: + return strconv.ParseFloat(string(line[1:]), 64) + case RespString: + s, err := r.readStringReply(line) + if err != nil { + return 0, err + } + return strconv.ParseFloat(s, 64) + } + return 0, fmt.Errorf("redis: can't parse float reply: %.100q", line) } func (r *Reader) ReadString() (string, error) { @@ -142,191 +344,180 @@ func (r *Reader) ReadString() (string, error) { if err != nil { return "", err } + switch line[0] { - case ErrorReply: - return "", ParseErrorReply(line) - case StringReply: + case RespStatus, RespInt, RespFloat: + return string(line[1:]), nil + case RespString: return r.readStringReply(line) - case StatusReply: - return string(line[1:]), nil - case IntReply: - return string(line[1:]), nil - default: - return "", fmt.Errorf("redis: can't parse reply=%.100q reading string", line) + case RespBool: + b, err := r.readBool(line) + return strconv.FormatBool(b), err + case RespVerbatim: + return r.readVerb(line) + case RespBigInt: + b, err := r.readBigInt(line) + if err != nil { + return "", err + } + return b.String(), nil } + return "", fmt.Errorf("redis: can't parse reply=%.100q reading string", line) } -func (r *Reader) readStringReply(line []byte) (string, error) { - if isNilReply(line) { - return "", Nil - } - - replyLen, err := util.Atoi(line[1:]) +func (r *Reader) ReadBool() (bool, error) { + s, err := r.ReadString() if err != nil { - return "", err + return false, err } - - b := make([]byte, replyLen+2) - _, err = io.ReadFull(r.rd, b) - if err != nil { - return "", err - } - - return util.BytesToString(b[:replyLen]), nil + return s == "OK" || s == "1" || s == "true", nil } -func (r *Reader) ReadArrayReply(m MultiBulkParse) (interface{}, error) { +func (r *Reader) ReadSlice() ([]interface{}, error) { line, err := r.ReadLine() if err != nil { return nil, err } - switch line[0] { - case ErrorReply: - return nil, ParseErrorReply(line) - case ArrayReply: - n, err := parseArrayLen(line) - if err != nil { - return nil, err - } - return m(r, n) - default: - return nil, fmt.Errorf("redis: can't parse array reply: %.100q", line) - } + return r.readSlice(line) } +// ReadFixedArrayLen read fixed array length. +func (r *Reader) ReadFixedArrayLen(fixedLen int) error { + n, err := r.ReadArrayLen() + if err != nil { + return err + } + if n != fixedLen { + return fmt.Errorf("redis: got %d elements of array length, wanted %d", n, fixedLen) + } + return nil +} + +// ReadArrayLen Read and return the length of the array. func (r *Reader) ReadArrayLen() (int, error) { line, err := r.ReadLine() if err != nil { return 0, err } switch line[0] { - case ErrorReply: - return 0, ParseErrorReply(line) - case ArrayReply: - n, err := parseArrayLen(line) + case RespArray, RespSet, RespPush: + return replyLen(line) + default: + return 0, fmt.Errorf("redis: can't parse array(array/set/push) reply: %.100q", line) + } +} + +// ReadFixedMapLen read fixed map length. +func (r *Reader) ReadFixedMapLen(fixedLen int) error { + n, err := r.ReadMapLen() + if err != nil { + return err + } + if n != fixedLen { + return fmt.Errorf("redis: got %d elements of map length, wanted %d", n, fixedLen) + } + return nil +} + +// ReadMapLen read the length of the map type. +// If responding to the array type (RespArray/RespSet/RespPush), +// it must be a multiple of 2 and return n/2. +// Other types will return an error. +func (r *Reader) ReadMapLen() (int, error) { + line, err := r.ReadLine() + if err != nil { + return 0, err + } + switch line[0] { + case RespMap: + return replyLen(line) + case RespArray, RespSet, RespPush: + // Some commands and RESP2 protocol may respond to array types. + n, err := replyLen(line) if err != nil { return 0, err } - return int(n), nil - default: - return 0, fmt.Errorf("redis: can't parse array reply: %.100q", line) - } -} - -func (r *Reader) ReadScanReply() ([]string, uint64, error) { - n, err := r.ReadArrayLen() - if err != nil { - return nil, 0, err - } - if n != 2 { - return nil, 0, fmt.Errorf("redis: got %d elements in scan reply, expected 2", n) - } - - cursor, err := r.ReadUint() - if err != nil { - return nil, 0, err - } - - n, err = r.ReadArrayLen() - if err != nil { - return nil, 0, err - } - - keys := make([]string, n) - - for i := 0; i < n; i++ { - key, err := r.ReadString() - if err != nil { - return nil, 0, err + if n%2 != 0 { + return 0, fmt.Errorf("redis: the length of the array must be a multiple of 2, got: %d", n) } - keys[i] = key + return n / 2, nil + default: + return 0, fmt.Errorf("redis: can't parse map reply: %.100q", line) } - - return keys, cursor, err } -func (r *Reader) ReadInt() (int64, error) { - b, err := r.readTmpBytesReply() - if err != nil { - return 0, err - } - return util.ParseInt(b, 10, 64) -} - -func (r *Reader) ReadUint() (uint64, error) { - b, err := r.readTmpBytesReply() - if err != nil { - return 0, err - } - return util.ParseUint(b, 10, 64) -} - -func (r *Reader) ReadFloatReply() (float64, error) { - b, err := r.readTmpBytesReply() - if err != nil { - return 0, err - } - return util.ParseFloat(b, 64) -} - -func (r *Reader) readTmpBytesReply() ([]byte, error) { - line, err := r.ReadLine() - if err != nil { - return nil, err +// Discard the data represented by line. +func (r *Reader) Discard(line []byte) (err error) { + if len(line) == 0 { + return errors.New("redis: invalid line") } switch line[0] { - case ErrorReply: - return nil, ParseErrorReply(line) - case StringReply: - return r._readTmpBytesReply(line) - case StatusReply: - return line[1:], nil - default: - return nil, fmt.Errorf("redis: can't parse string reply: %.100q", line) + case RespStatus, RespError, RespInt, RespNil, RespFloat, RespBool, RespBigInt: + return nil } + + n, err := replyLen(line) + if err != nil && err != Nil { + return err + } + + switch line[0] { + case RespBlobError, RespString, RespVerbatim: + // +\r\n + _, err = r.rd.Discard(n + 2) + return err + case RespArray, RespSet, RespPush: + for i := 0; i < n; i++ { + if err = r.DiscardNext(); err != nil { + return err + } + } + return nil + case RespMap, RespAttr: + // Read key & value. + for i := 0; i < n*2; i++ { + if err = r.DiscardNext(); err != nil { + return err + } + } + return nil + } + + return fmt.Errorf("redis: can't parse %.100q", line) } -func (r *Reader) _readTmpBytesReply(line []byte) ([]byte, error) { - if isNilReply(line) { - return nil, Nil - } - - replyLen, err := util.Atoi(line[1:]) +// DiscardNext read and discard the data represented by the next line. +func (r *Reader) DiscardNext() error { + line, err := r.readLine() if err != nil { - return nil, err + return err } + return r.Discard(line) +} - buf := r.buf(replyLen + 2) - _, err = io.ReadFull(r.rd, buf) +func replyLen(line []byte) (n int, err error) { + n, err = util.Atoi(line[1:]) if err != nil { - return nil, err + return 0, err } - return buf[:replyLen], nil -} - -func (r *Reader) buf(n int) []byte { - if n <= cap(r._buf) { - return r._buf[:n] + if n < -1 { + return 0, fmt.Errorf("redis: invalid reply: %q", line) } - d := n - cap(r._buf) - r._buf = append(r._buf, make([]byte, d)...) - return r._buf -} -func isNilReply(b []byte) bool { - return len(b) == 3 && - (b[0] == StringReply || b[0] == ArrayReply) && - b[1] == '-' && b[2] == '1' -} - -func ParseErrorReply(line []byte) error { - return RedisError(string(line[1:])) -} - -func parseArrayLen(line []byte) (int64, error) { - if isNilReply(line) { - return 0, Nil + switch line[0] { + case RespString, RespVerbatim, RespBlobError, + RespArray, RespSet, RespPush, RespMap, RespAttr: + if n == -1 { + return 0, Nil + } } - return util.ParseInt(line[1:], 10, 64) + return n, nil +} + +// IsNilReply detect redis.Nil of RESP2. +func IsNilReply(line []byte) bool { + return len(line) == 3 && + (line[0] == RespString || line[0] == RespArray) && + line[1] == '-' && line[2] == '1' } diff --git a/internal/proto/reader_test.go b/internal/proto/reader_test.go index b8c99dd6..9881047b 100644 --- a/internal/proto/reader_test.go +++ b/internal/proto/reader_test.go @@ -9,23 +9,63 @@ import ( ) func BenchmarkReader_ParseReply_Status(b *testing.B) { - benchmarkParseReply(b, "+OK\r\n", nil, false) + benchmarkParseReply(b, "+OK\r\n", false) } func BenchmarkReader_ParseReply_Int(b *testing.B) { - benchmarkParseReply(b, ":1\r\n", nil, false) + benchmarkParseReply(b, ":1\r\n", false) +} + +func BenchmarkReader_ParseReply_Float(b *testing.B) { + benchmarkParseReply(b, ",123.456\r\n", false) +} + +func BenchmarkReader_ParseReply_Bool(b *testing.B) { + benchmarkParseReply(b, "#t\r\n", false) +} + +func BenchmarkReader_ParseReply_BigInt(b *testing.B) { + benchmarkParseReply(b, "(3492890328409238509324850943850943825024385\r\n", false) } func BenchmarkReader_ParseReply_Error(b *testing.B) { - benchmarkParseReply(b, "-Error message\r\n", nil, true) + benchmarkParseReply(b, "-Error message\r\n", true) +} + +func BenchmarkReader_ParseReply_Nil(b *testing.B) { + benchmarkParseReply(b, "_\r\n", true) +} + +func BenchmarkReader_ParseReply_BlobError(b *testing.B) { + benchmarkParseReply(b, "!21\r\nSYNTAX invalid syntax", true) } func BenchmarkReader_ParseReply_String(b *testing.B) { - benchmarkParseReply(b, "$5\r\nhello\r\n", nil, false) + benchmarkParseReply(b, "$5\r\nhello\r\n", false) +} + +func BenchmarkReader_ParseReply_Verb(b *testing.B) { + benchmarkParseReply(b, "$9\r\ntxt:hello\r\n", false) } func BenchmarkReader_ParseReply_Slice(b *testing.B) { - benchmarkParseReply(b, "*2\r\n$5\r\nhello\r\n$5\r\nworld\r\n", multiBulkParse, false) + benchmarkParseReply(b, "*2\r\n$5\r\nhello\r\n$5\r\nworld\r\n", false) +} + +func BenchmarkReader_ParseReply_Set(b *testing.B) { + benchmarkParseReply(b, "~2\r\n$5\r\nhello\r\n$5\r\nworld\r\n", false) +} + +func BenchmarkReader_ParseReply_Push(b *testing.B) { + benchmarkParseReply(b, ">2\r\n$5\r\nhello\r\n$5\r\nworld\r\n", false) +} + +func BenchmarkReader_ParseReply_Map(b *testing.B) { + benchmarkParseReply(b, "%2\r\n$5\r\nhello\r\n$5\r\nworld\r\n+key\r\n+value\r\n", false) +} + +func BenchmarkReader_ParseReply_Attr(b *testing.B) { + benchmarkParseReply(b, "%1\r\n+key\r\n+value\r\n+hello\r\n", false) } func TestReader_ReadLine(t *testing.T) { @@ -43,7 +83,7 @@ func TestReader_ReadLine(t *testing.T) { } } -func benchmarkParseReply(b *testing.B, reply string, m proto.MultiBulkParse, wanterr bool) { +func benchmarkParseReply(b *testing.B, reply string, wanterr bool) { buf := new(bytes.Buffer) for i := 0; i < b.N; i++ { buf.WriteString(reply) @@ -52,21 +92,9 @@ func benchmarkParseReply(b *testing.B, reply string, m proto.MultiBulkParse, wan b.ResetTimer() for i := 0; i < b.N; i++ { - _, err := p.ReadReply(m) + _, err := p.ReadReply() if !wanterr && err != nil { b.Fatal(err) } } } - -func multiBulkParse(p *proto.Reader, n int64) (interface{}, error) { - vv := make([]interface{}, 0, n) - for i := int64(0); i < n; i++ { - v, err := p.ReadReply(multiBulkParse) - if err != nil { - return nil, err - } - vv = append(vv, v) - } - return vv, nil -} diff --git a/internal/proto/writer.go b/internal/proto/writer.go index c5d48cda..c674d44b 100644 --- a/internal/proto/writer.go +++ b/internal/proto/writer.go @@ -14,7 +14,7 @@ import ( type writer interface { io.Writer io.ByteWriter - // io.StringWriter + // WriteString implement io.StringWriter. WriteString(s string) (n int, err error) } @@ -35,7 +35,7 @@ func NewWriter(wr writer) *Writer { } func (w *Writer) WriteArgs(args []interface{}) error { - if err := w.WriteByte(ArrayReply); err != nil { + if err := w.WriteByte(RespArray); err != nil { return err } @@ -116,7 +116,7 @@ func (w *Writer) WriteArg(v interface{}) error { } func (w *Writer) bytes(b []byte) error { - if err := w.WriteByte(StringReply); err != nil { + if err := w.WriteByte(RespString); err != nil { return err } diff --git a/options.go b/options.go index e74f599a..bea7efbf 100644 --- a/options.go +++ b/options.go @@ -270,7 +270,10 @@ func setupTCPConn(u *url.URL) (*Options, error) { } if u.Scheme == "rediss" { - o.TLSConfig = &tls.Config{ServerName: h} + o.TLSConfig = &tls.Config{ + ServerName: h, + MinVersion: tls.VersionTLS12, + } } return setupConnParams(u, o) diff --git a/pipeline.go b/pipeline.go index 31bab971..b9845ba3 100644 --- a/pipeline.go +++ b/pipeline.go @@ -3,8 +3,6 @@ package redis import ( "context" "sync" - - "github.com/go-redis/redis/v8/internal/pool" ) type pipelineExecer func(context.Context, []Cmder) error @@ -27,8 +25,7 @@ type Pipeliner interface { Len() int Do(ctx context.Context, args ...interface{}) *Cmd Process(ctx context.Context, cmd Cmder) error - Close() error - Discard() error + Discard() Exec(ctx context.Context) ([]Cmder, error) } @@ -44,9 +41,8 @@ type Pipeline struct { ctx context.Context exec pipelineExecer - mu sync.Mutex - cmds []Cmder - closed bool + mu sync.Mutex + cmds []Cmder } func (c *Pipeline) init() { @@ -77,29 +73,11 @@ func (c *Pipeline) Process(ctx context.Context, cmd Cmder) error { return nil } -// Close closes the pipeline, releasing any open resources. -func (c *Pipeline) Close() error { - c.mu.Lock() - _ = c.discard() - c.closed = true - c.mu.Unlock() - return nil -} - // Discard resets the pipeline and discards queued commands. -func (c *Pipeline) Discard() error { +func (c *Pipeline) Discard() { c.mu.Lock() - err := c.discard() - c.mu.Unlock() - return err -} - -func (c *Pipeline) discard() error { - if c.closed { - return pool.ErrClosed - } c.cmds = c.cmds[:0] - return nil + c.mu.Unlock() } // Exec executes all previously queued commands using one @@ -111,10 +89,6 @@ func (c *Pipeline) Exec(ctx context.Context) ([]Cmder, error) { c.mu.Lock() defer c.mu.Unlock() - if c.closed { - return nil, pool.ErrClosed - } - if len(c.cmds) == 0 { return nil, nil } @@ -129,9 +103,7 @@ func (c *Pipeline) Pipelined(ctx context.Context, fn func(Pipeliner) error) ([]C if err := fn(c); err != nil { return nil, err } - cmds, err := c.Exec(ctx) - _ = c.Close() - return cmds, err + return c.Exec(ctx) } func (c *Pipeline) Pipeline() Pipeliner { diff --git a/pool_test.go b/pool_test.go index dbef72ec..e297b010 100644 --- a/pool_test.go +++ b/pool_test.go @@ -72,7 +72,6 @@ var _ = Describe("pool", func() { Expect(cmds).To(HaveLen(1)) Expect(ping.Err()).NotTo(HaveOccurred()) Expect(ping.Val()).To(Equal("PONG")) - Expect(pipe.Close()).NotTo(HaveOccurred()) }) pool := client.Pool() diff --git a/pubsub_test.go b/pubsub_test.go index 2dfa66bd..5776bbde 100644 --- a/pubsub_test.go +++ b/pubsub_test.go @@ -1,7 +1,6 @@ package redis_test import ( - "context" "io" "net" "sync" @@ -15,16 +14,11 @@ import ( var _ = Describe("PubSub", func() { var client *redis.Client - var clientID int64 BeforeEach(func() { opt := redisOptions() opt.MinIdleConns = 0 opt.MaxConnAge = 0 - opt.OnConnect = func(ctx context.Context, cn *redis.Conn) (err error) { - clientID, err = cn.ClientID(ctx).Result() - return err - } client = redis.NewClient(opt) Expect(client.FlushDB(ctx).Err()).NotTo(HaveOccurred()) }) @@ -421,30 +415,6 @@ var _ = Describe("PubSub", func() { Expect(msg.Payload).To(Equal(string(bigVal))) }) - It("handles message payload slice with server-assisted client-size caching", func() { - pubsub := client.Subscribe(ctx, "__redis__:invalidate") - defer pubsub.Close() - - client2 := redis.NewClient(redisOptions()) - defer client2.Close() - - err := client2.Do(ctx, "CLIENT", "TRACKING", "on", "REDIRECT", clientID).Err() - Expect(err).NotTo(HaveOccurred()) - - err = client2.Do(ctx, "GET", "mykey").Err() - Expect(err).To(Equal(redis.Nil)) - - err = client2.Do(ctx, "SET", "mykey", "myvalue").Err() - Expect(err).NotTo(HaveOccurred()) - - ch := pubsub.Channel() - - var msg *redis.Message - Eventually(ch).Should(Receive(&msg)) - Expect(msg.Channel).To(Equal("__redis__:invalidate")) - Expect(msg.PayloadSlice).To(Equal([]string{"mykey"})) - }) - It("supports concurrent Ping and Receive", func() { const N = 100 diff --git a/redis.go b/redis.go index fa57dee3..e5d0eb1c 100644 --- a/redis.go +++ b/redis.go @@ -15,6 +15,7 @@ import ( // Nil reply returned by Redis when key does not exist. const Nil = proto.Nil +// SetLogger set custom log func SetLogger(logger internal.Logging) { internal.Logger = logger } @@ -232,8 +233,19 @@ func (c *baseClient) initConn(ctx context.Context, cn *pool.Conn) error { connPool := pool.NewSingleConnPool(c.connPool, cn) conn := newConn(ctx, c.opt, connPool) + var auth bool + + // The low version of redis-server does not support the hello command. + // For redis-server (<6.0) that does not support the Hello command, + // we continue to provide services with RESP2. + if err := conn.Hello(ctx, 3, c.opt.Username, c.opt.Password, "").Err(); err == nil { + auth = true + } else if err.Error() != "ERR unknown command 'hello'" { + return err + } + _, err := conn.Pipelined(ctx, func(pipe Pipeliner) error { - if password != "" { + if !auth && password != "" { if username != "" { pipe.AuthACL(ctx, username, password) } else { @@ -522,14 +534,8 @@ func txPipelineReadQueued(rd *proto.Reader, statusCmd *StatusCmd, cmds []Cmder) return err } - switch line[0] { - case proto.ErrorReply: - return proto.ParseErrorReply(line) - case proto.ArrayReply: - // ok - default: - err := fmt.Errorf("redis: expected '*', but got line %q", line) - return err + if line[0] != proto.RespArray { + return fmt.Errorf("redis: expected '*', but got line %q", line) } return nil @@ -537,9 +543,11 @@ func txPipelineReadQueued(rd *proto.Reader, statusCmd *StatusCmd, cmds []Cmder) //------------------------------------------------------------------------------ -// Client is a Redis client representing a pool of zero or more -// underlying connections. It's safe for concurrent use by multiple -// goroutines. +// Client is a Redis client representing a pool of zero or more underlying connections. +// It's safe for concurrent use by multiple goroutines. +// +// Client creates and frees connections automatically; it also maintains a free pool +// of idle connections. You can control the pool size with Config.PoolSize option. type Client struct { *baseClient cmdable diff --git a/redis_test.go b/redis_test.go index 47792ac8..2215228f 100644 --- a/redis_test.go +++ b/redis_test.go @@ -136,17 +136,6 @@ var _ = Describe("Client", func() { Expect(client.Ping(ctx).Err()).NotTo(HaveOccurred()) }) - It("should close pipeline without closing the client", func() { - pipeline := client.Pipeline() - Expect(pipeline.Close()).NotTo(HaveOccurred()) - - pipeline.Ping(ctx) - _, err := pipeline.Exec(ctx) - Expect(err).To(MatchError("redis: client is closed")) - - Expect(client.Ping(ctx).Err()).NotTo(HaveOccurred()) - }) - It("should close pubsub when client is closed", func() { pubsub := client.Subscribe(ctx) Expect(client.Close()).NotTo(HaveOccurred()) @@ -157,12 +146,6 @@ var _ = Describe("Client", func() { Expect(pubsub.Close()).NotTo(HaveOccurred()) }) - It("should close pipeline when client is closed", func() { - pipeline := client.Pipeline() - Expect(client.Close()).NotTo(HaveOccurred()) - Expect(pipeline.Close()).NotTo(HaveOccurred()) - }) - It("should select DB", func() { db2 := redis.NewClient(&redis.Options{ Addr: redisAddr, diff --git a/result.go b/result.go index 34e9de8b..9401dfee 100644 --- a/result.go +++ b/result.go @@ -83,8 +83,8 @@ func NewBoolSliceResult(val []bool, err error) *BoolSliceCmd { } // NewStringStringMapResult returns a StringStringMapCmd initialised with val and err for testing. -func NewStringStringMapResult(val map[string]string, err error) *StringStringMapCmd { - var cmd StringStringMapCmd +func NewStringStringMapResult(val map[string]string, err error) *MapStringStringCmd { + var cmd MapStringStringCmd cmd.val = val cmd.SetErr(err) return &cmd diff --git a/ring.go b/ring.go index 4df00fc8..8309cbe4 100644 --- a/ring.go +++ b/ring.go @@ -309,7 +309,7 @@ func (c *ringShards) Random() (*ringShard, error) { return c.GetByKey(strconv.Itoa(rand.Int())) } -// heartbeat monitors state of each shard in the ring. +// Heartbeat monitors state of each shard in the ring. func (c *ringShards) Heartbeat(frequency time.Duration) { ticker := time.NewTicker(frequency) defer ticker.Stop() diff --git a/ring_test.go b/ring_test.go index 03a49fd7..b0705c5b 100644 --- a/ring_test.go +++ b/ring_test.go @@ -123,7 +123,6 @@ var _ = Describe("Redis Ring", func() { cmds, err := pipe.Exec(ctx) Expect(err).NotTo(HaveOccurred()) Expect(cmds).To(HaveLen(100)) - Expect(pipe.Close()).NotTo(HaveOccurred()) for _, cmd := range cmds { Expect(cmd.Err()).NotTo(HaveOccurred()) @@ -177,6 +176,7 @@ var _ = Describe("Redis Ring", func() { It("can be initialized with a new client callback", func() { opts := redisRingOptions() opts.NewClient = func(name string, opt *redis.Options) *redis.Client { + opt.Username = "username1" opt.Password = "password1" return redis.NewClient(opt) } @@ -184,7 +184,7 @@ var _ = Describe("Redis Ring", func() { err := ring.Ping(ctx).Err() Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("ERR AUTH")) + Expect(err.Error()).To(ContainSubstring("WRONGPASS")) }) }) diff --git a/sentinel.go b/sentinel.go index ec6221dc..205f8a12 100644 --- a/sentinel.go +++ b/sentinel.go @@ -335,8 +335,8 @@ func (c *SentinelClient) GetMasterAddrByName(ctx context.Context, name string) * return cmd } -func (c *SentinelClient) Sentinels(ctx context.Context, name string) *SliceCmd { - cmd := NewSliceCmd(ctx, "sentinel", "sentinels", name) +func (c *SentinelClient) Sentinels(ctx context.Context, name string) *MapStringStringSliceCmd { + cmd := NewMapStringStringSliceCmd(ctx, "sentinel", "sentinels", name) _ = c.Process(ctx, cmd) return cmd } @@ -368,8 +368,8 @@ func (c *SentinelClient) FlushConfig(ctx context.Context) *StatusCmd { } // Master shows the state and info of the specified master. -func (c *SentinelClient) Master(ctx context.Context, name string) *StringStringMapCmd { - cmd := NewStringStringMapCmd(ctx, "sentinel", "master", name) +func (c *SentinelClient) Master(ctx context.Context, name string) *MapStringStringCmd { + cmd := NewMapStringStringCmd(ctx, "sentinel", "master", name) _ = c.Process(ctx, cmd) return cmd } @@ -382,8 +382,8 @@ func (c *SentinelClient) Masters(ctx context.Context) *SliceCmd { } // Slaves shows a list of slaves for the specified master and their state. -func (c *SentinelClient) Slaves(ctx context.Context, name string) *SliceCmd { - cmd := NewSliceCmd(ctx, "sentinel", "slaves", name) +func (c *SentinelClient) Slaves(ctx context.Context, name string) *MapStringStringSliceCmd { + cmd := NewMapStringStringSliceCmd(ctx, "sentinel", "slaves", name) _ = c.Process(ctx, cmd) return cmd } @@ -601,40 +601,24 @@ func (c *sentinelFailover) getSlaveAddrs(ctx context.Context, sentinel *Sentinel return parseSlaveAddrs(addrs, false) } -func parseSlaveAddrs(addrs []interface{}, keepDisconnected bool) []string { +func parseSlaveAddrs(addrs []map[string]string, keepDisconnected bool) []string { nodes := make([]string, 0, len(addrs)) for _, node := range addrs { - ip := "" - port := "" - flags := []string{} - lastkey := "" isDown := false - - for _, key := range node.([]interface{}) { - switch lastkey { - case "ip": - ip = key.(string) - case "port": - port = key.(string) - case "flags": - flags = strings.Split(key.(string), ",") - } - lastkey = key.(string) - } - - for _, flag := range flags { - switch flag { - case "s_down", "o_down": - isDown = true - case "disconnected": - if !keepDisconnected { + if flags, ok := node["flags"]; ok { + for _, flag := range strings.Split(flags, ",") { + switch flag { + case "s_down", "o_down": isDown = true + case "disconnected": + if !keepDisconnected { + isDown = true + } } } } - - if !isDown { - nodes = append(nodes, net.JoinHostPort(ip, port)) + if !isDown && node["ip"] != "" && node["port"] != "" { + nodes = append(nodes, net.JoinHostPort(node["ip"], node["port"])) } } @@ -683,16 +667,13 @@ func (c *sentinelFailover) discoverSentinels(ctx context.Context) { return } for _, sentinel := range sentinels { - vals := sentinel.([]interface{}) - var ip, port string - for i := 0; i < len(vals); i += 2 { - key := vals[i].(string) - switch key { - case "ip": - ip = vals[i+1].(string) - case "port": - port = vals[i+1].(string) - } + ip, ok := sentinel["ip"] + if !ok { + continue + } + port, ok := sentinel["port"] + if !ok { + continue } if ip != "" && port != "" { sentinelAddr := net.JoinHostPort(ip, port) diff --git a/sentinel_test.go b/sentinel_test.go index 753e0fc2..cc56cbab 100644 --- a/sentinel_test.go +++ b/sentinel_test.go @@ -185,7 +185,8 @@ var _ = Describe("NewFailoverClusterClient", func() { } // Create subscription. - ch := client.Subscribe(ctx, "foo").Channel() + sub := client.Subscribe(ctx, "foo") + ch := sub.Channel() // Kill master. err = master.Shutdown(ctx).Err() @@ -207,6 +208,7 @@ var _ = Describe("NewFailoverClusterClient", func() { }, "15s", "100ms").Should(Receive(&msg)) Expect(msg.Channel).To(Equal("foo")) Expect(msg.Payload).To(Equal("hello")) + Expect(sub.Close()).NotTo(HaveOccurred()) _, err = startRedis(masterPort) Expect(err).NotTo(HaveOccurred()) diff --git a/testdata/redis.conf b/testdata/redis.conf deleted file mode 100644 index 235b2954..00000000 --- a/testdata/redis.conf +++ /dev/null @@ -1,10 +0,0 @@ -# Minimal redis.conf - -port 6379 -daemonize no -dir . -save "" -appendonly yes -cluster-config-file nodes.conf -cluster-node-timeout 30000 -maxclients 1001 \ No newline at end of file