From 8ad01240a494fe590a414df0c50fdb612af127e4 Mon Sep 17 00:00:00 2001 From: monkey92t Date: Tue, 27 Apr 2021 15:04:46 +0800 Subject: [PATCH 01/17] Add support for resp3 protocol (#1739) * support resp3 protocol Signed-off-by: monkey * Upgrade mod version limit go1.14 https://github.com/go-redis/redis/issues/1715#issuecomment-820685614 Signed-off-by: monkey * Remove the redundant check of ReadReply Signed-off-by: monkey * fix the problem Signed-off-by: monkey * workflows add v9 Signed-off-by: monkey * update StringStringMapCmd to MapStringStringCmd Signed-off-by: monkey --- .github/workflows/build.yml | 2 +- bench_decode_test.go | 18 +- cluster.go | 7 +- cluster_test.go | 11 +- command.go | 1616 +++++++++++++++++---------------- commands.go | 79 +- commands_test.go | 44 +- example_test.go | 6 +- go.mod | 2 +- internal/proto/reader.go | 549 +++++++---- internal/proto/reader_test.go | 66 +- internal/proto/writer.go | 4 +- pubsub_test.go | 30 - redis.go | 26 +- result.go | 4 +- ring_test.go | 3 +- sentinel.go | 67 +- sentinel_test.go | 4 +- 18 files changed, 1402 insertions(+), 1136 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f58a11cc..e6858a32 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,7 +4,7 @@ on: push: branches: [master] pull_request: - branches: [master] + branches: [master, v9] jobs: build: 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/cluster.go b/cluster.go index e5d49dde..738d50a4 100644 --- a/cluster.go +++ b/cluster.go @@ -1392,12 +1392,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 3880d437..4c4e4d31 100644 --- a/cluster_test.go +++ b/cluster_test.go @@ -1182,16 +1182,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 f10c4781..28fd8c13 100644 --- a/command.go +++ b/command.go @@ -316,31 +316,10 @@ func (cmd *Cmd) Bool() (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 { @@ -392,13 +371,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 } //------------------------------------------------------------------------------ @@ -473,7 +448,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 } @@ -509,18 +484,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 } //------------------------------------------------------------------------------ @@ -557,7 +531,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 } @@ -604,25 +578,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 } //------------------------------------------------------------------------------ @@ -656,27 +624,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 } //------------------------------------------------------------------------------ @@ -811,7 +768,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 } @@ -847,21 +804,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 } //------------------------------------------------------------------------------ @@ -900,21 +859,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 } //------------------------------------------------------------------------------ @@ -949,32 +998,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, @@ -982,21 +1030,21 @@ func NewStringStringMapCmd(ctx context.Context, args ...interface{}) *StringStri } } -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) 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(dst interface{}) error { +func (cmd *MapStringStringCmd) Scan(dst interface{}) error { if cmd.err != nil { return cmd.err } @@ -1015,25 +1063,27 @@ func (cmd *StringStringMapCmd) Scan(dst 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 } //------------------------------------------------------------------------------ @@ -1068,24 +1118,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 } //------------------------------------------------------------------------------ @@ -1120,18 +1171,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 } //------------------------------------------------------------------------------ @@ -1170,8 +1223,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 } @@ -1183,10 +1235,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 } } @@ -1194,40 +1244,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 @@ -1280,38 +1326,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 } //------------------------------------------------------------------------------ @@ -1352,68 +1395,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 } //------------------------------------------------------------------------------ @@ -1454,49 +1474,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 } //------------------------------------------------------------------------------ @@ -1540,62 +1548,39 @@ 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": + cmd.val[i].Idle, err = rd.ReadInt() + 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 { @@ -1638,64 +1623,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 { @@ -1737,49 +1699,40 @@ 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) case "last-entry": - info.LastEntry, err = readXMessage(rd) + cmd.val.LastEntry, err = readXMessage(rd) 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 } //------------------------------------------------------------------------------ @@ -1813,28 +1766,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 } //------------------------------------------------------------------------------ @@ -1868,33 +1840,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 } //------------------------------------------------------------------------------ @@ -1932,9 +1894,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. @@ -1987,69 +1969,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 != 3 { - err := fmt.Errorf("got %d elements in cluster info address, expected 2 or 3", 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 - } - } - - 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 } //------------------------------------------------------------------------------ @@ -2074,6 +2057,9 @@ type GeoRadiusQuery struct { Sort string Store string StoreDist string + + // WithCoord+WithDist+WithGeoHash + withLen int } type GeoLocationCmd struct { @@ -2104,12 +2090,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) @@ -2141,82 +2130,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 - } -} - //------------------------------------------------------------------------------ type GeoPos struct { @@ -2253,38 +2215,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 } //------------------------------------------------------------------------------ @@ -2330,112 +2292,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 } //------------------------------------------------------------------------------ @@ -2517,75 +2461,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/commands.go b/commands.go index 37746334..65ca4640 100644 --- a/commands.go +++ b/commands.go @@ -158,7 +158,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 @@ -168,7 +168,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 @@ -274,7 +275,8 @@ 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, withScores bool) *StringSliceCmd + ZRandMember(ctx context.Context, key string, count int) *StringSliceCmd + ZRandMemberWithScores(ctx context.Context, key string, count int) *ZSliceCmd PFAdd(ctx context.Context, key string, els ...interface{}) *IntCmd PFCount(ctx context.Context, keys ...string) *IntCmd @@ -287,7 +289,7 @@ 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 + 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 @@ -358,6 +360,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 ( @@ -413,6 +416,26 @@ func (c statefulCmdable) ClientSetName(ctx context.Context, name string) *BoolCm return cmd } +// 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 { @@ -440,7 +463,7 @@ func (c cmdable) Ping(ctx context.Context) *StatusCmd { return cmd } -func (c cmdable) Quit(ctx context.Context) *StatusCmd { +func (c cmdable) Quit(_ context.Context) *StatusCmd { panic("not implemented") } @@ -1138,8 +1161,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 } @@ -1222,16 +1245,15 @@ func (c cmdable) HVals(ctx context.Context, key string) *StringSliceCmd { } // 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...) +// 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 } @@ -2316,17 +2338,16 @@ func (c cmdable) ZUnionStore(ctx context.Context, dest string, store *ZStore) *I return cmd } -// 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) +// ZRandMember redis-server version >= 6.2.0. +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 } @@ -2431,8 +2452,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 } @@ -2553,7 +2574,7 @@ func (c cmdable) SlowLogGet(ctx context.Context, num int64) *SlowLogCmd { return cmd } -func (c cmdable) Sync(ctx context.Context) { +func (c cmdable) Sync(_ context.Context) { panic("not implemented") } diff --git a/commands_test.go b/commands_test.go index e927c58d..14495874 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")) }) @@ -1839,18 +1851,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"}}), + )) }) }) @@ -3919,18 +3933,20 @@ var _ = Describe("Commands", func() { 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}}), + )) }) }) @@ -4675,7 +4691,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 := rdb.Do(ctx, "slowlog", "reset").Err() Expect(err).NotTo(HaveOccurred()) diff --git a/example_test.go b/example_test.go index 7d9f7405..73b2f2d6 100644 --- a/example_test.go +++ b/example_test.go @@ -276,9 +276,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", @@ -615,7 +615,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/go.mod b/go.mod index 938768f2..1d3dfe1b 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/go-redis/redis/v8 -go 1.13 +go 1.14 require ( github.com/cespare/xxhash/v2 v2.1.1 diff --git a/internal/proto/reader.go b/internal/proto/reader.go index 0ab8c9d2..410e4666 100644 --- a/internal/proto/reader.go +++ b/internal/proto/reader.go @@ -2,20 +2,39 @@ package proto import ( "bufio" + "errors" "fmt" "io" + "math" + "math/big" + "strconv" "github.com/go-redis/redis/v8/internal/util" ) 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") @@ -26,19 +45,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), } } @@ -54,14 +73,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 } @@ -92,48 +150,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) { @@ -141,191 +343,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 81b09b8e..72b30441 100644 --- a/internal/proto/writer.go +++ b/internal/proto/writer.go @@ -34,7 +34,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 } @@ -111,7 +111,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/pubsub_test.go b/pubsub_test.go index d32d5e0b..b9633b2f 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 7995c436..8bf44038 100644 --- a/redis.go +++ b/redis.go @@ -230,21 +230,21 @@ func (c *baseClient) initConn(ctx context.Context, cn *pool.Conn) error { } cn.Inited = true - if c.opt.Password == "" && - c.opt.DB == 0 && - !c.opt.readOnly && - c.opt.OnConnect == nil { - return nil - } - ctx, span := internal.StartSpan(ctx, "redis.init_conn") defer span.End() 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. + if conn.Hello(ctx, 3, c.opt.Username, c.opt.Password, "").Err() == nil { + auth = true + } + _, err := conn.Pipelined(ctx, func(pipe Pipeliner) error { - if c.opt.Password != "" { + if !auth && c.opt.Password != "" { if c.opt.Username != "" { pipe.AuthACL(ctx, c.opt.Username, c.opt.Password) } else { @@ -542,14 +542,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 diff --git a/result.go b/result.go index 24cfd499..5043bf93 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_test.go b/ring_test.go index 2189cd63..4a434a5b 100644 --- a/ring_test.go +++ b/ring_test.go @@ -177,6 +177,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 +185,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 ca2e088a..15f3366d 100644 --- a/sentinel.go +++ b/sentinel.go @@ -322,8 +322,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 } @@ -355,8 +355,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 } @@ -369,8 +369,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 } @@ -588,40 +588,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"])) } } @@ -670,16 +654,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 7b4aabdb..f5cfa3d3 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()) From 5ed546f303dae50ac28e5b9296c2b1523119fd4c Mon Sep 17 00:00:00 2001 From: nigel_li Date: Wed, 28 Apr 2021 13:56:44 +0800 Subject: [PATCH 02/17] Add command SMISMEMBER --- commands.go | 12 ++++++++++++ commands_test.go | 9 +++++++++ 2 files changed, 21 insertions(+) diff --git a/commands.go b/commands.go index 65ca4640..0926b870 100644 --- a/commands.go +++ b/commands.go @@ -201,6 +201,7 @@ type Cmdable interface { SInter(ctx context.Context, keys ...string) *StringSliceCmd SInterStore(ctx context.Context, destination string, keys ...string) *IntCmd SIsMember(ctx context.Context, key string, member interface{}) *BoolCmd + SMIsMember(ctx context.Context, key string, members ...interface{}) *BoolSliceCmd SMembers(ctx context.Context, key string) *StringSliceCmd SMembersMap(ctx context.Context, key string) *StringStructMapCmd SMove(ctx context.Context, source, destination string, member interface{}) *BoolCmd @@ -1530,6 +1531,17 @@ func (c cmdable) SIsMember(ctx context.Context, key string, member interface{}) return cmd } +// Redis `SMISMEMBER key member [member ...]` command. +func (c cmdable) SMIsMember(ctx context.Context, key string, members ...interface{}) *BoolSliceCmd { + args := make([]interface{}, 2, 2+len(members)) + args[0] = "smismember" + args[1] = key + args = appendArgs(args, members) + cmd := NewBoolSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + // Redis `SMEMBERS key` command output as a slice. func (c cmdable) SMembers(ctx context.Context, key string) *StringSliceCmd { cmd := NewStringSliceCmd(ctx, "smembers", key) diff --git a/commands_test.go b/commands_test.go index 14495874..fb492bfb 100644 --- a/commands_test.go +++ b/commands_test.go @@ -2500,6 +2500,15 @@ var _ = Describe("Commands", func() { Expect(sIsMember.Val()).To(Equal(false)) }) + It("should SMIsMember", func() { + sAdd := client.SAdd(ctx, "set", "one") + Expect(sAdd.Err()).NotTo(HaveOccurred()) + + sMIsMember := client.SMIsMember(ctx, "set", "one", "two") + Expect(sMIsMember.Err()).NotTo(HaveOccurred()) + Expect(sMIsMember.Val()).To(Equal([]bool{true, false})) + }) + It("should SMembers", func() { sAdd := client.SAdd(ctx, "set", "Hello") Expect(sAdd.Err()).NotTo(HaveOccurred()) From eca1bb6b36886b069cd96c8470044d6e3ea11bd5 Mon Sep 17 00:00:00 2001 From: nigel_li Date: Wed, 28 Apr 2021 14:39:03 +0800 Subject: [PATCH 03/17] Use the right redis client in tests --- commands_test.go | 2 +- redis_test.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/commands_test.go b/commands_test.go index fb492bfb..f42792c4 100644 --- a/commands_test.go +++ b/commands_test.go @@ -4702,7 +4702,7 @@ var _ = Describe("Commands", func() { client.ConfigSet(ctx, key, "0") defer client.ConfigSet(ctx, key, old[key]) - err := rdb.Do(ctx, "slowlog", "reset").Err() + err := client.Do(ctx, "slowlog", "reset").Err() Expect(err).NotTo(HaveOccurred()) client.Set(ctx, "test", "true", 0) diff --git a/redis_test.go b/redis_test.go index bbfa4c14..9f189052 100644 --- a/redis_test.go +++ b/redis_test.go @@ -290,18 +290,18 @@ var _ = Describe("Client", func() { It("should set and scan time", func() { tm := time.Now() - err := rdb.Set(ctx, "now", tm, 0).Err() + err := client.Set(ctx, "now", tm, 0).Err() Expect(err).NotTo(HaveOccurred()) var tm2 time.Time - err = rdb.Get(ctx, "now").Scan(&tm2) + err = client.Get(ctx, "now").Scan(&tm2) Expect(err).NotTo(HaveOccurred()) Expect(tm2).To(BeTemporally("==", tm)) }) It("should Conn", func() { - err := rdb.Conn(ctx).Get(ctx, "this-key-does-not-exist").Err() + err := client.Conn(ctx).Get(ctx, "this-key-does-not-exist").Err() Expect(err).To(Equal(redis.Nil)) }) }) From b60d47d3291da41616c5943b2e504ae46c95354d Mon Sep 17 00:00:00 2001 From: Vladimir Mihailenco Date: Thu, 13 May 2021 09:25:36 +0300 Subject: [PATCH 04/17] Tweak comments --- redis.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/redis.go b/redis.go index 8bf44038..aeb7f4ea 100644 --- a/redis.go +++ b/redis.go @@ -551,9 +551,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 @@ -729,7 +731,11 @@ type conn struct { hooks // TODO: inherit hooks } -// Conn is like Client, but its pool contains single connection. +// Conn represents a single Redis connection rather than a pool of database connections. +// It's safe for concurrent use by multiple goroutines. + +// Prefer running commands from Client unless there is a specific need for a continuous +// single Redis connection. type Conn struct { *conn ctx context.Context From 81df6895a8d98b689b354e620bb681f4ffa24da5 Mon Sep 17 00:00:00 2001 From: monkey Date: Thu, 13 May 2021 17:56:51 +0800 Subject: [PATCH 05/17] fix comments Signed-off-by: monkey --- redis.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/redis.go b/redis.go index aeb7f4ea..cce5fb92 100644 --- a/redis.go +++ b/redis.go @@ -733,7 +733,7 @@ type conn struct { // Conn represents a single Redis connection rather than a pool of database connections. // It's safe for concurrent use by multiple goroutines. - +// // Prefer running commands from Client unless there is a specific need for a continuous // single Redis connection. type Conn struct { From d42071c47a72804a5a47ed373e16699b68f19564 Mon Sep 17 00:00:00 2001 From: Vladimir Mihailenco Date: Mon, 17 May 2021 11:56:12 +0300 Subject: [PATCH 06/17] Replace go-pg with bun --- README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 1a0e460e..e58d867d 100644 --- a/README.md +++ b/README.md @@ -163,7 +163,5 @@ go test ## See also -- [Fast and flexible HTTP router](https://github.com/vmihailenco/treemux) -- [Golang PostgreSQL ORM](https://github.com/go-pg/pg) -- [Golang msgpack](https://github.com/vmihailenco/msgpack) -- [Golang message task queue](https://github.com/vmihailenco/taskq) +- [Fast and flexible ORM](https://github.com/uptrace/bun) +- [msgpack for Go](https://github.com/vmihailenco/msgpack) From fe9bc126b7edd0d72c3861d57f3b730181f42fa2 Mon Sep 17 00:00:00 2001 From: monkey92t Date: Wed, 19 May 2021 17:43:31 +0800 Subject: [PATCH 07/17] sync master to v9 (#1760) * Added missing idle args in XPendingExtArgs (#1750) Added missing idle args in XPendingExtArgs * fix #1754 (#1756) * Replace go-pg with bun * fix #1755 Signed-off-by: monkey * fix read data Signed-off-by: monkey * fix #1758 (#1759) fix #1758 Co-authored-by: Parvez Co-authored-by: Vladimir Mihailenco --- .github/workflows/build.yml | 2 +- .github/workflows/golangci-lint.yml | 1 + command.go | 279 +++++++++++++++++++++++++++- commands.go | 29 ++- commands_test.go | 177 +++++++++++++++++- 5 files changed, 481 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e6858a32..59ee91cb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,7 +2,7 @@ name: Go on: push: - branches: [master] + branches: [master, v9] pull_request: branches: [master, v9] diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 6c12b83b..1d9708c0 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/command.go b/command.go index 28fd8c13..74f89246 100644 --- a/command.go +++ b/command.go @@ -1520,7 +1520,7 @@ type XInfoConsumer struct { Idle int64 } -var _ Cmder = (*XInfoGroupsCmd)(nil) +var _ Cmder = (*XInfoConsumersCmd)(nil) func NewXInfoConsumersCmd(ctx context.Context, stream string, group string) *XInfoConsumersCmd { return &XInfoConsumersCmd{ @@ -1722,8 +1722,14 @@ func (cmd *XInfoStreamCmd) readReply(rd *proto.Reader) error { cmd.val.LastGeneratedID, err = rd.ReadString() case "first-entry": cmd.val.FirstEntry, err = readXMessage(rd) + if err == Nil { + err = nil + } case "last-entry": cmd.val.LastEntry, err = readXMessage(rd) + if err == Nil { + err = nil + } default: return fmt.Errorf("redis: unexpected content %s "+ "in XINFO STREAM reply", key) @@ -1737,6 +1743,277 @@ func (cmd *XInfoStreamCmd) readReply(rd *proto.Reader) error { //------------------------------------------------------------------------------ +type XInfoStreamFullCmd struct { + baseCmd + val *XInfoStreamFull +} + +type XInfoStreamFull struct { + Length int64 + RadixTreeKeys int64 + RadixTreeNodes int64 + LastGeneratedID string + Entries []XMessage + Groups []XInfoStreamGroup +} + +type XInfoStreamGroup struct { + Name string + LastDeliveredID string + PelCount int64 + Pending []XInfoStreamGroupPending + Consumers []XInfoStreamConsumer +} + +type XInfoStreamGroupPending struct { + ID string + Consumer string + DeliveryTime time.Time + DeliveryCount int64 +} + +type XInfoStreamConsumer struct { + Name string + SeenTime time.Time + PelCount int64 + Pending []XInfoStreamConsumerPending +} + +type XInfoStreamConsumerPending struct { + ID string + DeliveryTime time.Time + DeliveryCount int64 +} + +var _ Cmder = (*XInfoStreamFullCmd)(nil) + +func NewXInfoStreamFullCmd(ctx context.Context, args ...interface{}) *XInfoStreamFullCmd { + return &XInfoStreamFullCmd{ + baseCmd: baseCmd{ + ctx: ctx, + args: args, + }, + } +} + +func (cmd *XInfoStreamFullCmd) Val() *XInfoStreamFull { + return cmd.val +} + +func (cmd *XInfoStreamFullCmd) Result() (*XInfoStreamFull, error) { + return cmd.val, cmd.err +} + +func (cmd *XInfoStreamFullCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *XInfoStreamFullCmd) readReply(rd *proto.Reader) error { + if err := rd.ReadFixedMapLen(6); err != nil { + return err + } + + cmd.val = &XInfoStreamFull{} + + for i := 0; i < 6; i++ { + key, err := rd.ReadString() + if err != nil { + return err + } + + switch key { + case "length": + cmd.val.Length, err = rd.ReadInt() + case "radix-tree-keys": + cmd.val.RadixTreeKeys, err = rd.ReadInt() + case "radix-tree-nodes": + cmd.val.RadixTreeNodes, err = rd.ReadInt() + case "last-generated-id": + cmd.val.LastGeneratedID, err = rd.ReadString() + case "entries": + cmd.val.Entries, err = readXMessageSlice(rd) + case "groups": + cmd.val.Groups, err = readStreamGroups(rd) + default: + return fmt.Errorf("redis: unexpected content %s "+ + "in XINFO STREAM FULL reply", key) + } + if err != nil { + return err + } + } + return nil +} + +func readStreamGroups(rd *proto.Reader) ([]XInfoStreamGroup, error) { + n, err := rd.ReadArrayLen() + if err != nil { + return nil, err + } + groups := make([]XInfoStreamGroup, 0, n) + for i := 0; i < n; i++ { + if err = rd.ReadFixedMapLen(5); err != nil { + return nil, err + } + + group := XInfoStreamGroup{} + + for f := 0; f < 5; f++ { + key, err := rd.ReadString() + if err != nil { + return nil, err + } + + switch key { + case "name": + group.Name, err = rd.ReadString() + case "last-delivered-id": + group.LastDeliveredID, err = rd.ReadString() + case "pel-count": + 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 FULL reply", key) + } + + if err != nil { + return nil, err + } + } + + groups = append(groups, group) + } + + return groups, nil +} + +func readXInfoStreamGroupPending(rd *proto.Reader) ([]XInfoStreamGroupPending, error) { + n, err := rd.ReadArrayLen() + if err != nil { + return nil, err + } + + pending := make([]XInfoStreamGroupPending, 0, n) + + for i := 0; i < n; i++ { + if err = rd.ReadFixedArrayLen(4); err != nil { + return nil, err + } + + p := XInfoStreamGroupPending{} + + p.ID, err = rd.ReadString() + if err != nil { + return nil, err + } + + p.Consumer, err = rd.ReadString() + if err != nil { + return nil, err + } + + 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.ReadInt() + if err != nil { + return nil, err + } + + pending = append(pending, p) + } + + return pending, nil +} + +func readXInfoStreamConsumers(rd *proto.Reader) ([]XInfoStreamConsumer, error) { + n, err := rd.ReadArrayLen() + if err != nil { + return nil, err + } + + consumers := make([]XInfoStreamConsumer, 0, n) + + for i := 0; i < n; i++ { + if err = rd.ReadFixedMapLen(4); err != nil { + return nil, err + } + + c := XInfoStreamConsumer{} + + for f := 0; f < 4; f++ { + cKey, err := rd.ReadString() + if err != nil { + return nil, err + } + + switch cKey { + case "name": + c.Name, err = rd.ReadString() + case "seen-time": + 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.ReadInt() + case "pending": + pendingNumber, err := rd.ReadArrayLen() + if err != nil { + return nil, err + } + + c.Pending = make([]XInfoStreamConsumerPending, 0, pendingNumber) + + for pn := 0; pn < pendingNumber; pn++ { + if err = rd.ReadFixedArrayLen(3); err != nil { + return nil, err + } + + p := XInfoStreamConsumerPending{} + + p.ID, err = rd.ReadString() + if err != nil { + return nil, err + } + + 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.ReadInt() + if err != nil { + return nil, err + } + + c.Pending = append(c.Pending, p) + } + default: + return nil, fmt.Errorf("redis: unexpected content %s "+ + "in XINFO STREAM FULL reply", cKey) + } + if err != nil { + return nil, err + } + } + consumers = append(consumers, c) + } + + return consumers, nil +} + +//------------------------------------------------------------------------------ + type ZSliceCmd struct { baseCmd diff --git a/commands.go b/commands.go index 0926b870..7990b72e 100644 --- a/commands.go +++ b/commands.go @@ -180,6 +180,7 @@ type Cmdable interface { LInsertAfter(ctx context.Context, key string, pivot, value interface{}) *IntCmd LLen(ctx context.Context, key string) *IntCmd LPop(ctx context.Context, key string) *StringCmd + LPopCount(ctx context.Context, key string, count int) *StringSliceCmd LPos(ctx context.Context, key string, value string, args LPosArgs) *IntCmd LPosCount(ctx context.Context, key string, value string, count int64, args LPosArgs) *IntSliceCmd LPush(ctx context.Context, key string, values ...interface{}) *IntCmd @@ -1336,6 +1337,12 @@ func (c cmdable) LPop(ctx context.Context, key string) *StringCmd { return cmd } +func (c cmdable) LPopCount(ctx context.Context, key string, count int) *StringSliceCmd { + cmd := NewStringSliceCmd(ctx, "lpop", key, count) + _ = c(ctx, cmd) + return cmd +} + type LPosArgs struct { Rank, MaxLen int64 } @@ -1833,6 +1840,7 @@ func (c cmdable) XPending(ctx context.Context, stream, group string) *XPendingCm type XPendingExtArgs struct { Stream string Group string + Idle time.Duration Start string End string Count int64 @@ -1840,8 +1848,12 @@ type XPendingExtArgs struct { } func (c cmdable) XPendingExt(ctx context.Context, a *XPendingExtArgs) *XPendingExtCmd { - args := make([]interface{}, 0, 7) - args = append(args, "xpending", a.Stream, a.Group, a.Start, a.End, a.Count) + args := make([]interface{}, 0, 9) + args = append(args, "xpending", a.Stream, a.Group) + if a.Idle != 0 { + args = append(args, "idle", formatMs(ctx, a.Idle)) + } + args = append(args, a.Start, a.End, a.Count) if a.Consumer != "" { args = append(args, a.Consumer) } @@ -1916,6 +1928,19 @@ func (c cmdable) XInfoStream(ctx context.Context, key string) *XInfoStreamCmd { return cmd } +// XInfoStreamFull XINFO STREAM FULL [COUNT count] +// redis-server >= 6.0. +func (c cmdable) XInfoStreamFull(ctx context.Context, key string, count int) *XInfoStreamFullCmd { + args := make([]interface{}, 0, 6) + args = append(args, "xinfo", "stream", key, "full") + if count > 0 { + args = append(args, "count", count) + } + cmd := NewXInfoStreamFullCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + //------------------------------------------------------------------------------ // Z represents sorted set member. diff --git a/commands_test.go b/commands_test.go index f42792c4..3db4fe02 100644 --- a/commands_test.go +++ b/commands_test.go @@ -2064,6 +2064,25 @@ var _ = Describe("Commands", func() { Expect(lRange.Val()).To(Equal([]string{"two", "three"})) }) + It("should LPopCount", func() { + rPush := client.RPush(ctx, "list", "one") + Expect(rPush.Err()).NotTo(HaveOccurred()) + rPush = client.RPush(ctx, "list", "two") + Expect(rPush.Err()).NotTo(HaveOccurred()) + rPush = client.RPush(ctx, "list", "three") + Expect(rPush.Err()).NotTo(HaveOccurred()) + rPush = client.RPush(ctx, "list", "four") + Expect(rPush.Err()).NotTo(HaveOccurred()) + + lPopCount := client.LPopCount(ctx, "list", 2) + Expect(lPopCount.Err()).NotTo(HaveOccurred()) + Expect(lPopCount.Val()).To(Equal([]string{"one", "two"})) + + lRange := client.LRange(ctx, "list", 0, -1) + Expect(lRange.Err()).NotTo(HaveOccurred()) + Expect(lRange.Val()).To(Equal([]string{"three", "four"})) + }) + It("should LPos", func() { rPush := client.RPush(ctx, "list", "a") Expect(rPush.Err()).NotTo(HaveOccurred()) @@ -4241,15 +4260,15 @@ var _ = Describe("Commands", func() { Higher: "3-0", Consumers: map[string]int64{"consumer": 3}, })) - - infoExt, err := client.XPendingExt(ctx, &redis.XPendingExtArgs{ + args := &redis.XPendingExtArgs{ Stream: "stream", Group: "group", Start: "-", End: "+", Count: 10, Consumer: "consumer", - }).Result() + } + infoExt, err := client.XPendingExt(ctx, args).Result() Expect(err).NotTo(HaveOccurred()) for i := range infoExt { infoExt[i].Idle = 0 @@ -4260,6 +4279,11 @@ var _ = Describe("Commands", func() { {ID: "3-0", Consumer: "consumer", Idle: 0, RetryCount: 1}, })) + args.Idle = 72 * time.Hour + infoExt, err = client.XPendingExt(ctx, args).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(infoExt).To(HaveLen(0)) + n, err := client.XGroupDelConsumer(ctx, "stream", "group", "consumer").Result() Expect(err).NotTo(HaveOccurred()) Expect(n).To(Equal(int64(3))) @@ -4382,6 +4406,153 @@ var _ = Describe("Commands", func() { FirstEntry: redis.XMessage{ID: "1-0", Values: map[string]interface{}{"uno": "un"}}, LastEntry: redis.XMessage{ID: "3-0", Values: map[string]interface{}{"tres": "troix"}}, })) + + // stream is empty + n, err := client.XDel(ctx, "stream", "1-0", "2-0", "3-0").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(n).To(Equal(int64(3))) + + res, err = client.XInfoStream(ctx, "stream").Result() + Expect(err).NotTo(HaveOccurred()) + res.RadixTreeKeys = 0 + res.RadixTreeNodes = 0 + + Expect(res).To(Equal(&redis.XInfoStream{ + Length: 0, + RadixTreeKeys: 0, + RadixTreeNodes: 0, + Groups: 2, + LastGeneratedID: "3-0", + FirstEntry: redis.XMessage{}, + LastEntry: redis.XMessage{}, + })) + }) + + It("should XINFO STREAM FULL", func() { + res, err := client.XInfoStreamFull(ctx, "stream", 2).Result() + Expect(err).NotTo(HaveOccurred()) + res.RadixTreeKeys = 0 + res.RadixTreeNodes = 0 + + // Verify DeliveryTime + now := time.Now() + maxElapsed := 10 * time.Minute + for k, g := range res.Groups { + for k2, p := range g.Pending { + Expect(now.Sub(p.DeliveryTime)).To(BeNumerically("<=", maxElapsed)) + res.Groups[k].Pending[k2].DeliveryTime = time.Time{} + } + for k3, c := range g.Consumers { + Expect(now.Sub(c.SeenTime)).To(BeNumerically("<=", maxElapsed)) + res.Groups[k].Consumers[k3].SeenTime = time.Time{} + + for k4, p := range c.Pending { + Expect(now.Sub(p.DeliveryTime)).To(BeNumerically("<=", maxElapsed)) + res.Groups[k].Consumers[k3].Pending[k4].DeliveryTime = time.Time{} + } + } + } + + Expect(res).To(Equal(&redis.XInfoStreamFull{ + Length: 3, + RadixTreeKeys: 0, + RadixTreeNodes: 0, + LastGeneratedID: "3-0", + Entries: []redis.XMessage{ + {ID: "1-0", Values: map[string]interface{}{"uno": "un"}}, + {ID: "2-0", Values: map[string]interface{}{"dos": "deux"}}, + }, + Groups: []redis.XInfoStreamGroup{ + { + Name: "group1", + LastDeliveredID: "3-0", + PelCount: 3, + Pending: []redis.XInfoStreamGroupPending{ + { + ID: "1-0", + Consumer: "consumer1", + DeliveryTime: time.Time{}, + DeliveryCount: 1, + }, + { + ID: "2-0", + Consumer: "consumer1", + DeliveryTime: time.Time{}, + DeliveryCount: 1, + }, + }, + Consumers: []redis.XInfoStreamConsumer{ + { + Name: "consumer1", + SeenTime: time.Time{}, + PelCount: 2, + Pending: []redis.XInfoStreamConsumerPending{ + { + ID: "1-0", + DeliveryTime: time.Time{}, + DeliveryCount: 1, + }, + { + ID: "2-0", + DeliveryTime: time.Time{}, + DeliveryCount: 1, + }, + }, + }, + { + Name: "consumer2", + SeenTime: time.Time{}, + PelCount: 1, + Pending: []redis.XInfoStreamConsumerPending{ + { + ID: "3-0", + DeliveryTime: time.Time{}, + DeliveryCount: 1, + }, + }, + }, + }, + }, + { + Name: "group2", + LastDeliveredID: "3-0", + PelCount: 2, + Pending: []redis.XInfoStreamGroupPending{ + { + ID: "2-0", + Consumer: "consumer1", + DeliveryTime: time.Time{}, + DeliveryCount: 1, + }, + { + ID: "3-0", + Consumer: "consumer1", + DeliveryTime: time.Time{}, + DeliveryCount: 1, + }, + }, + Consumers: []redis.XInfoStreamConsumer{ + { + Name: "consumer1", + SeenTime: time.Time{}, + PelCount: 2, + Pending: []redis.XInfoStreamConsumerPending{ + { + ID: "2-0", + DeliveryTime: time.Time{}, + DeliveryCount: 1, + }, + { + ID: "3-0", + DeliveryTime: time.Time{}, + DeliveryCount: 1, + }, + }, + }, + }, + }, + }, + })) }) It("should XINFO GROUPS", func() { From 63df0e5e75b8842c1d1bf52f0506682d0a5989c8 Mon Sep 17 00:00:00 2001 From: monkey92t Date: Mon, 28 Jun 2021 17:40:38 +0800 Subject: [PATCH 08/17] sync master (#1800) * Remove OpenTelemetry from the code (but leave redisotel as is) (#1782) * Add XAutoClaim command (#1780) * fix typo (#1788) * xgroup/xadd/xtrim supports new options (#1787) * support cmd option XGROUP CREATECONSUMER XTRIM MINID LIMIT XADD NOMKSTREAM MINID LIMIT Signed-off-by: monkey * add XAddArgs.Approx doc Signed-off-by: monkey92t * Add Bun to readme * Upgrade the series of commands (#1792) * Upgrade the series of commands Signed-off-by: monkey92t * Cancel the Deprecated mark of ZAddNX and ZAddXX Signed-off-by: monkey92t * Explain the use restrictions of KeepTTL. (#1799) Signed-off-by: monkey92t * Adjust KeepTTL annotation. Signed-off-by: monkey92t * the hello command throws possible errors, It may affect the "read timeout" test result. Signed-off-by: monkey92t Co-authored-by: Vladimir Mihailenco Co-authored-by: ericmillin <31105612+ericmillin@users.noreply.github.com> Co-authored-by: heyanfu <1145291570@qq.com> --- CHANGELOG.md | 13 + README.md | 8 +- bench_test.go | 2 +- command.go | 106 +++++++ commands.go | 538 ++++++++++++++++++++++----------- commands_test.go | 648 ++++++++++++++++++++++++++++------------ example_test.go | 4 +- go.mod | 2 - internal/hscan/hscan.go | 2 +- internal/pool/conn.go | 19 +- internal/util.go | 24 -- options.go | 18 +- redis.go | 19 +- 13 files changed, 951 insertions(+), 452 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b540a4f..42d89b72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,19 @@ > :heart: > [**Uptrace.dev** - All-in-one tool to optimize performance and monitor errors & logs](https://uptrace.dev) +## 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`, diff --git a/README.md b/README.md index e58d867d..5f52a552 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,9 @@ - [Examples](https://pkg.go.dev/github.com/go-redis/redis/v8?tab=doc#pkg-examples) - [RealWorld example app](https://github.com/uptrace/go-treemux-realworld-example-app) +> :heart: Please check [Bun](https://bun.uptrace.dev) - fast and simple SQL client for PostgreSQL, +> MySQL, and SQLite. + ## Ecosystem - [Redis Mock](https://github.com/go-redis/redismock). @@ -160,8 +163,3 @@ Lastly, run: ``` go test ``` - -## See also - -- [Fast and flexible ORM](https://github.com/uptrace/bun) -- [msgpack for Go](https://github.com/vmihailenco/msgpack) diff --git a/bench_test.go b/bench_test.go index 708da5d9..0f26152d 100644 --- a/bench_test.go +++ b/bench_test.go @@ -222,7 +222,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/command.go b/command.go index 74f89246..1c53a660 100644 --- a/command.go +++ b/command.go @@ -1509,6 +1509,112 @@ func (cmd *XPendingExtCmd) readReply(rd *proto.Reader) error { //------------------------------------------------------------------------------ +type XAutoClaimCmd struct { + baseCmd + + start string + val []XMessage +} + +var _ Cmder = (*XAutoClaimCmd)(nil) + +func NewXAutoClaimCmd(ctx context.Context, args ...interface{}) *XAutoClaimCmd { + return &XAutoClaimCmd{ + baseCmd: baseCmd{ + ctx: ctx, + args: args, + }, + } +} + +func (cmd *XAutoClaimCmd) Val() (messages []XMessage, start string) { + return cmd.val, cmd.start +} + +func (cmd *XAutoClaimCmd) Result() (messages []XMessage, start string, err error) { + return cmd.val, cmd.start, cmd.err +} + +func (cmd *XAutoClaimCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *XAutoClaimCmd) readReply(rd *proto.Reader) error { + var err error + if err = rd.ReadFixedArrayLen(2); err != 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 +} + +//------------------------------------------------------------------------------ + +type XAutoClaimJustIDCmd struct { + baseCmd + + start string + val []string +} + +var _ Cmder = (*XAutoClaimJustIDCmd)(nil) + +func NewXAutoClaimJustIDCmd(ctx context.Context, args ...interface{}) *XAutoClaimJustIDCmd { + return &XAutoClaimJustIDCmd{ + baseCmd: baseCmd{ + ctx: ctx, + args: args, + }, + } +} + +func (cmd *XAutoClaimJustIDCmd) Val() (ids []string, start string) { + return cmd.val, cmd.start +} + +func (cmd *XAutoClaimJustIDCmd) Result() (ids []string, start string, err error) { + return cmd.val, cmd.start, cmd.err +} + +func (cmd *XAutoClaimJustIDCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *XAutoClaimJustIDCmd) readReply(rd *proto.Reader) error { + var err error + if err = rd.ReadFixedArrayLen(2); err != nil { + return err + } + + 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 err + } + } + return nil +} + +//------------------------------------------------------------------------------ + type XInfoConsumersCmd struct { baseCmd val []XInfoConsumer diff --git a/commands.go b/commands.go index 540c3b59..423d6e80 100644 --- a/commands.go +++ b/commands.go @@ -9,7 +9,8 @@ import ( "github.com/go-redis/redis/v8/internal" ) -// KeepTTL is an option for Set command to keep key's existing TTL. +// KeepTTL is a Redis KEEPTTL option to keep existing TTL, it requires your redis-server version >= 6.0, +// otherwise you will receive an error: (error) ERR syntax error. // For example: // // rdb.Set(ctx, key, value, redis.KeepTTL) @@ -132,8 +133,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 @@ -227,6 +227,7 @@ type Cmdable interface { XGroupCreateMkStream(ctx context.Context, stream, group, start string) *StatusCmd XGroupSetID(ctx context.Context, stream, group, start string) *StatusCmd XGroupDestroy(ctx context.Context, stream, group string) *IntCmd + XGroupCreateConsumer(ctx context.Context, stream, group, consumer string) *IntCmd XGroupDelConsumer(ctx context.Context, stream, group, consumer string) *IntCmd XReadGroup(ctx context.Context, a *XReadGroupArgs) *XStreamSliceCmd XAck(ctx context.Context, stream, group string, ids ...string) *IntCmd @@ -234,23 +235,23 @@ type Cmdable interface { XPendingExt(ctx context.Context, a *XPendingExtArgs) *XPendingExtCmd XClaim(ctx context.Context, a *XClaimArgs) *XMessageSliceCmd XClaimJustID(ctx context.Context, a *XClaimArgs) *StringSliceCmd - 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 + XTrimMinIDApprox(ctx context.Context, key string, minID string, limit int64) *IntCmd XInfoGroups(ctx context.Context, key string) *XInfoGroupsCmd XInfoStream(ctx context.Context, key string) *XInfoStreamCmd XInfoConsumers(ctx context.Context, key string, group string) *XInfoConsumersCmd BZPopMax(ctx context.Context, timeout time.Duration, keys ...string) *ZWithKeyCmd BZPopMin(ctx context.Context, timeout time.Duration, keys ...string) *ZWithKeyCmd - 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 - 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 + + 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 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 @@ -266,6 +267,9 @@ type Cmdable interface { ZRangeByScore(ctx context.Context, key string, opt *ZRangeBy) *StringSliceCmd ZRangeByLex(ctx context.Context, key string, opt *ZRangeBy) *StringSliceCmd ZRangeByScoreWithScores(ctx context.Context, key string, opt *ZRangeBy) *ZSliceCmd + ZRangeArgs(ctx context.Context, z ZRangeArgs) *StringSliceCmd + ZRangeArgsWithScores(ctx context.Context, z ZRangeArgs) *ZSliceCmd + ZRangeStore(ctx context.Context, dst string, z ZRangeArgs) *IntCmd ZRank(ctx context.Context, key, member string) *IntCmd ZRem(ctx context.Context, key string, members ...interface{}) *IntCmd ZRemRangeByRank(ctx context.Context, key string, start, stop int64) *IntCmd @@ -281,6 +285,8 @@ type Cmdable interface { 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 ZDiff(ctx context.Context, keys ...string) *StringSliceCmd ZDiffWithScores(ctx context.Context, keys ...string) *ZSliceCmd ZDiffStore(ctx context.Context, destination string, keys ...string) *IntCmd @@ -733,7 +739,7 @@ func (c cmdable) DecrBy(ctx context.Context, key string, decrement int64) *IntCm return cmd } -// Get redis `GET key` command. It returns redis.Nil error when key does not exist. +// Get Redis `GET key` command. It returns redis.Nil error when key does not exist. func (c cmdable) Get(ctx context.Context, key string) *StringCmd { cmd := NewStringCmd(ctx, "get", key) _ = c(ctx, cmd) @@ -835,10 +841,11 @@ 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(-1) expiration is a Redis KEEPTTL option to keep existing TTL. +// KeepTTL is a Redis KEEPTTL option to keep existing TTL, it requires your redis-server version >= 6.0, +// otherwise you will receive an error: (error) ERR syntax error. func (c cmdable) Set(ctx context.Context, key string, value interface{}, expiration time.Duration) *StatusCmd { args := make([]interface{}, 3, 5) args[0] = "set" @@ -871,7 +878,8 @@ type SetArgs struct { // When Get is true, the command returns the old value stored at key, or nil when key did not exist. Get bool - // KeepTTL is a Redis KEEPTTL option to keep existing TTL. + // KeepTTL is a Redis KEEPTTL option to keep existing TTL, it requires your redis-server version >= 6.0, + // otherwise you will receive an error: (error) ERR syntax error. KeepTTL bool } @@ -909,8 +917,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 @@ -919,7 +927,8 @@ func (c cmdable) SetEX(ctx context.Context, key string, value interface{}, expir // SetNX Redis `SET key value [expiration] NX` command. // // Zero expiration means the key has no expiration time. -// KeepTTL(-1) expiration is a Redis KEEPTTL option to keep existing TTL. +// KeepTTL is a Redis KEEPTTL option to keep existing TTL, it requires your redis-server version >= 6.0, +// otherwise you will receive an error: (error) ERR syntax error. func (c cmdable) SetNX(ctx context.Context, key string, value interface{}, expiration time.Duration) *BoolCmd { var cmd *BoolCmd switch expiration { @@ -943,7 +952,8 @@ func (c cmdable) SetNX(ctx context.Context, key string, value interface{}, expir // SetXX Redis `SET key value [expiration] XX` command. // // Zero expiration means the key has no expiration time. -// KeepTTL(-1) expiration is a Redis KEEPTTL option to keep existing TTL. +// KeepTTL is a Redis KEEPTTL option to keep existing TTL, it requires your redis-server version >= 6.0, +// otherwise you will receive an error: (error) ERR syntax error. func (c cmdable) SetXX(ctx context.Context, key string, value interface{}, expiration time.Duration) *BoolCmd { var cmd *BoolCmd switch expiration { @@ -1643,22 +1653,44 @@ func (c cmdable) SUnionStore(ctx context.Context, destination string, keys ...st // - XAddArgs.Values = map[string]interface{}{"key1": "value1", "key2": "value2"} // // Note that map will not preserve the order of key-value pairs. +// MaxLen/MaxLenApprox and MinID are in conflict, only one of them can be used. type XAddArgs struct { - Stream string - MaxLen int64 // MAXLEN N - MaxLenApprox int64 // MAXLEN ~ N - ID string - Values interface{} + Stream string + NoMkStream bool + MaxLen int64 // MAXLEN N + MinID string + + // Approx causes MaxLen and MinID to use "~" matcher (instead of "="). + Approx bool + Limit int64 + ID string + Values interface{} } +// XAdd a.Limit has a bug, please confirm it and use it. +// issue: https://github.com/redis/redis/issues/9046 func (c cmdable) XAdd(ctx context.Context, a *XAddArgs) *StringCmd { - args := make([]interface{}, 0, 8) - args = append(args, "xadd") - args = append(args, a.Stream) - if a.MaxLen > 0 { - args = append(args, "maxlen", a.MaxLen) - } else if a.MaxLenApprox > 0 { - args = append(args, "maxlen", "~", a.MaxLenApprox) + args := make([]interface{}, 0, 11) + args = append(args, "xadd", a.Stream) + if a.NoMkStream { + args = append(args, "nomkstream") + } + switch { + case a.MaxLen > 0: + if a.Approx { + args = append(args, "maxlen", "~", a.MaxLen) + } else { + args = append(args, "maxlen", a.MaxLen) + } + case a.MinID != "": + if a.Approx { + args = append(args, "minid", "~", a.MinID) + } else { + args = append(args, "minid", a.MinID) + } + } + if a.Limit > 0 { + args = append(args, "limit", a.Limit) } if a.ID != "" { args = append(args, a.ID) @@ -1779,6 +1811,12 @@ func (c cmdable) XGroupDestroy(ctx context.Context, stream, group string) *IntCm return cmd } +func (c cmdable) XGroupCreateConsumer(ctx context.Context, stream, group, consumer string) *IntCmd { + cmd := NewIntCmd(ctx, "xgroup", "createconsumer", stream, group, consumer) + _ = c(ctx, cmd) + return cmd +} + func (c cmdable) XGroupDelConsumer(ctx context.Context, stream, group, consumer string) *IntCmd { cmd := NewIntCmd(ctx, "xgroup", "delconsumer", stream, group, consumer) _ = c(ctx, cmd) @@ -1867,6 +1905,39 @@ func (c cmdable) XPendingExt(ctx context.Context, a *XPendingExtArgs) *XPendingE return cmd } +type XAutoClaimArgs struct { + Stream string + Group string + MinIdle time.Duration + Start string + Count int64 + Consumer string +} + +func (c cmdable) XAutoClaim(ctx context.Context, a *XAutoClaimArgs) *XAutoClaimCmd { + args := xAutoClaimArgs(ctx, a) + cmd := NewXAutoClaimCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) XAutoClaimJustID(ctx context.Context, a *XAutoClaimArgs) *XAutoClaimJustIDCmd { + args := xAutoClaimArgs(ctx, a) + args = append(args, "justid") + cmd := NewXAutoClaimJustIDCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +func xAutoClaimArgs(ctx context.Context, a *XAutoClaimArgs) []interface{} { + args := make([]interface{}, 0, 9) + args = append(args, "xautoclaim", a.Stream, a.Group, a.Consumer, formatMs(ctx, a.MinIdle), a.Start) + if a.Count > 0 { + args = append(args, "count", a.Count) + } + return args +} + type XClaimArgs struct { Stream string Group string @@ -1903,16 +1974,53 @@ func xClaimArgs(a *XClaimArgs) []interface{} { return args } -func (c cmdable) XTrim(ctx context.Context, key string, maxLen int64) *IntCmd { - cmd := NewIntCmd(ctx, "xtrim", key, "maxlen", maxLen) +// xTrim If approx is true, add the "~" parameter, otherwise it is the default "=" (redis default). +// example: +// XTRIM key MAXLEN/MINID threshold LIMIT limit. +// XTRIM key MAXLEN/MINID ~ threshold LIMIT limit. +// The redis-server version is lower than 6.2, please set limit to 0. +func (c cmdable) xTrim( + ctx context.Context, key, strategy string, + approx bool, threshold interface{}, limit int64, +) *IntCmd { + args := make([]interface{}, 0, 7) + args = append(args, "xtrim", key, strategy) + if approx { + args = append(args, "~") + } + args = append(args, threshold) + if limit > 0 { + args = append(args, "limit", limit) + } + cmd := NewIntCmd(ctx, args...) _ = c(ctx, cmd) return cmd } -func (c cmdable) XTrimApprox(ctx context.Context, key string, maxLen int64) *IntCmd { - cmd := NewIntCmd(ctx, "xtrim", key, "maxlen", "~", maxLen) - _ = c(ctx, cmd) - return cmd +// 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 { + return c.xTrim(ctx, key, "maxlen", false, maxLen, 0) +} + +// XTrimMaxLenApprox LIMIT has a bug, please confirm it and use it. +// issue: https://github.com/redis/redis/issues/9046 +// cmd: XTRIM key MAXLEN ~ maxLen LIMIT limit +func (c cmdable) XTrimMaxLenApprox(ctx context.Context, key string, maxLen, limit int64) *IntCmd { + return c.xTrim(ctx, key, "maxlen", true, maxLen, limit) +} + +// XTrimMinID No `~` rules are used, `limit` cannot be used. +// cmd: XTRIM key MINID minID +func (c cmdable) XTrimMinID(ctx context.Context, key string, minID string) *IntCmd { + return c.xTrim(ctx, key, "minid", false, minID, 0) +} + +// XTrimMinIDApprox LIMIT has a bug, please confirm it and use it. +// issue: https://github.com/redis/redis/issues/9046 +// cmd: XTRIM key MINID ~ minID LIMIT limit +func (c cmdable) XTrimMinIDApprox(ctx context.Context, key string, minID string, limit int64) *IntCmd { + return c.xTrim(ctx, key, "minid", true, minID, limit) } func (c cmdable) XInfoConsumers(ctx context.Context, key string, group string) *XInfoConsumersCmd { @@ -1960,7 +2068,7 @@ type ZWithKey struct { Key string } -// ZStore is used as an arg to ZInterStore and ZUnionStore. +// ZStore is used as an arg to ZInter/ZInterStore and ZUnion/ZUnionStore. type ZStore struct { Keys []string Weights []float64 @@ -1968,7 +2076,7 @@ type ZStore struct { Aggregate string } -func (z *ZStore) len() (n int) { +func (z ZStore) len() (n int) { n = len(z.Keys) if len(z.Weights) > 0 { n += 1 + len(z.Weights) @@ -1979,6 +2087,22 @@ func (z *ZStore) len() (n int) { return n } +func (z ZStore) appendArgs(args []interface{}) []interface{} { + for _, key := range z.Keys { + args = append(args, key) + } + if len(z.Weights) > 0 { + args = append(args, "weights") + for _, weights := range z.Weights { + args = append(args, weights) + } + } + if z.Aggregate != "" { + args = append(args, "aggregate", z.Aggregate) + } + return args +} + // BZPopMax Redis `BZPOPMAX key [key ...] timeout` command. func (c cmdable) BZPopMax(ctx context.Context, timeout time.Duration, keys ...string) *ZWithKeyCmd { args := make([]interface{}, 1+len(keys)+1) @@ -2007,96 +2131,79 @@ func (c cmdable) BZPopMin(ctx context.Context, timeout time.Duration, keys ...st return cmd } -func (c cmdable) zAdd(ctx context.Context, a []interface{}, n int, members ...*Z) *IntCmd { - for i, m := range members { - a[n+2*i] = m.Score - a[n+2*i+1] = m.Member +// ZAddArgs WARN: The GT, LT and NX options are mutually exclusive. +type ZAddArgs struct { + NX bool + XX bool + LT bool + GT bool + Ch bool + Members []Z +} + +func (c cmdable) zAddArgs(key string, args ZAddArgs, incr bool) []interface{} { + a := make([]interface{}, 0, 6+2*len(args.Members)) + a = append(a, "zadd", key) + + // The GT, LT and NX options are mutually exclusive. + if args.NX { + a = append(a, "nx") + } else { + if args.XX { + a = append(a, "xx") + } + if args.GT { + a = append(a, "gt") + } else if args.LT { + a = append(a, "lt") + } } - cmd := NewIntCmd(ctx, a...) + if args.Ch { + a = append(a, "ch") + } + if incr { + a = append(a, "incr") + } + for _, m := range args.Members { + a = append(a, m.Score) + a = append(a, m.Member) + } + return a +} + +func (c cmdable) ZAddArgs(ctx context.Context, key string, args ZAddArgs) *IntCmd { + cmd := NewIntCmd(ctx, c.zAddArgs(key, args, false)...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) ZAddArgsIncr(ctx context.Context, key string, args ZAddArgs) *FloatCmd { + cmd := NewFloatCmd(ctx, c.zAddArgs(key, args, true)...) _ = 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 { - const n = 2 - a := make([]interface{}, n+2*len(members)) - a[0], a[1] = "zadd", key - return c.zAdd(ctx, a, n, 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 { - const n = 3 - a := make([]interface{}, n+2*len(members)) - a[0], a[1], a[2] = "zadd", key, "nx" - return c.zAdd(ctx, a, n, 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 { - const n = 3 - a := make([]interface{}, n+2*len(members)) - a[0], a[1], a[2] = "zadd", key, "xx" - return c.zAdd(ctx, a, n, members...) -} - -// ZAddCh Redis `ZADD key CH score member [score member ...]` command. -func (c cmdable) ZAddCh(ctx context.Context, key string, members ...*Z) *IntCmd { - const n = 3 - a := make([]interface{}, n+2*len(members)) - a[0], a[1], a[2] = "zadd", key, "ch" - return c.zAdd(ctx, a, n, members...) -} - -// ZAddNXCh Redis `ZADD key NX CH score member [score member ...]` command. -func (c cmdable) ZAddNXCh(ctx context.Context, key string, members ...*Z) *IntCmd { - const n = 4 - a := make([]interface{}, n+2*len(members)) - a[0], a[1], a[2], a[3] = "zadd", key, "nx", "ch" - return c.zAdd(ctx, a, n, members...) -} - -// ZAddXXCh Redis `ZADD key XX CH score member [score member ...]` command. -func (c cmdable) ZAddXXCh(ctx context.Context, key string, members ...*Z) *IntCmd { - const n = 4 - a := make([]interface{}, n+2*len(members)) - a[0], a[1], a[2], a[3] = "zadd", key, "xx", "ch" - return c.zAdd(ctx, a, n, members...) -} - -func (c cmdable) zIncr(ctx context.Context, a []interface{}, n int, members ...*Z) *FloatCmd { - for i, m := range members { - a[n+2*i] = m.Score - a[n+2*i+1] = m.Member - } - cmd := NewFloatCmd(ctx, a...) - _ = c(ctx, cmd) - return cmd -} - -// ZIncr Redis `ZADD key INCR score member` command. -func (c cmdable) ZIncr(ctx context.Context, key string, member *Z) *FloatCmd { - const n = 3 - a := make([]interface{}, n+2) - a[0], a[1], a[2] = "zadd", key, "incr" - return c.zIncr(ctx, a, n, member) -} - -// ZIncrNX Redis `ZADD key NX INCR score member` command. -func (c cmdable) ZIncrNX(ctx context.Context, key string, member *Z) *FloatCmd { - const n = 4 - a := make([]interface{}, n+2) - a[0], a[1], a[2], a[3] = "zadd", key, "incr", "nx" - return c.zIncr(ctx, a, n, member) -} - -// ZIncrXX Redis `ZADD key XX INCR score member` command. -func (c cmdable) ZIncrXX(ctx context.Context, key string, member *Z) *FloatCmd { - const n = 4 - a := make([]interface{}, n+2) - a[0], a[1], a[2], a[3] = "zadd", key, "incr", "xx" - return c.zIncr(ctx, a, n, member) +func (c cmdable) ZAddXX(ctx context.Context, key string, members ...Z) *IntCmd { + return c.ZAddArgs(ctx, key, ZAddArgs{ + XX: true, + Members: members, + }) } func (c cmdable) ZCard(ctx context.Context, key string) *IntCmd { @@ -2126,18 +2233,7 @@ func (c cmdable) ZIncrBy(ctx context.Context, key string, increment float64, mem func (c cmdable) ZInterStore(ctx context.Context, destination string, store *ZStore) *IntCmd { args := make([]interface{}, 0, 3+store.len()) args = append(args, "zinterstore", destination, len(store.Keys)) - for _, key := range store.Keys { - args = append(args, key) - } - if len(store.Weights) > 0 { - args = append(args, "weights") - for _, weight := range store.Weights { - args = append(args, weight) - } - } - if store.Aggregate != "" { - args = append(args, "aggregate", store.Aggregate) - } + args = store.appendArgs(args) cmd := NewIntCmd(ctx, args...) cmd.setFirstKeyPos(3) _ = c(ctx, cmd) @@ -2147,19 +2243,7 @@ func (c cmdable) ZInterStore(ctx context.Context, destination string, store *ZSt func (c cmdable) ZInter(ctx context.Context, store *ZStore) *StringSliceCmd { args := make([]interface{}, 0, 2+store.len()) args = append(args, "zinter", len(store.Keys)) - for _, key := range store.Keys { - args = append(args, key) - } - if len(store.Weights) > 0 { - args = append(args, "weights") - for _, weights := range store.Weights { - args = append(args, weights) - } - } - - if store.Aggregate != "" { - args = append(args, "aggregate", store.Aggregate) - } + args = store.appendArgs(args) cmd := NewStringSliceCmd(ctx, args...) cmd.setFirstKeyPos(2) _ = c(ctx, cmd) @@ -2169,18 +2253,7 @@ func (c cmdable) ZInter(ctx context.Context, store *ZStore) *StringSliceCmd { func (c cmdable) ZInterWithScores(ctx context.Context, store *ZStore) *ZSliceCmd { args := make([]interface{}, 0, 3+store.len()) args = append(args, "zinter", len(store.Keys)) - for _, key := range store.Keys { - args = append(args, key) - } - if len(store.Weights) > 0 { - args = append(args, "weights") - for _, weights := range store.Weights { - args = append(args, weights) - } - } - if store.Aggregate != "" { - args = append(args, "aggregate", store.Aggregate) - } + args = store.appendArgs(args) args = append(args, "withscores") cmd := NewZSliceCmd(ctx, args...) cmd.setFirstKeyPos(2) @@ -2240,29 +2313,112 @@ func (c cmdable) ZPopMin(ctx context.Context, key string, count ...int64) *ZSlic return cmd } -func (c cmdable) zRange(ctx context.Context, key string, start, stop int64, withScores bool) *StringSliceCmd { - args := []interface{}{ - "zrange", - key, - start, - stop, +// ZRangeArgs is all the options of the ZRange command. +// In version> 6.2.0, you can replace the(cmd): +// ZREVRANGE, +// ZRANGEBYSCORE, +// ZREVRANGEBYSCORE, +// ZRANGEBYLEX, +// ZREVRANGEBYLEX. +// Please pay attention to your redis-server version. +// +// Rev, ByScore, ByLex and Offset+Count options require redis-server 6.2.0 and higher. +type ZRangeArgs struct { + Key string + + // When the ByScore option is provided, the open interval(exclusive) can be set. + // By default, the score intervals specified by and are closed (inclusive). + // It is similar to the deprecated(6.2.0+) ZRangeByScore command. + // For example: + // ZRangeArgs{ + // Key: "example-key", + // Start: "(3", + // Stop: 8, + // ByScore: true, + // } + // cmd: "ZRange example-key (3 8 ByScore" (3 < score <= 8). + // + // For the ByLex option, it is similar to the deprecated(6.2.0+) ZRangeByLex command. + // You can set the and options as follows: + // ZRangeArgs{ + // Key: "example-key", + // Start: "[abc", + // Stop: "(def", + // ByLex: true, + // } + // cmd: "ZRange example-key [abc (def ByLex" + // + // For normal cases (ByScore==false && ByLex==false), and should be set to the index range (int). + // You can read the documentation for more information: https://redis.io/commands/zrange + Start interface{} + Stop interface{} + + // The ByScore and ByLex options are mutually exclusive. + ByScore bool + ByLex bool + + Rev bool + + // limit offset count. + Offset int64 + Count int64 +} + +func (z ZRangeArgs) appendArgs(args []interface{}) []interface{} { + // For Rev+ByScore/ByLex, we need to adjust the position of and . + if z.Rev && (z.ByScore || z.ByLex) { + args = append(args, z.Key, z.Stop, z.Start) + } else { + args = append(args, z.Key, z.Start, z.Stop) } - if withScores { - args = append(args, "withscores") + + if z.ByScore { + args = append(args, "byscore") + } else if z.ByLex { + args = append(args, "bylex") } + if z.Rev { + args = append(args, "rev") + } + if z.Offset != 0 || z.Count != 0 { + args = append(args, "limit", z.Offset, z.Count) + } + return args +} + +func (c cmdable) ZRangeArgs(ctx context.Context, z ZRangeArgs) *StringSliceCmd { + args := make([]interface{}, 0, 9) + args = append(args, "zrange") + args = z.appendArgs(args) cmd := NewStringSliceCmd(ctx, args...) _ = c(ctx, cmd) return cmd } +func (c cmdable) ZRangeArgsWithScores(ctx context.Context, z ZRangeArgs) *ZSliceCmd { + args := make([]interface{}, 0, 10) + args = append(args, "zrange") + args = z.appendArgs(args) + args = append(args, "withscores") + cmd := NewZSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + func (c cmdable) ZRange(ctx context.Context, key string, start, stop int64) *StringSliceCmd { - return c.zRange(ctx, key, start, stop, false) + return c.ZRangeArgs(ctx, ZRangeArgs{ + Key: key, + Start: start, + Stop: stop, + }) } func (c cmdable) ZRangeWithScores(ctx context.Context, key string, start, stop int64) *ZSliceCmd { - cmd := NewZSliceCmd(ctx, "zrange", key, start, stop, "withscores") - _ = c(ctx, cmd) - return cmd + return c.ZRangeArgsWithScores(ctx, ZRangeArgs{ + Key: key, + Start: start, + Stop: stop, + }) } type ZRangeBy struct { @@ -2311,6 +2467,15 @@ func (c cmdable) ZRangeByScoreWithScores(ctx context.Context, key string, opt *Z return cmd } +func (c cmdable) ZRangeStore(ctx context.Context, dst string, z ZRangeArgs) *IntCmd { + args := make([]interface{}, 0, 10) + args = append(args, "zrangestore", dst) + args = z.appendArgs(args) + cmd := NewIntCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + func (c cmdable) ZRank(ctx context.Context, key, member string) *IntCmd { cmd := NewIntCmd(ctx, "zrank", key, member) _ = c(ctx, cmd) @@ -2413,22 +2578,31 @@ func (c cmdable) ZScore(ctx context.Context, key, member string) *FloatCmd { return cmd } +func (c cmdable) ZUnion(ctx context.Context, store ZStore) *StringSliceCmd { + args := make([]interface{}, 0, 2+store.len()) + args = append(args, "zunion", len(store.Keys)) + args = store.appendArgs(args) + cmd := NewStringSliceCmd(ctx, args...) + cmd.setFirstKeyPos(2) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) ZUnionWithScores(ctx context.Context, store ZStore) *ZSliceCmd { + args := make([]interface{}, 0, 3+store.len()) + args = append(args, "zunion", len(store.Keys)) + args = store.appendArgs(args) + args = append(args, "withscores") + cmd := NewZSliceCmd(ctx, args...) + cmd.setFirstKeyPos(2) + _ = c(ctx, cmd) + return cmd +} + func (c cmdable) ZUnionStore(ctx context.Context, dest string, store *ZStore) *IntCmd { args := make([]interface{}, 0, 3+store.len()) args = append(args, "zunionstore", dest, len(store.Keys)) - for _, key := range store.Keys { - args = append(args, key) - } - if len(store.Weights) > 0 { - args = append(args, "weights") - for _, weight := range store.Weights { - args = append(args, weight) - } - } - if store.Aggregate != "" { - args = append(args, "aggregate", store.Aggregate) - } - + args = store.appendArgs(args) cmd := NewIntCmd(ctx, args...) cmd.setFirstKeyPos(3) _ = c(ctx, cmd) diff --git a/commands_test.go b/commands_test.go index a47a5fb3..66f69a5a 100644 --- a/commands_test.go +++ b/commands_test.go @@ -827,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() @@ -847,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()) @@ -1508,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() @@ -2705,17 +2705,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() @@ -2759,7 +2759,7 @@ var _ = Describe("Commands", func() { // ok } - zAdd := client.ZAdd(ctx, "zset", &redis.Z{ + zAdd := client.ZAdd(ctx, "zset", redis.Z{ Member: "a", Score: 1, }) @@ -2787,17 +2787,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() @@ -2841,7 +2841,7 @@ var _ = Describe("Commands", func() { // ok } - zAdd := client.ZAdd(ctx, "zset", &redis.Z{ + zAdd := client.ZAdd(ctx, "zset", redis.Z{ Member: "a", Score: 1, }) @@ -2869,28 +2869,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() @@ -2912,28 +2912,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() @@ -2954,8 +2954,44 @@ var _ = Describe("Commands", func() { }})) }) - It("should ZAddNX", func() { - added, err := client.ZAddNX(ctx, "zset", &redis.Z{ + It("should ZAddArgsGTAndLT", func() { + // Test only the GT+LT options. + added, err := client.ZAddArgs(ctx, "zset", redis.ZAddArgs{ + GT: true, + Members: []redis.Z{{Score: 1, Member: "one"}}, + }).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(added).To(Equal(int64(1))) + + vals, err := client.ZRangeWithScores(ctx, "zset", 0, -1).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(vals).To(Equal([]redis.Z{{Score: 1, Member: "one"}})) + + added, err = client.ZAddArgs(ctx, "zset", redis.ZAddArgs{ + GT: true, + Members: []redis.Z{{Score: 2, Member: "one"}}, + }).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(added).To(Equal(int64(0))) + + vals, err = client.ZRangeWithScores(ctx, "zset", 0, -1).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(vals).To(Equal([]redis.Z{{Score: 2, Member: "one"}})) + + added, err = client.ZAddArgs(ctx, "zset", redis.ZAddArgs{ + LT: true, + Members: []redis.Z{{Score: 1, Member: "one"}}, + }).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(added).To(Equal(int64(0))) + + vals, err = client.ZRangeWithScores(ctx, "zset", 0, -1).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(vals).To(Equal([]redis.Z{{Score: 1, Member: "one"}})) + }) + + It("should ZAddArgsNX", func() { + added, err := client.ZAddNX(ctx, "zset", redis.Z{ Score: 1, Member: "one", }).Result() @@ -2966,7 +3002,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() @@ -2978,8 +3014,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() @@ -2990,14 +3026,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() @@ -3009,26 +3045,33 @@ var _ = Describe("Commands", func() { Expect(vals).To(Equal([]redis.Z{{Score: 2, Member: "one"}})) }) - 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))) }) - 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))) @@ -3037,9 +3080,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))) @@ -3052,10 +3098,13 @@ var _ = Describe("Commands", func() { }})) }) - 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))) @@ -3064,16 +3113,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))) @@ -3083,10 +3135,11 @@ var _ = Describe("Commands", func() { Expect(vals).To(Equal([]redis.Z{{Score: 2, Member: "one"}})) }) - 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))) @@ -3095,9 +3148,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))) @@ -3107,10 +3161,12 @@ var _ = Describe("Commands", func() { Expect(vals).To(Equal([]redis.Z{{Score: 2, Member: "one"}})) }) - 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))) @@ -3119,9 +3175,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))) @@ -3131,10 +3189,12 @@ var _ = Describe("Commands", func() { Expect(vals).To(Equal([]redis.Z{{Score: 1, Member: "one"}})) }) - 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))) @@ -3143,16 +3203,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))) @@ -3163,12 +3225,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() @@ -3180,17 +3242,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() @@ -3210,12 +3272,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() @@ -3237,22 +3299,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{ @@ -3279,11 +3341,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") @@ -3301,17 +3363,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() @@ -3325,7 +3387,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() @@ -3341,12 +3403,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() @@ -3366,17 +3428,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() @@ -3390,7 +3452,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() @@ -3406,13 +3468,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() @@ -3433,11 +3495,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) @@ -3454,11 +3516,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() @@ -3489,12 +3551,74 @@ var _ = Describe("Commands", func() { }})) }) + It("should ZRangeArgs", func() { + added, err := client.ZAddArgs(ctx, "zset", redis.ZAddArgs{ + Members: []redis.Z{ + {Score: 1, Member: "one"}, + {Score: 2, Member: "two"}, + {Score: 3, Member: "three"}, + {Score: 4, Member: "four"}, + }, + }).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(added).To(Equal(int64(4))) + + zRange, err := client.ZRangeArgs(ctx, redis.ZRangeArgs{ + Key: "zset", + Start: 1, + Stop: 4, + ByScore: true, + Rev: true, + Offset: 1, + Count: 2, + }).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(zRange).To(Equal([]string{"three", "two"})) + + zRange, err = client.ZRangeArgs(ctx, redis.ZRangeArgs{ + Key: "zset", + Start: "-", + Stop: "+", + ByLex: true, + Rev: true, + Offset: 2, + Count: 2, + }).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(zRange).To(Equal([]string{"two", "one"})) + + zRange, err = client.ZRangeArgs(ctx, redis.ZRangeArgs{ + Key: "zset", + Start: "(1", + Stop: "(4", + ByScore: true, + }).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(zRange).To(Equal([]string{"two", "three"})) + + // withScores. + zSlice, err := client.ZRangeArgsWithScores(ctx, redis.ZRangeArgs{ + Key: "zset", + Start: 1, + Stop: 4, + ByScore: true, + Rev: true, + Offset: 1, + Count: 2, + }).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(zSlice).To(Equal([]redis.Z{ + {Score: 3, Member: "three"}, + {Score: 2, Member: "two"}, + })) + }) + 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{ @@ -3527,17 +3651,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() @@ -3573,11 +3697,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{ @@ -3624,12 +3748,41 @@ var _ = Describe("Commands", func() { Expect(vals).To(Equal([]redis.Z{})) }) + It("should ZRangeStore", func() { + added, err := client.ZAddArgs(ctx, "zset", redis.ZAddArgs{ + Members: []redis.Z{ + {Score: 1, Member: "one"}, + {Score: 2, Member: "two"}, + {Score: 3, Member: "three"}, + {Score: 4, Member: "four"}, + }, + }).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(added).To(Equal(int64(4))) + + rangeStore, err := client.ZRangeStore(ctx, "new-zset", redis.ZRangeArgs{ + Key: "zset", + Start: 1, + Stop: 4, + ByScore: true, + Rev: true, + Offset: 1, + Count: 2, + }).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(rangeStore).To(Equal(int64(2))) + + zRange, err := client.ZRange(ctx, "new-zset", 0, -1).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(zRange).To(Equal([]string{"two", "three"})) + }) + 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") @@ -3642,11 +3795,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") @@ -3665,11 +3818,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) @@ -3685,11 +3838,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") @@ -3708,7 +3861,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"}, @@ -3735,11 +3888,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) @@ -3756,11 +3909,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() @@ -3792,11 +3945,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( @@ -3816,11 +3969,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( @@ -3840,11 +3993,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( @@ -3863,11 +4016,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( @@ -3896,11 +4049,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") @@ -3913,25 +4066,64 @@ 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() { + err := client.ZAddArgs(ctx, "zset1", redis.ZAddArgs{ + Members: []redis.Z{ + {Score: 1, Member: "one"}, + {Score: 2, Member: "two"}, + }, + }).Err() + Expect(err).NotTo(HaveOccurred()) + + err = client.ZAddArgs(ctx, "zset2", redis.ZAddArgs{ + Members: []redis.Z{ + {Score: 1, Member: "one"}, + {Score: 2, Member: "two"}, + {Score: 3, Member: "three"}, + }, + }).Err() + Expect(err).NotTo(HaveOccurred()) + + union, err := client.ZUnion(ctx, redis.ZStore{ + Keys: []string{"zset1", "zset2"}, + Weights: []float64{2, 3}, + Aggregate: "sum", + }).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(union).To(Equal([]string{"one", "three", "two"})) + + unionScores, err := client.ZUnionWithScores(ctx, redis.ZStore{ + Keys: []string{"zset1", "zset2"}, + Weights: []float64{2, 3}, + Aggregate: "sum", + }).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(unionScores).To(Equal([]redis.Z{ + {Score: 5, Member: "one"}, + {Score: 9, Member: "three"}, + {Score: 10, Member: "two"}, + })) }) 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{ @@ -3956,9 +4148,9 @@ 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) @@ -3978,13 +4170,13 @@ var _ = Describe("Commands", func() { }) 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() @@ -3993,13 +4185,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() @@ -4017,15 +4209,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{ @@ -4036,15 +4228,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{ @@ -4066,15 +4258,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()) @@ -4120,14 +4312,29 @@ var _ = Describe("Commands", func() { Expect(id).To(Equal("3-0")) }) - It("should XTrim", func() { - n, err := client.XTrim(ctx, "stream", 0).Result() + // 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 + It("should XTrimMaxLen", func() { + n, err := client.XTrimMaxLen(ctx, "stream", 0).Result() Expect(err).NotTo(HaveOccurred()) Expect(n).To(Equal(int64(3))) }) - It("should XTrimApprox", func() { - n, err := client.XTrimApprox(ctx, "stream", 0).Result() + It("should XTrimMaxLenApprox", func() { + n, err := client.XTrimMaxLenApprox(ctx, "stream", 0, 0).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(n).To(Equal(int64(3))) + }) + + It("should XTrimMinID", func() { + n, err := client.XTrimMinID(ctx, "stream", "4-0").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(n).To(Equal(int64(3))) + }) + + It("should XTrimMinIDApprox", func() { + n, err := client.XTrimMinIDApprox(ctx, "stream", "4-0", 0).Result() Expect(err).NotTo(HaveOccurred()) Expect(n).To(Equal(int64(3))) }) @@ -4149,6 +4356,9 @@ var _ = Describe("Commands", func() { })) }) + // TODO XAdd There is a bug in the limit parameter. + // TODO Don't test it for now. + // TODO link: https://github.com/redis/redis/issues/9046 It("should XAdd with MaxLen", func() { id, err := client.XAdd(ctx, &redis.XAddArgs{ Stream: "stream", @@ -4164,6 +4374,21 @@ var _ = Describe("Commands", func() { })) }) + It("should XAdd with MinID", func() { + id, err := client.XAdd(ctx, &redis.XAddArgs{ + Stream: "stream", + MinID: "5-0", + ID: "4-0", + Values: map[string]interface{}{"quatro": "quatre"}, + }).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(id).To(Equal("4-0")) + + vals, err := client.XRange(ctx, "stream", "-", "+").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(vals).To(HaveLen(0)) + }) + It("should XDel", func() { n, err := client.XDel(ctx, "stream", "1-0", "2-0", "3-0").Result() Expect(err).NotTo(HaveOccurred()) @@ -4396,12 +4621,55 @@ var _ = Describe("Commands", func() { infoExt, err = client.XPendingExt(ctx, args).Result() Expect(err).NotTo(HaveOccurred()) Expect(infoExt).To(HaveLen(0)) + }) - n, err := client.XGroupDelConsumer(ctx, "stream", "group", "consumer").Result() + It("should XGroup Create Delete Consumer", func() { + n, err := client.XGroupCreateConsumer(ctx, "stream", "group", "c1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(n).To(Equal(int64(1))) + + n, err = client.XGroupDelConsumer(ctx, "stream", "group", "consumer").Result() Expect(err).NotTo(HaveOccurred()) Expect(n).To(Equal(int64(3))) }) + It("should XAutoClaim", func() { + xca := &redis.XAutoClaimArgs{ + Stream: "stream", + Group: "group", + Consumer: "consumer", + Start: "-", + Count: 2, + } + msgs, start, err := client.XAutoClaim(ctx, xca).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(start).To(Equal("2-0")) + Expect(msgs).To(Equal([]redis.XMessage{{ + ID: "1-0", + Values: map[string]interface{}{"uno": "un"}, + }, { + ID: "2-0", + Values: map[string]interface{}{"dos": "deux"}, + }})) + + xca.Start = start + msgs, start, err = client.XAutoClaim(ctx, xca).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(start).To(Equal("3-0")) + Expect(msgs).To(Equal([]redis.XMessage{{ + ID: "2-0", + Values: map[string]interface{}{"dos": "deux"}, + }, { + ID: "3-0", + Values: map[string]interface{}{"tres": "troix"}, + }})) + + ids, start, err := client.XAutoClaimJustID(ctx, xca).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(start).To(Equal("3-0")) + Expect(ids).To(Equal([]string{"2-0", "3-0"})) + }) + It("should XClaim", func() { msgs, err := client.XClaim(ctx, &redis.XClaimArgs{ Stream: "stream", @@ -4898,7 +5166,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)}, @@ -4909,7 +5177,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)}, } diff --git a/example_test.go b/example_test.go index 73b2f2d6..7e042758 100644 --- a/example_test.go +++ b/example_test.go @@ -188,8 +188,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) } diff --git a/go.mod b/go.mod index 4fb27a8f..0b2ed131 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,5 @@ require ( github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f github.com/onsi/ginkgo v1.15.0 github.com/onsi/gomega v1.10.5 - go.opentelemetry.io/otel v0.20.0 go.opentelemetry.io/otel/metric v0.20.0 - go.opentelemetry.io/otel/trace v0.20.0 ) diff --git a/internal/hscan/hscan.go b/internal/hscan/hscan.go index 6f97f928..852c8bd5 100644 --- a/internal/hscan/hscan.go +++ b/internal/hscan/hscan.go @@ -49,7 +49,7 @@ var ( func Struct(dst interface{}) (StructValue, error) { v := reflect.ValueOf(dst) - // The dstination to scan into should be a struct pointer. + // The destination to scan into should be a struct pointer. if v.Kind() != reflect.Ptr || v.IsNil() { return StructValue{}, fmt.Errorf("redis.Scan(non-pointer %T)", dst) } diff --git a/internal/pool/conn.go b/internal/pool/conn.go index ee064c9f..1ce29edd 100644 --- a/internal/pool/conn.go +++ b/internal/pool/conn.go @@ -65,26 +65,17 @@ func (cn *Conn) RemoteAddr() net.Addr { } func (cn *Conn) WithReader(ctx context.Context, timeout time.Duration, fn func(rd *proto.Reader) error) error { - ctx, span := internal.StartSpan(ctx, "redis.with_reader") - defer span.End() - if err := cn.netConn.SetReadDeadline(cn.deadline(ctx, timeout)); err != nil { - return internal.RecordError(ctx, span, err) + return err } - if err := fn(cn.rd); err != nil { - return internal.RecordError(ctx, span, err) - } - return nil + return fn(cn.rd) } func (cn *Conn) WithWriter( ctx context.Context, timeout time.Duration, fn func(wr *proto.Writer) error, ) error { - ctx, span := internal.StartSpan(ctx, "redis.with_writer") - defer span.End() - if err := cn.netConn.SetWriteDeadline(cn.deadline(ctx, timeout)); err != nil { - return internal.RecordError(ctx, span, err) + return err } if cn.bw.Buffered() > 0 { @@ -92,11 +83,11 @@ func (cn *Conn) WithWriter( } if err := fn(cn.wr); err != nil { - return internal.RecordError(ctx, span, err) + return err } if err := cn.bw.Flush(); err != nil { - return internal.RecordError(ctx, span, err) + return err } internal.WritesCounter.Add(ctx, 1) diff --git a/internal/util.go b/internal/util.go index 1a648fe6..e34a7f03 100644 --- a/internal/util.go +++ b/internal/util.go @@ -4,16 +4,10 @@ import ( "context" "time" - "github.com/go-redis/redis/v8/internal/proto" "github.com/go-redis/redis/v8/internal/util" - "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/trace" ) func Sleep(ctx context.Context, dur time.Duration) error { - _, span := StartSpan(ctx, "time.Sleep") - defer span.End() - t := time.NewTimer(dur) defer t.Stop() @@ -50,21 +44,3 @@ func isLower(s string) bool { } return true } - -//------------------------------------------------------------------------------ - -var tracer = otel.Tracer("github.com/go-redis/redis") - -func StartSpan(ctx context.Context, name string) (context.Context, trace.Span) { - if span := trace.SpanFromContext(ctx); !span.IsRecording() { - return ctx, span - } - return tracer.Start(ctx, name) -} - -func RecordError(ctx context.Context, span trace.Span, err error) error { - if err != proto.Nil { - span.RecordError(err) - } - return err -} diff --git a/options.go b/options.go index 0fd8e880..7cf1bc1b 100644 --- a/options.go +++ b/options.go @@ -12,9 +12,7 @@ import ( "strings" "time" - "github.com/go-redis/redis/v8/internal" "github.com/go-redis/redis/v8/internal/pool" - "go.opentelemetry.io/otel/attribute" ) // Limiter is the interface of a rate limiter or a circuit breaker. @@ -291,21 +289,7 @@ func getUserPassword(u *url.URL) (string, string) { func newConnPool(opt *Options) *pool.ConnPool { return pool.NewConnPool(&pool.Options{ Dialer: func(ctx context.Context) (net.Conn, error) { - ctx, span := internal.StartSpan(ctx, "redis.dial") - defer span.End() - - if span.IsRecording() { - span.SetAttributes( - attribute.String("db.connection_string", opt.Addr), - ) - } - - cn, err := opt.Dialer(ctx, opt.Network, opt.Addr) - if err != nil { - return nil, internal.RecordError(ctx, span, err) - } - - return cn, nil + return opt.Dialer(ctx, opt.Network, opt.Addr) }, PoolSize: opt.PoolSize, MinIdleConns: opt.MinIdleConns, diff --git a/redis.go b/redis.go index cce5fb92..2673e84b 100644 --- a/redis.go +++ b/redis.go @@ -10,7 +10,6 @@ import ( "github.com/go-redis/redis/v8/internal" "github.com/go-redis/redis/v8/internal/pool" "github.com/go-redis/redis/v8/internal/proto" - "go.opentelemetry.io/otel/attribute" ) // Nil reply returned by Redis when key does not exist. @@ -230,17 +229,18 @@ func (c *baseClient) initConn(ctx context.Context, cn *pool.Conn) error { } cn.Inited = true - ctx, span := internal.StartSpan(ctx, "redis.init_conn") - defer span.End() - 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. - if conn.Hello(ctx, 3, c.opt.Username, c.opt.Password, "").Err() == nil { + // 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 { @@ -287,20 +287,11 @@ func (c *baseClient) releaseConn(ctx context.Context, cn *pool.Conn, err error) func (c *baseClient) withConn( ctx context.Context, fn func(context.Context, *pool.Conn) error, ) error { - ctx, span := internal.StartSpan(ctx, "redis.with_conn") - defer span.End() - cn, err := c.getConn(ctx) if err != nil { return err } - if span.IsRecording() { - if remoteAddr := cn.RemoteAddr(); remoteAddr != nil { - span.SetAttributes(attribute.String("net.peer.ip", remoteAddr.String())) - } - } - defer func() { c.releaseConn(ctx, cn, err) }() From 916da5b2ce5874f5dff788aef4ce49b8c3f44fac Mon Sep 17 00:00:00 2001 From: monkey92t Date: Fri, 2 Jul 2021 20:56:25 +0800 Subject: [PATCH 09/17] sync v8.11.0 --- CHANGELOG.md | 5 +++++ README.md | 7 +++++-- cluster.go | 2 +- commands.go | 4 ++-- extra/redisotel/example/go.mod | 8 +++++--- extra/redisotel/example/go.sum | 36 +++++++++++++++++++-------------- extra/redisotel/example/main.go | 4 ++-- extra/redisotel/go.mod | 4 ++-- go.mod | 2 +- go.sum | 21 ++----------------- internal/instruments.go | 33 ------------------------------ internal/pool/conn.go | 9 +-------- internal/pool/pool.go | 1 - options.go | 4 ++-- redis.go | 2 +- 15 files changed, 50 insertions(+), 92 deletions(-) delete mode 100644 internal/instruments.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 42d89b72..63aabd3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,11 @@ > :heart: > [**Uptrace.dev** - All-in-one tool to optimize performance and monitor errors & logs](https://uptrace.dev) +## 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 diff --git a/README.md b/README.md index 5f52a552..bc4624e0 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,11 @@ - [Examples](https://pkg.go.dev/github.com/go-redis/redis/v8?tab=doc#pkg-examples) - [RealWorld example app](https://github.com/uptrace/go-treemux-realworld-example-app) -> :heart: Please check [Bun](https://bun.uptrace.dev) - fast and simple SQL client for PostgreSQL, -> MySQL, and SQLite. +My other projects: + +- [Bun](https://bun.uptrace.dev) - fast and simple SQL client for PostgreSQL, MySQL, and SQLite. +- [treemux](https://github.com/vmihailenco/treemux) - high-speed, flexible, tree-based HTTP router + for Go. ## Ecosystem diff --git a/cluster.go b/cluster.go index 738d50a4..95d6585b 100644 --- a/cluster.go +++ b/cluster.go @@ -91,7 +91,7 @@ func (opt *ClusterOptions) init() { } if opt.PoolSize == 0 { - opt.PoolSize = 5 * runtime.NumCPU() + opt.PoolSize = 5 * runtime.GOMAXPROCS(0) } switch opt.ReadTimeout { diff --git a/commands.go b/commands.go index 423d6e80..57ff0d0d 100644 --- a/commands.go +++ b/commands.go @@ -235,7 +235,8 @@ type Cmdable interface { XPendingExt(ctx context.Context, a *XPendingExtArgs) *XPendingExtCmd XClaim(ctx context.Context, a *XClaimArgs) *XMessageSliceCmd XClaimJustID(ctx context.Context, a *XClaimArgs) *StringSliceCmd - + XAutoClaim(ctx context.Context, a *XAutoClaimArgs) *XAutoClaimCmd + XAutoClaimJustID(ctx context.Context, a *XAutoClaimArgs) *XAutoClaimJustIDCmd 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 @@ -1659,7 +1660,6 @@ type XAddArgs struct { NoMkStream bool MaxLen int64 // MAXLEN N MinID string - // Approx causes MaxLen and MinID to use "~" matcher (instead of "="). Approx bool Limit int64 diff --git a/extra/redisotel/example/go.mod b/extra/redisotel/example/go.mod index b621aab5..1cbaf7fe 100644 --- a/extra/redisotel/example/go.mod +++ b/extra/redisotel/example/go.mod @@ -4,7 +4,9 @@ go 1.16 require ( github.com/go-redis/redis/extra/redisotel/v8 v8.8.2 - go.opentelemetry.io/otel v0.19.0 - go.opentelemetry.io/otel/exporters/stdout v0.19.0 - go.opentelemetry.io/otel/sdk v0.19.0 + github.com/go-redis/redis/v8 v8.8.2 + go.opentelemetry.io/otel v1.0.0-RC1 + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.0.0-RC1 + go.opentelemetry.io/otel/sdk v1.0.0-RC1 + go.opentelemetry.io/otel/sdk/metric v0.21.0 ) diff --git a/extra/redisotel/example/go.sum b/extra/redisotel/example/go.sum index 59284e50..4e05e032 100644 --- a/extra/redisotel/example/go.sum +++ b/extra/redisotel/example/go.sum @@ -1,5 +1,4 @@ -github.com/benbjohnson/clock v1.0.3 h1:vkLuvpK4fmtSCuo60+yC63p7y0BmQ8gm5ZXGuBCJyXg= -github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= @@ -27,8 +26,9 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= @@ -50,22 +50,28 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opentelemetry.io/otel v0.15.0/go.mod h1:e4GKElweB8W2gWUqbghw0B8t5MCTccc9212eNHnOHwA= -go.opentelemetry.io/otel v0.19.0 h1:Lenfy7QHRXPZVsw/12CWpxX6d/JkrX8wrx2vO8G80Ng= go.opentelemetry.io/otel v0.19.0/go.mod h1:j9bF567N9EfomkSidSfmMwIwIBuP37AMAIzVW85OxSg= -go.opentelemetry.io/otel/exporters/stdout v0.19.0 h1:6+QJvepCJ/YS3rOlsnjhVo527ohlPowOBgsZThR9Hoc= -go.opentelemetry.io/otel/exporters/stdout v0.19.0/go.mod h1:UI2JnNRaSt9ChIHkk4+uqieH27qKt9isV9e2qRorCtg= -go.opentelemetry.io/otel/metric v0.19.0 h1:dtZ1Ju44gkJkYvo+3qGqVXmf88tc+a42edOywypengg= +go.opentelemetry.io/otel v1.0.0-RC1 h1:4CeoX93DNTWt8awGK9JmNXzF9j7TyOu9upscEdtcdXc= +go.opentelemetry.io/otel v1.0.0-RC1/go.mod h1:x9tRa9HK4hSSq7jf2TKbqFbtt58/TGk0f9XiEYISI1I= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.0.0-RC1 h1:SEfJImgKQ5TP2aTJwN08qhS8oFlYWr/neECGsyuxKWg= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.0.0-RC1/go.mod h1:TAM/UYjVd1UdaifWkof3qj9cCW9oINemHfj0K6yodSo= +go.opentelemetry.io/otel/internal/metric v0.21.0 h1:gZlIBo5O51hZOOZz8vEcuRx/l5dnADadKfpT70AELoo= +go.opentelemetry.io/otel/internal/metric v0.21.0/go.mod h1:iOfAaY2YycsXfYD4kaRSbLx2LKmfpKObWBEv9QK5zFo= go.opentelemetry.io/otel/metric v0.19.0/go.mod h1:8f9fglJPRnXuskQmKpnad31lcLJ2VmNNqIsx/uIwBSc= -go.opentelemetry.io/otel/oteltest v0.19.0 h1:YVfA0ByROYqTwOxqHVZYZExzEpfZor+MU1rU+ip2v9Q= +go.opentelemetry.io/otel/metric v0.21.0 h1:ZtcJlHqVE4l8Su0WOLOd9fEPheJuYEiQ0wr9wv2p25I= +go.opentelemetry.io/otel/metric v0.21.0/go.mod h1:JWCt1bjivC4iCrz/aCrM1GSw+ZcvY44KCbaeeRhzHnc= go.opentelemetry.io/otel/oteltest v0.19.0/go.mod h1:tI4yxwh8U21v7JD6R3BcA/2+RBoTKFexE/PJ/nSO7IA= -go.opentelemetry.io/otel/sdk v0.19.0 h1:13pQquZyGbIvGxBWcVzUqe8kg5VGbTBiKKKXpYCylRM= -go.opentelemetry.io/otel/sdk v0.19.0/go.mod h1:ouO7auJYMivDjywCHA6bqTI7jJMVQV1HdKR5CmH8DGo= -go.opentelemetry.io/otel/sdk/export/metric v0.19.0 h1:9A1PC2graOx3epRLRWbq4DPCdpMUYK8XeCrdAg6ycbI= -go.opentelemetry.io/otel/sdk/export/metric v0.19.0/go.mod h1:exXalzlU6quLTXiv29J+Qpj/toOzL3H5WvpbbjouTBo= -go.opentelemetry.io/otel/sdk/metric v0.19.0 h1:fka1Zc/lpRMS+KlTP/TRXZuaFtSjUg/maHV3U8rt1Mc= -go.opentelemetry.io/otel/sdk/metric v0.19.0/go.mod h1:t12+Mqmj64q1vMpxHlCGXGggo0sadYxEG6U+Us/9OA4= -go.opentelemetry.io/otel/trace v0.19.0 h1:1ucYlenXIDA1OlHVLDZKX0ObXV5RLaq06DtUKz5e5zc= +go.opentelemetry.io/otel/oteltest v1.0.0-RC1 h1:G685iP3XiskCwk/z0eIabL55XUl2gk0cljhGk9sB0Yk= +go.opentelemetry.io/otel/oteltest v1.0.0-RC1/go.mod h1:+eoIG0gdEOaPNftuy1YScLr1Gb4mL/9lpDkZ0JjMRq4= +go.opentelemetry.io/otel/sdk v1.0.0-RC1 h1:Sy2VLOOg24bipyC29PhuMXYNJrLsxkie8hyI7kUlG9Q= +go.opentelemetry.io/otel/sdk v1.0.0-RC1/go.mod h1:kj6yPn7Pgt5ByRuwesbaWcRLA+V7BSDg3Hf8xRvsvf8= +go.opentelemetry.io/otel/sdk/export/metric v0.21.0 h1:4tSMVkDbvrowOeP/6rOfGABEWv5n+0gCfhI/TWleUvc= +go.opentelemetry.io/otel/sdk/export/metric v0.21.0/go.mod h1:gTaOMSQmL4zfsTL47desIPbPla5MyMG29lN3PzcibVg= +go.opentelemetry.io/otel/sdk/metric v0.21.0 h1:LNLUj35NNdEpyJQwj/htiEsfnY6GeTIwYHweCJNV+nc= +go.opentelemetry.io/otel/sdk/metric v0.21.0/go.mod h1:OHOcF8ZjE/L8oL/QXpUFWklPwtaukrfHgoAiPek53rQ= go.opentelemetry.io/otel/trace v0.19.0/go.mod h1:4IXiNextNOpPnRlI4ryK69mn5iC84bjBWZQA5DXz/qg= +go.opentelemetry.io/otel/trace v1.0.0-RC1 h1:jrjqKJZEibFrDz+umEASeU3LvdVyWKlnTh7XEfwrT58= +go.opentelemetry.io/otel/trace v1.0.0-RC1/go.mod h1:86UHmyHWFEtWjfWPSbu0+d0Pf9Q6e1U+3ViBOc+NXAg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= diff --git a/extra/redisotel/example/main.go b/extra/redisotel/example/main.go index 8853ee00..335eb8f2 100644 --- a/extra/redisotel/example/main.go +++ b/extra/redisotel/example/main.go @@ -6,14 +6,14 @@ import ( "github.com/go-redis/redis/extra/redisotel/v8" "github.com/go-redis/redis/v8" "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/exporters/stdout" + stdoutexporter "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" sdktrace "go.opentelemetry.io/otel/sdk/trace" ) var tracer = otel.Tracer("app_or_package_name") func main() { - exporter, err := stdout.NewExporter(stdout.WithPrettyPrint()) + exporter, err := stdoutexporter.New(stdoutexporter.WithPrettyPrint()) if err != nil { panic(err) } diff --git a/extra/redisotel/go.mod b/extra/redisotel/go.mod index 9e601388..28ba2d98 100644 --- a/extra/redisotel/go.mod +++ b/extra/redisotel/go.mod @@ -9,6 +9,6 @@ replace github.com/go-redis/redis/extra/rediscmd/v8 => ../rediscmd require ( github.com/go-redis/redis/extra/rediscmd/v8 v8.8.2 github.com/go-redis/redis/v8 v8.8.2 - go.opentelemetry.io/otel v0.20.0 - go.opentelemetry.io/otel/trace v0.20.0 + go.opentelemetry.io/otel v1.0.0-RC1 + go.opentelemetry.io/otel/trace v1.0.0-RC1 ) diff --git a/go.mod b/go.mod index 0b2ed131..c792cdd4 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.14 require ( github.com/cespare/xxhash/v2 v2.1.1 github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f + github.com/google/go-cmp v0.5.6 // indirect github.com/onsi/ginkgo v1.15.0 github.com/onsi/gomega v1.10.5 - go.opentelemetry.io/otel/metric v0.20.0 ) diff --git a/go.sum b/go.sum index b8309364..76abf5b4 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,5 @@ github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -18,8 +16,8 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= @@ -31,20 +29,7 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.10.5 h1:7n6FEkpFmfCoo2t+YYqXH0evK+a9ICQz0xcAy9dYcaQ= github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.opentelemetry.io/otel v0.20.0 h1:eaP0Fqu7SXHwvjiqDq83zImeehOHX8doTvU9AwXON8g= -go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= -go.opentelemetry.io/otel/metric v0.20.0 h1:4kzhXFP+btKm4jwxpjIqjs41A7MakRFUS86bqLHTIw8= -go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= -go.opentelemetry.io/otel/oteltest v0.20.0 h1:HiITxCawalo5vQzdHfKeZurV8x7ljcqAgiWzF6Vaeaw= -go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= -go.opentelemetry.io/otel/trace v0.20.0 h1:1DL6EXUdcg95gukhuRRvLDO/4X5THh/5dIV52lqtnbw= -go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -95,5 +80,3 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/instruments.go b/internal/instruments.go deleted file mode 100644 index 49d6111e..00000000 --- a/internal/instruments.go +++ /dev/null @@ -1,33 +0,0 @@ -package internal - -import ( - "context" - - "go.opentelemetry.io/otel/metric" - "go.opentelemetry.io/otel/metric/global" -) - -var ( - // WritesCounter is a count of write commands performed. - WritesCounter metric.Int64Counter - // NewConnectionsCounter is a count of new connections. - NewConnectionsCounter metric.Int64Counter -) - -func init() { - defer func() { - if r := recover(); r != nil { - Logger.Printf(context.Background(), "Error creating meter github.com/go-redis/redis for Instruments", r) - } - }() - - meter := metric.Must(global.Meter("github.com/go-redis/redis")) - - WritesCounter = meter.NewInt64Counter("redis.writes", - metric.WithDescription("the number of writes initiated"), - ) - - NewConnectionsCounter = meter.NewInt64Counter("redis.new_connections", - metric.WithDescription("the number of connections created"), - ) -} diff --git a/internal/pool/conn.go b/internal/pool/conn.go index 1ce29edd..56616598 100644 --- a/internal/pool/conn.go +++ b/internal/pool/conn.go @@ -7,7 +7,6 @@ import ( "sync/atomic" "time" - "github.com/go-redis/redis/v8/internal" "github.com/go-redis/redis/v8/internal/proto" ) @@ -86,13 +85,7 @@ func (cn *Conn) WithWriter( return err } - if err := cn.bw.Flush(); err != nil { - return err - } - - internal.WritesCounter.Add(ctx, 1) - - return nil + return cn.bw.Flush() } func (cn *Conn) Close() error { diff --git a/internal/pool/pool.go b/internal/pool/pool.go index 4d247b30..a8f0a6d4 100644 --- a/internal/pool/pool.go +++ b/internal/pool/pool.go @@ -188,7 +188,6 @@ func (p *ConnPool) dialConn(ctx context.Context, pooled bool) (*Conn, error) { return nil, err } - internal.NewConnectionsCounter.Add(ctx, 1) cn := NewConn(netConn) cn.pooled = pooled return cn, nil diff --git a/options.go b/options.go index 7cf1bc1b..8bbc27b8 100644 --- a/options.go +++ b/options.go @@ -77,7 +77,7 @@ type Options struct { WriteTimeout time.Duration // Maximum number of socket connections. - // Default is 10 connections per every CPU as reported by runtime.NumCPU. + // Default is 10 connections per every available CPU as reported by runtime.GOMAXPROCS. PoolSize int // Minimum number of idle connections which is useful when establishing // new connection is slow. @@ -136,7 +136,7 @@ func (opt *Options) init() { } } if opt.PoolSize == 0 { - opt.PoolSize = 10 * runtime.NumCPU() + opt.PoolSize = 10 * runtime.GOMAXPROCS(0) } switch opt.ReadTimeout { case -1: diff --git a/redis.go b/redis.go index 2673e84b..051fa594 100644 --- a/redis.go +++ b/redis.go @@ -128,7 +128,7 @@ func (hs hooks) processTxPipeline( return hs.processPipeline(ctx, cmds, fn) } -func (hs hooks) withContext(ctx context.Context, fn func() error) error { +func (hs hooks) withContext(_ context.Context, fn func() error) error { return fn() } From b8245b56f966ed9437d11d0afbe3792ae484f35d Mon Sep 17 00:00:00 2001 From: monkey Date: Sat, 24 Jul 2021 22:24:17 +0800 Subject: [PATCH 10/17] Remove redundant code. Signed-off-by: monkey --- commands_test.go | 66 ------------------------------------------------ 1 file changed, 66 deletions(-) diff --git a/commands_test.go b/commands_test.go index fb9bbfcd..eb46a790 100644 --- a/commands_test.go +++ b/commands_test.go @@ -4127,45 +4127,6 @@ var _ = Describe("Commands", func() { })) }) - It("should ZUnion", func() { - err := client.ZAddArgs(ctx, "zset1", redis.ZAddArgs{ - Members: []redis.Z{ - {Score: 1, Member: "one"}, - {Score: 2, Member: "two"}, - }, - }).Err() - Expect(err).NotTo(HaveOccurred()) - - err = client.ZAddArgs(ctx, "zset2", redis.ZAddArgs{ - Members: []redis.Z{ - {Score: 1, Member: "one"}, - {Score: 2, Member: "two"}, - {Score: 3, Member: "three"}, - }, - }).Err() - Expect(err).NotTo(HaveOccurred()) - - union, err := client.ZUnion(ctx, redis.ZStore{ - Keys: []string{"zset1", "zset2"}, - Weights: []float64{2, 3}, - Aggregate: "sum", - }).Result() - Expect(err).NotTo(HaveOccurred()) - Expect(union).To(Equal([]string{"one", "three", "two"})) - - unionScores, err := client.ZUnionWithScores(ctx, redis.ZStore{ - Keys: []string{"zset1", "zset2"}, - Weights: []float64{2, 3}, - Aggregate: "sum", - }).Result() - Expect(err).NotTo(HaveOccurred()) - Expect(unionScores).To(Equal([]redis.Z{ - {Score: 5, Member: "one"}, - {Score: 9, Member: "three"}, - {Score: 10, Member: "two"}, - })) - }) - It("should ZUnionStore", func() { err := client.ZAdd(ctx, "zset1", redis.Z{Score: 1, Member: "one"}).Err() Expect(err).NotTo(HaveOccurred()) @@ -4392,33 +4353,6 @@ var _ = Describe("Commands", func() { 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 - It("should XTrimMaxLen", func() { - n, err := client.XTrimMaxLen(ctx, "stream", 0).Result() - Expect(err).NotTo(HaveOccurred()) - Expect(n).To(Equal(int64(3))) - }) - - It("should XTrimMaxLenApprox", func() { - n, err := client.XTrimMaxLenApprox(ctx, "stream", 0, 0).Result() - Expect(err).NotTo(HaveOccurred()) - Expect(n).To(Equal(int64(3))) - }) - - It("should XTrimMinID", func() { - n, err := client.XTrimMinID(ctx, "stream", "4-0").Result() - Expect(err).NotTo(HaveOccurred()) - Expect(n).To(Equal(int64(3))) - }) - - It("should XTrimMinIDApprox", func() { - n, err := client.XTrimMinIDApprox(ctx, "stream", "4-0", 0).Result() - Expect(err).NotTo(HaveOccurred()) - Expect(n).To(Equal(int64(3))) - }) - It("should XAdd", func() { id, err := client.XAdd(ctx, &redis.XAddArgs{ Stream: "stream", From 38d1749d56f6ee41baf8222b3aa0aa0c6950f41d Mon Sep 17 00:00:00 2001 From: monkey92t Date: Mon, 2 Aug 2021 19:01:01 +0800 Subject: [PATCH 11/17] adjust the code (#1842) * Upgrade redis-server version (#1833) * Upgrade redis-server version Signed-off-by: monkey * XAutoClaim changed the return value Signed-off-by: monkey * add cmd: geosearch, geosearchstore (#1836) * add cmd: geosearch, geosearchstore Signed-off-by: monkey92t * GeoSearchQuery and GeoSearchLocationQuery changed to pointer passing Signed-off-by: monkey92t * adjust the code, and fix #1553, #1676 Signed-off-by: monkey92t --- Makefile | 2 +- cluster.go | 1 - cluster_test.go | 8 +- command.go | 177 +++++++++++++++++++++++++++++++++ command_test.go | 2 +- commands.go | 39 ++++++++ commands_test.go | 209 +++++++++++++++++++++++++++++++++++++-- export_test.go | 10 +- internal/proto/writer.go | 2 +- options.go | 5 +- pipeline.go | 40 ++------ pool_test.go | 1 - redis_test.go | 17 ---- ring.go | 2 +- ring_test.go | 1 - 15 files changed, 439 insertions(+), 77 deletions(-) diff --git a/Makefile b/Makefile index 5501164f..b16709c8 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,7 @@ bench: testdeps testdata/redis: mkdir -p $@ - wget -qO- https://download.redis.io/releases/redis-6.2.1.tar.gz | tar xvz --strip-components=1 -C $@ + wget -qO- https://download.redis.io/releases/redis-6.2.5.tar.gz | tar xvz --strip-components=1 -C $@ testdata/redis/src/redis-server: testdata/redis cd $< && make all diff --git a/cluster.go b/cluster.go index d85c043a..bfc7d4e5 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) diff --git a/cluster_test.go b/cluster_test.go index 4c4e4d31..fa692620 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() }) diff --git a/command.go b/command.go index 1c53a660..d1be1043 100644 --- a/command.go +++ b/command.go @@ -2564,6 +2564,183 @@ func (cmd *GeoLocationCmd) readReply(rd *proto.Reader) error { //------------------------------------------------------------------------------ +// GeoSearchQuery is used for GEOSearch/GEOSearchStore command query. +type GeoSearchQuery struct { + Member string + + // Latitude and Longitude when using FromLonLat option. + Longitude float64 + Latitude float64 + + // Distance and unit when using ByRadius option. + // Can use m, km, ft, or mi. Default is km. + Radius float64 + RadiusUnit string + + // Height, width and unit when using ByBox option. + // Can be m, km, ft, or mi. Default is km. + BoxWidth float64 + BoxHeight float64 + BoxUnit string + + // Can be ASC or DESC. Default is no sort order. + Sort string + Count int + CountAny bool +} + +type GeoSearchLocationQuery struct { + GeoSearchQuery + + WithCoord bool + WithDist bool + WithHash bool +} + +type GeoSearchStoreQuery struct { + GeoSearchQuery + + // When using the StoreDist option, the command stores the items in a + // sorted set populated with their distance from the center of the circle or box, + // as a floating-point number, in the same unit specified for that shape. + StoreDist bool +} + +func geoSearchLocationArgs(q *GeoSearchLocationQuery, args []interface{}) []interface{} { + args = geoSearchArgs(&q.GeoSearchQuery, args) + + if q.WithCoord { + args = append(args, "withcoord") + } + if q.WithDist { + args = append(args, "withdist") + } + if q.WithHash { + args = append(args, "withhash") + } + + return args +} + +func geoSearchArgs(q *GeoSearchQuery, args []interface{}) []interface{} { + if q.Member != "" { + args = append(args, "frommember", q.Member) + } else { + args = append(args, "fromlonlat", q.Longitude, q.Latitude) + } + + if q.Radius > 0 { + if q.RadiusUnit == "" { + q.RadiusUnit = "km" + } + args = append(args, "byradius", q.Radius, q.RadiusUnit) + } else { + if q.BoxUnit == "" { + q.BoxUnit = "km" + } + args = append(args, "bybox", q.BoxWidth, q.BoxHeight, q.BoxUnit) + } + + if q.Sort != "" { + args = append(args, q.Sort) + } + + if q.Count > 0 { + args = append(args, "count", q.Count) + if q.CountAny { + args = append(args, "any") + } + } + + return args +} + +type GeoSearchLocationCmd struct { + baseCmd + + opt *GeoSearchLocationQuery + val []GeoLocation +} + +var _ Cmder = (*GeoSearchLocationCmd)(nil) + +func NewGeoSearchLocationCmd( + ctx context.Context, opt *GeoSearchLocationQuery, args ...interface{}, +) *GeoSearchLocationCmd { + return &GeoSearchLocationCmd{ + baseCmd: baseCmd{ + ctx: ctx, + args: args, + }, + opt: opt, + } +} + +func (cmd *GeoSearchLocationCmd) Val() []GeoLocation { + return cmd.val +} + +func (cmd *GeoSearchLocationCmd) Result() ([]GeoLocation, error) { + return cmd.val, cmd.err +} + +func (cmd *GeoSearchLocationCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *GeoSearchLocationCmd) readReply(rd *proto.Reader) error { + n, err := rd.ReadArrayLen() + if err != nil { + return err + } + + cmd.val = make([]GeoLocation, n) + for i := 0; i < n; i++ { + _, err = rd.ReadArrayLen() + if err != nil { + return err + } + + var loc GeoLocation + + loc.Name, err = rd.ReadString() + if err != nil { + return err + } + if cmd.opt.WithDist { + loc.Dist, err = rd.ReadFloat() + if err != nil { + return err + } + } + if cmd.opt.WithHash { + loc.GeoHash, err = rd.ReadInt() + if err != nil { + return err + } + } + if cmd.opt.WithCoord { + if err = rd.ReadFixedArrayLen(2); err != nil { + return err + } + loc.Longitude, err = rd.ReadFloat() + if err != nil { + return err + } + loc.Latitude, err = rd.ReadFloat() + if err != nil { + return err + } + } + + cmd.val[i] = loc + } + + return nil +} + +//------------------------------------------------------------------------------ + type GeoPos struct { Longitude, Latitude float64 } diff --git a/command_test.go b/command_test.go index d110d0c3..9877df42 100644 --- a/command_test.go +++ b/command_test.go @@ -4,7 +4,7 @@ import ( "errors" "time" - redis "github.com/go-redis/redis/v8" + "github.com/go-redis/redis/v8" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" diff --git a/commands.go b/commands.go index 346e38e9..b63aa013 100644 --- a/commands.go +++ b/commands.go @@ -244,6 +244,7 @@ type Cmdable interface { XTrimMinIDApprox(ctx context.Context, key string, minID string, limit int64) *IntCmd XInfoGroups(ctx context.Context, key string) *XInfoGroupsCmd XInfoStream(ctx context.Context, key string) *XInfoStreamCmd + XInfoStreamFull(ctx context.Context, key string, count int) *XInfoStreamFullCmd XInfoConsumers(ctx context.Context, key string, group string) *XInfoConsumersCmd BZPopMax(ctx context.Context, timeout time.Duration, keys ...string) *ZWithKeyCmd @@ -304,6 +305,8 @@ type Cmdable interface { ClientList(ctx context.Context) *StringCmd ClientPause(ctx context.Context, dur time.Duration) *BoolCmd ClientID(ctx context.Context) *IntCmd + 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 @@ -320,6 +323,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 @@ -364,6 +368,9 @@ type Cmdable interface { GeoRadiusStore(ctx context.Context, key string, longitude, latitude float64, query *GeoRadiusQuery) *IntCmd GeoRadiusByMember(ctx context.Context, key, member string, query *GeoRadiusQuery) *GeoLocationCmd GeoRadiusByMemberStore(ctx context.Context, key, member string, query *GeoRadiusQuery) *IntCmd + GeoSearch(ctx context.Context, key string, q *GeoSearchQuery) *StringSliceCmd + GeoSearchLocation(ctx context.Context, key string, q *GeoSearchLocationQuery) *GeoSearchLocationCmd + GeoSearchStore(ctx context.Context, key, store string, q *GeoSearchStoreQuery) *IntCmd GeoDist(ctx context.Context, key string, member1, member2, unit string) *FloatCmd GeoHash(ctx context.Context, key string, members ...string) *StringSliceCmd } @@ -3240,6 +3247,38 @@ func (c cmdable) GeoRadiusByMemberStore( return cmd } +func (c cmdable) GeoSearch(ctx context.Context, key string, q *GeoSearchQuery) *StringSliceCmd { + args := make([]interface{}, 0, 13) + args = append(args, "geosearch", key) + args = geoSearchArgs(q, args) + cmd := NewStringSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) GeoSearchLocation( + ctx context.Context, key string, q *GeoSearchLocationQuery, +) *GeoSearchLocationCmd { + args := make([]interface{}, 0, 16) + args = append(args, "geosearch", key) + args = geoSearchLocationArgs(q, args) + cmd := NewGeoSearchLocationCmd(ctx, q, args...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) GeoSearchStore(ctx context.Context, key, store string, q *GeoSearchStoreQuery) *IntCmd { + args := make([]interface{}, 0, 15) + args = append(args, "geosearchstore", store, key) + args = geoSearchArgs(&q.GeoSearchQuery, args) + if q.StoreDist { + args = append(args, "storedist") + } + cmd := NewIntCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + func (c cmdable) GeoDist( ctx context.Context, key string, member1, member2, unit string, ) *FloatCmd { diff --git a/commands_test.go b/commands_test.go index eb46a790..06d6e401 100644 --- a/commands_test.go +++ b/commands_test.go @@ -4657,7 +4657,7 @@ var _ = Describe("Commands", func() { } msgs, start, err := client.XAutoClaim(ctx, xca).Result() Expect(err).NotTo(HaveOccurred()) - Expect(start).To(Equal("2-0")) + Expect(start).To(Equal("3-0")) Expect(msgs).To(Equal([]redis.XMessage{{ ID: "1-0", Values: map[string]interface{}{"uno": "un"}, @@ -4669,19 +4669,16 @@ var _ = Describe("Commands", func() { xca.Start = start msgs, start, err = client.XAutoClaim(ctx, xca).Result() Expect(err).NotTo(HaveOccurred()) - Expect(start).To(Equal("3-0")) + Expect(start).To(Equal("0-0")) Expect(msgs).To(Equal([]redis.XMessage{{ - ID: "2-0", - Values: map[string]interface{}{"dos": "deux"}, - }, { ID: "3-0", Values: map[string]interface{}{"tres": "troix"}, }})) ids, start, err := client.XAutoClaimJustID(ctx, xca).Result() Expect(err).NotTo(HaveOccurred()) - Expect(start).To(Equal("3-0")) - Expect(ids).To(Equal([]string{"2-0", "3-0"})) + Expect(start).To(Equal("0-0")) + Expect(ids).To(Equal([]string{"3-0"})) }) It("should XClaim", func() { @@ -5167,6 +5164,204 @@ var _ = Describe("Commands", func() { nil, })) }) + + It("should geo search", func() { + q := &redis.GeoSearchQuery{ + Member: "Catania", + BoxWidth: 400, + BoxHeight: 100, + BoxUnit: "km", + Sort: "asc", + } + val, err := client.GeoSearch(ctx, "Sicily", q).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal([]string{"Catania"})) + + q.BoxHeight = 400 + val, err = client.GeoSearch(ctx, "Sicily", q).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal([]string{"Catania", "Palermo"})) + + q.Count = 1 + val, err = client.GeoSearch(ctx, "Sicily", q).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal([]string{"Catania"})) + + q.CountAny = true + val, err = client.GeoSearch(ctx, "Sicily", q).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal([]string{"Palermo"})) + + q = &redis.GeoSearchQuery{ + Member: "Catania", + Radius: 100, + RadiusUnit: "km", + Sort: "asc", + } + val, err = client.GeoSearch(ctx, "Sicily", q).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal([]string{"Catania"})) + + q.Radius = 400 + val, err = client.GeoSearch(ctx, "Sicily", q).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal([]string{"Catania", "Palermo"})) + + q.Count = 1 + val, err = client.GeoSearch(ctx, "Sicily", q).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal([]string{"Catania"})) + + q.CountAny = true + val, err = client.GeoSearch(ctx, "Sicily", q).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal([]string{"Palermo"})) + + q = &redis.GeoSearchQuery{ + Longitude: 15, + Latitude: 37, + BoxWidth: 200, + BoxHeight: 200, + BoxUnit: "km", + Sort: "asc", + } + val, err = client.GeoSearch(ctx, "Sicily", q).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal([]string{"Catania"})) + + q.BoxWidth, q.BoxHeight = 400, 400 + val, err = client.GeoSearch(ctx, "Sicily", q).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal([]string{"Catania", "Palermo"})) + + q.Count = 1 + val, err = client.GeoSearch(ctx, "Sicily", q).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal([]string{"Catania"})) + + q.CountAny = true + val, err = client.GeoSearch(ctx, "Sicily", q).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal([]string{"Palermo"})) + + q = &redis.GeoSearchQuery{ + Longitude: 15, + Latitude: 37, + Radius: 100, + RadiusUnit: "km", + Sort: "asc", + } + val, err = client.GeoSearch(ctx, "Sicily", q).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal([]string{"Catania"})) + + q.Radius = 200 + val, err = client.GeoSearch(ctx, "Sicily", q).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal([]string{"Catania", "Palermo"})) + + q.Count = 1 + val, err = client.GeoSearch(ctx, "Sicily", q).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal([]string{"Catania"})) + + q.CountAny = true + val, err = client.GeoSearch(ctx, "Sicily", q).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal([]string{"Palermo"})) + }) + + It("should geo search with options", func() { + q := &redis.GeoSearchLocationQuery{ + GeoSearchQuery: redis.GeoSearchQuery{ + Longitude: 15, + Latitude: 37, + Radius: 200, + RadiusUnit: "km", + Sort: "asc", + }, + WithHash: true, + WithDist: true, + WithCoord: true, + } + val, err := client.GeoSearchLocation(ctx, "Sicily", q).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal([]redis.GeoLocation{ + { + Name: "Catania", + Longitude: 15.08726745843887329, + Latitude: 37.50266842333162032, + Dist: 56.4413, + GeoHash: 3479447370796909, + }, + { + Name: "Palermo", + Longitude: 13.36138933897018433, + Latitude: 38.11555639549629859, + Dist: 190.4424, + GeoHash: 3479099956230698, + }, + })) + }) + + It("should geo search store", func() { + q := &redis.GeoSearchStoreQuery{ + GeoSearchQuery: redis.GeoSearchQuery{ + Longitude: 15, + Latitude: 37, + Radius: 200, + RadiusUnit: "km", + Sort: "asc", + }, + StoreDist: false, + } + + val, err := client.GeoSearchStore(ctx, "Sicily", "key1", q).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal(int64(2))) + + q.StoreDist = true + val, err = client.GeoSearchStore(ctx, "Sicily", "key2", q).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal(int64(2))) + + loc, err := client.GeoSearchLocation(ctx, "key1", &redis.GeoSearchLocationQuery{ + GeoSearchQuery: q.GeoSearchQuery, + WithCoord: true, + WithDist: true, + WithHash: true, + }).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(loc).To(Equal([]redis.GeoLocation{ + { + Name: "Catania", + Longitude: 15.08726745843887329, + Latitude: 37.50266842333162032, + Dist: 56.4413, + GeoHash: 3479447370796909, + }, + { + Name: "Palermo", + Longitude: 13.36138933897018433, + Latitude: 38.11555639549629859, + Dist: 190.4424, + GeoHash: 3479099956230698, + }, + })) + + v, err := client.ZRangeWithScores(ctx, "key2", 0, -1).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(v).To(Equal([]redis.Z{ + { + Score: 56.441257870158204, + Member: "Catania", + }, + { + Score: 190.44242984775784, + Member: "Palermo", + }, + })) + }) }) Describe("marshaling/unmarshaling", func() { 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/proto/writer.go b/internal/proto/writer.go index 72b30441..f2dc5c93 100644 --- a/internal/proto/writer.go +++ b/internal/proto/writer.go @@ -13,7 +13,7 @@ import ( type writer interface { io.Writer io.ByteWriter - // io.StringWriter + // WriteString implement io.StringWriter. WriteString(s string) (n int, err error) } diff --git a/options.go b/options.go index 5d39bf04..e1e85a66 100644 --- a/options.go +++ b/options.go @@ -247,7 +247,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 o, nil diff --git a/pipeline.go b/pipeline.go index c6ec3409..aa2fc8f0 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 @@ -26,8 +24,7 @@ type Pipeliner interface { StatefulCmdable 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) } @@ -43,9 +40,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() { @@ -67,29 +63,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 @@ -101,10 +79,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 } @@ -119,9 +93,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 81318199..5e83549c 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/redis_test.go b/redis_test.go index 9f189052..c2d2dc02 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/ring.go b/ring.go index 5e35b6bb..7446d32c 100644 --- a/ring.go +++ b/ring.go @@ -308,7 +308,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 4a434a5b..432a1c07 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()) From 4261a2cb2a9bf1a2a1c0f8d2ac655c78db10f709 Mon Sep 17 00:00:00 2001 From: Vladimir Mihailenco Date: Mon, 2 Aug 2021 14:03:38 +0300 Subject: [PATCH 12/17] Update changelog for v9 (#1843) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 63aabd3d..c6c62602 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,13 @@ > :heart: > [**Uptrace.dev** - All-in-one tool to optimize performance and monitor errors & logs](https://uptrace.dev) +## v9 UNRELEASED + +- 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. + ## v8.11 - Remove OpenTelemetry metrics. From 335d946cd67daeb38683eb426dcb7b42783e44ef Mon Sep 17 00:00:00 2001 From: Monkey Date: Sat, 19 Mar 2022 12:40:31 +0800 Subject: [PATCH 13/17] chore: sync master (#2051) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Upgrade redis-server version (#1833) * Upgrade redis-server version Signed-off-by: monkey * XAutoClaim changed the return value Signed-off-by: monkey * add cmd: geosearch, geosearchstore (#1836) * add cmd: geosearch, geosearchstore Signed-off-by: monkey92t * GeoSearchQuery and GeoSearchLocationQuery changed to pointer passing Signed-off-by: monkey92t * Added missing method XInfoStreamFull to Cmdable interface * Run go mod tidy in redisotel Signed-off-by: Bogdan Drutu * Revert "ConnPool check fd for bad conns (#1824)" (#1849) This reverts commit 346bfafddd36dd52d51b064033048de5552ee91e. * Automate release process (#1852) * Bump github.com/onsi/gomega from 1.10.5 to 1.14.0 (#1832) * Bump github.com/onsi/gomega from 1.10.5 to 1.14.0 Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.10.5 to 1.14.0. - [Release notes](https://github.com/onsi/gomega/releases) - [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md) - [Commits](https://github.com/onsi/gomega/compare/v1.10.5...v1.14.0) --- updated-dependencies: - dependency-name: github.com/onsi/gomega dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * Upgrade gomega to v1.15.0 Signed-off-by: monkey92t Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: monkey92t * Add version.go * Fix otel example * Fix package name in release script * More fixes for otel example * And more * Fix release.sh * Release v8.11.3 (release.sh) * Create an annotated tag to give release.yml chance to run * Tweak tag.sh * Add Cmd.Slice helper to cast to []interface{} (#1859) * after the connection pool is closed, no new connections should be added (#1863) * after the connection pool is closed, no new connections should be added Signed-off-by: monkey92t * remove runGoroutine Signed-off-by: monkey92t * pool.popIdle add p.closed check Signed-off-by: monkey92t * upgrade golangci-lint v1.42.0 Signed-off-by: monkey92t * Bump github.com/onsi/gomega from 1.15.0 to 1.16.0 (#1865) Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.15.0 to 1.16.0. - [Release notes](https://github.com/onsi/gomega/releases) - [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md) - [Commits](https://github.com/onsi/gomega/compare/v1.15.0...v1.16.0) --- updated-dependencies: - dependency-name: github.com/onsi/gomega dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Add go 1.17 to the build matrix * Remove go 1.15 from build matrix * Add scan struct example (#1870) * Replace release job * Bump github.com/cespare/xxhash/v2 from 2.1.1 to 2.1.2 (#1872) Bumps [github.com/cespare/xxhash/v2](https://github.com/cespare/xxhash) from 2.1.1 to 2.1.2. - [Release notes](https://github.com/cespare/xxhash/releases) - [Commits](https://github.com/cespare/xxhash/compare/v2.1.1...v2.1.2) --- updated-dependencies: - dependency-name: github.com/cespare/xxhash/v2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Fix tag script to push tag by tag * Fix releasing.md * Fix/pubsub ping mutex (#1878) * Fix PubSub.Ping to hold the lock * Fix PubSub.Ping to hold the lock * add write cmd data-race test Signed-off-by: monkey92t Co-authored-by: monkey92t * chore: cleanup OpenTelemetry example * chore: gofmt all code * Refactor TestParseURL This is in preparation for supporting query parameters in ParseURL: - use an expected *Options instance to execute assertions on - extract assertions into helper function - enable parallel testing - condense test table * Add query parameter parsing to ParseURL() Before this change, ParseURL would only accept a very restricted set of URLs (it returned an error, if it encountered any parameter). This commit introduces the ability to process URLs like redis://localhost/1?dial_timeout=10s and similar. Go programs which were providing a configuration tunable (e.g. CLI flag, config entry or environment variable) to configure the Redis connection now don't need to perform this task themselves. * chore: add links to readme * chore: fix discussions link * empty hooks.withContext removed * chore: gofmt * chore: use conventional commits and auto-generate changelog * feat: add acl auth support for sentinels * chore: swap to acl auth at the test-level * Add support for BLMove command * chore: update dependencies * chore: update link * feat: add SetVal method for each command * feat: add Cmd.{String,Int,Float,Bool}Slice helpers and an example * chore: tweak GH actions to run all jobs * chore: add Lua scripting example * Fix Redis Cluster issue during roll outs of new nodes with same addr (#1914) * fix: recycle connections in some Redis Cluster scenarios This issue was surfaced in a Cloud Provider solution that used for rolling out new nodes using the same address (hostname) of the nodes that will be replaced in a Redis Cluster, while the former ones once depromoted as Slaves would continue in service during some mintues for redirecting traffic. The solution basically identifies when the connection could be stale since a MOVED response will be returned using the same address (hostname) that is being used by the connection. At that moment we consider the connection as no longer usable forcing to recycle the connection. * chore: lazy reload when moved or ask * chore: use conv commit message * chore: release v8.11.4 (release.sh) * fix: add whitespace for avoid unlikely colisions * fix: format * chore: fix links * chore: use ctx parameter in cmdInfo * Bump github.com/onsi/ginkgo from 1.16.4 to 1.16.5 (#1925) Bumps [github.com/onsi/ginkgo](https://github.com/onsi/ginkgo) from 1.16.4 to 1.16.5. - [Release notes](https://github.com/onsi/ginkgo/releases) - [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md) - [Commits](https://github.com/onsi/ginkgo/compare/v1.16.4...v1.16.5) --- updated-dependencies: - dependency-name: github.com/onsi/ginkgo dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * feat: add support for time.Duration write and scan * test: add test case for setting and scanning durations * chore: fix linter * fix(extra/redisotel): set span.kind attribute to client According to the opentelemetry specification this should always be set to client for database client libraries. I've also removed the SetAttributes call and instead set the attributes during creation of the span. This is what the library SHOULD be doing according to the opentelemetry api specification. * chore: update otel example * fix: update some argument counts in pre-allocs In some cases number of pre-allocated places in argument array is missing 1 or 2 elements, which results in re-allocation of twice as large array * chore: add example how to delete keys without a ttl * chore: don't enable all lints * chore(deps): bump github.com/onsi/gomega from 1.16.0 to 1.17.0 Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.16.0 to 1.17.0. - [Release notes](https://github.com/onsi/gomega/releases) - [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md) - [Commits](https://github.com/onsi/gomega/compare/v1.16.0...v1.17.0) --- updated-dependencies: - dependency-name: github.com/onsi/gomega dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * feat: Add redis v7's NX, XX, GT, LT expire variants * chore: add missing readme * chore: tweak feature links * chore: remove Discord * fix: set timeout for WAIT command. Fixes #1963 * build: update `go` directive in `go.mod` to 1.17 This commit enables support for module graph pruning and lazy module loading for projects that are at Go 1.17 or higher. Reference: https://go.dev/ref/mod#go-mod-file-go Reference: https://go.dev/ref/mod#graph-pruning Reference: https://go.dev/ref/mod#lazy-loading Signed-off-by: Eng Zer Jun * chore: update link * chore: export cmder.SetFirstKeyPos to support build module commands * feat(redisotel): ability to override TracerProvider (#1998) * fix: add missing Expire methods to Cmdable This is a followup to https://github.com/go-redis/redis/pull/1928 * chore(deps): bump github.com/onsi/gomega from 1.17.0 to 1.18.1 Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.17.0 to 1.18.1. - [Release notes](https://github.com/onsi/gomega/releases) - [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md) - [Commits](https://github.com/onsi/gomega/compare/v1.17.0...v1.18.1) --- updated-dependencies: - dependency-name: github.com/onsi/gomega dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * Update README.md (#2011) chore: add fmt library in example code * chore: instrumentation name and version (#2012) * fix: invalid type assert in stringArg * chore: cleanup * fix: example/otel compile error (#2028) * fix: rename Golang to Go (#2030) https://go.dev/doc/faq#go_or_golang * feat: add support for passing extra attributes added to spans * feat: set net.peer.name and net.peer.port in otel example * chore: tweak Uptrace copy * feat: add support for COPY command (#2016) * feat: add support for acl sentinel auth in universal client * chore(deps): bump actions/checkout from 2 to 3 Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v2...v3) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * chore: add hll example * chore: tweak release script * chore: release v8.11.5 (release.sh) * chore: add discord back Co-authored-by: Eugene Ponizovsky Co-authored-by: Bogdan Drutu Co-authored-by: Vladimir Mihailenco Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Kishan B Co-authored-by: Dominik Menke Co-authored-by: Gökhan Özeloğlu Co-authored-by: Justin Sievenpiper Co-authored-by: Алексей Романовский Co-authored-by: Stavros Panakakakis Co-authored-by: Pau Freixes Co-authored-by: Ethan Hur Co-authored-by: Jackie <18378976+Pyrodash@users.noreply.github.com> Co-authored-by: Kristinn Björgvin Árdal Co-authored-by: ffenix113 Co-authored-by: Bastien Penavayre Co-authored-by: James3 Li(李麒傑) Co-authored-by: Eng Zer Jun Co-authored-by: gzjiangtao2014 Co-authored-by: Nelz Co-authored-by: Daniel Richter Co-authored-by: Seyed Ali Ghaffari Co-authored-by: lintanghui Co-authored-by: hidu Co-authored-by: Jonas Lergell Co-authored-by: Alex Kahn --- .github/FUNDING.yml | 2 +- .github/ISSUE_TEMPLATE/config.yml | 3 - .github/workflows/build.yml | 5 +- .github/workflows/commitlint.yml | 11 + .github/workflows/golangci-lint.yml | 2 +- .github/workflows/release.yml | 17 + .golangci.yml | 22 -- .prettierrc => .prettierrc.yml | 0 CHANGELOG.md | 40 ++- Makefile | 19 +- README.md | 38 +-- RELEASING.md | 15 + bench_test.go | 66 ++++ cluster.go | 20 +- cluster_test.go | 6 +- command.go | 307 +++++++++++++++++- command_test.go | 2 +- commands.go | 92 +++++- commands_test.go | 58 +++- error.go | 26 +- example/del-keys-without-ttl/README.md | 11 + example/del-keys-without-ttl/go.mod | 7 + .../del-keys-without-ttl}/go.sum | 99 +++--- example/del-keys-without-ttl/main.go | 142 ++++++++ example/hll/README.md | 9 + example/hll/go.mod | 7 + example/hll/go.sum | 110 +++++++ example/hll/main.go | 30 ++ example/lua-scripting/README.md | 8 + example/lua-scripting/go.mod | 7 + example/lua-scripting/go.sum | 110 +++++++ example/lua-scripting/main.go | 66 ++++ example/otel/README.md | 39 ++- example/otel/docker-compose.yml | 3 +- example/otel/example-client.go | 85 ----- example/otel/go.mod | 23 +- example/otel/go.sum | 253 +++++++++++++-- example/otel/main.go | 72 ++++ example/redis-bloom/README.md | 12 + example/redis-bloom/go.mod | 7 + example/redis-bloom/go.sum | 110 +++++++ example/redis-bloom/main.go | 155 +++++++++ example/scan-struct/README.md | 11 + example/scan-struct/go.mod | 10 + example/scan-struct/go.sum | 111 +++++++ example/scan-struct/main.go | 51 +++ example_test.go | 4 +- extra/rediscensus/go.mod | 9 +- extra/rediscensus/go.sum | 76 +++-- extra/rediscensus/rediscensus.go | 3 +- extra/rediscmd/go.mod | 8 +- extra/rediscmd/go.sum | 71 ++-- extra/rediscmd/safe.go | 1 + extra/rediscmd/unsafe.go | 1 + extra/redisotel/example/Makefile | 8 - extra/redisotel/example/README.md | 13 - extra/redisotel/example/docker-compose.yml | 8 - extra/redisotel/example/go.mod | 12 - extra/redisotel/example/main.go | 39 --- extra/redisotel/go.mod | 9 +- extra/redisotel/go.sum | 86 +++-- extra/redisotel/redisotel.go | 108 ++++-- extra/redisotel/redisotel_test.go | 68 ++++ fuzz/fuzz.go | 4 +- go.mod | 19 +- go.sum | 60 +++- internal/hashtag/hashtag_test.go | 4 +- internal/hscan/hscan_test.go | 2 +- internal/hscan/structmap.go | 6 +- internal/pool/conncheck.go | 45 --- internal/pool/conncheck_dummy.go | 9 - internal/pool/conncheck_test.go | 46 --- internal/pool/export_test.go | 4 +- internal/pool/main_test.go | 87 +---- internal/pool/pool.go | 40 ++- internal/pool/pool_test.go | 41 ++- internal/proto/reader.go | 2 +- internal/proto/scan.go | 8 + internal/proto/scan_test.go | 3 +- internal/proto/writer.go | 2 + internal/proto/writer_test.go | 6 +- internal/safe.go | 1 + internal/unsafe.go | 1 + internal/util/safe.go | 1 + internal/util/unsafe.go | 1 + iterator_test.go | 4 +- main_test.go | 8 +- options.go | 151 ++++++++- options_test.go | 295 +++++++++-------- package.json | 8 + pipeline.go | 10 + pipeline_test.go | 4 +- pool_test.go | 7 +- pubsub.go | 12 +- pubsub_test.go | 4 +- race_test.go | 16 +- redis.go | 31 +- redis_test.go | 16 +- ring.go | 5 +- ring_test.go | 8 +- scripts/bump_deps.sh | 9 + scripts/release.sh | 69 ++++ scripts/tag.sh | 42 +++ sentinel.go | 9 +- sentinel_test.go | 79 ++++- tx.go | 3 +- tx_test.go | 13 +- universal.go | 2 + version.go | 6 + 109 files changed, 3082 insertions(+), 944 deletions(-) create mode 100644 .github/workflows/commitlint.yml create mode 100644 .github/workflows/release.yml rename .prettierrc => .prettierrc.yml (100%) create mode 100644 RELEASING.md create mode 100644 example/del-keys-without-ttl/README.md create mode 100644 example/del-keys-without-ttl/go.mod rename {extra/redisotel/example => example/del-keys-without-ttl}/go.sum (56%) create mode 100644 example/del-keys-without-ttl/main.go create mode 100644 example/hll/README.md create mode 100644 example/hll/go.mod create mode 100644 example/hll/go.sum create mode 100644 example/hll/main.go create mode 100644 example/lua-scripting/README.md create mode 100644 example/lua-scripting/go.mod create mode 100644 example/lua-scripting/go.sum create mode 100644 example/lua-scripting/main.go delete mode 100644 example/otel/example-client.go create mode 100644 example/otel/main.go create mode 100644 example/redis-bloom/README.md create mode 100644 example/redis-bloom/go.mod create mode 100644 example/redis-bloom/go.sum create mode 100644 example/redis-bloom/main.go create mode 100644 example/scan-struct/README.md create mode 100644 example/scan-struct/go.mod create mode 100644 example/scan-struct/go.sum create mode 100644 example/scan-struct/main.go delete mode 100644 extra/redisotel/example/Makefile delete mode 100644 extra/redisotel/example/README.md delete mode 100644 extra/redisotel/example/docker-compose.yml delete mode 100644 extra/redisotel/example/go.mod delete mode 100644 extra/redisotel/example/main.go create mode 100644 extra/redisotel/redisotel_test.go delete mode 100644 internal/pool/conncheck.go delete mode 100644 internal/pool/conncheck_dummy.go delete mode 100644 internal/pool/conncheck_test.go create mode 100644 package.json create mode 100755 scripts/bump_deps.sh create mode 100755 scripts/release.sh create mode 100755 scripts/tag.sh create mode 100644 version.go diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index caef4b31..707670d0 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1 +1 @@ -custom: ['https://uptrace.dev'] +custom: ['https://uptrace.dev/sponsor'] diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index fbaa570b..e86d7a66 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -3,6 +3,3 @@ contact_links: - name: Discussions url: https://github.com/go-redis/redis/discussions about: Ask a question via GitHub Discussions - - name: Discord - url: https://discord.gg/rWtp5Aj - about: Ask a question via Discord diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 59ee91cb..32dc6868 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,8 +11,9 @@ jobs: name: build runs-on: ubuntu-latest strategy: + fail-fast: false matrix: - go-version: [1.15.x, 1.16.x] + go-version: [1.16.x, 1.17.x] services: redis: @@ -29,7 +30,7 @@ jobs: go-version: ${{ matrix.go-version }} - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Test run: make test diff --git a/.github/workflows/commitlint.yml b/.github/workflows/commitlint.yml new file mode 100644 index 00000000..5fcfeaea --- /dev/null +++ b/.github/workflows/commitlint.yml @@ -0,0 +1,11 @@ +name: Lint Commit Messages +on: [pull_request] + +jobs: + commitlint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - uses: wagoid/commitlint-github-action@v4 diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 1d9708c0..91c4b53e 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -15,6 +15,6 @@ jobs: name: lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: golangci-lint uses: golangci/golangci-lint-action@v2 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..685693ae --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,17 @@ +name: Releases + +on: + push: + tags: + - 'v*' + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: ncipollo/release-action@v1 + with: + body: + Please refer to + [CHANGELOG.md](https://github.com/go-redis/redis/blob/master/CHANGELOG.md) for details diff --git a/.golangci.yml b/.golangci.yml index d15b5ac5..de514554 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -2,25 +2,3 @@ run: concurrency: 8 deadline: 5m tests: false -linters: - enable-all: true - disable: - - funlen - - gochecknoglobals - - gochecknoinits - - gocognit - - goconst - - godox - - gosec - - maligned - - wsl - - gomnd - - goerr113 - - exhaustive - - nestif - - nlreturn - - exhaustivestruct - - wrapcheck - - errorlint - - cyclop - - forcetypeassert diff --git a/.prettierrc b/.prettierrc.yml similarity index 100% rename from .prettierrc rename to .prettierrc.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index c6c62602..83fcbe2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,41 @@ -# Changelog +## [8.11.5](https://github.com/go-redis/redis/compare/v8.11.4...v8.11.5) (2022-03-17) + + +### 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)) + -> :heart: -> [**Uptrace.dev** - All-in-one tool to optimize performance and monitor errors & logs](https://uptrace.dev) ## v9 UNRELEASED diff --git a/Makefile b/Makefile index b16709c8..a4cfe057 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,5 @@ +PACKAGE_DIRS := $(shell find . -mindepth 2 -type f -name 'go.mod' -exec dirname {} \; | sort) + test: testdeps go test ./... go test ./... -short -race @@ -19,8 +21,15 @@ testdata/redis: testdata/redis/src/redis-server: testdata/redis cd $< && make all -tag: - git tag $(VERSION) - git tag extra/rediscmd/$(VERSION) - git tag extra/redisotel/$(VERSION) - git tag extra/rediscensus/$(VERSION) +fmt: + gofmt -w -s ./ + goimports -w -local github.com/go-redis/redis ./ + +go_mod_tidy: + go get -u && go mod tidy + set -e; for dir in $(PACKAGE_DIRS); do \ + echo "go mod tidy in $${dir}"; \ + (cd "$${dir}" && \ + go get -u && \ + go mod tidy); \ + done diff --git a/README.md b/README.md index 2c36f786..2fffbc00 100644 --- a/README.md +++ b/README.md @@ -1,34 +1,33 @@ -

- - All-in-one tool to optimize performance and monitor errors & logs - -

- -# Redis client for Golang +# Redis client for Go ![build workflow](https://github.com/go-redis/redis/actions/workflows/build.yml/badge.svg) [![PkgGoDev](https://pkg.go.dev/badge/github.com/go-redis/redis/v8)](https://pkg.go.dev/github.com/go-redis/redis/v8?tab=doc) [![Documentation](https://img.shields.io/badge/redis-documentation-informational)](https://redis.uptrace.dev/) [![Chat](https://discordapp.com/api/guilds/752070105847955518/widget.png)](https://discord.gg/rWtp5Aj) -- Join [Discord](https://discord.gg/rWtp5Aj) to ask questions. +go-redis is brought to you by :star: [**uptrace/uptrace**](https://github.com/uptrace/uptrace). +Uptrace is an open source and blazingly fast **distributed tracing** backend powered by +OpenTelemetry and ClickHouse. Give it a star as well! + +## Resources + +- [Discussions](https://github.com/go-redis/redis/discussions) - [Documentation](https://redis.uptrace.dev) - [Reference](https://pkg.go.dev/github.com/go-redis/redis/v8?tab=doc) - [Examples](https://pkg.go.dev/github.com/go-redis/redis/v8?tab=doc#pkg-examples) - [RealWorld example app](https://github.com/uptrace/go-treemux-realworld-example-app) -My other projects: +Other projects you may like: - [Bun](https://bun.uptrace.dev) - fast and simple SQL client for PostgreSQL, MySQL, and SQLite. -- [treemux](https://github.com/vmihailenco/treemux) - high-speed, flexible, tree-based HTTP router - for Go. +- [BunRouter](https://bunrouter.uptrace.dev/) - fast and flexible HTTP router for Go. ## Ecosystem -- [Redis Mock](https://github.com/go-redis/redismock). -- [Distributed Locks](https://github.com/bsm/redislock). -- [Redis Cache](https://github.com/go-redis/cache). -- [Rate limiting](https://github.com/go-redis/redis_rate). +- [Redis Mock](https://github.com/go-redis/redismock) +- [Distributed Locks](https://github.com/bsm/redislock) +- [Redis Cache](https://github.com/go-redis/cache) +- [Rate limiting](https://github.com/go-redis/redis_rate) ## Features @@ -37,16 +36,16 @@ My other projects: [circuit breaker](https://en.wikipedia.org/wiki/Circuit_breaker_design_pattern) support. - [Pub/Sub](https://pkg.go.dev/github.com/go-redis/redis/v8?tab=doc#PubSub). - [Transactions](https://pkg.go.dev/github.com/go-redis/redis/v8?tab=doc#example-Client-TxPipeline). -- [Pipeline](https://pkg.go.dev/github.com/go-redis/redis/v8?tab=doc#example-Client-Pipeline) and - [TxPipeline](https://pkg.go.dev/github.com/go-redis/redis/v8?tab=doc#example-Client-TxPipeline). +- [Pipeline](https://pkg.go.dev/github.com/go-redis/redis/v8?tab=doc#example-Client.Pipeline) and + [TxPipeline](https://pkg.go.dev/github.com/go-redis/redis/v8?tab=doc#example-Client.TxPipeline). - [Scripting](https://pkg.go.dev/github.com/go-redis/redis/v8?tab=doc#Script). - [Timeouts](https://pkg.go.dev/github.com/go-redis/redis/v8?tab=doc#Options). - [Redis Sentinel](https://pkg.go.dev/github.com/go-redis/redis/v8?tab=doc#NewFailoverClient). - [Redis Cluster](https://pkg.go.dev/github.com/go-redis/redis/v8?tab=doc#NewClusterClient). -- [Cluster of Redis Servers](https://pkg.go.dev/github.com/go-redis/redis/v8?tab=doc#example-NewClusterClient--ManualSetup) +- [Cluster of Redis Servers](https://pkg.go.dev/github.com/go-redis/redis/v8?tab=doc#example-NewClusterClient-ManualSetup) without using cluster mode and Redis Sentinel. - [Ring](https://pkg.go.dev/github.com/go-redis/redis/v8?tab=doc#NewRing). -- [Instrumentation](https://pkg.go.dev/github.com/go-redis/redis/v8?tab=doc#ex-package--Instrumentation). +- [Instrumentation](https://pkg.go.dev/github.com/go-redis/redis/v8?tab=doc#example-package-Instrumentation). ## Installation @@ -70,6 +69,7 @@ go get github.com/go-redis/redis/v8 import ( "context" "github.com/go-redis/redis/v8" + "fmt" ) var ctx = context.Background() diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 00000000..1115db4e --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,15 @@ +# Releasing + +1. Run `release.sh` script which updates versions in go.mod files and pushes a new branch to GitHub: + +```shell +TAG=v1.0.0 ./scripts/release.sh +``` + +2. Open a pull request and wait for the build to finish. + +3. Merge the pull request and run `tag.sh` to create tags for packages: + +```shell +TAG=v1.0.0 ./scripts/tag.sh +``` diff --git a/bench_test.go b/bench_test.go index 0f26152d..df43d890 100644 --- a/bench_test.go +++ b/bench_test.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "fmt" + "strconv" "strings" "sync" "testing" @@ -233,6 +234,45 @@ func BenchmarkZAdd(b *testing.B) { }) } +func BenchmarkXRead(b *testing.B) { + ctx := context.Background() + client := benchmarkRedisClient(ctx, 10) + defer client.Close() + + args := redis.XAddArgs{ + Stream: "1", + ID: "*", + Values: map[string]string{"uno": "dos"}, + } + + lenStreams := 16 + streams := make([]string, 0, lenStreams) + for i := 0; i < lenStreams; i++ { + streams = append(streams, strconv.Itoa(i)) + } + for i := 0; i < lenStreams; i++ { + streams = append(streams, "0") + } + + b.ReportAllocs() + b.ResetTimer() + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + client.XAdd(ctx, &args) + + err := client.XRead(ctx, &redis.XReadArgs{ + Streams: streams, + Count: 1, + Block: time.Second, + }).Err() + if err != nil { + b.Fatal(err) + } + } + }) +} + var clientSink *redis.Client func BenchmarkWithContext(b *testing.B) { @@ -301,6 +341,32 @@ func BenchmarkClusterPing(b *testing.B) { }) } +func BenchmarkClusterDoInt(b *testing.B) { + if testing.Short() { + b.Skip("skipping in short mode") + } + + ctx := context.Background() + cluster := newClusterScenario() + if err := startCluster(ctx, cluster); err != nil { + b.Fatal(err) + } + defer cluster.Close() + + client := cluster.newClusterClient(ctx, redisClusterOptions()) + defer client.Close() + + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + err := client.Do(ctx, "SET", 10, 10).Err() + if err != nil { + b.Fatal(err) + } + } + }) +} + func BenchmarkClusterSetString(b *testing.B) { if testing.Short() { b.Skip("skipping in short mode") diff --git a/cluster.go b/cluster.go index bfc7d4e5..27dd31b3 100644 --- a/cluster.go +++ b/cluster.go @@ -352,7 +352,7 @@ func (c *clusterNodes) GC(generation uint32) { } } -func (c *clusterNodes) Get(addr string) (*clusterNode, error) { +func (c *clusterNodes) GetOrCreate(addr string) (*clusterNode, error) { node, err := c.get(addr) if err != nil { return nil, err @@ -416,7 +416,7 @@ func (c *clusterNodes) Random() (*clusterNode, error) { } n := rand.Intn(len(addrs)) - return c.Get(addrs[n]) + return c.GetOrCreate(addrs[n]) } //------------------------------------------------------------------------------ @@ -474,7 +474,7 @@ func newClusterState( addr = replaceLoopbackHost(addr, originHost) } - node, err := c.nodes.Get(addr) + node, err := c.nodes.GetOrCreate(addr) if err != nil { return nil, err } @@ -823,8 +823,10 @@ func (c *ClusterClient) process(ctx context.Context, cmd Cmder) error { var addr string moved, ask, addr = isMovedError(lastErr) if moved || ask { + c.state.LazyReload() + var err error - node, err = c.nodes.Get(addr) + node, err = c.nodes.GetOrCreate(addr) if err != nil { return err } @@ -1021,7 +1023,7 @@ func (c *ClusterClient) loadState(ctx context.Context) (*clusterState, error) { for _, idx := range rand.Perm(len(addrs)) { addr := addrs[idx] - node, err := c.nodes.Get(addr) + node, err := c.nodes.GetOrCreate(addr) if err != nil { if firstErr == nil { firstErr = err @@ -1235,7 +1237,7 @@ func (c *ClusterClient) checkMovedErr( return false } - node, err := c.nodes.Get(addr) + node, err := c.nodes.GetOrCreate(addr) if err != nil { return false } @@ -1416,7 +1418,7 @@ func (c *ClusterClient) cmdsMoved( addr string, failedCmds *cmdsMap, ) error { - node, err := c.nodes.Get(addr) + node, err := c.nodes.GetOrCreate(addr) if err != nil { return err } @@ -1471,7 +1473,7 @@ func (c *ClusterClient) Watch(ctx context.Context, fn func(*Tx) error, keys ...s moved, ask, addr := isMovedError(err) if moved || ask { - node, err = c.nodes.Get(addr) + node, err = c.nodes.GetOrCreate(addr) if err != nil { return err } @@ -1583,7 +1585,7 @@ func (c *ClusterClient) cmdsInfo(ctx context.Context) (map[string]*CommandInfo, for _, idx := range perm { addr := addrs[idx] - node, err := c.nodes.Get(addr) + node, err := c.nodes.GetOrCreate(addr) if err != nil { if firstErr == nil { firstErr = err diff --git a/cluster_test.go b/cluster_test.go index fa692620..2d2021cf 100644 --- a/cluster_test.go +++ b/cluster_test.go @@ -9,11 +9,11 @@ import ( "sync" "time" - "github.com/go-redis/redis/v8" - "github.com/go-redis/redis/v8/internal/hashtag" - . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + + "github.com/go-redis/redis/v8" + "github.com/go-redis/redis/v8/internal/hashtag" ) type clusterScenario struct { diff --git a/command.go b/command.go index d1be1043..810deded 100644 --- a/command.go +++ b/command.go @@ -20,7 +20,7 @@ type Cmder interface { String() string stringArg(int) string firstKeyPos() int8 - setFirstKeyPos(int8) + SetFirstKeyPos(int8) readTimeout() *time.Duration readReply(rd *proto.Reader) error @@ -151,15 +151,21 @@ func (cmd *baseCmd) stringArg(pos int) string { if pos < 0 || pos >= len(cmd.args) { return "" } - s, _ := cmd.args[pos].(string) - return s + arg := cmd.args[pos] + switch v := arg.(type) { + case string: + return v + default: + // TODO: consider using appendArg + return fmt.Sprint(v) + } } func (cmd *baseCmd) firstKeyPos() int8 { return cmd.keyPos } -func (cmd *baseCmd) setFirstKeyPos(keyPos int8) { +func (cmd *baseCmd) SetFirstKeyPos(keyPos int8) { cmd.keyPos = keyPos } @@ -200,6 +206,10 @@ func (cmd *Cmd) String() string { return cmdString(cmd, cmd.val) } +func (cmd *Cmd) SetVal(val interface{}) { + cmd.val = val +} + func (cmd *Cmd) Val() interface{} { return cmd.val } @@ -212,7 +222,11 @@ func (cmd *Cmd) Text() (string, error) { if cmd.err != nil { return "", cmd.err } - switch val := cmd.val.(type) { + return toString(cmd.val) +} + +func toString(val interface{}) (string, error) { + switch val := val.(type) { case string: return val, nil default: @@ -240,7 +254,11 @@ func (cmd *Cmd) Int64() (int64, error) { if cmd.err != nil { return 0, cmd.err } - switch val := cmd.val.(type) { + return toInt64(cmd.val) +} + +func toInt64(val interface{}) (int64, error) { + switch val := val.(type) { case int64: return val, nil case string: @@ -255,7 +273,11 @@ func (cmd *Cmd) Uint64() (uint64, error) { if cmd.err != nil { return 0, cmd.err } - switch val := cmd.val.(type) { + return toUint64(cmd.val) +} + +func toUint64(val interface{}) (uint64, error) { + switch val := val.(type) { case int64: return uint64(val), nil case string: @@ -270,7 +292,11 @@ func (cmd *Cmd) Float32() (float32, error) { if cmd.err != nil { return 0, cmd.err } - switch val := cmd.val.(type) { + return toFloat32(cmd.val) +} + +func toFloat32(val interface{}) (float32, error) { + switch val := val.(type) { case int64: return float32(val), nil case string: @@ -289,7 +315,11 @@ func (cmd *Cmd) Float64() (float64, error) { if cmd.err != nil { return 0, cmd.err } - switch val := cmd.val.(type) { + return toFloat64(cmd.val) +} + +func toFloat64(val interface{}) (float64, error) { + switch val := val.(type) { case int64: return float64(val), nil case string: @@ -304,7 +334,11 @@ func (cmd *Cmd) Bool() (bool, error) { if cmd.err != nil { return false, cmd.err } - switch val := cmd.val.(type) { + return toBool(cmd.val) +} + +func toBool(val interface{}) (bool, error) { + switch val := val.(type) { case int64: return val != 0, nil case string: @@ -315,6 +349,120 @@ func (cmd *Cmd) Bool() (bool, error) { } } +func (cmd *Cmd) Slice() ([]interface{}, error) { + if cmd.err != nil { + return nil, cmd.err + } + switch val := cmd.val.(type) { + case []interface{}: + return val, nil + default: + return nil, fmt.Errorf("redis: unexpected type=%T for Slice", val) + } +} + +func (cmd *Cmd) StringSlice() ([]string, error) { + slice, err := cmd.Slice() + if err != nil { + return nil, err + } + + ss := make([]string, len(slice)) + for i, iface := range slice { + val, err := toString(iface) + if err != nil { + return nil, err + } + ss[i] = val + } + return ss, nil +} + +func (cmd *Cmd) Int64Slice() ([]int64, error) { + slice, err := cmd.Slice() + if err != nil { + return nil, err + } + + nums := make([]int64, len(slice)) + for i, iface := range slice { + val, err := toInt64(iface) + if err != nil { + return nil, err + } + nums[i] = val + } + return nums, nil +} + +func (cmd *Cmd) Uint64Slice() ([]uint64, error) { + slice, err := cmd.Slice() + if err != nil { + return nil, err + } + + nums := make([]uint64, len(slice)) + for i, iface := range slice { + val, err := toUint64(iface) + if err != nil { + return nil, err + } + nums[i] = val + } + return nums, nil +} + +func (cmd *Cmd) Float32Slice() ([]float32, error) { + slice, err := cmd.Slice() + if err != nil { + return nil, err + } + + floats := make([]float32, len(slice)) + for i, iface := range slice { + val, err := toFloat32(iface) + if err != nil { + return nil, err + } + floats[i] = val + } + return floats, nil +} + +func (cmd *Cmd) Float64Slice() ([]float64, error) { + slice, err := cmd.Slice() + if err != nil { + return nil, err + } + + floats := make([]float64, len(slice)) + for i, iface := range slice { + val, err := toFloat64(iface) + if err != nil { + return nil, err + } + floats[i] = val + } + return floats, nil +} + +func (cmd *Cmd) BoolSlice() ([]bool, error) { + slice, err := cmd.Slice() + if err != nil { + return nil, err + } + + bools := make([]bool, len(slice)) + for i, iface := range slice { + val, err := toBool(iface) + if err != nil { + return nil, err + } + bools[i] = val + } + return bools, nil +} + func (cmd *Cmd) readReply(rd *proto.Reader) (err error) { cmd.val, err = rd.ReadReply() return err @@ -339,6 +487,10 @@ func NewSliceCmd(ctx context.Context, args ...interface{}) *SliceCmd { } } +func (cmd *SliceCmd) SetVal(val []interface{}) { + cmd.val = val +} + func (cmd *SliceCmd) Val() []interface{} { return cmd.val } @@ -395,6 +547,10 @@ func NewStatusCmd(ctx context.Context, args ...interface{}) *StatusCmd { } } +func (cmd *StatusCmd) SetVal(val string) { + cmd.val = val +} + func (cmd *StatusCmd) Val() string { return cmd.val } @@ -431,6 +587,10 @@ func NewIntCmd(ctx context.Context, args ...interface{}) *IntCmd { } } +func (cmd *IntCmd) SetVal(val int64) { + cmd.val = val +} + func (cmd *IntCmd) Val() int64 { return cmd.val } @@ -471,6 +631,10 @@ func NewIntSliceCmd(ctx context.Context, args ...interface{}) *IntSliceCmd { } } +func (cmd *IntSliceCmd) SetVal(val []int64) { + cmd.val = val +} + func (cmd *IntSliceCmd) Val() []int64 { return cmd.val } @@ -518,6 +682,10 @@ func NewDurationCmd(ctx context.Context, precision time.Duration, args ...interf } } +func (cmd *DurationCmd) SetVal(val time.Duration) { + cmd.val = val +} + func (cmd *DurationCmd) Val() time.Duration { return cmd.val } @@ -565,6 +733,10 @@ func NewTimeCmd(ctx context.Context, args ...interface{}) *TimeCmd { } } +func (cmd *TimeCmd) SetVal(val time.Time) { + cmd.val = val +} + func (cmd *TimeCmd) Val() time.Time { return cmd.val } @@ -612,6 +784,10 @@ func NewBoolCmd(ctx context.Context, args ...interface{}) *BoolCmd { } } +func (cmd *BoolCmd) SetVal(val bool) { + cmd.val = val +} + func (cmd *BoolCmd) Val() bool { return cmd.val } @@ -655,6 +831,10 @@ func NewStringCmd(ctx context.Context, args ...interface{}) *StringCmd { } } +func (cmd *StringCmd) SetVal(val string) { + cmd.val = val +} + func (cmd *StringCmd) Val() string { return cmd.val } @@ -755,6 +935,10 @@ func NewFloatCmd(ctx context.Context, args ...interface{}) *FloatCmd { } } +func (cmd *FloatCmd) SetVal(val float64) { + cmd.val = val +} + func (cmd *FloatCmd) Val() float64 { return cmd.val } @@ -791,6 +975,10 @@ func NewFloatSliceCmd(ctx context.Context, args ...interface{}) *FloatSliceCmd { } } +func (cmd *FloatSliceCmd) SetVal(val []float64) { + cmd.val = val +} + func (cmd *FloatSliceCmd) Val() []float64 { return cmd.val } @@ -842,6 +1030,10 @@ func NewStringSliceCmd(ctx context.Context, args ...interface{}) *StringSliceCmd } } +func (cmd *StringSliceCmd) SetVal(val []string) { + cmd.val = val +} + func (cmd *StringSliceCmd) Val() []string { return cmd.val } @@ -985,6 +1177,10 @@ func NewBoolSliceCmd(ctx context.Context, args ...interface{}) *BoolSliceCmd { } } +func (cmd *BoolSliceCmd) SetVal(val []bool) { + cmd.val = val +} + func (cmd *BoolSliceCmd) Val() []bool { return cmd.val } @@ -1034,6 +1230,10 @@ func (cmd *MapStringStringCmd) Val() map[string]string { return cmd.val } +func (cmd *MapStringStringCmd) SetVal(val map[string]string) { + cmd.val = val +} + func (cmd *MapStringStringCmd) Result() (map[string]string, error) { return cmd.val, cmd.err } @@ -1044,12 +1244,12 @@ func (cmd *MapStringStringCmd) String() string { // 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 *MapStringStringCmd) Scan(dst interface{}) error { +func (cmd *MapStringStringCmd) Scan(dest interface{}) error { if cmd.err != nil { return cmd.err } - strct, err := hscan.Struct(dst) + strct, err := hscan.Struct(dest) if err != nil { return err } @@ -1105,6 +1305,10 @@ func NewStringIntMapCmd(ctx context.Context, args ...interface{}) *StringIntMapC } } +func (cmd *StringIntMapCmd) SetVal(val map[string]int64) { + cmd.val = val +} + func (cmd *StringIntMapCmd) Val() map[string]int64 { return cmd.val } @@ -1158,6 +1362,10 @@ func NewStringStructMapCmd(ctx context.Context, args ...interface{}) *StringStru } } +func (cmd *StringStructMapCmd) SetVal(val map[string]struct{}) { + cmd.val = val +} + func (cmd *StringStructMapCmd) Val() map[string]struct{} { return cmd.val } @@ -1211,6 +1419,10 @@ func NewXMessageSliceCmd(ctx context.Context, args ...interface{}) *XMessageSlic } } +func (cmd *XMessageSliceCmd) SetVal(val []XMessage) { + cmd.val = val +} + func (cmd *XMessageSliceCmd) Val() []XMessage { return cmd.val } @@ -1313,6 +1525,10 @@ func NewXStreamSliceCmd(ctx context.Context, args ...interface{}) *XStreamSliceC } } +func (cmd *XStreamSliceCmd) SetVal(val []XStream) { + cmd.val = val +} + func (cmd *XStreamSliceCmd) Val() []XStream { return cmd.val } @@ -1382,6 +1598,10 @@ func NewXPendingCmd(ctx context.Context, args ...interface{}) *XPendingCmd { } } +func (cmd *XPendingCmd) SetVal(val *XPending) { + cmd.val = val +} + func (cmd *XPendingCmd) Val() *XPending { return cmd.val } @@ -1461,6 +1681,10 @@ func NewXPendingExtCmd(ctx context.Context, args ...interface{}) *XPendingExtCmd } } +func (cmd *XPendingExtCmd) SetVal(val []XPendingExt) { + cmd.val = val +} + func (cmd *XPendingExtCmd) Val() []XPendingExt { return cmd.val } @@ -1527,6 +1751,11 @@ func NewXAutoClaimCmd(ctx context.Context, args ...interface{}) *XAutoClaimCmd { } } +func (cmd *XAutoClaimCmd) SetVal(val []XMessage, start string) { + cmd.val = val + cmd.start = start +} + func (cmd *XAutoClaimCmd) Val() (messages []XMessage, start string) { return cmd.val, cmd.start } @@ -1576,6 +1805,11 @@ func NewXAutoClaimJustIDCmd(ctx context.Context, args ...interface{}) *XAutoClai } } +func (cmd *XAutoClaimJustIDCmd) SetVal(val []string, start string) { + cmd.val = val + cmd.start = start +} + func (cmd *XAutoClaimJustIDCmd) Val() (ids []string, start string) { return cmd.val, cmd.start } @@ -1637,6 +1871,10 @@ func NewXInfoConsumersCmd(ctx context.Context, stream string, group string) *XIn } } +func (cmd *XInfoConsumersCmd) SetVal(val []XInfoConsumer) { + cmd.val = val +} + func (cmd *XInfoConsumersCmd) Val() []XInfoConsumer { return cmd.val } @@ -1712,6 +1950,10 @@ func NewXInfoGroupsCmd(ctx context.Context, stream string) *XInfoGroupsCmd { } } +func (cmd *XInfoGroupsCmd) SetVal(val []XInfoGroup) { + cmd.val = val +} + func (cmd *XInfoGroupsCmd) Val() []XInfoGroup { return cmd.val } @@ -1792,6 +2034,10 @@ func NewXInfoStreamCmd(ctx context.Context, stream string) *XInfoStreamCmd { } } +func (cmd *XInfoStreamCmd) SetVal(val *XInfoStream) { + cmd.val = val +} + func (cmd *XInfoStreamCmd) Val() *XInfoStream { return cmd.val } @@ -1902,6 +2148,10 @@ func NewXInfoStreamFullCmd(ctx context.Context, args ...interface{}) *XInfoStrea } } +func (cmd *XInfoStreamFullCmd) SetVal(val *XInfoStreamFull) { + cmd.val = val +} + func (cmd *XInfoStreamFullCmd) Val() *XInfoStreamFull { return cmd.val } @@ -2137,6 +2387,10 @@ func NewZSliceCmd(ctx context.Context, args ...interface{}) *ZSliceCmd { } } +func (cmd *ZSliceCmd) SetVal(val []Z) { + cmd.val = val +} + func (cmd *ZSliceCmd) Val() []Z { return cmd.val } @@ -2211,6 +2465,10 @@ func NewZWithKeyCmd(ctx context.Context, args ...interface{}) *ZWithKeyCmd { } } +func (cmd *ZWithKeyCmd) SetVal(val *ZWithKey) { + cmd.val = val +} + func (cmd *ZWithKeyCmd) Val() *ZWithKey { return cmd.val } @@ -2265,6 +2523,11 @@ func NewScanCmd(ctx context.Context, process cmdable, args ...interface{}) *Scan } } +func (cmd *ScanCmd) SetVal(page []string, cursor uint64) { + cmd.page = page + cmd.cursor = cursor +} + func (cmd *ScanCmd) Val() (keys []string, cursor uint64) { return cmd.page, cmd.cursor } @@ -2339,6 +2602,10 @@ func NewClusterSlotsCmd(ctx context.Context, args ...interface{}) *ClusterSlotsC } } +func (cmd *ClusterSlotsCmd) SetVal(val []ClusterSlot) { + cmd.val = val +} + func (cmd *ClusterSlotsCmd) Val() []ClusterSlot { return cmd.val } @@ -2500,6 +2767,10 @@ func geoLocationArgs(q *GeoRadiusQuery, args ...interface{}) []interface{} { return args } +func (cmd *GeoLocationCmd) SetVal(locations []GeoLocation) { + cmd.locations = locations +} + func (cmd *GeoLocationCmd) Val() []GeoLocation { return cmd.locations } @@ -2762,6 +3033,10 @@ func NewGeoPosCmd(ctx context.Context, args ...interface{}) *GeoPosCmd { } } +func (cmd *GeoPosCmd) SetVal(val []*GeoPos) { + cmd.val = val +} + func (cmd *GeoPosCmd) Val() []*GeoPos { return cmd.val } @@ -2839,6 +3114,10 @@ func NewCommandsInfoCmd(ctx context.Context, args ...interface{}) *CommandsInfoC } } +func (cmd *CommandsInfoCmd) SetVal(val map[string]*CommandInfo) { + cmd.val = val +} + func (cmd *CommandsInfoCmd) Val() map[string]*CommandInfo { return cmd.val } @@ -3008,6 +3287,10 @@ func NewSlowLogCmd(ctx context.Context, args ...interface{}) *SlowLogCmd { } } +func (cmd *SlowLogCmd) SetVal(val []SlowLog) { + cmd.val = val +} + func (cmd *SlowLogCmd) Val() []SlowLog { return cmd.val } diff --git a/command_test.go b/command_test.go index 9877df42..775987fc 100644 --- a/command_test.go +++ b/command_test.go @@ -72,7 +72,7 @@ var _ = Describe("Cmd", func() { }) It("supports time.Time", func() { - tm := time.Date(2019, 01, 01, 9, 45, 10, 222125, time.UTC) + tm := time.Date(2019, 1, 1, 9, 45, 10, 222125, time.UTC) err := client.Set(ctx, "time_key", tm, 0).Err() Expect(err).NotTo(HaveOccurred()) diff --git a/commands.go b/commands.go index b63aa013..eb0757d1 100644 --- a/commands.go +++ b/commands.go @@ -96,6 +96,10 @@ type Cmdable interface { Exists(ctx context.Context, keys ...string) *IntCmd Expire(ctx context.Context, key string, expiration time.Duration) *BoolCmd ExpireAt(ctx context.Context, key string, tm time.Time) *BoolCmd + ExpireNX(ctx context.Context, key string, expiration time.Duration) *BoolCmd + ExpireXX(ctx context.Context, key string, expiration time.Duration) *BoolCmd + ExpireGT(ctx context.Context, key string, expiration time.Duration) *BoolCmd + ExpireLT(ctx context.Context, key string, expiration time.Duration) *BoolCmd Keys(ctx context.Context, pattern string) *StringSliceCmd Migrate(ctx context.Context, host, port, key string, db int, timeout time.Duration) *StatusCmd Move(ctx context.Context, key string, db int) *BoolCmd @@ -138,6 +142,7 @@ type Cmdable interface { SetXX(ctx context.Context, key string, value interface{}, expiration time.Duration) *BoolCmd SetRange(ctx context.Context, key string, offset int64, value string) *IntCmd StrLen(ctx context.Context, key string) *IntCmd + Copy(ctx context.Context, sourceKey string, destKey string, db int, replace bool) *IntCmd GetBit(ctx context.Context, key string, offset int64) *IntCmd SetBit(ctx context.Context, key string, offset int64, value int) *IntCmd @@ -195,6 +200,7 @@ type Cmdable interface { RPush(ctx context.Context, key string, values ...interface{}) *IntCmd RPushX(ctx context.Context, key string, values ...interface{}) *IntCmd LMove(ctx context.Context, source, destination, srcpos, destpos string) *StringCmd + BLMove(ctx context.Context, source, destination, srcpos, destpos string, timeout time.Duration) *StringCmd SAdd(ctx context.Context, key string, members ...interface{}) *IntCmd SCard(ctx context.Context, key string) *IntCmd @@ -415,6 +421,7 @@ func (c statefulCmdable) AuthACL(ctx context.Context, username, password string) func (c cmdable) Wait(ctx context.Context, numSlaves int, timeout time.Duration) *IntCmd { cmd := NewIntCmd(ctx, "wait", numSlaves, int(timeout/time.Millisecond)) + cmd.setReadTimeout(timeout) _ = c(ctx, cmd) return cmd } @@ -529,7 +536,37 @@ func (c cmdable) Exists(ctx context.Context, keys ...string) *IntCmd { } func (c cmdable) Expire(ctx context.Context, key string, expiration time.Duration) *BoolCmd { - cmd := NewBoolCmd(ctx, "expire", key, formatSec(ctx, expiration)) + return c.expire(ctx, key, expiration, "") +} + +func (c cmdable) ExpireNX(ctx context.Context, key string, expiration time.Duration) *BoolCmd { + return c.expire(ctx, key, expiration, "NX") +} + +func (c cmdable) ExpireXX(ctx context.Context, key string, expiration time.Duration) *BoolCmd { + return c.expire(ctx, key, expiration, "XX") +} + +func (c cmdable) ExpireGT(ctx context.Context, key string, expiration time.Duration) *BoolCmd { + return c.expire(ctx, key, expiration, "GT") +} + +func (c cmdable) ExpireLT(ctx context.Context, key string, expiration time.Duration) *BoolCmd { + return c.expire(ctx, key, expiration, "LT") +} + +func (c cmdable) expire( + ctx context.Context, key string, expiration time.Duration, mode string, +) *BoolCmd { + args := make([]interface{}, 3, 4) + args[0] = "expire" + args[1] = key + args[2] = formatSec(ctx, expiration) + if mode != "" { + args = append(args, mode) + } + + cmd := NewBoolCmd(ctx, args...) _ = c(ctx, cmd) return cmd } @@ -994,6 +1031,16 @@ func (c cmdable) StrLen(ctx context.Context, key string) *IntCmd { return cmd } +func (c cmdable) Copy(ctx context.Context, sourceKey, destKey string, db int, replace bool) *IntCmd { + args := []interface{}{"copy", sourceKey, destKey, "DB", db} + if replace { + args = append(args, "REPLACE") + } + cmd := NewIntCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + //------------------------------------------------------------------------------ func (c cmdable) GetBit(ctx context.Context, key string, offset int64) *IntCmd { @@ -1498,6 +1545,15 @@ func (c cmdable) LMove(ctx context.Context, source, destination, srcpos, destpos return cmd } +func (c cmdable) BLMove( + ctx context.Context, source, destination, srcpos, destpos string, timeout time.Duration, +) *StringCmd { + cmd := NewStringCmd(ctx, "blmove", source, destination, srcpos, destpos, formatSec(ctx, timeout)) + cmd.setReadTimeout(timeout) + _ = c(ctx, cmd) + return cmd +} + //------------------------------------------------------------------------------ func (c cmdable) SAdd(ctx context.Context, key string, members ...interface{}) *IntCmd { @@ -1765,7 +1821,7 @@ type XReadArgs struct { } func (c cmdable) XRead(ctx context.Context, a *XReadArgs) *XStreamSliceCmd { - args := make([]interface{}, 0, 5+len(a.Streams)) + args := make([]interface{}, 0, 6+len(a.Streams)) args = append(args, "xread") keyPos := int8(1) @@ -1789,7 +1845,7 @@ func (c cmdable) XRead(ctx context.Context, a *XReadArgs) *XStreamSliceCmd { if a.Block >= 0 { cmd.setReadTimeout(a.Block) } - cmd.setFirstKeyPos(keyPos) + cmd.SetFirstKeyPos(keyPos) _ = c(ctx, cmd) return cmd } @@ -1847,7 +1903,7 @@ type XReadGroupArgs struct { } func (c cmdable) XReadGroup(ctx context.Context, a *XReadGroupArgs) *XStreamSliceCmd { - args := make([]interface{}, 0, 8+len(a.Streams)) + args := make([]interface{}, 0, 10+len(a.Streams)) args = append(args, "xreadgroup", "group", a.Group, a.Consumer) keyPos := int8(4) @@ -1873,7 +1929,7 @@ func (c cmdable) XReadGroup(ctx context.Context, a *XReadGroupArgs) *XStreamSlic if a.Block >= 0 { cmd.setReadTimeout(a.Block) } - cmd.setFirstKeyPos(keyPos) + cmd.SetFirstKeyPos(keyPos) _ = c(ctx, cmd) return cmd } @@ -1944,7 +2000,7 @@ func (c cmdable) XAutoClaimJustID(ctx context.Context, a *XAutoClaimArgs) *XAuto } func xAutoClaimArgs(ctx context.Context, a *XAutoClaimArgs) []interface{} { - args := make([]interface{}, 0, 9) + args := make([]interface{}, 0, 8) args = append(args, "xautoclaim", a.Stream, a.Group, a.Consumer, formatMs(ctx, a.MinIdle), a.Start) if a.Count > 0 { args = append(args, "count", a.Count) @@ -1976,7 +2032,7 @@ func (c cmdable) XClaimJustID(ctx context.Context, a *XClaimArgs) *StringSliceCm } func xClaimArgs(a *XClaimArgs) []interface{} { - args := make([]interface{}, 0, 4+len(a.Messages)) + args := make([]interface{}, 0, 5+len(a.Messages)) args = append(args, "xclaim", a.Stream, @@ -2249,7 +2305,7 @@ func (c cmdable) ZInterStore(ctx context.Context, destination string, store *ZSt args = append(args, "zinterstore", destination, len(store.Keys)) args = store.appendArgs(args) cmd := NewIntCmd(ctx, args...) - cmd.setFirstKeyPos(3) + cmd.SetFirstKeyPos(3) _ = c(ctx, cmd) return cmd } @@ -2259,7 +2315,7 @@ func (c cmdable) ZInter(ctx context.Context, store *ZStore) *StringSliceCmd { args = append(args, "zinter", len(store.Keys)) args = store.appendArgs(args) cmd := NewStringSliceCmd(ctx, args...) - cmd.setFirstKeyPos(2) + cmd.SetFirstKeyPos(2) _ = c(ctx, cmd) return cmd } @@ -2270,7 +2326,7 @@ func (c cmdable) ZInterWithScores(ctx context.Context, store *ZStore) *ZSliceCmd args = store.appendArgs(args) args = append(args, "withscores") cmd := NewZSliceCmd(ctx, args...) - cmd.setFirstKeyPos(2) + cmd.SetFirstKeyPos(2) _ = c(ctx, cmd) return cmd } @@ -2597,7 +2653,7 @@ func (c cmdable) ZUnion(ctx context.Context, store ZStore) *StringSliceCmd { args = append(args, "zunion", len(store.Keys)) args = store.appendArgs(args) cmd := NewStringSliceCmd(ctx, args...) - cmd.setFirstKeyPos(2) + cmd.SetFirstKeyPos(2) _ = c(ctx, cmd) return cmd } @@ -2608,7 +2664,7 @@ func (c cmdable) ZUnionWithScores(ctx context.Context, store ZStore) *ZSliceCmd args = store.appendArgs(args) args = append(args, "withscores") cmd := NewZSliceCmd(ctx, args...) - cmd.setFirstKeyPos(2) + cmd.SetFirstKeyPos(2) _ = c(ctx, cmd) return cmd } @@ -2618,7 +2674,7 @@ func (c cmdable) ZUnionStore(ctx context.Context, dest string, store *ZStore) *I args = append(args, "zunionstore", dest, len(store.Keys)) args = store.appendArgs(args) cmd := NewIntCmd(ctx, args...) - cmd.setFirstKeyPos(3) + cmd.SetFirstKeyPos(3) _ = c(ctx, cmd) return cmd } @@ -2647,7 +2703,7 @@ func (c cmdable) ZDiff(ctx context.Context, keys ...string) *StringSliceCmd { } cmd := NewStringSliceCmd(ctx, args...) - cmd.setFirstKeyPos(2) + cmd.SetFirstKeyPos(2) _ = c(ctx, cmd) return cmd } @@ -2663,7 +2719,7 @@ func (c cmdable) ZDiffWithScores(ctx context.Context, keys ...string) *ZSliceCmd args[len(keys)+2] = "withscores" cmd := NewZSliceCmd(ctx, args...) - cmd.setFirstKeyPos(2) + cmd.SetFirstKeyPos(2) _ = c(ctx, cmd) return cmd } @@ -2939,7 +2995,7 @@ func (c cmdable) MemoryUsage(ctx context.Context, key string, samples ...int) *I args = append(args, "SAMPLES", samples[0]) } cmd := NewIntCmd(ctx, args...) - cmd.setFirstKeyPos(2) + cmd.SetFirstKeyPos(2) _ = c(ctx, cmd) return cmd } @@ -2956,7 +3012,7 @@ func (c cmdable) Eval(ctx context.Context, script string, keys []string, args .. } cmdArgs = appendArgs(cmdArgs, args) cmd := NewCmd(ctx, cmdArgs...) - cmd.setFirstKeyPos(3) + cmd.SetFirstKeyPos(3) _ = c(ctx, cmd) return cmd } @@ -2971,7 +3027,7 @@ func (c cmdable) EvalSha(ctx context.Context, sha1 string, keys []string, args . } cmdArgs = appendArgs(cmdArgs, args) cmd := NewCmd(ctx, cmdArgs...) - cmd.setFirstKeyPos(3) + cmd.SetFirstKeyPos(3) _ = c(ctx, cmd) return cmd } diff --git a/commands_test.go b/commands_test.go index 06d6e401..81c1bdb8 100644 --- a/commands_test.go +++ b/commands_test.go @@ -484,11 +484,11 @@ var _ = Describe("Commands", func() { idleTime := client.ObjectIdleTime(ctx, "key") Expect(idleTime.Err()).NotTo(HaveOccurred()) - //Redis returned milliseconds/1000, which may cause ObjectIdleTime to be at a critical value, - //should be +1s to deal with the critical value problem. - //if too much time (>1s) is used during command execution, it may also cause the test to fail. - //so the ObjectIdleTime result should be <=now-start+1s - //link: https://github.com/redis/redis/blob/5b48d900498c85bbf4772c1d466c214439888115/src/object.c#L1265-L1272 + // Redis returned milliseconds/1000, which may cause ObjectIdleTime to be at a critical value, + // should be +1s to deal with the critical value problem. + // if too much time (>1s) is used during command execution, it may also cause the test to fail. + // so the ObjectIdleTime result should be <=now-start+1s + // link: https://github.com/redis/redis/blob/5b48d900498c85bbf4772c1d466c214439888115/src/object.c#L1265-L1272 Expect(idleTime.Val()).To(BeNumerically("<=", time.Now().Sub(start)+time.Second)) }) @@ -1645,6 +1645,32 @@ var _ = Describe("Commands", func() { Expect(strLen.Err()).NotTo(HaveOccurred()) Expect(strLen.Val()).To(Equal(int64(0))) }) + + It("should Copy", func() { + set := client.Set(ctx, "key", "hello", 0) + Expect(set.Err()).NotTo(HaveOccurred()) + Expect(set.Val()).To(Equal("OK")) + + copy := client.Copy(ctx, "key", "newKey", redisOptions().DB, false) + Expect(copy.Err()).NotTo(HaveOccurred()) + Expect(copy.Val()).To(Equal(int64(1))) + + // Value is available by both keys now + getOld := client.Get(ctx, "key") + Expect(getOld.Err()).NotTo(HaveOccurred()) + Expect(getOld.Val()).To(Equal("hello")) + getNew := client.Get(ctx, "newKey") + Expect(getNew.Err()).NotTo(HaveOccurred()) + Expect(getNew.Val()).To(Equal("hello")) + + // Overwriting an existing key should not succeed + overwrite := client.Copy(ctx, "newKey", "key", redisOptions().DB, false) + Expect(overwrite.Val()).To(Equal(int64(0))) + + // Overwrite is allowed when replace=rue + replace := client.Copy(ctx, "newKey", "key", redisOptions().DB, true) + Expect(replace.Val()).To(Equal(int64(1))) + }) }) Describe("hashes", func() { @@ -2386,6 +2412,28 @@ var _ = Describe("Commands", func() { Expect(lRange.Err()).NotTo(HaveOccurred()) Expect(lRange.Val()).To(Equal([]string{"san"})) }) + + It("should BLMove", func() { + rPush := client.RPush(ctx, "blmove1", "ichi") + Expect(rPush.Err()).NotTo(HaveOccurred()) + Expect(rPush.Val()).To(Equal(int64(1))) + + rPush = client.RPush(ctx, "blmove1", "ni") + Expect(rPush.Err()).NotTo(HaveOccurred()) + Expect(rPush.Val()).To(Equal(int64(2))) + + rPush = client.RPush(ctx, "blmove1", "san") + Expect(rPush.Err()).NotTo(HaveOccurred()) + Expect(rPush.Val()).To(Equal(int64(3))) + + blMove := client.BLMove(ctx, "blmove1", "blmove2", "RIGHT", "LEFT", time.Second) + Expect(blMove.Err()).NotTo(HaveOccurred()) + Expect(blMove.Val()).To(Equal("san")) + + lRange := client.LRange(ctx, "blmove2", 0, -1) + Expect(lRange.Err()).NotTo(HaveOccurred()) + Expect(lRange.Val()).To(Equal([]string{"san"})) + }) }) Describe("sets", func() { diff --git a/error.go b/error.go index ad9e173e..521594bb 100644 --- a/error.go +++ b/error.go @@ -65,7 +65,7 @@ func isRedisError(err error) bool { return ok } -func isBadConn(err error, allowTimeout bool) bool { +func isBadConn(err error, allowTimeout bool, addr string) bool { switch err { case nil: return false @@ -74,9 +74,19 @@ func isBadConn(err error, allowTimeout bool) bool { } if isRedisError(err) { - // Close connections in read only state in case domain addr is used - // and domain resolves to a different Redis Server. See #790. - return isReadOnlyError(err) + switch { + case isReadOnlyError(err): + // Close connections in read only state in case domain addr is used + // and domain resolves to a different Redis Server. See #790. + return true + case isMovedSameConnAddr(err, addr): + // Close connections when we are asked to move to the same addr + // of the connection. Force a DNS resolution when all connections + // of the pool are recycled + return true + default: + return false + } } if allowTimeout { @@ -119,6 +129,14 @@ func isReadOnlyError(err error) bool { return strings.HasPrefix(err.Error(), "READONLY ") } +func isMovedSameConnAddr(err error, addr string) bool { + redisError := err.Error() + if !strings.HasPrefix(redisError, "MOVED ") { + return false + } + return strings.HasSuffix(redisError, " "+addr) +} + //------------------------------------------------------------------------------ type timeoutError interface { diff --git a/example/del-keys-without-ttl/README.md b/example/del-keys-without-ttl/README.md new file mode 100644 index 00000000..c112ee25 --- /dev/null +++ b/example/del-keys-without-ttl/README.md @@ -0,0 +1,11 @@ +# Delete keys without a ttl + +This example demonstrates how to use `SCAN` and pipelines to efficiently delete keys without a TTL. + +To run this example: + +```shell +go run . +``` + +See [documentation](https://redis.uptrace.dev/guide/get-all-keys.html) for more details. diff --git a/example/del-keys-without-ttl/go.mod b/example/del-keys-without-ttl/go.mod new file mode 100644 index 00000000..4421e29f --- /dev/null +++ b/example/del-keys-without-ttl/go.mod @@ -0,0 +1,7 @@ +module github.com/go-redis/redis/example/del-keys-without-ttl + +go 1.14 + +replace github.com/go-redis/redis/v8 => ../.. + +require github.com/go-redis/redis/v8 v8.11.5 diff --git a/extra/redisotel/example/go.sum b/example/del-keys-without-ttl/go.sum similarity index 56% rename from extra/redisotel/example/go.sum rename to example/del-keys-without-ttl/go.sum index 4e05e032..142b2afe 100644 --- a/extra/redisotel/example/go.sum +++ b/example/del-keys-without-ttl/go.sum @@ -1,20 +1,16 @@ -github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/go-redis/redis/extra/rediscmd/v8 v8.8.2 h1:kjH+FAEz3G8gq8zDBgoLSCZj19Z1SK4dg/VtD3QE1IE= -github.com/go-redis/redis/extra/rediscmd/v8 v8.8.2/go.mod h1:1BL5EW0eGp6pj3Id4HYKCH0QTJWZZNKhWjYIGzKASqY= -github.com/go-redis/redis/extra/redisotel/v8 v8.8.2 h1:bJEPlEwcXNJkF7DF6m9thpyvU95PBqjDY+U80TIWfsY= -github.com/go-redis/redis/extra/redisotel/v8 v8.8.2/go.mod h1:zNGA0eMppmrbs2+VUMRx32FCb+nrB5yEGtYp1wKqeC8= -github.com/go-redis/redis/v8 v8.4.4/go.mod h1:nA0bQuF0i5JFx4Ta9RZxGKXFrQ8cRWntra97f0196iY= -github.com/go-redis/redis/v8 v8.8.2 h1:O/NcHqobw7SEptA0yA6up6spZVFtwE06SXM8rgLtsP8= -github.com/go-redis/redis/v8 v8.8.2/go.mod h1:F7resOH5Kdug49Otu24RjHWwgK7u9AmtqWMnCV1iP5Y= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= @@ -22,56 +18,36 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= -github.com/onsi/ginkgo v1.15.0 h1:1V1NfVQR87RtWAgp1lv9JZJ5Jap+XFGKPi00andXGi4= -github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.0.0 h1:CcuG/HvWNkkaqCUpJifQY8z7qEMBJya6aLPx6ftGyjQ= +github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.10.4/go.mod h1:g/HbgYopi++010VEqkFgJHKC09uJiW9UkXvMUuKHUCQ= -github.com/onsi/gomega v1.10.5 h1:7n6FEkpFmfCoo2t+YYqXH0evK+a9ICQz0xcAy9dYcaQ= -github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= +github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.opentelemetry.io/otel v0.15.0/go.mod h1:e4GKElweB8W2gWUqbghw0B8t5MCTccc9212eNHnOHwA= -go.opentelemetry.io/otel v0.19.0/go.mod h1:j9bF567N9EfomkSidSfmMwIwIBuP37AMAIzVW85OxSg= -go.opentelemetry.io/otel v1.0.0-RC1 h1:4CeoX93DNTWt8awGK9JmNXzF9j7TyOu9upscEdtcdXc= -go.opentelemetry.io/otel v1.0.0-RC1/go.mod h1:x9tRa9HK4hSSq7jf2TKbqFbtt58/TGk0f9XiEYISI1I= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.0.0-RC1 h1:SEfJImgKQ5TP2aTJwN08qhS8oFlYWr/neECGsyuxKWg= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.0.0-RC1/go.mod h1:TAM/UYjVd1UdaifWkof3qj9cCW9oINemHfj0K6yodSo= -go.opentelemetry.io/otel/internal/metric v0.21.0 h1:gZlIBo5O51hZOOZz8vEcuRx/l5dnADadKfpT70AELoo= -go.opentelemetry.io/otel/internal/metric v0.21.0/go.mod h1:iOfAaY2YycsXfYD4kaRSbLx2LKmfpKObWBEv9QK5zFo= -go.opentelemetry.io/otel/metric v0.19.0/go.mod h1:8f9fglJPRnXuskQmKpnad31lcLJ2VmNNqIsx/uIwBSc= -go.opentelemetry.io/otel/metric v0.21.0 h1:ZtcJlHqVE4l8Su0WOLOd9fEPheJuYEiQ0wr9wv2p25I= -go.opentelemetry.io/otel/metric v0.21.0/go.mod h1:JWCt1bjivC4iCrz/aCrM1GSw+ZcvY44KCbaeeRhzHnc= -go.opentelemetry.io/otel/oteltest v0.19.0/go.mod h1:tI4yxwh8U21v7JD6R3BcA/2+RBoTKFexE/PJ/nSO7IA= -go.opentelemetry.io/otel/oteltest v1.0.0-RC1 h1:G685iP3XiskCwk/z0eIabL55XUl2gk0cljhGk9sB0Yk= -go.opentelemetry.io/otel/oteltest v1.0.0-RC1/go.mod h1:+eoIG0gdEOaPNftuy1YScLr1Gb4mL/9lpDkZ0JjMRq4= -go.opentelemetry.io/otel/sdk v1.0.0-RC1 h1:Sy2VLOOg24bipyC29PhuMXYNJrLsxkie8hyI7kUlG9Q= -go.opentelemetry.io/otel/sdk v1.0.0-RC1/go.mod h1:kj6yPn7Pgt5ByRuwesbaWcRLA+V7BSDg3Hf8xRvsvf8= -go.opentelemetry.io/otel/sdk/export/metric v0.21.0 h1:4tSMVkDbvrowOeP/6rOfGABEWv5n+0gCfhI/TWleUvc= -go.opentelemetry.io/otel/sdk/export/metric v0.21.0/go.mod h1:gTaOMSQmL4zfsTL47desIPbPla5MyMG29lN3PzcibVg= -go.opentelemetry.io/otel/sdk/metric v0.21.0 h1:LNLUj35NNdEpyJQwj/htiEsfnY6GeTIwYHweCJNV+nc= -go.opentelemetry.io/otel/sdk/metric v0.21.0/go.mod h1:OHOcF8ZjE/L8oL/QXpUFWklPwtaukrfHgoAiPek53rQ= -go.opentelemetry.io/otel/trace v0.19.0/go.mod h1:4IXiNextNOpPnRlI4ryK69mn5iC84bjBWZQA5DXz/qg= -go.opentelemetry.io/otel/trace v1.0.0-RC1 h1:jrjqKJZEibFrDz+umEASeU3LvdVyWKlnTh7XEfwrT58= -go.opentelemetry.io/otel/trace v1.0.0-RC1/go.mod h1:86UHmyHWFEtWjfWPSbu0+d0Pf9Q6e1U+3ViBOc+NXAg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -81,8 +57,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb h1:eBmm0M9fYhWpKZLjQUUKka/LtIxf46G4fxeEz5KJr9U= -golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -92,21 +68,26 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210112080510-489259a85091 h1:DMyOG0U+gKfu8JZzg2UQe9MeaC1X+xQWlAKcRnjxjCw= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -114,12 +95,16 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/example/del-keys-without-ttl/main.go b/example/del-keys-without-ttl/main.go new file mode 100644 index 00000000..2331ed10 --- /dev/null +++ b/example/del-keys-without-ttl/main.go @@ -0,0 +1,142 @@ +package main + +import ( + "context" + "fmt" + "sync" + + "github.com/go-redis/redis/v8" +) + +func main() { + ctx := context.Background() + + rdb := redis.NewClient(&redis.Options{ + Addr: ":6379", + }) + + ch := make(chan string, 100) + var wg sync.WaitGroup + + wg.Add(1) + go func() { + defer wg.Done() + deleted, err := process(ctx, rdb, ch) + if err != nil { + panic(err) + } + fmt.Println("deleted", deleted, "keys") + }() + + iter := rdb.Scan(ctx, 0, "", 0).Iterator() + for iter.Next(ctx) { + ch <- iter.Val() + } + if err := iter.Err(); err != nil { + panic(err) + } + + close(ch) + wg.Wait() +} + +func process(ctx context.Context, rdb *redis.Client, in <-chan string) (int, error) { + var wg sync.WaitGroup + + out := make(chan string, 100) + defer func() { + close(out) + wg.Wait() + }() + + wg.Add(1) + go func() { + defer wg.Done() + if err := del(ctx, rdb, out); err != nil { + panic(err) + } + }() + + var deleted int + + keys := make([]string, 0, 100) + for key := range in { + keys = append(keys, key) + if len(keys) < 100 { + continue + } + + var err error + keys, err = checkTTL(ctx, rdb, keys) + if err != nil { + return 0, err + } + + for _, key := range keys { + out <- key + } + deleted += len(keys) + + keys = keys[:0] + } + + if len(keys) > 0 { + keys, err := checkTTL(ctx, rdb, keys) + if err != nil { + return 0, err + } + + for _, key := range keys { + out <- key + } + deleted += len(keys) + } + + return deleted, nil +} + +func checkTTL(ctx context.Context, rdb *redis.Client, keys []string) ([]string, error) { + cmds, err := rdb.Pipelined(ctx, func(pipe redis.Pipeliner) error { + for _, key := range keys { + pipe.TTL(ctx, key) + } + return nil + }) + if err != nil { + return nil, err + } + + for i := len(cmds) - 1; i >= 0; i-- { + d, err := cmds[i].(*redis.DurationCmd).Result() + if err != nil { + return nil, err + } + if d != -1 { + keys = append(keys[:i], keys[i+1:]...) + } + } + + return keys, nil +} + +func del(ctx context.Context, rdb *redis.Client, in <-chan string) error { + pipe := rdb.Pipeline() + + for key := range in { + pipe.Del(ctx, key) + + if pipe.Len() < 100 { + continue + } + + if _, err := pipe.Exec(ctx); err != nil { + return err + } + } + + if _, err := pipe.Exec(ctx); err != nil { + return err + } + + return nil +} diff --git a/example/hll/README.md b/example/hll/README.md new file mode 100644 index 00000000..cd78d7f1 --- /dev/null +++ b/example/hll/README.md @@ -0,0 +1,9 @@ +# Redis HyperLogLog example + +To run this example: + +```shell +go run . +``` + +See [Using HyperLogLog command with go-redis](https://redis.uptrace.dev/guide/hll.html) for details. diff --git a/example/hll/go.mod b/example/hll/go.mod new file mode 100644 index 00000000..33d76306 --- /dev/null +++ b/example/hll/go.mod @@ -0,0 +1,7 @@ +module github.com/go-redis/redis/example/hll + +go 1.14 + +replace github.com/go-redis/redis/v8 => ../.. + +require github.com/go-redis/redis/v8 v8.11.5 diff --git a/example/hll/go.sum b/example/hll/go.sum new file mode 100644 index 00000000..142b2afe --- /dev/null +++ b/example/hll/go.sum @@ -0,0 +1,110 @@ +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.0.0 h1:CcuG/HvWNkkaqCUpJifQY8z7qEMBJya6aLPx6ftGyjQ= +github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= +github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/example/hll/main.go b/example/hll/main.go new file mode 100644 index 00000000..60a957e9 --- /dev/null +++ b/example/hll/main.go @@ -0,0 +1,30 @@ +package main + +import ( + "context" + "fmt" + + "github.com/go-redis/redis/v8" +) + +func main() { + ctx := context.Background() + + rdb := redis.NewClient(&redis.Options{ + Addr: ":6379", + }) + _ = rdb.FlushDB(ctx).Err() + + for i := 0; i < 10; i++ { + if err := rdb.PFAdd(ctx, "myset", fmt.Sprint(i)).Err(); err != nil { + panic(err) + } + } + + card, err := rdb.PFCount(ctx, "myset").Result() + if err != nil { + panic(err) + } + + fmt.Println("set cardinality", card) +} diff --git a/example/lua-scripting/README.md b/example/lua-scripting/README.md new file mode 100644 index 00000000..3272d7d3 --- /dev/null +++ b/example/lua-scripting/README.md @@ -0,0 +1,8 @@ +# Redis Lua scripting example + +This is an example for [Redis Lua scripting](https://redis.uptrace.dev/guide/lua-scripting.html) +article. To run it: + +```shell +go run . +``` diff --git a/example/lua-scripting/go.mod b/example/lua-scripting/go.mod new file mode 100644 index 00000000..be4ecab2 --- /dev/null +++ b/example/lua-scripting/go.mod @@ -0,0 +1,7 @@ +module github.com/go-redis/redis/example/redis-bloom + +go 1.14 + +replace github.com/go-redis/redis/v8 => ../.. + +require github.com/go-redis/redis/v8 v8.11.5 diff --git a/example/lua-scripting/go.sum b/example/lua-scripting/go.sum new file mode 100644 index 00000000..142b2afe --- /dev/null +++ b/example/lua-scripting/go.sum @@ -0,0 +1,110 @@ +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.0.0 h1:CcuG/HvWNkkaqCUpJifQY8z7qEMBJya6aLPx6ftGyjQ= +github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= +github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/example/lua-scripting/main.go b/example/lua-scripting/main.go new file mode 100644 index 00000000..04cb44d9 --- /dev/null +++ b/example/lua-scripting/main.go @@ -0,0 +1,66 @@ +package main + +import ( + "context" + "fmt" + + "github.com/go-redis/redis/v8" +) + +func main() { + ctx := context.Background() + + rdb := redis.NewClient(&redis.Options{ + Addr: ":6379", + }) + _ = rdb.FlushDB(ctx).Err() + + fmt.Printf("# INCR BY\n") + for _, change := range []int{+1, +5, 0} { + num, err := incrBy.Run(ctx, rdb, []string{"my_counter"}, change).Int() + if err != nil { + panic(err) + } + fmt.Printf("incr by %d: %d\n", change, num) + } + + fmt.Printf("\n# SUM\n") + sum, err := sum.Run(ctx, rdb, []string{"my_sum"}, 1, 2, 3).Int() + if err != nil { + panic(err) + } + fmt.Printf("sum is: %d\n", sum) +} + +var incrBy = redis.NewScript(` +local key = KEYS[1] +local change = ARGV[1] + +local value = redis.call("GET", key) +if not value then + value = 0 +end + +value = value + change +redis.call("SET", key, value) + +return value +`) + +var sum = redis.NewScript(` +local key = KEYS[1] + +local sum = redis.call("GET", key) +if not sum then + sum = 0 +end + +local num_arg = #ARGV +for i = 1, num_arg do + sum = sum + ARGV[i] +end + +redis.call("SET", key, sum) + +return sum +`) diff --git a/example/otel/README.md b/example/otel/README.md index 60d4d121..567a8827 100644 --- a/example/otel/README.md +++ b/example/otel/README.md @@ -1,9 +1,34 @@ -### OpenTelemetry Example -Prints spans and metrics to the console. -#### To Run: -- `docker-compose up -d` -- `go run .` +# Example for go-redis OpenTelemetry instrumentation -When you're finished, be sure to run `docker-compose down` to shutdown -the redis server. +See [Monitoring performance and errors](https://redis.uptrace.dev/guide/tracing.html) for details. +This example requires Redis Server on port `:6379`. You can start Redis Server using Docker: + +```shell +docker-compose up -d +``` + +You can run this example with different OpenTelemetry exporters by providing environment variables. + +**Stdout** exporter (default): + +```shell +go run . +``` + +**Jaeger** exporter: + +```shell +OTEL_EXPORTER_JAEGER_ENDPOINT=http://localhost:14268/api/traces go run . +``` + +**Uptrace** exporter: + +```shell +UPTRACE_DSN="https://@uptrace.dev/" go run . +``` + +## Links + +- [Find instrumentations](https://opentelemetry.uptrace.dev/instrumentations/?lang=go) +- [OpenTelemetry Tracing API](https://opentelemetry.uptrace.dev/guide/go-tracing.html) diff --git a/example/otel/docker-compose.yml b/example/otel/docker-compose.yml index c2d7f1d4..ff8f0669 100644 --- a/example/otel/docker-compose.yml +++ b/example/otel/docker-compose.yml @@ -1,8 +1,9 @@ version: '3' + services: redis-server: image: redis ports: - - "6379:6379" + - '6379:6379' redis-cli: image: redis diff --git a/example/otel/example-client.go b/example/otel/example-client.go deleted file mode 100644 index 2eb8d50b..00000000 --- a/example/otel/example-client.go +++ /dev/null @@ -1,85 +0,0 @@ -package main - -import ( - "context" - "log" - "sync" - "time" - - "github.com/go-redis/redis/v8" - "github.com/go-redis/redis/v8/extra/redisotel" - "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/exporters/stdout" - controller "go.opentelemetry.io/otel/sdk/metric/controller/basic" - processor "go.opentelemetry.io/otel/sdk/metric/processor/basic" - "go.opentelemetry.io/otel/sdk/metric/selector/simple" - sdktrace "go.opentelemetry.io/otel/sdk/trace" -) - -func main() { - ctx := context.Background() - - stop := runExporter(ctx) - defer stop(ctx) - - rdb := redis.NewClient(&redis.Options{ - Addr: ":6379", - }) - - rdb.AddHook(redisotel.TracingHook{}) - - tracer := otel.Tracer("Example tracer") - ctx, span := tracer.Start(ctx, "start-test-span") - - rdb.Set(ctx, "First value", "value_1", 0) - - rdb.Set(ctx, "Second value", "value_2", 0) - - var group sync.WaitGroup - - for i := 0; i < 20; i++ { - group.Add(1) - go func() { - defer group.Done() - val := rdb.Get(ctx, "Second value").Val() - if val != "value_2" { - log.Fatalf("val was not set. expected: %s but got: %s", "value_2", val) - } - }() - } - group.Wait() - - rdb.Del(ctx, "First value") - rdb.Del(ctx, "Second value") - - // Wait some time to allow spans to export - <-time.After(5 * time.Second) - span.End() -} - -func runExporter(ctx context.Context) func(context.Context) error { - exporter, err := stdout.NewExporter(stdout.WithPrettyPrint()) - if err != nil { - log.Fatal(err) - } - - tp := sdktrace.NewTracerProvider( - sdktrace.WithSyncer(exporter), - sdktrace.WithConfig(sdktrace.Config{DefaultSampler: sdktrace.AlwaysSample()}), - ) - otel.SetTracerProvider(tp) - - ctrl := controller.New( - processor.New( - simple.NewWithExactDistribution(), - exporter, - ), - controller.WithPusher(exporter), - controller.WithCollectPeriod(1*time.Second), - ) - if err := ctrl.Start(ctx); err != nil { - log.Fatal(err) - } - - return ctrl.Stop -} diff --git a/example/otel/go.mod b/example/otel/go.mod index a9f26ff8..afe12ab8 100644 --- a/example/otel/go.mod +++ b/example/otel/go.mod @@ -2,14 +2,19 @@ module github.com/go-redis/redis/example/otel go 1.14 +replace github.com/go-redis/redis/v8 => ../.. + +replace github.com/go-redis/redis/extra/redisotel/v8 => ../../extra/redisotel + +replace github.com/go-redis/redis/extra/rediscmd/v8 => ../../extra/rediscmd + require ( - github.com/go-redis/redis/v8 v8.4.4 - github.com/go-redis/redis/v8/extra/redisotel v0.0.0-00010101000000-000000000000 - go.opentelemetry.io/otel v0.16.0 - go.opentelemetry.io/otel/exporters/stdout v0.16.0 - go.opentelemetry.io/otel/sdk v0.16.0 + github.com/go-redis/redis/extra/redisotel/v8 v8.11.5 + github.com/go-redis/redis/v8 v8.11.5 + github.com/uptrace/opentelemetry-go-extra/otelplay v0.1.10 + go.opentelemetry.io/otel v1.5.0 + go.opentelemetry.io/otel/exporters/jaeger v1.5.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.5.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.5.0 // indirect + golang.org/x/sys v0.0.0-20220317061510-51cd9980dadf // indirect ) - -replace github.com/go-redis/redis/v8 => ../../ - -replace github.com/go-redis/redis/v8/extra/redisotel => ../../extra/redisotel diff --git a/example/otel/go.sum b/example/otel/go.sum index 0a833c9f..4c41bc40 100644 --- a/example/otel/go.sum +++ b/example/otel/go.sum @@ -1,106 +1,291 @@ -github.com/benbjohnson/clock v1.0.3 h1:vkLuvpK4fmtSCuo60+yC63p7y0BmQ8gm5ZXGuBCJyXg= -github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= -github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= +github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/cenkalti/backoff/v4 v4.1.2 h1:6Yo7N8UP2K6LWZnW94DLVSSrbobcWdVzAYOisuDPIFo= +github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/go-redis/redis/extra/rediscmd v0.2.0 h1:A3bhCsCKsedClEH9/jYlcKqOuBoeeV+H0yDie5t+a6w= -github.com/go-redis/redis/extra/rediscmd v0.2.0/go.mod h1:Z5bP1EHl9PvWhx/DupfCdZwB0JgOO3aVxWc/PFux+BE= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-logr/logr v1.2.2 h1:ahHml/yUpnlb96Rp8HCvtYVPY8ZYpxq3g7UYchIYwbs= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= -github.com/onsi/ginkgo v1.15.0 h1:1V1NfVQR87RtWAgp1lv9JZJ5Jap+XFGKPi00andXGi4= -github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.0.0 h1:CcuG/HvWNkkaqCUpJifQY8z7qEMBJya6aLPx6ftGyjQ= +github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= -github.com/onsi/gomega v1.10.5 h1:7n6FEkpFmfCoo2t+YYqXH0evK+a9ICQz0xcAy9dYcaQ= -github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= +github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/uptrace/opentelemetry-go-extra/otelplay v0.1.10 h1:STSRpcp18xb6huePD0yj4uiUFd1cOdb2xexhhPNWwCo= +github.com/uptrace/opentelemetry-go-extra/otelplay v0.1.10/go.mod h1:7UDvwITVMKhXZGmxKa/lJujNIOKb5ReIRAVwwrc60bI= +github.com/uptrace/uptrace-go v1.4.0 h1:WPYpiCi84nSAXVdtZQamcmDnTZz9724EONo8v1ud9/0= +github.com/uptrace/uptrace-go v1.4.0/go.mod h1:KAPqyJuSaRA6YOea6UZEuSeXtWap92Qws9rPYNW8GhU= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.opentelemetry.io/otel v0.16.0 h1:uIWEbdeb4vpKPGITLsRVUS44L5oDbDUCZxn8lkxhmgw= -go.opentelemetry.io/otel v0.16.0/go.mod h1:e4GKElweB8W2gWUqbghw0B8t5MCTccc9212eNHnOHwA= -go.opentelemetry.io/otel/exporters/stdout v0.16.0 h1:lQG6ZZYLh3NxnmrHltRmqZolT/jPJ8Qfl74lWT8g69Y= -go.opentelemetry.io/otel/exporters/stdout v0.16.0/go.mod h1:bq7m22M7WIxz30KnxH9lI4RLKPajk0lnLsd5P2MsSv8= -go.opentelemetry.io/otel/sdk v0.16.0 h1:5o+fkNsOfH5Mix1bHUApNBqeDcAYczHDa7Ix+R73K2U= -go.opentelemetry.io/otel/sdk v0.16.0/go.mod h1:Jb0B4wrxerxtBeapvstmAZvJGQmvah4dHgKSngDpiCo= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.opentelemetry.io/contrib/instrumentation/runtime v0.27.0/go.mod h1:6ZYYPfSKyhnkpw3xOssb1PMclnNs8dnSzO5POa+02ys= +go.opentelemetry.io/contrib/instrumentation/runtime v0.29.0 h1:ZnLxB76QjHDLnrUWLYgcCc8lcdRGoraTLvJEWmwwC80= +go.opentelemetry.io/contrib/instrumentation/runtime v0.29.0/go.mod h1:qGVpMUNpULYYXRYCQB3DNUe+of22Y+M28i1Oke8SuKQ= +go.opentelemetry.io/otel v1.2.0/go.mod h1:aT17Fk0Z1Nor9e0uisf98LrntPGMnk4frBO9+dkf69I= +go.opentelemetry.io/otel v1.4.0/go.mod h1:jeAqMFKy2uLIxCtKxoFj0FAL5zAPKQagc3+GtBWakzk= +go.opentelemetry.io/otel v1.4.1/go.mod h1:StM6F/0fSwpd8dKWDCdRr7uRvEPYdW0hBSlbdTiUde4= +go.opentelemetry.io/otel v1.5.0 h1:DhCU8oR2sJH9rfnwPdoV/+BJ7UIN5kXHL8DuSGrPU8E= +go.opentelemetry.io/otel v1.5.0/go.mod h1:Jm/m+rNp/z0eqJc74H7LPwQ3G87qkU/AnnAydAjSAHk= +go.opentelemetry.io/otel/exporters/jaeger v1.4.1/go.mod h1:ZW7vkOu9nC1CxsD8bHNHCia5JUbwP39vxgd1q4Z5rCI= +go.opentelemetry.io/otel/exporters/jaeger v1.5.0 h1:ZR7nhLSfLufS5AHk/iN11Q+W9XYwsJrVZ1Frb833d+Y= +go.opentelemetry.io/otel/exporters/jaeger v1.5.0/go.mod h1:rSeUArMBRe1eQLo1T0WxOazohN1M2mYThWJQmn1BjRQ= +go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.4.0/go.mod h1:VpP4/RMn8bv8gNo9uK7/IMY4mtWLELsS+JIP0inH0h4= +go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.4.1/go.mod h1:VpP4/RMn8bv8gNo9uK7/IMY4mtWLELsS+JIP0inH0h4= +go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.5.0 h1:lC0ldaVQwBpO1G5IaOYRbBCa67h6ioGkK6qYkqZbYOI= +go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.5.0/go.mod h1:VpP4/RMn8bv8gNo9uK7/IMY4mtWLELsS+JIP0inH0h4= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.27.0 h1:t1aPfMj5oZzv2EaRmdC2QPQg1a7MaBjraOh4Hjwuia8= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.27.0/go.mod h1:aZnoYVx7GIuMROciGC3cjZhYxMD/lKroRJUnFY0afu0= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.27.0 h1:RJURCSrqUjJiCY3GuFCVP2EPKOQLwNXQ4FI3aH2KoHg= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.27.0/go.mod h1:LIc1eCpkU94tPnXxH40ya41Oyxm7sL+oDvxCYPFpnV8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.4.0/go.mod h1:3oS+j2WUoJVyj6/BzQN/52G17lNJDulngsOxDm1w2PY= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.4.1/go.mod h1:o5RW5o2pKpJLD5dNTCmjF1DorYwMeFJmb/rKr5sLaa8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.5.0 h1:Arn+HOtC6neocvr6J4ykfILvtiSwoDkkLFMaVLFKBnY= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.5.0/go.mod h1:VoN81wyy6jVVCzHImh8S+IYhw+oAUj6XgEsTkP8DyrQ= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.4.0/go.mod h1:ew1NcwkHo0QFT3uTm3m2IVZMkZdVIpbOYNPasgWwpdk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.4.1/go.mod h1:c6E4V3/U+miqjs/8l950wggHGL1qzlp0Ypj9xoGrPqo= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.5.0 h1:dOXExSS490NJaVZD496oIK2Z22S1JQnOsrrMh/p/mLU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.5.0/go.mod h1:Es/Ag4ORtjwWCRjS0aEXgmxB5VqKQlnp481/P5aZyPQ= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.4.0/go.mod h1:TqC+Li5O2V5hRWq4TkIx0oHFu/McCi/KAVGBaKkhU5Q= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.4.1/go.mod h1:BFiGsTMZdqtxufux8ANXuMeRz9dMPVFdJZadUWDFD7o= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.5.0 h1:/Lu2JuL9Mb+B+kSv/RsDMgA/5FaBaxfyfMnICFepiBs= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.5.0/go.mod h1:5gUXICq93HyDh8Rij7p8ilJEC1Sqk0u3lSGs62i8hJQ= +go.opentelemetry.io/otel/internal/metric v0.25.0/go.mod h1:Nhuw26QSX7d6n4duoqAFi5KOQR4AuzyMcl5eXOgwxtc= +go.opentelemetry.io/otel/internal/metric v0.27.0 h1:9dAVGAfFiiEq5NVB9FUJ5et+btbDQAUIJehJ+ikyryk= +go.opentelemetry.io/otel/internal/metric v0.27.0/go.mod h1:n1CVxRqKqYZtqyTh9U/onvKapPGv7y/rpyOTI+LFNzw= +go.opentelemetry.io/otel/metric v0.25.0/go.mod h1:E884FSpQfnJOMMUaq+05IWlJ4rjZpk2s/F1Ju+TEEm8= +go.opentelemetry.io/otel/metric v0.27.0 h1:HhJPsGhJoKRSegPQILFbODU56NS/L1UE4fS1sC5kIwQ= +go.opentelemetry.io/otel/metric v0.27.0/go.mod h1:raXDJ7uP2/Jc0nVZWQjJtzoyssOYWu/+pjZqRzfvZ7g= +go.opentelemetry.io/otel/sdk v1.4.0/go.mod h1:71GJPNJh4Qju6zJuYl1CrYtXbrgfau/M9UAggqiy1UE= +go.opentelemetry.io/otel/sdk v1.4.1/go.mod h1:NBwHDgDIBYjwK2WNu1OPgsIc2IJzmBXNnvIJxJc8BpE= +go.opentelemetry.io/otel/sdk v1.5.0 h1:QKhWBbcOC9fDCZKCfPFjWTWpfIlJR+i9xiUDYrLVmZs= +go.opentelemetry.io/otel/sdk v1.5.0/go.mod h1:CU4J1v+7iEljnm1G14QjdFWOXUyYLHVh0Lh+/BTYyFg= +go.opentelemetry.io/otel/sdk/metric v0.27.0 h1:CDEu96Js5IP7f4bJ8eimxF09V5hKYmE7CeyKSjmAL1s= +go.opentelemetry.io/otel/sdk/metric v0.27.0/go.mod h1:lOgrT5C3ORdbqp2LsDrx+pBj6gbZtQ5Omk27vH3EaW0= +go.opentelemetry.io/otel/trace v1.2.0/go.mod h1:N5FLswTubnxKxOJHM7XZC074qpeEdLy3CgAVsdMucK0= +go.opentelemetry.io/otel/trace v1.4.0/go.mod h1:uc3eRsqDfWs9R7b92xbQbU42/eTNz4N+gLP8qJCi4aE= +go.opentelemetry.io/otel/trace v1.4.1/go.mod h1:iYEVbroFCNut9QkwEczV9vMRPHNKSSwYZjulEtsmhFc= +go.opentelemetry.io/otel/trace v1.5.0 h1:AKQZ9zJsBRFAp7zLdyGNkqG2rToCDIt3i5tcLzQlbmU= +go.opentelemetry.io/otel/trace v1.5.0/go.mod h1:sq55kfhjXYr1zVSyexg0w1mpa03AYXR5eyTkB9NPPdE= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v0.12.0 h1:CMJ/3Wp7iOWES+CYLfnBv+DVmPbB+kmy9PJ92XvlR6c= +go.opentelemetry.io/proto/otlp v0.12.0/go.mod h1:TsIjwGWIx5VFYv9KGVlOpxoBl5Dy+63SUguV7GGvlSQ= +go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= +go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb h1:eBmm0M9fYhWpKZLjQUUKka/LtIxf46G4fxeEz5KJr9U= -golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210112080510-489259a85091 h1:DMyOG0U+gKfu8JZzg2UQe9MeaC1X+xQWlAKcRnjxjCw= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220317061510-51cd9980dadf h1:Fm4IcnUL803i92qDlmB0obyHmosDrxZWxJL3gIeNqOw= +golang.org/x/sys v0.0.0-20220317061510-51cd9980dadf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20220211171837-173942840c17/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220314164441-57ef72a4c106 h1:ErU+UA6wxadoU8nWrsy5MZUVBs75K17zUCsUCIfrXCE= +google.golang.org/genproto v0.0.0-20220314164441-57ef72a4c106/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.45.0 h1:NEpgUqV3Z+ZjkqMsxMg11IaDrXY4RY6CQukSGK0uI1M= +google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/example/otel/main.go b/example/otel/main.go new file mode 100644 index 00000000..952b24b7 --- /dev/null +++ b/example/otel/main.go @@ -0,0 +1,72 @@ +package main + +import ( + "context" + "log" + "sync" + + "github.com/uptrace/opentelemetry-go-extra/otelplay" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/codes" + semconv "go.opentelemetry.io/otel/semconv/v1.7.0" + + "github.com/go-redis/redis/extra/redisotel/v8" + "github.com/go-redis/redis/v8" +) + +var tracer = otel.Tracer("redisexample") + +func main() { + ctx := context.Background() + + shutdown := otelplay.ConfigureOpentelemetry(ctx) + defer shutdown() + + rdb := redis.NewClient(&redis.Options{ + Addr: ":6379", + }) + rdb.AddHook(redisotel.NewTracingHook(redisotel.WithAttributes(semconv.NetPeerNameKey.String("localhost"), semconv.NetPeerPortKey.String("6379")))) + + ctx, span := tracer.Start(ctx, "handleRequest") + defer span.End() + + if err := handleRequest(ctx, rdb); err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + } + + otelplay.PrintTraceID(ctx) +} + +func handleRequest(ctx context.Context, rdb *redis.Client) error { + if err := rdb.Set(ctx, "First value", "value_1", 0).Err(); err != nil { + return err + } + if err := rdb.Set(ctx, "Second value", "value_2", 0).Err(); err != nil { + return err + } + + var group sync.WaitGroup + + for i := 0; i < 20; i++ { + group.Add(1) + go func() { + defer group.Done() + val := rdb.Get(ctx, "Second value").Val() + if val != "value_2" { + log.Printf("%q != %q", val, "value_2") + } + }() + } + + group.Wait() + + if err := rdb.Del(ctx, "First value").Err(); err != nil { + return err + } + if err := rdb.Del(ctx, "Second value").Err(); err != nil { + return err + } + + return nil +} diff --git a/example/redis-bloom/README.md b/example/redis-bloom/README.md new file mode 100644 index 00000000..e2a7542a --- /dev/null +++ b/example/redis-bloom/README.md @@ -0,0 +1,12 @@ +# RedisBloom example for go-redis + +This is an example for +[Bloom, Cuckoo, Count-Min, Top-K](https://redis.uptrace.dev/guide/bloom-cuckoo-count-min-top-k.html) +article. + +To run it, you need to compile and install +[RedisBloom](https://oss.redis.com/redisbloom/Quick_Start/#building) module: + +```shell +go run . +``` diff --git a/example/redis-bloom/go.mod b/example/redis-bloom/go.mod new file mode 100644 index 00000000..be4ecab2 --- /dev/null +++ b/example/redis-bloom/go.mod @@ -0,0 +1,7 @@ +module github.com/go-redis/redis/example/redis-bloom + +go 1.14 + +replace github.com/go-redis/redis/v8 => ../.. + +require github.com/go-redis/redis/v8 v8.11.5 diff --git a/example/redis-bloom/go.sum b/example/redis-bloom/go.sum new file mode 100644 index 00000000..142b2afe --- /dev/null +++ b/example/redis-bloom/go.sum @@ -0,0 +1,110 @@ +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.0.0 h1:CcuG/HvWNkkaqCUpJifQY8z7qEMBJya6aLPx6ftGyjQ= +github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= +github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/example/redis-bloom/main.go b/example/redis-bloom/main.go new file mode 100644 index 00000000..c2ae7c08 --- /dev/null +++ b/example/redis-bloom/main.go @@ -0,0 +1,155 @@ +package main + +import ( + "context" + "fmt" + "math/rand" + + "github.com/go-redis/redis/v8" +) + +func main() { + ctx := context.Background() + + rdb := redis.NewClient(&redis.Options{ + Addr: ":6379", + }) + _ = rdb.FlushDB(ctx).Err() + + fmt.Printf("# BLOOM\n") + bloomFilter(ctx, rdb) + + fmt.Printf("\n# CUCKOO\n") + cuckooFilter(ctx, rdb) + + fmt.Printf("\n# COUNT-MIN\n") + countMinSketch(ctx, rdb) + + fmt.Printf("\n# TOP-K\n") + topK(ctx, rdb) +} + +func bloomFilter(ctx context.Context, rdb *redis.Client) { + inserted, err := rdb.Do(ctx, "BF.ADD", "bf_key", "item0").Bool() + if err != nil { + panic(err) + } + if inserted { + fmt.Println("item0 was inserted") + } else { + fmt.Println("item0 already exists") + } + + for _, item := range []string{"item0", "item1"} { + exists, err := rdb.Do(ctx, "BF.EXISTS", "bf_key", item).Bool() + if err != nil { + panic(err) + } + if exists { + fmt.Printf("%s does exist\n", item) + } else { + fmt.Printf("%s does not exist\n", item) + } + } + + bools, err := rdb.Do(ctx, "BF.MADD", "bf_key", "item1", "item2", "item3").BoolSlice() + if err != nil { + panic(err) + } + fmt.Println("adding multiple items:", bools) +} + +func cuckooFilter(ctx context.Context, rdb *redis.Client) { + inserted, err := rdb.Do(ctx, "CF.ADDNX", "cf_key", "item0").Bool() + if err != nil { + panic(err) + } + if inserted { + fmt.Println("item0 was inserted") + } else { + fmt.Println("item0 already exists") + } + + for _, item := range []string{"item0", "item1"} { + exists, err := rdb.Do(ctx, "CF.EXISTS", "cf_key", item).Bool() + if err != nil { + panic(err) + } + if exists { + fmt.Printf("%s does exist\n", item) + } else { + fmt.Printf("%s does not exist\n", item) + } + } + + deleted, err := rdb.Do(ctx, "CF.DEL", "cf_key", "item0").Bool() + if err != nil { + panic(err) + } + if deleted { + fmt.Println("item0 was deleted") + } +} + +func countMinSketch(ctx context.Context, rdb *redis.Client) { + if err := rdb.Do(ctx, "CMS.INITBYPROB", "count_min", 0.001, 0.01).Err(); err != nil { + panic(err) + } + + items := []string{"item1", "item2", "item3", "item4", "item5"} + counts := make(map[string]int, len(items)) + + for i := 0; i < 10000; i++ { + n := rand.Intn(len(items)) + item := items[n] + + if err := rdb.Do(ctx, "CMS.INCRBY", "count_min", item, 1).Err(); err != nil { + panic(err) + } + counts[item]++ + } + + for item, count := range counts { + ns, err := rdb.Do(ctx, "CMS.QUERY", "count_min", item).Int64Slice() + if err != nil { + panic(err) + } + fmt.Printf("%s: count-min=%d actual=%d\n", item, ns[0], count) + } +} + +func topK(ctx context.Context, rdb *redis.Client) { + if err := rdb.Do(ctx, "TOPK.RESERVE", "top_items", 3).Err(); err != nil { + panic(err) + } + + counts := map[string]int{ + "item1": 1000, + "item2": 2000, + "item3": 3000, + "item4": 4000, + "item5": 5000, + "item6": 6000, + } + + for item, count := range counts { + for i := 0; i < count; i++ { + if err := rdb.Do(ctx, "TOPK.INCRBY", "top_items", item, 1).Err(); err != nil { + panic(err) + } + } + } + + items, err := rdb.Do(ctx, "TOPK.LIST", "top_items").StringSlice() + if err != nil { + panic(err) + } + + for _, item := range items { + ns, err := rdb.Do(ctx, "TOPK.COUNT", "top_items", item).Int64Slice() + if err != nil { + panic(err) + } + fmt.Printf("%s: top-k=%d actual=%d\n", item, ns[0], counts[item]) + } +} diff --git a/example/scan-struct/README.md b/example/scan-struct/README.md new file mode 100644 index 00000000..676f8e20 --- /dev/null +++ b/example/scan-struct/README.md @@ -0,0 +1,11 @@ +# Example for scanning hash fields into a struct + +To run this example: + +```shell +go run . +``` + +See +[Redis: Scanning hash fields into a struct](https://redis.uptrace.dev/guide/scanning-hash-fields.html) +for details. diff --git a/example/scan-struct/go.mod b/example/scan-struct/go.mod new file mode 100644 index 00000000..8c42bef3 --- /dev/null +++ b/example/scan-struct/go.mod @@ -0,0 +1,10 @@ +module github.com/go-redis/redis/example/scan-struct + +go 1.14 + +replace github.com/go-redis/redis/v8 => ../.. + +require ( + github.com/davecgh/go-spew v1.1.1 + github.com/go-redis/redis/v8 v8.11.5 +) diff --git a/example/scan-struct/go.sum b/example/scan-struct/go.sum new file mode 100644 index 00000000..f3be7f19 --- /dev/null +++ b/example/scan-struct/go.sum @@ -0,0 +1,111 @@ +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.0.0 h1:CcuG/HvWNkkaqCUpJifQY8z7qEMBJya6aLPx6ftGyjQ= +github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= +github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/example/scan-struct/main.go b/example/scan-struct/main.go new file mode 100644 index 00000000..aee7ef71 --- /dev/null +++ b/example/scan-struct/main.go @@ -0,0 +1,51 @@ +package main + +import ( + "context" + + "github.com/davecgh/go-spew/spew" + + "github.com/go-redis/redis/v8" +) + +type Model struct { + Str1 string `redis:"str1"` + Str2 string `redis:"str2"` + Int int `redis:"int"` + Bool bool `redis:"bool"` + Ignored struct{} `redis:"-"` +} + +func main() { + ctx := context.Background() + + rdb := redis.NewClient(&redis.Options{ + Addr: ":6379", + }) + + // Set some fields. + if _, err := rdb.Pipelined(ctx, func(rdb redis.Pipeliner) error { + rdb.HSet(ctx, "key", "str1", "hello") + rdb.HSet(ctx, "key", "str2", "world") + rdb.HSet(ctx, "key", "int", 123) + rdb.HSet(ctx, "key", "bool", 1) + return nil + }); err != nil { + panic(err) + } + + var model1, model2 Model + + // Scan all fields into the model. + if err := rdb.HGetAll(ctx, "key").Scan(&model1); err != nil { + panic(err) + } + + // Or scan a subset of the fields. + if err := rdb.HMGet(ctx, "key", "str1", "int").Scan(&model2); err != nil { + panic(err) + } + + spew.Dump(model1) + spew.Dump(model2) +} diff --git a/example_test.go b/example_test.go index 53bd90fe..75b5cbbb 100644 --- a/example_test.go +++ b/example_test.go @@ -39,13 +39,14 @@ func ExampleNewClient() { } func ExampleParseURL() { - opt, err := redis.ParseURL("redis://:qwerty@localhost:6379/1") + opt, err := redis.ParseURL("redis://:qwerty@localhost:6379/1?dial_timeout=5s") if err != nil { panic(err) } fmt.Println("addr is", opt.Addr) fmt.Println("db is", opt.DB) fmt.Println("password is", opt.Password) + fmt.Println("dial timeout is", opt.DialTimeout) // Create client as usually. _ = redis.NewClient(opt) @@ -53,6 +54,7 @@ func ExampleParseURL() { // Output: addr is localhost:6379 // db is 1 // password is qwerty + // dial timeout is 5s } func ExampleNewFailoverClient() { diff --git a/extra/rediscensus/go.mod b/extra/rediscensus/go.mod index a926b4ea..8f82a0c8 100644 --- a/extra/rediscensus/go.mod +++ b/extra/rediscensus/go.mod @@ -2,10 +2,13 @@ module github.com/go-redis/redis/extra/rediscensus/v8 go 1.15 +replace github.com/go-redis/redis/v8 => ../.. + replace github.com/go-redis/redis/extra/rediscmd/v8 => ../rediscmd require ( - github.com/go-redis/redis/extra/rediscmd/v8 v8.8.2 - github.com/go-redis/redis/v8 v8.8.2 - go.opencensus.io v0.22.6 + github.com/go-redis/redis/extra/rediscmd/v8 v8.11.5 + github.com/go-redis/redis/v8 v8.11.5 + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + go.opencensus.io v0.23.0 ) diff --git a/extra/rediscensus/go.sum b/extra/rediscensus/go.sum index aaa94f45..5bfa139c 100644 --- a/extra/rediscensus/go.sum +++ b/extra/rediscensus/go.sum @@ -1,12 +1,15 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -16,11 +19,11 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/go-redis/redis/v8 v8.4.4 h1:fGqgxCTR1sydaKI00oQf3OmkU/DIe/I/fYXvGklCIuc= -github.com/go-redis/redis/v8 v8.4.4/go.mod h1:nA0bQuF0i5JFx4Ta9RZxGKXFrQ8cRWntra97f0196iY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -31,41 +34,45 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= -github.com/onsi/ginkgo v1.15.0 h1:1V1NfVQR87RtWAgp1lv9JZJ5Jap+XFGKPi00andXGi4= -github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.0.0 h1:CcuG/HvWNkkaqCUpJifQY8z7qEMBJya6aLPx6ftGyjQ= +github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.10.4/go.mod h1:g/HbgYopi++010VEqkFgJHKC09uJiW9UkXvMUuKHUCQ= -github.com/onsi/gomega v1.10.5 h1:7n6FEkpFmfCoo2t+YYqXH0evK+a9ICQz0xcAy9dYcaQ= -github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= +github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.opencensus.io v0.22.6 h1:BdkrbWrzDlV9dnbzoP7sfN+dHheJ4J9JOaYxcUDL+ok= -go.opencensus.io v0.22.6/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.opentelemetry.io/otel v0.15.0 h1:CZFy2lPhxd4HlhZnYK8gRyDotksO3Ip9rBweY1vVYJw= -go.opentelemetry.io/otel v0.15.0/go.mod h1:e4GKElweB8W2gWUqbghw0B8t5MCTccc9212eNHnOHwA= +go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -84,8 +91,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb h1:eBmm0M9fYhWpKZLjQUUKka/LtIxf46G4fxeEz5KJr9U= -golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -98,15 +105,19 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210112080510-489259a85091 h1:DMyOG0U+gKfu8JZzg2UQe9MeaC1X+xQWlAKcRnjxjCw= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -137,17 +148,20 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/extra/rediscensus/rediscensus.go b/extra/rediscensus/rediscensus.go index 1ea6a57c..9af094e9 100644 --- a/extra/rediscensus/rediscensus.go +++ b/extra/rediscensus/rediscensus.go @@ -3,9 +3,10 @@ package rediscensus import ( "context" + "go.opencensus.io/trace" + "github.com/go-redis/redis/extra/rediscmd/v8" "github.com/go-redis/redis/v8" - "go.opencensus.io/trace" ) type TracingHook struct{} diff --git a/extra/rediscmd/go.mod b/extra/rediscmd/go.mod index 8cc00e7c..1bc5bef4 100644 --- a/extra/rediscmd/go.mod +++ b/extra/rediscmd/go.mod @@ -2,8 +2,10 @@ module github.com/go-redis/redis/extra/rediscmd/v8 go 1.15 +replace github.com/go-redis/redis/v8 => ../.. + require ( - github.com/go-redis/redis/v8 v8.4.4 - github.com/onsi/ginkgo v1.15.0 - github.com/onsi/gomega v1.10.5 + github.com/go-redis/redis/v8 v8.11.5 + github.com/onsi/ginkgo v1.16.5 + github.com/onsi/gomega v1.18.1 ) diff --git a/extra/rediscmd/go.sum b/extra/rediscmd/go.sum index 964ee70e..142b2afe 100644 --- a/extra/rediscmd/go.sum +++ b/extra/rediscmd/go.sum @@ -1,48 +1,53 @@ -github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/go-redis/redis/v8 v8.4.4 h1:fGqgxCTR1sydaKI00oQf3OmkU/DIe/I/fYXvGklCIuc= -github.com/go-redis/redis/v8 v8.4.4/go.mod h1:nA0bQuF0i5JFx4Ta9RZxGKXFrQ8cRWntra97f0196iY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= -github.com/onsi/ginkgo v1.15.0 h1:1V1NfVQR87RtWAgp1lv9JZJ5Jap+XFGKPi00andXGi4= -github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.0.0 h1:CcuG/HvWNkkaqCUpJifQY8z7qEMBJya6aLPx6ftGyjQ= +github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.10.4/go.mod h1:g/HbgYopi++010VEqkFgJHKC09uJiW9UkXvMUuKHUCQ= -github.com/onsi/gomega v1.10.5 h1:7n6FEkpFmfCoo2t+YYqXH0evK+a9ICQz0xcAy9dYcaQ= -github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= +github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.opentelemetry.io/otel v0.15.0 h1:CZFy2lPhxd4HlhZnYK8gRyDotksO3Ip9rBweY1vVYJw= -go.opentelemetry.io/otel v0.15.0/go.mod h1:e4GKElweB8W2gWUqbghw0B8t5MCTccc9212eNHnOHwA= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -52,8 +57,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb h1:eBmm0M9fYhWpKZLjQUUKka/LtIxf46G4fxeEz5KJr9U= -golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -63,15 +68,19 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210112080510-489259a85091 h1:DMyOG0U+gKfu8JZzg2UQe9MeaC1X+xQWlAKcRnjxjCw= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= @@ -85,15 +94,17 @@ google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/extra/rediscmd/safe.go b/extra/rediscmd/safe.go index efe92f6e..6d3a8b7a 100644 --- a/extra/rediscmd/safe.go +++ b/extra/rediscmd/safe.go @@ -1,3 +1,4 @@ +//go:build appengine // +build appengine package rediscmd diff --git a/extra/rediscmd/unsafe.go b/extra/rediscmd/unsafe.go index a90a48b7..7ccdf2fe 100644 --- a/extra/rediscmd/unsafe.go +++ b/extra/rediscmd/unsafe.go @@ -1,3 +1,4 @@ +//go:build !appengine // +build !appengine package rediscmd diff --git a/extra/redisotel/example/Makefile b/extra/redisotel/example/Makefile deleted file mode 100644 index b986e357..00000000 --- a/extra/redisotel/example/Makefile +++ /dev/null @@ -1,8 +0,0 @@ -build: - docker-compose build -up: build - docker-compose up --detach redis-server -down: - docker-compose down -logs: - docker-compose logs redis-server diff --git a/extra/redisotel/example/README.md b/extra/redisotel/example/README.md deleted file mode 100644 index 59abcc35..00000000 --- a/extra/redisotel/example/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# OpenTelemetry instrumentation for go-redis - -To run this example you need a Redis server. You can start one with Docker: - -```shell -make up -``` - -Then run the example: - -```shell -go run main.go -``` diff --git a/extra/redisotel/example/docker-compose.yml b/extra/redisotel/example/docker-compose.yml deleted file mode 100644 index acbd50d5..00000000 --- a/extra/redisotel/example/docker-compose.yml +++ /dev/null @@ -1,8 +0,0 @@ -version: '3.7' -services: - redis-server: - image: 'bitnami/redis:latest' - environment: - - ALLOW_EMPTY_PASSWORD=yes - ports: - - 6379:6379 diff --git a/extra/redisotel/example/go.mod b/extra/redisotel/example/go.mod deleted file mode 100644 index 1cbaf7fe..00000000 --- a/extra/redisotel/example/go.mod +++ /dev/null @@ -1,12 +0,0 @@ -module example - -go 1.16 - -require ( - github.com/go-redis/redis/extra/redisotel/v8 v8.8.2 - github.com/go-redis/redis/v8 v8.8.2 - go.opentelemetry.io/otel v1.0.0-RC1 - go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.0.0-RC1 - go.opentelemetry.io/otel/sdk v1.0.0-RC1 - go.opentelemetry.io/otel/sdk/metric v0.21.0 -) diff --git a/extra/redisotel/example/main.go b/extra/redisotel/example/main.go deleted file mode 100644 index 335eb8f2..00000000 --- a/extra/redisotel/example/main.go +++ /dev/null @@ -1,39 +0,0 @@ -package main - -import ( - "context" - - "github.com/go-redis/redis/extra/redisotel/v8" - "github.com/go-redis/redis/v8" - "go.opentelemetry.io/otel" - stdoutexporter "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" - sdktrace "go.opentelemetry.io/otel/sdk/trace" -) - -var tracer = otel.Tracer("app_or_package_name") - -func main() { - exporter, err := stdoutexporter.New(stdoutexporter.WithPrettyPrint()) - if err != nil { - panic(err) - } - - provider := sdktrace.NewTracerProvider() - provider.RegisterSpanProcessor(sdktrace.NewSimpleSpanProcessor(exporter)) - - otel.SetTracerProvider(provider) - - rdb := redis.NewClient(&redis.Options{ - Addr: ":6379", - }) - defer rdb.Close() - - rdb.AddHook(redisotel.NewTracingHook()) - - ctx, span := tracer.Start(context.TODO(), "main") - defer span.End() - - if err := rdb.Ping(ctx).Err(); err != nil { - panic(err) - } -} diff --git a/extra/redisotel/go.mod b/extra/redisotel/go.mod index 28ba2d98..144cf9c5 100644 --- a/extra/redisotel/go.mod +++ b/extra/redisotel/go.mod @@ -7,8 +7,9 @@ replace github.com/go-redis/redis/v8 => ../.. replace github.com/go-redis/redis/extra/rediscmd/v8 => ../rediscmd require ( - github.com/go-redis/redis/extra/rediscmd/v8 v8.8.2 - github.com/go-redis/redis/v8 v8.8.2 - go.opentelemetry.io/otel v1.0.0-RC1 - go.opentelemetry.io/otel/trace v1.0.0-RC1 + github.com/go-redis/redis/extra/rediscmd/v8 v8.11.5 + github.com/go-redis/redis/v8 v8.11.5 + go.opentelemetry.io/otel v1.5.0 + go.opentelemetry.io/otel/sdk v1.4.1 + go.opentelemetry.io/otel/trace v1.5.0 ) diff --git a/extra/redisotel/go.sum b/extra/redisotel/go.sum index 1aa1088b..1bcf1e7f 100644 --- a/extra/redisotel/go.sum +++ b/extra/redisotel/go.sum @@ -1,57 +1,70 @@ -github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/go-logr/logr v1.2.2 h1:ahHml/yUpnlb96Rp8HCvtYVPY8ZYpxq3g7UYchIYwbs= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.15.0 h1:1V1NfVQR87RtWAgp1lv9JZJ5Jap+XFGKPi00andXGi4= -github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.0.0 h1:CcuG/HvWNkkaqCUpJifQY8z7qEMBJya6aLPx6ftGyjQ= +github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.10.5 h1:7n6FEkpFmfCoo2t+YYqXH0evK+a9ICQz0xcAy9dYcaQ= -github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= +github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.opentelemetry.io/otel v0.19.0 h1:Lenfy7QHRXPZVsw/12CWpxX6d/JkrX8wrx2vO8G80Ng= -go.opentelemetry.io/otel v0.19.0/go.mod h1:j9bF567N9EfomkSidSfmMwIwIBuP37AMAIzVW85OxSg= -go.opentelemetry.io/otel v0.20.0 h1:eaP0Fqu7SXHwvjiqDq83zImeehOHX8doTvU9AwXON8g= -go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= -go.opentelemetry.io/otel/metric v0.19.0 h1:dtZ1Ju44gkJkYvo+3qGqVXmf88tc+a42edOywypengg= -go.opentelemetry.io/otel/metric v0.19.0/go.mod h1:8f9fglJPRnXuskQmKpnad31lcLJ2VmNNqIsx/uIwBSc= -go.opentelemetry.io/otel/metric v0.20.0 h1:4kzhXFP+btKm4jwxpjIqjs41A7MakRFUS86bqLHTIw8= -go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= -go.opentelemetry.io/otel/oteltest v0.19.0 h1:YVfA0ByROYqTwOxqHVZYZExzEpfZor+MU1rU+ip2v9Q= -go.opentelemetry.io/otel/oteltest v0.19.0/go.mod h1:tI4yxwh8U21v7JD6R3BcA/2+RBoTKFexE/PJ/nSO7IA= -go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= -go.opentelemetry.io/otel/trace v0.19.0 h1:1ucYlenXIDA1OlHVLDZKX0ObXV5RLaq06DtUKz5e5zc= -go.opentelemetry.io/otel/trace v0.19.0/go.mod h1:4IXiNextNOpPnRlI4ryK69mn5iC84bjBWZQA5DXz/qg= -go.opentelemetry.io/otel/trace v0.20.0 h1:1DL6EXUdcg95gukhuRRvLDO/4X5THh/5dIV52lqtnbw= -go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= +go.opentelemetry.io/otel v1.4.1/go.mod h1:StM6F/0fSwpd8dKWDCdRr7uRvEPYdW0hBSlbdTiUde4= +go.opentelemetry.io/otel v1.5.0 h1:DhCU8oR2sJH9rfnwPdoV/+BJ7UIN5kXHL8DuSGrPU8E= +go.opentelemetry.io/otel v1.5.0/go.mod h1:Jm/m+rNp/z0eqJc74H7LPwQ3G87qkU/AnnAydAjSAHk= +go.opentelemetry.io/otel/sdk v1.4.1 h1:J7EaW71E0v87qflB4cDolaqq3AcujGrtyIPGQoZOB0Y= +go.opentelemetry.io/otel/sdk v1.4.1/go.mod h1:NBwHDgDIBYjwK2WNu1OPgsIc2IJzmBXNnvIJxJc8BpE= +go.opentelemetry.io/otel/trace v1.4.1/go.mod h1:iYEVbroFCNut9QkwEczV9vMRPHNKSSwYZjulEtsmhFc= +go.opentelemetry.io/otel/trace v1.5.0 h1:AKQZ9zJsBRFAp7zLdyGNkqG2rToCDIt3i5tcLzQlbmU= +go.opentelemetry.io/otel/trace v1.5.0/go.mod h1:sq55kfhjXYr1zVSyexg0w1mpa03AYXR5eyTkB9NPPdE= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -61,8 +74,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb h1:eBmm0M9fYhWpKZLjQUUKka/LtIxf46G4fxeEz5KJr9U= -golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -72,13 +85,20 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210112080510-489259a85091 h1:DMyOG0U+gKfu8JZzg2UQe9MeaC1X+xQWlAKcRnjxjCw= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= @@ -92,15 +112,19 @@ google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/extra/redisotel/redisotel.go b/extra/redisotel/redisotel.go index 3da61130..70157806 100644 --- a/extra/redisotel/redisotel.go +++ b/extra/redisotel/redisotel.go @@ -3,39 +3,62 @@ package redisotel import ( "context" - "github.com/go-redis/redis/extra/rediscmd/v8" - "github.com/go-redis/redis/v8" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" + semconv "go.opentelemetry.io/otel/semconv/v1.7.0" "go.opentelemetry.io/otel/trace" + + "github.com/go-redis/redis/extra/rediscmd/v8" + "github.com/go-redis/redis/v8" ) -var tracer = otel.Tracer("github.com/go-redis/redis") +const ( + defaultTracerName = "github.com/go-redis/redis/extra/redisotel" +) -type TracingHook struct{} - -var _ redis.Hook = (*TracingHook)(nil) - -func NewTracingHook() *TracingHook { - return new(TracingHook) +type TracingHook struct { + tracer trace.Tracer + attrs []attribute.KeyValue } -func (TracingHook) BeforeProcess(ctx context.Context, cmd redis.Cmder) (context.Context, error) { +func NewTracingHook(opts ...Option) *TracingHook { + cfg := &config{ + tp: otel.GetTracerProvider(), + attrs: []attribute.KeyValue{ + semconv.DBSystemRedis, + }, + } + for _, opt := range opts { + opt.apply(cfg) + } + + tracer := cfg.tp.Tracer( + defaultTracerName, + trace.WithInstrumentationVersion("semver:"+redis.Version()), + ) + return &TracingHook{tracer: tracer, attrs: cfg.attrs} +} + +func (th *TracingHook) BeforeProcess(ctx context.Context, cmd redis.Cmder) (context.Context, error) { if !trace.SpanFromContext(ctx).IsRecording() { return ctx, nil } - ctx, span := tracer.Start(ctx, cmd.FullName()) - span.SetAttributes( - attribute.String("db.system", "redis"), - attribute.String("db.statement", rediscmd.CmdString(cmd)), - ) + opts := []trace.SpanStartOption{ + trace.WithSpanKind(trace.SpanKindClient), + trace.WithAttributes(th.attrs...), + trace.WithAttributes( + semconv.DBStatementKey.String(rediscmd.CmdString(cmd)), + ), + } + + ctx, _ = th.tracer.Start(ctx, cmd.FullName(), opts...) return ctx, nil } -func (TracingHook) AfterProcess(ctx context.Context, cmd redis.Cmder) error { +func (th *TracingHook) AfterProcess(ctx context.Context, cmd redis.Cmder) error { span := trace.SpanFromContext(ctx) if err := cmd.Err(); err != nil { recordError(ctx, span, err) @@ -44,24 +67,28 @@ func (TracingHook) AfterProcess(ctx context.Context, cmd redis.Cmder) error { return nil } -func (TracingHook) BeforeProcessPipeline(ctx context.Context, cmds []redis.Cmder) (context.Context, error) { +func (th *TracingHook) BeforeProcessPipeline(ctx context.Context, cmds []redis.Cmder) (context.Context, error) { if !trace.SpanFromContext(ctx).IsRecording() { return ctx, nil } summary, cmdsString := rediscmd.CmdsString(cmds) - ctx, span := tracer.Start(ctx, "pipeline "+summary) - span.SetAttributes( - attribute.String("db.system", "redis"), - attribute.Int("db.redis.num_cmd", len(cmds)), - attribute.String("db.statement", cmdsString), - ) + opts := []trace.SpanStartOption{ + trace.WithSpanKind(trace.SpanKindClient), + trace.WithAttributes(th.attrs...), + trace.WithAttributes( + semconv.DBStatementKey.String(cmdsString), + attribute.Int("db.redis.num_cmd", len(cmds)), + ), + } + + ctx, _ = th.tracer.Start(ctx, "pipeline "+summary, opts...) return ctx, nil } -func (TracingHook) AfterProcessPipeline(ctx context.Context, cmds []redis.Cmder) error { +func (th *TracingHook) AfterProcessPipeline(ctx context.Context, cmds []redis.Cmder) error { span := trace.SpanFromContext(ctx) if err := cmds[0].Err(); err != nil { recordError(ctx, span, err) @@ -76,3 +103,36 @@ func recordError(ctx context.Context, span trace.Span, err error) { span.SetStatus(codes.Error, err.Error()) } } + +type config struct { + tp trace.TracerProvider + attrs []attribute.KeyValue +} + +// Option specifies instrumentation configuration options. +type Option interface { + apply(*config) +} + +type optionFunc func(*config) + +func (o optionFunc) apply(c *config) { + o(c) +} + +// WithTracerProvider specifies a tracer provider to use for creating a tracer. +// If none is specified, the global provider is used. +func WithTracerProvider(provider trace.TracerProvider) Option { + return optionFunc(func(cfg *config) { + if provider != nil { + cfg.tp = provider + } + }) +} + +// WithAttributes specifies additional attributes to be added to the span. +func WithAttributes(attrs ...attribute.KeyValue) Option { + return optionFunc(func(cfg *config) { + cfg.attrs = append(cfg.attrs, attrs...) + }) +} diff --git a/extra/redisotel/redisotel_test.go b/extra/redisotel/redisotel_test.go new file mode 100644 index 00000000..68aacc4a --- /dev/null +++ b/extra/redisotel/redisotel_test.go @@ -0,0 +1,68 @@ +package redisotel_test + +import ( + "context" + semconv "go.opentelemetry.io/otel/semconv/v1.7.0" + "testing" + + "github.com/go-redis/redis/extra/redisotel/v8" + "github.com/go-redis/redis/v8" + "go.opentelemetry.io/otel" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + "go.opentelemetry.io/otel/trace" +) + +func TestNew(t *testing.T) { + // this also functions as a compile-time test that the + // TracingHook conforms to the Hook interface + var _ redis.Hook = redisotel.NewTracingHook() +} + +type providerFunc func(name string, opts ...trace.TracerOption) trace.Tracer + +func (fn providerFunc) Tracer(name string, opts ...trace.TracerOption) trace.Tracer { + return fn(name, opts...) +} + +func TestNewWithTracerProvider(t *testing.T) { + invoked := false + + tp := providerFunc(func(name string, opts ...trace.TracerOption) trace.Tracer { + invoked = true + return otel.GetTracerProvider().Tracer(name, opts...) + }) + + _ = redisotel.NewTracingHook(redisotel.WithTracerProvider(tp)) + + if !invoked { + t.Fatalf("did not call custom TraceProvider") + } +} + +func TestNewWithAttributes(t *testing.T) { + provider := sdktrace.NewTracerProvider() + hook := redisotel.NewTracingHook(redisotel.WithTracerProvider(provider), redisotel.WithAttributes(semconv.NetPeerNameKey.String("localhost"))) + ctx, span := provider.Tracer("redis-test").Start(context.TODO(), "redis-test") + cmd := redis.NewCmd(ctx, "ping") + defer span.End() + + ctx, err := hook.BeforeProcess(ctx, cmd) + if err != nil { + t.Fatal(err) + } + err = hook.AfterProcess(ctx, cmd) + if err != nil { + t.Fatal(err) + } + + attrs := trace.SpanFromContext(ctx).(sdktrace.ReadOnlySpan).Attributes() + if !(attrs[0] == semconv.DBSystemRedis) { + t.Fatalf("expected attrs[0] to be semconv.DBSystemRedis, got: %v", attrs[0]) + } + if !(attrs[1] == semconv.NetPeerNameKey.String("localhost")) { + t.Fatalf("expected attrs[1] to be semconv.NetPeerNameKey.String(\"localhost\"), got: %v", attrs[1]) + } + if !(attrs[2] == semconv.DBStatementKey.String("ping")) { + t.Fatalf("expected attrs[2] to be semconv.DBStatementKey.String(\"ping\"), got: %v", attrs[2]) + } +} diff --git a/fuzz/fuzz.go b/fuzz/fuzz.go index c8497404..3225d245 100644 --- a/fuzz/fuzz.go +++ b/fuzz/fuzz.go @@ -1,11 +1,13 @@ +//go:build gofuzz // +build gofuzz package fuzz import ( "context" - "github.com/go-redis/redis/v8" "time" + + "github.com/go-redis/redis/v8" ) var ( diff --git a/go.mod b/go.mod index c792cdd4..d2610c2a 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,20 @@ module github.com/go-redis/redis/v8 -go 1.14 +go 1.17 require ( - github.com/cespare/xxhash/v2 v2.1.1 + github.com/cespare/xxhash/v2 v2.1.2 github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f - github.com/google/go-cmp v0.5.6 // indirect - github.com/onsi/ginkgo v1.15.0 - github.com/onsi/gomega v1.10.5 + github.com/onsi/ginkgo v1.16.5 + github.com/onsi/gomega v1.18.1 +) + +require ( + github.com/fsnotify/fsnotify v1.4.9 // indirect + github.com/nxadm/tail v1.4.8 // indirect + golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 // indirect + golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect + golang.org/x/text v0.3.6 // indirect + gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index 76abf5b4..e88f31a6 100644 --- a/go.sum +++ b/go.sum @@ -1,34 +1,51 @@ -github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.15.0 h1:1V1NfVQR87RtWAgp1lv9JZJ5Jap+XFGKPi00andXGi4= -github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.0.0 h1:CcuG/HvWNkkaqCUpJifQY8z7qEMBJya6aLPx6ftGyjQ= +github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.10.5 h1:7n6FEkpFmfCoo2t+YYqXH0evK+a9ICQz0xcAy9dYcaQ= -github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= +github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -39,8 +56,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb h1:eBmm0M9fYhWpKZLjQUUKka/LtIxf46G4fxeEz5KJr9U= -golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -50,33 +67,42 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210112080510-489259a85091 h1:DMyOG0U+gKfu8JZzg2UQe9MeaC1X+xQWlAKcRnjxjCw= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/internal/hashtag/hashtag_test.go b/internal/hashtag/hashtag_test.go index 879e7bfe..c0b6396f 100644 --- a/internal/hashtag/hashtag_test.go +++ b/internal/hashtag/hashtag_test.go @@ -3,10 +3,10 @@ package hashtag import ( "testing" - "github.com/go-redis/redis/v8/internal/rand" - . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + + "github.com/go-redis/redis/v8/internal/rand" ) func TestGinkgoSuite(t *testing.T) { diff --git a/internal/hscan/hscan_test.go b/internal/hscan/hscan_test.go index 1f78bc7e..ab4c0e1d 100644 --- a/internal/hscan/hscan_test.go +++ b/internal/hscan/hscan_test.go @@ -69,7 +69,7 @@ var _ = Describe("Scan", func() { Expect(Scan(&d, i{v}, i{vals[k]})).To(HaveOccurred()) } - //success + // success f = func(v uint64) string { return strconv.FormatUint(v, 10) } diff --git a/internal/hscan/structmap.go b/internal/hscan/structmap.go index 1b233258..6839412b 100644 --- a/internal/hscan/structmap.go +++ b/internal/hscan/structmap.go @@ -39,12 +39,12 @@ func (s *structSpec) set(tag string, sf *structField) { } func newStructSpec(t reflect.Type, fieldTag string) *structSpec { + numField := t.NumField() out := &structSpec{ - m: make(map[string]*structField), + m: make(map[string]*structField, numField), } - num := t.NumField() - for i := 0; i < num; i++ { + for i := 0; i < numField; i++ { f := t.Field(i) tag := f.Tag.Get(fieldTag) diff --git a/internal/pool/conncheck.go b/internal/pool/conncheck.go deleted file mode 100644 index 5dd60dfa..00000000 --- a/internal/pool/conncheck.go +++ /dev/null @@ -1,45 +0,0 @@ -// +build linux darwin dragonfly freebsd netbsd openbsd solaris illumos - -package pool - -import ( - "errors" - "io" - "net" - "syscall" -) - -var errUnexpectedRead = errors.New("unexpected read from socket") - -func connCheck(conn net.Conn) error { - 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 -} diff --git a/internal/pool/conncheck_dummy.go b/internal/pool/conncheck_dummy.go deleted file mode 100644 index 1daf9861..00000000 --- a/internal/pool/conncheck_dummy.go +++ /dev/null @@ -1,9 +0,0 @@ -// +build !linux,!darwin,!dragonfly,!freebsd,!netbsd,!openbsd,!solaris,!illumos - -package pool - -import "net" - -func connCheck(conn net.Conn) error { - return nil -} diff --git a/internal/pool/conncheck_test.go b/internal/pool/conncheck_test.go deleted file mode 100644 index 03321746..00000000 --- a/internal/pool/conncheck_test.go +++ /dev/null @@ -1,46 +0,0 @@ -// +build linux darwin dragonfly freebsd netbsd openbsd solaris illumos - -package pool - -import ( - "net" - "net/http/httptest" - "testing" - "time" -) - -func Test_connCheck(t *testing.T) { - // tests with real conns - ts := httptest.NewServer(nil) - defer ts.Close() - - t.Run("good conn", func(t *testing.T) { - conn, err := net.DialTimeout(ts.Listener.Addr().Network(), ts.Listener.Addr().String(), time.Second) - if err != nil { - t.Fatalf(err.Error()) - } - defer conn.Close() - if err = connCheck(conn); err != nil { - t.Fatalf(err.Error()) - } - conn.Close() - - if err = connCheck(conn); err == nil { - t.Fatalf("expect has error") - } - }) - - t.Run("bad conn 2", func(t *testing.T) { - conn, err := net.DialTimeout(ts.Listener.Addr().Network(), ts.Listener.Addr().String(), time.Second) - if err != nil { - t.Fatalf(err.Error()) - } - defer conn.Close() - - ts.Close() - - if err = connCheck(conn); err == nil { - t.Fatalf("expect has err") - } - }) -} diff --git a/internal/pool/export_test.go b/internal/pool/export_test.go index de7a644e..75dd4ad6 100644 --- a/internal/pool/export_test.go +++ b/internal/pool/export_test.go @@ -1,6 +1,8 @@ package pool -import "time" +import ( + "time" +) func (cn *Conn) SetCreatedAt(tm time.Time) { cn.createdAt = tm diff --git a/internal/pool/main_test.go b/internal/pool/main_test.go index c54a38d5..2365dbc6 100644 --- a/internal/pool/main_test.go +++ b/internal/pool/main_test.go @@ -2,12 +2,9 @@ package pool_test import ( "context" - "fmt" "net" "sync" - "syscall" "testing" - "time" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -35,87 +32,5 @@ func perform(n int, cbs ...func(int)) { } func dummyDialer(context.Context) (net.Conn, error) { - // return &net.TCPConn{}, nil - return newDummyConn(), nil -} - -func newDummyConn() net.Conn { - return &dummyConn{ - rawConn: &dummyRawConn{}, - } -} - -var _ net.Conn = (*dummyConn)(nil) -var _ syscall.Conn = (*dummyConn)(nil) - -type dummyConn struct { - rawConn *dummyRawConn -} - -func (d *dummyConn) SyscallConn() (syscall.RawConn, error) { - return d.rawConn, nil -} - -var errDummy = fmt.Errorf("dummyConn err") - -func (d *dummyConn) Read(b []byte) (n int, err error) { - return 0, errDummy -} - -func (d *dummyConn) Write(b []byte) (n int, err error) { - return 0, errDummy -} - -func (d *dummyConn) Close() error { - d.rawConn.Close() - return nil -} - -func (d *dummyConn) LocalAddr() net.Addr { - return &net.TCPAddr{} -} - -func (d *dummyConn) RemoteAddr() net.Addr { - return &net.TCPAddr{} -} - -func (d *dummyConn) SetDeadline(t time.Time) error { - return nil -} - -func (d *dummyConn) SetReadDeadline(t time.Time) error { - return nil -} - -func (d *dummyConn) SetWriteDeadline(t time.Time) error { - return nil -} - -var _ syscall.RawConn = (*dummyRawConn)(nil) - -type dummyRawConn struct { - closed bool - mux sync.Mutex -} - -func (d *dummyRawConn) Control(f func(fd uintptr)) error { - return nil -} - -func (d *dummyRawConn) Read(f func(fd uintptr) (done bool)) error { - d.mux.Lock() - defer d.mux.Unlock() - if d.closed { - return fmt.Errorf("dummyRawConn closed") - } - return nil -} - -func (d *dummyRawConn) Write(f func(fd uintptr) (done bool)) error { - return nil -} -func (d *dummyRawConn) Close() { - d.mux.Lock() - d.closed = true - d.mux.Unlock() + return &net.TCPConn{}, nil } diff --git a/internal/pool/pool.go b/internal/pool/pool.go index 577923a7..44a4e779 100644 --- a/internal/pool/pool.go +++ b/internal/pool/pool.go @@ -121,9 +121,10 @@ func (p *ConnPool) checkMinIdleConns() { for p.poolSize < p.opt.PoolSize && p.idleConnsLen < p.opt.MinIdleConns { p.poolSize++ p.idleConnsLen++ + go func() { err := p.addIdleConn() - if err != nil { + if err != nil && err != ErrClosed { p.connsMu.Lock() p.poolSize-- p.idleConnsLen-- @@ -140,9 +141,16 @@ func (p *ConnPool) addIdleConn() error { } p.connsMu.Lock() + defer p.connsMu.Unlock() + + // It is not allowed to add new connections to the closed connection pool. + if p.closed() { + _ = cn.Close() + return ErrClosed + } + p.conns = append(p.conns, cn) p.idleConns = append(p.idleConns, cn) - p.connsMu.Unlock() return nil } @@ -157,6 +165,14 @@ func (p *ConnPool) newConn(ctx context.Context, pooled bool) (*Conn, error) { } p.connsMu.Lock() + defer p.connsMu.Unlock() + + // It is not allowed to add new connections to the closed connection pool. + if p.closed() { + _ = cn.Close() + return nil, ErrClosed + } + p.conns = append(p.conns, cn) if pooled { // If pool is full remove the cn on next Put. @@ -166,7 +182,6 @@ func (p *ConnPool) newConn(ctx context.Context, pooled bool) (*Conn, error) { p.poolSize++ } } - p.connsMu.Unlock() return cn, nil } @@ -237,9 +252,13 @@ func (p *ConnPool) Get(ctx context.Context) (*Conn, error) { for { p.connsMu.Lock() - cn := p.popIdle() + cn, err := p.popIdle() p.connsMu.Unlock() + if err != nil { + return nil, err + } + if cn == nil { break } @@ -308,10 +327,13 @@ func (p *ConnPool) freeTurn() { <-p.queue } -func (p *ConnPool) popIdle() *Conn { +func (p *ConnPool) popIdle() (*Conn, error) { + if p.closed() { + return nil, ErrClosed + } n := len(p.idleConns) if n == 0 { - return nil + return nil, nil } var cn *Conn @@ -326,7 +348,7 @@ func (p *ConnPool) popIdle() *Conn { } p.idleConnsLen-- p.checkMinIdleConns() - return cn + return cn, nil } func (p *ConnPool) Put(ctx context.Context, cn *Conn) { @@ -520,7 +542,7 @@ func (p *ConnPool) reapStaleConn() *Conn { func (p *ConnPool) isStaleConn(cn *Conn) bool { if p.opt.IdleTimeout == 0 && p.opt.MaxConnAge == 0 { - return connCheck(cn.netConn) != nil + return false } now := time.Now() @@ -531,5 +553,5 @@ func (p *ConnPool) isStaleConn(cn *Conn) bool { return true } - return connCheck(cn.netConn) != nil + return false } diff --git a/internal/pool/pool_test.go b/internal/pool/pool_test.go index 6c94fc27..423a783c 100644 --- a/internal/pool/pool_test.go +++ b/internal/pool/pool_test.go @@ -2,6 +2,7 @@ package pool_test import ( "context" + "net" "sync" "testing" "time" @@ -30,6 +31,43 @@ var _ = Describe("ConnPool", func() { connPool.Close() }) + It("should safe close", func() { + const minIdleConns = 10 + + var ( + wg sync.WaitGroup + closedChan = make(chan struct{}) + ) + wg.Add(minIdleConns) + connPool = pool.NewConnPool(&pool.Options{ + Dialer: func(ctx context.Context) (net.Conn, error) { + wg.Done() + <-closedChan + return &net.TCPConn{}, nil + }, + PoolSize: 10, + PoolTimeout: time.Hour, + IdleTimeout: time.Millisecond, + IdleCheckFrequency: time.Millisecond, + MinIdleConns: minIdleConns, + }) + wg.Wait() + Expect(connPool.Close()).NotTo(HaveOccurred()) + close(closedChan) + + // We wait for 1 second and believe that checkMinIdleConns has been executed. + time.Sleep(time.Second) + + Expect(connPool.Stats()).To(Equal(&pool.Stats{ + Hits: 0, + Misses: 0, + Timeouts: 0, + TotalConns: 0, + IdleConns: 0, + StaleConns: 0, + })) + }) + It("should unblock client when conn is removed", func() { // Reserve one connection. cn, err := connPool.Get(ctx) @@ -285,8 +323,6 @@ 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) @@ -373,7 +409,6 @@ 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 415e2d97..3ceedca9 100644 --- a/internal/proto/reader.go +++ b/internal/proto/reader.go @@ -38,7 +38,7 @@ const ( //------------------------------------------------------------------------------ -const Nil = RedisError("redis: nil") +const Nil = RedisError("redis: nil") // nolint:errname type RedisError string diff --git a/internal/proto/scan.go b/internal/proto/scan.go index 7d7183c3..0e994765 100644 --- a/internal/proto/scan.go +++ b/internal/proto/scan.go @@ -10,6 +10,7 @@ import ( ) // Scan parses bytes `b` to `v` with appropriate type. +//nolint:gocyclo func Scan(b []byte, v interface{}) error { switch v := v.(type) { case nil: @@ -105,6 +106,13 @@ func Scan(b []byte, v interface{}) error { var err error *v, err = time.Parse(time.RFC3339Nano, util.BytesToString(b)) return err + case *time.Duration: + n, err := util.ParseInt(b, 10, 64) + if err != nil { + return err + } + *v = time.Duration(n) + return nil case encoding.BinaryUnmarshaler: return v.UnmarshalBinary(b) default: diff --git a/internal/proto/scan_test.go b/internal/proto/scan_test.go index 5df3a6af..55df550b 100644 --- a/internal/proto/scan_test.go +++ b/internal/proto/scan_test.go @@ -3,9 +3,10 @@ package proto_test import ( "encoding/json" - "github.com/go-redis/redis/v8/internal/proto" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + + "github.com/go-redis/redis/v8/internal/proto" ) type testScanSliceStruct struct { diff --git a/internal/proto/writer.go b/internal/proto/writer.go index f2dc5c93..690312e7 100644 --- a/internal/proto/writer.go +++ b/internal/proto/writer.go @@ -98,6 +98,8 @@ func (w *Writer) WriteArg(v interface{}) error { case time.Time: w.numBuf = v.AppendFormat(w.numBuf[:0], time.RFC3339Nano) return w.bytes(w.numBuf) + case time.Duration: + return w.int(v.Nanoseconds()) case encoding.BinaryMarshaler: b, err := v.MarshalBinary() if err != nil { diff --git a/internal/proto/writer_test.go b/internal/proto/writer_test.go index c5df9a6f..ebae5692 100644 --- a/internal/proto/writer_test.go +++ b/internal/proto/writer_test.go @@ -6,10 +6,10 @@ import ( "testing" "time" - "github.com/go-redis/redis/v8/internal/proto" - . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + + "github.com/go-redis/redis/v8/internal/proto" ) type MyType struct{} @@ -51,7 +51,7 @@ var _ = Describe("WriteBuffer", func() { }) It("should append time", func() { - tm := time.Date(2019, 01, 01, 9, 45, 10, 222125, time.UTC) + tm := time.Date(2019, 1, 1, 9, 45, 10, 222125, time.UTC) err := wr.WriteArgs([]interface{}{tm}) Expect(err).NotTo(HaveOccurred()) diff --git a/internal/safe.go b/internal/safe.go index 862ff0eb..fd2f4340 100644 --- a/internal/safe.go +++ b/internal/safe.go @@ -1,3 +1,4 @@ +//go:build appengine // +build appengine package internal diff --git a/internal/unsafe.go b/internal/unsafe.go index 4bc79701..9f2e418f 100644 --- a/internal/unsafe.go +++ b/internal/unsafe.go @@ -1,3 +1,4 @@ +//go:build !appengine // +build !appengine package internal diff --git a/internal/util/safe.go b/internal/util/safe.go index 1b3060eb..21307110 100644 --- a/internal/util/safe.go +++ b/internal/util/safe.go @@ -1,3 +1,4 @@ +//go:build appengine // +build appengine package util diff --git a/internal/util/unsafe.go b/internal/util/unsafe.go index c9868aac..daa8d769 100644 --- a/internal/util/unsafe.go +++ b/internal/util/unsafe.go @@ -1,3 +1,4 @@ +//go:build !appengine // +build !appengine package util diff --git a/iterator_test.go b/iterator_test.go index 7a64f025..68c8b77e 100644 --- a/iterator_test.go +++ b/iterator_test.go @@ -3,10 +3,10 @@ package redis_test import ( "fmt" - "github.com/go-redis/redis/v8" - . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + + "github.com/go-redis/redis/v8" ) var _ = Describe("ScanIterator", func() { diff --git a/main_test.go b/main_test.go index dd9d8741..5414310e 100644 --- a/main_test.go +++ b/main_test.go @@ -117,7 +117,7 @@ func TestGinkgoSuite(t *testing.T) { RunSpecs(t, "go-redis") } -// ------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ func redisOptions() *redis.Options { return &redis.Options{ @@ -295,7 +295,7 @@ func redisDir(port string) (string, error) { if err := os.RemoveAll(dir); err != nil { return "", err } - if err := os.MkdirAll(dir, 0775); err != nil { + if err := os.MkdirAll(dir, 0o775); err != nil { return "", err } return dir, nil @@ -364,7 +364,7 @@ func startSentinel(port, masterName, masterPort string) (*redisProcess, error) { return p, nil } -// ------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ type badConnError string @@ -409,7 +409,7 @@ func (cn *badConn) Write([]byte) (int, error) { return 0, badConnError("bad connection") } -// ------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ type hook struct { beforeProcess func(ctx context.Context, cmd redis.Cmder) (context.Context, error) diff --git a/options.go b/options.go index e1e85a66..52b35d6b 100644 --- a/options.go +++ b/options.go @@ -8,6 +8,7 @@ import ( "net" "net/url" "runtime" + "sort" "strconv" "strings" "time" @@ -192,9 +193,32 @@ func (opt *Options) clone() *Options { // Scheme is required. // There are two connection types: by tcp socket and by unix socket. // Tcp connection: -// redis://:@:/ +// redis://:@:/ // Unix connection: // unix://:@?db= +// Most Option fields can be set using query parameters, with the following restrictions: +// - field names are mapped using snake-case conversion: to set MaxRetries, use max_retries +// - only scalar type fields are supported (bool, int, time.Duration) +// - for time.Duration fields, values must be a valid input for time.ParseDuration(); +// additionally a plain integer as value (i.e. without unit) is intepreted as seconds +// - to disable a duration field, use value less than or equal to 0; to use the default +// value, leave the value blank or remove the parameter +// - only the last value is interpreted if a parameter is given multiple times +// - fields "network", "addr", "username" and "password" can only be set using other +// URL attributes (scheme, host, userinfo, resp.), query paremeters using these +// names will be treated as unknown parameters +// - unknown parameter names will result in an error +// Examples: +// redis://user:password@localhost:6789/3?dial_timeout=3&db=1&read_timeout=6s&max_retries=2 +// is equivalent to: +// &Options{ +// Network: "tcp", +// Addr: "localhost:6789", +// DB: 1, // path "/3" was overridden by "&db=1" +// DialTimeout: 3 * time.Second, // no time unit = seconds +// ReadTimeout: 6 * time.Second, +// MaxRetries: 2, +// } func ParseURL(redisURL string) (*Options, error) { u, err := url.Parse(redisURL) if err != nil { @@ -216,10 +240,6 @@ func setupTCPConn(u *url.URL) (*Options, error) { o.Username, o.Password = getUserPassword(u) - if len(u.Query()) > 0 { - return nil, errors.New("redis: no options supported") - } - h, p, err := net.SplitHostPort(u.Host) if err != nil { h = u.Host @@ -253,7 +273,7 @@ func setupTCPConn(u *url.URL) (*Options, error) { } } - return o, nil + return setupConnParams(u, o) } func setupUnixConn(u *url.URL) (*Options, error) { @@ -265,19 +285,122 @@ func setupUnixConn(u *url.URL) (*Options, error) { return nil, errors.New("redis: empty unix socket path") } o.Addr = u.Path - o.Username, o.Password = getUserPassword(u) + return setupConnParams(u, o) +} - dbStr := u.Query().Get("db") - if dbStr == "" { - return o, nil // if database is not set, connect to 0 db. +type queryOptions struct { + q url.Values + err error +} + +func (o *queryOptions) string(name string) string { + vs := o.q[name] + if len(vs) == 0 { + return "" + } + delete(o.q, name) // enable detection of unknown parameters + return vs[len(vs)-1] +} + +func (o *queryOptions) int(name string) int { + s := o.string(name) + if s == "" { + return 0 + } + i, err := strconv.Atoi(s) + if err == nil { + return i + } + if o.err == nil { + o.err = fmt.Errorf("redis: invalid %s number: %s", name, err) + } + return 0 +} + +func (o *queryOptions) duration(name string) time.Duration { + s := o.string(name) + if s == "" { + return 0 + } + // try plain number first + if i, err := strconv.Atoi(s); err == nil { + if i <= 0 { + // disable timeouts + return -1 + } + return time.Duration(i) * time.Second + } + dur, err := time.ParseDuration(s) + if err == nil { + return dur + } + if o.err == nil { + o.err = fmt.Errorf("redis: invalid %s duration: %w", name, err) + } + return 0 +} + +func (o *queryOptions) bool(name string) bool { + switch s := o.string(name); s { + case "true", "1": + return true + case "false", "0", "": + return false + default: + if o.err == nil { + o.err = fmt.Errorf("redis: invalid %s boolean: expected true/false/1/0 or an empty string, got %q", name, s) + } + return false + } +} + +func (o *queryOptions) remaining() []string { + if len(o.q) == 0 { + return nil + } + keys := make([]string, 0, len(o.q)) + for k := range o.q { + keys = append(keys, k) + } + sort.Strings(keys) + return keys +} + +// setupConnParams converts query parameters in u to option value in o. +func setupConnParams(u *url.URL, o *Options) (*Options, error) { + q := queryOptions{q: u.Query()} + + // compat: a future major release may use q.int("db") + if tmp := q.string("db"); tmp != "" { + db, err := strconv.Atoi(tmp) + if err != nil { + return nil, fmt.Errorf("redis: invalid database number: %w", err) + } + o.DB = db } - db, err := strconv.Atoi(dbStr) - if err != nil { - return nil, fmt.Errorf("redis: invalid database number: %w", err) + o.MaxRetries = q.int("max_retries") + o.MinRetryBackoff = q.duration("min_retry_backoff") + o.MaxRetryBackoff = q.duration("max_retry_backoff") + o.DialTimeout = q.duration("dial_timeout") + o.ReadTimeout = q.duration("read_timeout") + o.WriteTimeout = q.duration("write_timeout") + o.PoolFIFO = q.bool("pool_fifo") + o.PoolSize = q.int("pool_size") + o.MinIdleConns = q.int("min_idle_conns") + o.MaxConnAge = q.duration("max_conn_age") + o.PoolTimeout = q.duration("pool_timeout") + o.IdleTimeout = q.duration("idle_timeout") + o.IdleCheckFrequency = q.duration("idle_check_frequency") + if q.err != nil { + return nil, q.err + } + + // any parameters left? + if r := q.remaining(); len(r) > 0 { + return nil, fmt.Errorf("redis: unexpected option: %s", strings.Join(r, ", ")) } - o.DB = db return o, nil } diff --git a/options_test.go b/options_test.go index 9ffc6fca..14505239 100644 --- a/options_test.go +++ b/options_test.go @@ -1,8 +1,10 @@ +//go:build go1.7 // +build go1.7 package redis import ( + "crypto/tls" "errors" "testing" "time" @@ -10,150 +12,185 @@ import ( func TestParseURL(t *testing.T) { cases := []struct { - u string - addr string - db int - tls bool - err error - user string - pass string + url string + o *Options // expected value + err error }{ { - "redis://localhost:123/1", - "localhost:123", - 1, false, nil, - "", "", - }, - { - "redis://localhost:123", - "localhost:123", - 0, false, nil, - "", "", - }, - { - "redis://localhost/1", - "localhost:6379", - 1, false, nil, - "", "", - }, - { - "redis://12345", - "12345:6379", - 0, false, nil, - "", "", - }, - { - "rediss://localhost:123", - "localhost:123", - 0, true, nil, - "", "", - }, - { - "redis://:bar@localhost:123", - "localhost:123", - 0, false, nil, - "", "bar", - }, - { - "redis://foo@localhost:123", - "localhost:123", - 0, false, nil, - "foo", "", - }, - { - "redis://foo:bar@localhost:123", - "localhost:123", - 0, false, nil, - "foo", "bar", - }, - { - "unix:///tmp/redis.sock", - "/tmp/redis.sock", - 0, false, nil, - "", "", - }, - { - "unix://foo:bar@/tmp/redis.sock", - "/tmp/redis.sock", - 0, false, nil, - "foo", "bar", - }, - { - "unix://foo:bar@/tmp/redis.sock?db=3", - "/tmp/redis.sock", - 3, false, nil, - "foo", "bar", - }, - { - "unix://foo:bar@/tmp/redis.sock?db=test", - "/tmp/redis.sock", - 0, false, errors.New("redis: invalid database number: strconv.Atoi: parsing \"test\": invalid syntax"), - "", "", - }, - { - "redis://localhost/?abc=123", - "", - 0, false, errors.New("redis: no options supported"), - "", "", - }, - { - "http://google.com", - "", - 0, false, errors.New("redis: invalid URL scheme: http"), - "", "", - }, - { - "redis://localhost/1/2/3/4", - "", - 0, false, errors.New("redis: invalid URL path: /1/2/3/4"), - "", "", - }, - { - "12345", - "", - 0, false, errors.New("redis: invalid URL scheme: "), - "", "", - }, - { - "redis://localhost/iamadatabase", - "", - 0, false, errors.New(`redis: invalid database number: "iamadatabase"`), - "", "", + url: "redis://localhost:123/1", + o: &Options{Addr: "localhost:123", DB: 1}, + }, { + url: "redis://localhost:123", + o: &Options{Addr: "localhost:123"}, + }, { + url: "redis://localhost/1", + o: &Options{Addr: "localhost:6379", DB: 1}, + }, { + url: "redis://12345", + o: &Options{Addr: "12345:6379"}, + }, { + url: "rediss://localhost:123", + o: &Options{Addr: "localhost:123", TLSConfig: &tls.Config{ /* no deep comparison */ }}, + }, { + url: "redis://:bar@localhost:123", + o: &Options{Addr: "localhost:123", Password: "bar"}, + }, { + url: "redis://foo@localhost:123", + o: &Options{Addr: "localhost:123", Username: "foo"}, + }, { + url: "redis://foo:bar@localhost:123", + o: &Options{Addr: "localhost:123", Username: "foo", Password: "bar"}, + }, { + // multiple params + url: "redis://localhost:123/?db=2&read_timeout=2&pool_fifo=true", + o: &Options{Addr: "localhost:123", DB: 2, ReadTimeout: 2 * time.Second, PoolFIFO: true}, + }, { + // special case handling for disabled timeouts + url: "redis://localhost:123/?db=2&idle_timeout=0", + o: &Options{Addr: "localhost:123", DB: 2, IdleTimeout: -1}, + }, { + // negative values disable timeouts as well + url: "redis://localhost:123/?db=2&idle_timeout=-1", + o: &Options{Addr: "localhost:123", DB: 2, IdleTimeout: -1}, + }, { + // absent timeout values will use defaults + url: "redis://localhost:123/?db=2&idle_timeout=", + o: &Options{Addr: "localhost:123", DB: 2, IdleTimeout: 0}, + }, { + url: "redis://localhost:123/?db=2&idle_timeout", // missing "=" at the end + o: &Options{Addr: "localhost:123", DB: 2, IdleTimeout: 0}, + }, { + url: "unix:///tmp/redis.sock", + o: &Options{Addr: "/tmp/redis.sock"}, + }, { + url: "unix://foo:bar@/tmp/redis.sock", + o: &Options{Addr: "/tmp/redis.sock", Username: "foo", Password: "bar"}, + }, { + url: "unix://foo:bar@/tmp/redis.sock?db=3", + o: &Options{Addr: "/tmp/redis.sock", Username: "foo", Password: "bar", DB: 3}, + }, { + // invalid db format + url: "unix://foo:bar@/tmp/redis.sock?db=test", + err: errors.New(`redis: invalid database number: strconv.Atoi: parsing "test": invalid syntax`), + }, { + // invalid int value + url: "redis://localhost/?pool_size=five", + err: errors.New(`redis: invalid pool_size number: strconv.Atoi: parsing "five": invalid syntax`), + }, { + // invalid bool value + url: "redis://localhost/?pool_fifo=yes", + err: errors.New(`redis: invalid pool_fifo boolean: expected true/false/1/0 or an empty string, got "yes"`), + }, { + // it returns first error + url: "redis://localhost/?db=foo&pool_size=five", + err: errors.New(`redis: invalid database number: strconv.Atoi: parsing "foo": invalid syntax`), + }, { + url: "redis://localhost/?abc=123", + err: errors.New("redis: unexpected option: abc"), + }, { + url: "redis://foo@localhost/?username=bar", + err: errors.New("redis: unexpected option: username"), + }, { + url: "redis://localhost/?wrte_timout=10s&abc=123", + err: errors.New("redis: unexpected option: abc, wrte_timout"), + }, { + url: "http://google.com", + err: errors.New("redis: invalid URL scheme: http"), + }, { + url: "redis://localhost/1/2/3/4", + err: errors.New("redis: invalid URL path: /1/2/3/4"), + }, { + url: "12345", + err: errors.New("redis: invalid URL scheme: "), + }, { + url: "redis://localhost/iamadatabase", + err: errors.New(`redis: invalid database number: "iamadatabase"`), }, } - for _, c := range cases { - t.Run(c.u, func(t *testing.T) { - o, err := ParseURL(c.u) - if c.err == nil && err != nil { + for i := range cases { + tc := cases[i] + t.Run(tc.url, func(t *testing.T) { + t.Parallel() + + actual, err := ParseURL(tc.url) + if tc.err == nil && err != nil { t.Fatalf("unexpected error: %q", err) return } - if c.err != nil && err != nil { - if c.err.Error() != err.Error() { - t.Fatalf("got %q, expected %q", err, c.err) + if tc.err != nil && err != nil { + if tc.err.Error() != err.Error() { + t.Fatalf("got %q, expected %q", err, tc.err) } return } - if o.Addr != c.addr { - t.Errorf("got %q, want %q", o.Addr, c.addr) - } - if o.DB != c.db { - t.Errorf("got %q, expected %q", o.DB, c.db) - } - if c.tls && o.TLSConfig == nil { - t.Errorf("got nil TLSConfig, expected a TLSConfig") - } - if o.Username != c.user { - t.Errorf("got %q, expected %q", o.Username, c.user) - } - if o.Password != c.pass { - t.Errorf("got %q, expected %q", o.Password, c.pass) - } + comprareOptions(t, actual, tc.o) }) } } +func comprareOptions(t *testing.T, actual, expected *Options) { + t.Helper() + + if actual.Addr != expected.Addr { + t.Errorf("got %q, want %q", actual.Addr, expected.Addr) + } + if actual.DB != expected.DB { + t.Errorf("DB: got %q, expected %q", actual.DB, expected.DB) + } + if actual.TLSConfig == nil && expected.TLSConfig != nil { + t.Errorf("got nil TLSConfig, expected a TLSConfig") + } + if actual.TLSConfig != nil && expected.TLSConfig == nil { + t.Errorf("got TLSConfig, expected no TLSConfig") + } + if actual.Username != expected.Username { + t.Errorf("Username: got %q, expected %q", actual.Username, expected.Username) + } + if actual.Password != expected.Password { + t.Errorf("Password: got %q, expected %q", actual.Password, expected.Password) + } + if actual.MaxRetries != expected.MaxRetries { + t.Errorf("MaxRetries: got %v, expected %v", actual.MaxRetries, expected.MaxRetries) + } + if actual.MinRetryBackoff != expected.MinRetryBackoff { + t.Errorf("MinRetryBackoff: got %v, expected %v", actual.MinRetryBackoff, expected.MinRetryBackoff) + } + if actual.MaxRetryBackoff != expected.MaxRetryBackoff { + t.Errorf("MaxRetryBackoff: got %v, expected %v", actual.MaxRetryBackoff, expected.MaxRetryBackoff) + } + if actual.DialTimeout != expected.DialTimeout { + t.Errorf("DialTimeout: got %v, expected %v", actual.DialTimeout, expected.DialTimeout) + } + if actual.ReadTimeout != expected.ReadTimeout { + t.Errorf("ReadTimeout: got %v, expected %v", actual.ReadTimeout, expected.ReadTimeout) + } + if actual.WriteTimeout != expected.WriteTimeout { + t.Errorf("WriteTimeout: got %v, expected %v", actual.WriteTimeout, expected.WriteTimeout) + } + if actual.PoolFIFO != expected.PoolFIFO { + t.Errorf("PoolFIFO: got %v, expected %v", actual.PoolFIFO, expected.PoolFIFO) + } + if actual.PoolSize != expected.PoolSize { + t.Errorf("PoolSize: got %v, expected %v", actual.PoolSize, expected.PoolSize) + } + if actual.MinIdleConns != expected.MinIdleConns { + t.Errorf("MinIdleConns: got %v, expected %v", actual.MinIdleConns, expected.MinIdleConns) + } + if actual.MaxConnAge != expected.MaxConnAge { + t.Errorf("MaxConnAge: got %v, expected %v", actual.MaxConnAge, expected.MaxConnAge) + } + if actual.PoolTimeout != expected.PoolTimeout { + t.Errorf("PoolTimeout: got %v, expected %v", actual.PoolTimeout, expected.PoolTimeout) + } + if actual.IdleTimeout != expected.IdleTimeout { + t.Errorf("IdleTimeout: got %v, expected %v", actual.IdleTimeout, expected.IdleTimeout) + } + if actual.IdleCheckFrequency != expected.IdleCheckFrequency { + t.Errorf("IdleCheckFrequency: got %v, expected %v", actual.IdleCheckFrequency, expected.IdleCheckFrequency) + } +} + // Test ReadTimeout option initialization, including special values -1 and 0. // And also test behaviour of WriteTimeout option, when it is not explicitly set and use // ReadTimeout value. diff --git a/package.json b/package.json new file mode 100644 index 00000000..e4ea4bb0 --- /dev/null +++ b/package.json @@ -0,0 +1,8 @@ +{ + "name": "redis", + "version": "8.11.5", + "main": "index.js", + "repository": "git@github.com:go-redis/redis.git", + "author": "Vladimir Mihailenco ", + "license": "BSD-2-clause" +} diff --git a/pipeline.go b/pipeline.go index aa2fc8f0..b9845ba3 100644 --- a/pipeline.go +++ b/pipeline.go @@ -22,6 +22,7 @@ type pipelineExecer func(context.Context, []Cmder) error // depends of your batch size and/or use TxPipeline. type Pipeliner interface { StatefulCmdable + Len() int Do(ctx context.Context, args ...interface{}) *Cmd Process(ctx context.Context, cmd Cmder) error Discard() @@ -49,6 +50,15 @@ func (c *Pipeline) init() { c.statefulCmdable = c.Process } +// Len returns the number of queued commands. +func (c *Pipeline) Len() int { + c.mu.Lock() + ln := len(c.cmds) + c.mu.Unlock() + return ln +} + +// Do queues the custom command for later execution. func (c *Pipeline) Do(ctx context.Context, args ...interface{}) *Cmd { cmd := NewCmd(ctx, args...) _ = c.Process(ctx, cmd) diff --git a/pipeline_test.go b/pipeline_test.go index 9d2a60cf..f24114d7 100644 --- a/pipeline_test.go +++ b/pipeline_test.go @@ -3,10 +3,10 @@ package redis_test import ( "strconv" - "github.com/go-redis/redis/v8" - . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + + "github.com/go-redis/redis/v8" ) var _ = Describe("pipelining", func() { diff --git a/pool_test.go b/pool_test.go index 5e83549c..e297b010 100644 --- a/pool_test.go +++ b/pool_test.go @@ -4,10 +4,10 @@ import ( "context" "time" - "github.com/go-redis/redis/v8" - . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + + "github.com/go-redis/redis/v8" ) var _ = Describe("pool", func() { @@ -86,9 +86,8 @@ var _ = Describe("pool", func() { cn.SetNetConn(&badConn{}) client.Pool().Put(ctx, cn) - // connCheck will automatically remove damaged connections. err = client.Ping(ctx).Err() - Expect(err).NotTo(HaveOccurred()) + Expect(err).To(MatchError("bad connection")) val, err := client.Ping(ctx).Result() Expect(err).NotTo(HaveOccurred()) diff --git a/pubsub.go b/pubsub.go index c6ffb256..efc2354a 100644 --- a/pubsub.go +++ b/pubsub.go @@ -141,7 +141,7 @@ func (c *PubSub) releaseConn(ctx context.Context, cn *pool.Conn, err error, allo if c.cn != cn { return } - if isBadConn(err, allowTimeout) { + if isBadConn(err, allowTimeout, c.opt.Addr) { c.reconnect(ctx, err) } } @@ -252,13 +252,16 @@ func (c *PubSub) Ping(ctx context.Context, payload ...string) error { } cmd := NewCmd(ctx, args...) - cn, err := c.connWithLock(ctx) + c.mu.Lock() + defer c.mu.Unlock() + + cn, err := c.conn(ctx, nil) if err != nil { return err } err = c.writeCmd(ctx, cn, cmd) - c.releaseConnWithLock(ctx, cn, err, false) + c.releaseConn(ctx, cn, err, false) return err } @@ -361,6 +364,8 @@ func (c *PubSub) ReceiveTimeout(ctx context.Context, timeout time.Duration) (int c.cmd = NewCmd(ctx) } + // Don't hold the lock to allow subscriptions and pings. + cn, err := c.connWithLock(ctx) if err != nil { return nil, err @@ -371,6 +376,7 @@ func (c *PubSub) ReceiveTimeout(ctx context.Context, timeout time.Duration) (int }) c.releaseConnWithLock(ctx, cn, err, timeout > 0) + if err != nil { return nil, err } diff --git a/pubsub_test.go b/pubsub_test.go index 14e37c2b..5776bbde 100644 --- a/pubsub_test.go +++ b/pubsub_test.go @@ -6,10 +6,10 @@ import ( "sync" "time" - "github.com/go-redis/redis/v8" - . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + + "github.com/go-redis/redis/v8" ) var _ = Describe("PubSub", func() { diff --git a/race_test.go b/race_test.go index a1c2a080..34699d13 100644 --- a/race_test.go +++ b/race_test.go @@ -10,10 +10,10 @@ import ( "testing" "time" - "github.com/go-redis/redis/v8" - . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + + "github.com/go-redis/redis/v8" ) var _ = Describe("races", func() { @@ -373,6 +373,18 @@ var _ = Describe("cluster races", func() { Expect(err).NotTo(HaveOccurred()) Expect(val).To(Equal(int64(C * N))) }) + + It("write cmd data-race", func() { + pubsub := client.Subscribe(ctx) + defer pubsub.Close() + + pubsub.Channel(redis.WithChannelHealthCheckInterval(time.Millisecond)) + for i := 0; i < 100; i++ { + key := fmt.Sprintf("channel_%d", i) + pubsub.Subscribe(ctx, key) + pubsub.Unsubscribe(ctx, key) + } + }) }) func bigVal() []byte { diff --git a/redis.go b/redis.go index 051fa594..919c1642 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 } @@ -51,9 +52,7 @@ func (hs hooks) process( ctx context.Context, cmd Cmder, fn func(context.Context, Cmder) error, ) error { if len(hs.hooks) == 0 { - err := hs.withContext(ctx, func() error { - return fn(ctx, cmd) - }) + err := fn(ctx, cmd) cmd.SetErr(err) return err } @@ -69,9 +68,7 @@ func (hs hooks) process( } if retErr == nil { - retErr = hs.withContext(ctx, func() error { - return fn(ctx, cmd) - }) + retErr = fn(ctx, cmd) cmd.SetErr(retErr) } @@ -89,9 +86,7 @@ func (hs hooks) processPipeline( ctx context.Context, cmds []Cmder, fn func(context.Context, []Cmder) error, ) error { if len(hs.hooks) == 0 { - err := hs.withContext(ctx, func() error { - return fn(ctx, cmds) - }) + err := fn(ctx, cmds) return err } @@ -106,9 +101,7 @@ func (hs hooks) processPipeline( } if retErr == nil { - retErr = hs.withContext(ctx, func() error { - return fn(ctx, cmds) - }) + retErr = fn(ctx, cmds) } for hookIndex--; hookIndex >= 0; hookIndex-- { @@ -128,10 +121,6 @@ func (hs hooks) processTxPipeline( return hs.processPipeline(ctx, cmds, fn) } -func (hs hooks) withContext(_ context.Context, fn func() error) error { - return fn() -} - //------------------------------------------------------------------------------ type baseClient struct { @@ -277,7 +266,7 @@ func (c *baseClient) releaseConn(ctx context.Context, cn *pool.Conn, err error) c.opt.Limiter.ReportResult(err) } - if isBadConn(err, false) { + if isBadConn(err, false, c.opt.Addr) { c.connPool.Remove(ctx, cn, err) } else { c.connPool.Put(ctx, cn) @@ -722,11 +711,9 @@ type conn struct { hooks // TODO: inherit hooks } -// Conn represents a single Redis connection rather than a pool of database connections. -// It's safe for concurrent use by multiple goroutines. -// -// Prefer running commands from Client unless there is a specific need for a continuous -// single Redis connection. +// Conn represents a single Redis connection rather than a pool of connections. +// Prefer running commands from Client unless there is a specific need +// for a continuous single Redis connection. type Conn struct { *conn ctx context.Context diff --git a/redis_test.go b/redis_test.go index c2d2dc02..02daa3cf 100644 --- a/redis_test.go +++ b/redis_test.go @@ -8,10 +8,10 @@ import ( "testing" "time" - "github.com/go-redis/redis/v8" - . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + + "github.com/go-redis/redis/v8" ) type redisHookError struct { @@ -283,6 +283,18 @@ var _ = Describe("Client", func() { Expect(tm2).To(BeTemporally("==", tm)) }) + It("should set and scan durations", func() { + duration := 10 * time.Minute + err := client.Set(ctx, "duration", duration, 0).Err() + Expect(err).NotTo(HaveOccurred()) + + var duration2 time.Duration + err = client.Get(ctx, "duration").Scan(&duration2) + Expect(err).NotTo(HaveOccurred()) + + Expect(duration2).To(Equal(duration)) + }) + It("should Conn", func() { err := client.Conn(ctx).Get(ctx, "this-key-does-not-exist").Err() Expect(err).To(Equal(redis.Nil)) diff --git a/ring.go b/ring.go index 7446d32c..8309cbe4 100644 --- a/ring.go +++ b/ring.go @@ -12,7 +12,8 @@ import ( "time" "github.com/cespare/xxhash/v2" - "github.com/dgryski/go-rendezvous" + rendezvous "github.com/dgryski/go-rendezvous" //nolint + "github.com/go-redis/redis/v8/internal" "github.com/go-redis/redis/v8/internal/hashtag" "github.com/go-redis/redis/v8/internal/pool" @@ -575,7 +576,7 @@ func (c *Ring) cmdInfo(ctx context.Context, name string) *CommandInfo { } info := cmdsInfo[name] if info == nil { - internal.Logger.Printf(c.Context(), "info for cmd=%s not found", name) + internal.Logger.Printf(ctx, "info for cmd=%s not found", name) } return info } diff --git a/ring_test.go b/ring_test.go index 432a1c07..b0705c5b 100644 --- a/ring_test.go +++ b/ring_test.go @@ -9,10 +9,10 @@ import ( "sync" "time" - "github.com/go-redis/redis/v8" - . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + + "github.com/go-redis/redis/v8" ) var _ = Describe("Redis Ring", func() { @@ -190,8 +190,8 @@ var _ = Describe("Redis Ring", func() { Describe("Process hook", func() { BeforeEach(func() { - //the health check leads to data race for variable "stack []string". - //here, the health check time is set to 72 hours to avoid health check + // the health check leads to data race for variable "stack []string". + // here, the health check time is set to 72 hours to avoid health check opt := redisRingOptions() opt.HeartbeatFrequency = 72 * time.Hour ring = redis.NewRing(opt) diff --git a/scripts/bump_deps.sh b/scripts/bump_deps.sh new file mode 100755 index 00000000..f294c4f1 --- /dev/null +++ b/scripts/bump_deps.sh @@ -0,0 +1,9 @@ +PACKAGE_DIRS=$(find . -mindepth 2 -type f -name 'go.mod' -exec dirname {} \; \ + | sed 's/^\.\///' \ + | sort) + +for dir in $PACKAGE_DIRS +do + printf "${dir}: go get -d && go mod tidy\n" + (cd ./${dir} && go get -d && go mod tidy) +done diff --git a/scripts/release.sh b/scripts/release.sh new file mode 100755 index 00000000..2e78be61 --- /dev/null +++ b/scripts/release.sh @@ -0,0 +1,69 @@ +#!/bin/bash + +set -e + +help() { + cat <<- EOF +Usage: TAG=tag $0 + +Updates version in go.mod files and pushes a new brash to GitHub. + +VARIABLES: + TAG git tag, for example, v1.0.0 +EOF + exit 0 +} + +if [ -z "$TAG" ] +then + printf "TAG is required\n\n" + help +fi + +TAG_REGEX="^v(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)(\\-[0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*)?(\\+[0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*)?$" +if ! [[ "${TAG}" =~ ${TAG_REGEX} ]]; then + printf "TAG is not valid: ${TAG}\n\n" + exit 1 +fi + +TAG_FOUND=`git tag --list ${TAG}` +if [[ ${TAG_FOUND} = ${TAG} ]] ; then + printf "tag ${TAG} already exists\n\n" + exit 1 +fi + +if ! git diff --quiet +then + printf "working tree is not clean\n\n" + git status + exit 1 +fi + +git checkout master + +PACKAGE_DIRS=$(find . -mindepth 2 -type f -name 'go.mod' -exec dirname {} \; \ + | sed 's/^\.\///' \ + | sort) + +for dir in $PACKAGE_DIRS +do + printf "${dir}: go get -u && go mod tidy\n" + (cd ./${dir} && go get -u && go mod tidy) +done + +for dir in $PACKAGE_DIRS +do + sed --in-place \ + "s/go-redis\/redis\([^ ]*\) v.*/go-redis\/redis\1 ${TAG}/" "${dir}/go.mod" + (cd ./${dir} && go get -u && go mod tidy) +done + +sed --in-place "s/\(return \)\"[^\"]*\"/\1\"${TAG#v}\"/" ./version.go +sed --in-place "s/\(\"version\": \)\"[^\"]*\"/\1\"${TAG#v}\"/" ./package.json + +conventional-changelog -p angular -i CHANGELOG.md -s + +git checkout -b release/${TAG} master +git add -u +git commit -m "chore: release $TAG (release.sh)" +git push origin release/${TAG} diff --git a/scripts/tag.sh b/scripts/tag.sh new file mode 100755 index 00000000..121f00e0 --- /dev/null +++ b/scripts/tag.sh @@ -0,0 +1,42 @@ +#!/bin/bash + +set -e + +help() { + cat <<- EOF +Usage: TAG=tag $0 + +Creates git tags for public Go packages. + +VARIABLES: + TAG git tag, for example, v1.0.0 +EOF + exit 0 +} + +if [ -z "$TAG" ] +then + printf "TAG env var is required\n\n"; + help +fi + +if ! grep -Fq "\"${TAG#v}\"" version.go +then + printf "version.go does not contain ${TAG#v}\n" + exit 1 +fi + +PACKAGE_DIRS=$(find . -mindepth 2 -type f -name 'go.mod' -exec dirname {} \; \ + | grep -E -v "example|internal" \ + | sed 's/^\.\///' \ + | sort) + +git tag ${TAG} +git push origin ${TAG} + +for dir in $PACKAGE_DIRS +do + printf "tagging ${dir}/${TAG}\n" + git tag ${dir}/${TAG} + git push origin ${dir}/${TAG} +done diff --git a/sentinel.go b/sentinel.go index 361f229e..205f8a12 100644 --- a/sentinel.go +++ b/sentinel.go @@ -23,7 +23,13 @@ type FailoverOptions struct { MasterName string // A seed list of host:port addresses of sentinel nodes. SentinelAddrs []string - // Sentinel password from "requirepass " (if enabled) in Sentinel configuration + + // If specified with SentinelPassword, enables ACL-based authentication (via + // AUTH ). + SentinelUsername string + // Sentinel password from "requirepass " (if enabled) in Sentinel + // configuration, or, if SentinelUsername is also supplied, used for ACL-based + // authentication. SentinelPassword string // Allows routing read-only commands to the closest master or slave node. @@ -109,6 +115,7 @@ func (opt *FailoverOptions) sentinelOptions(addr string) *Options { OnConnect: opt.OnConnect, DB: 0, + Username: opt.SentinelUsername, Password: opt.SentinelPassword, MaxRetries: opt.MaxRetries, diff --git a/sentinel_test.go b/sentinel_test.go index 12e734da..cc56cbab 100644 --- a/sentinel_test.go +++ b/sentinel_test.go @@ -3,10 +3,10 @@ package redis_test import ( "net" - "github.com/go-redis/redis/v8" - . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + + "github.com/go-redis/redis/v8" ) var _ = Describe("Sentinel", func() { @@ -192,7 +192,7 @@ var _ = Describe("NewFailoverClusterClient", func() { err = master.Shutdown(ctx).Err() Expect(err).NotTo(HaveOccurred()) Eventually(func() error { - return master.Ping(ctx).Err() + return sentinelMaster.Ping(ctx).Err() }, "15s", "100ms").Should(HaveOccurred()) // Check that client picked up new master. @@ -214,3 +214,76 @@ var _ = Describe("NewFailoverClusterClient", func() { Expect(err).NotTo(HaveOccurred()) }) }) + +var _ = Describe("SentinelAclAuth", func() { + const ( + aclSentinelUsername = "sentinel-user" + aclSentinelPassword = "sentinel-pass" + ) + + var client *redis.Client + var sentinel *redis.SentinelClient + var sentinels = func() []*redisProcess { + return []*redisProcess{sentinel1, sentinel2, sentinel3} + } + + BeforeEach(func() { + authCmd := redis.NewStatusCmd(ctx, "ACL", "SETUSER", aclSentinelUsername, "ON", + ">"+aclSentinelPassword, "-@all", "+auth", "+client|getname", "+client|id", "+client|setname", + "+command", "+hello", "+ping", "+role", "+sentinel|get-master-addr-by-name", "+sentinel|master", + "+sentinel|myid", "+sentinel|replicas", "+sentinel|sentinels") + + for _, process := range sentinels() { + err := process.Client.Process(ctx, authCmd) + Expect(err).NotTo(HaveOccurred()) + } + + client = redis.NewFailoverClient(&redis.FailoverOptions{ + MasterName: sentinelName, + SentinelAddrs: sentinelAddrs, + MaxRetries: -1, + SentinelUsername: aclSentinelUsername, + SentinelPassword: aclSentinelPassword, + }) + + Expect(client.FlushDB(ctx).Err()).NotTo(HaveOccurred()) + + sentinel = redis.NewSentinelClient(&redis.Options{ + Addr: sentinelAddrs[0], + MaxRetries: -1, + Username: aclSentinelUsername, + Password: aclSentinelPassword, + }) + + _, err := sentinel.GetMasterAddrByName(ctx, sentinelName).Result() + Expect(err).NotTo(HaveOccurred()) + + // Wait until sentinels are picked up by each other. + for _, process := range sentinels() { + Eventually(func() string { + return process.Info(ctx).Val() + }, "15s", "100ms").Should(ContainSubstring("sentinels=3")) + } + }) + + AfterEach(func() { + unauthCommand := redis.NewStatusCmd(ctx, "ACL", "DELUSER", aclSentinelUsername) + + for _, process := range sentinels() { + err := process.Client.Process(ctx, unauthCommand) + Expect(err).NotTo(HaveOccurred()) + } + + _ = client.Close() + _ = sentinel.Close() + }) + + It("should still facilitate operations", func() { + err := client.Set(ctx, "wow", "acl-auth", 0).Err() + Expect(err).NotTo(HaveOccurred()) + + val, err := client.Get(ctx, "wow").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(val).To(Equal("acl-auth")) + }) +}) diff --git a/tx.go b/tx.go index 08d381a9..8c9d8720 100644 --- a/tx.go +++ b/tx.go @@ -13,7 +13,8 @@ const TxFailedErr = proto.RedisError("redis: transaction failed") // Tx implements Redis transactions as described in // http://redis.io/topics/transactions. It's NOT safe for concurrent use // by multiple goroutines, because Exec resets list of watched keys. -// If you don't need WATCH it is better to use Pipeline. +// +// If you don't need WATCH, use Pipeline instead. type Tx struct { baseClient cmdable diff --git a/tx_test.go b/tx_test.go index 11e5b0d9..7deb2dfd 100644 --- a/tx_test.go +++ b/tx_test.go @@ -5,10 +5,10 @@ import ( "strconv" "sync" - "github.com/go-redis/redis/v8" - . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + + "github.com/go-redis/redis/v8" ) var _ = Describe("Tx", func() { @@ -123,7 +123,7 @@ var _ = Describe("Tx", func() { Expect(num).To(Equal(int64(N))) }) - It("should remove from bad connection", func() { + It("should recover from bad connection", func() { // Put bad connection in the pool. cn, err := client.Pool().Get(context.Background()) Expect(err).NotTo(HaveOccurred()) @@ -134,14 +134,17 @@ var _ = Describe("Tx", func() { do := func() error { err := client.Watch(ctx, func(tx *redis.Tx) error { _, err := tx.TxPipelined(ctx, func(pipe redis.Pipeliner) error { - return pipe.Ping(ctx).Err() + pipe.Ping(ctx) + return nil }) return err }) return err } - // connCheck will automatically remove damaged connections. + err = do() + Expect(err).To(MatchError("bad connection")) + err = do() Expect(err).NotTo(HaveOccurred()) }) diff --git a/universal.go b/universal.go index 1e962ab3..c89b3e5d 100644 --- a/universal.go +++ b/universal.go @@ -25,6 +25,7 @@ type UniversalOptions struct { Username string Password string + SentinelUsername string SentinelPassword string MaxRetries int @@ -114,6 +115,7 @@ func (o *UniversalOptions) Failover() *FailoverOptions { DB: o.DB, Username: o.Username, Password: o.Password, + SentinelUsername: o.SentinelUsername, SentinelPassword: o.SentinelPassword, MaxRetries: o.MaxRetries, diff --git a/version.go b/version.go new file mode 100644 index 00000000..112c9a2d --- /dev/null +++ b/version.go @@ -0,0 +1,6 @@ +package redis + +// Version is the current release version. +func Version() string { + return "8.11.5" +} From 997ab5e7e3ddf53837917013a4babbded73e944f Mon Sep 17 00:00:00 2001 From: Monkey Date: Sat, 19 Mar 2022 18:22:12 +0800 Subject: [PATCH 14/17] fix(#1943): xInfoConsumer.Idle should be time.Duration instead of int64 (#2052) Signed-off-by: monkey92t --- command.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/command.go b/command.go index 810deded..00af6c1d 100644 --- a/command.go +++ b/command.go @@ -1857,7 +1857,7 @@ type XInfoConsumersCmd struct { type XInfoConsumer struct { Name string Pending int64 - Idle int64 + Idle time.Duration } var _ Cmder = (*XInfoConsumersCmd)(nil) @@ -1906,19 +1906,21 @@ func (cmd *XInfoConsumersCmd) readReply(rd *proto.Reader) error { return err } + var idle int64 switch key { case "name": cmd.val[i].Name, err = rd.ReadString() case "pending": cmd.val[i].Pending, err = rd.ReadInt() case "idle": - cmd.val[i].Idle, err = rd.ReadInt() + idle, err = rd.ReadInt() default: return fmt.Errorf("redis: unexpected content %s in XINFO CONSUMERS reply", key) } if err != nil { return err } + cmd.val[i].Idle = time.Duration(idle) * time.Millisecond } } From 6f1a1ac284ea3f683eeb3b06a59969e8424b6376 Mon Sep 17 00:00:00 2001 From: monkey92t Date: Sat, 19 Mar 2022 20:54:27 +0800 Subject: [PATCH 15/17] fix: add XInfoConsumers test Signed-off-by: monkey92t --- command.go | 4 ++-- commands_test.go | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/command.go b/command.go index 00af6c1d..f9d8af36 100644 --- a/command.go +++ b/command.go @@ -1906,21 +1906,21 @@ func (cmd *XInfoConsumersCmd) readReply(rd *proto.Reader) error { return err } - var idle int64 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 } - cmd.val[i].Idle = time.Duration(idle) * time.Millisecond } } diff --git a/commands_test.go b/commands_test.go index 81c1bdb8..e1cceb9f 100644 --- a/commands_test.go +++ b/commands_test.go @@ -5008,6 +5008,7 @@ var _ = Describe("Commands", func() { res, err := client.XInfoConsumers(ctx, "stream", "group1").Result() Expect(err).NotTo(HaveOccurred()) for i := range res { + Expect(res[i].Idle > 0).To(BeTrue()) res[i].Idle = 0 } Expect(res).To(Equal([]redis.XInfoConsumer{ From f5fbb367e7d9dfd7f391fc535a7387002232fa8a Mon Sep 17 00:00:00 2001 From: monkey92t Date: Sat, 19 Mar 2022 22:04:22 +0800 Subject: [PATCH 16/17] fix: remove test(XInfoConsumer.idle), not a stable return value when tested. Signed-off-by: monkey92t --- commands_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/commands_test.go b/commands_test.go index e1cceb9f..81c1bdb8 100644 --- a/commands_test.go +++ b/commands_test.go @@ -5008,7 +5008,6 @@ var _ = Describe("Commands", func() { res, err := client.XInfoConsumers(ctx, "stream", "group1").Result() Expect(err).NotTo(HaveOccurred()) for i := range res { - Expect(res[i].Idle > 0).To(BeTrue()) res[i].Idle = 0 } Expect(res).To(Equal([]redis.XInfoConsumer{ From a8a7665ddf8cc657c5226b1826a8ee83dab4b8c1 Mon Sep 17 00:00:00 2001 From: naiqianz Date: Mon, 21 Mar 2022 01:06:47 +0800 Subject: [PATCH 17/17] feat(pool): add check for badConnection * fix: badConn check(#2053) * fix: internalpool test * fix: sentinel test * fix: conncheck ut * fix: remove maxBadConnRetries * fix: add connCheck.deadline check Signed-off-by: monkey92t --- internal/pool/conn_check.go | 49 +++++++++++++++++ internal/pool/conn_check_dummy.go | 9 ++++ internal/pool/conn_check_test.go | 48 +++++++++++++++++ internal/pool/export_test.go | 5 ++ internal/pool/main_test.go | 87 ++++++++++++++++++++++++++++++- internal/pool/pool.go | 4 +- internal/pool/pool_test.go | 3 ++ pool_test.go | 2 +- sentinel_test.go | 2 +- tx_test.go | 3 -- 10 files changed, 204 insertions(+), 8 deletions(-) create mode 100644 internal/pool/conn_check.go create mode 100644 internal/pool/conn_check_dummy.go create mode 100644 internal/pool/conn_check_test.go 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..18c6bbd5 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 +} \ No newline at end of file diff --git a/internal/pool/main_test.go b/internal/pool/main_test.go index 2365dbc6..c54a38d5 100644 --- a/internal/pool/main_test.go +++ b/internal/pool/main_test.go @@ -2,9 +2,12 @@ package pool_test import ( "context" + "fmt" "net" "sync" + "syscall" "testing" + "time" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -32,5 +35,87 @@ func perform(n int, cbs ...func(int)) { } func dummyDialer(context.Context) (net.Conn, error) { - return &net.TCPConn{}, nil + // return &net.TCPConn{}, nil + return newDummyConn(), nil +} + +func newDummyConn() net.Conn { + return &dummyConn{ + rawConn: &dummyRawConn{}, + } +} + +var _ net.Conn = (*dummyConn)(nil) +var _ syscall.Conn = (*dummyConn)(nil) + +type dummyConn struct { + rawConn *dummyRawConn +} + +func (d *dummyConn) SyscallConn() (syscall.RawConn, error) { + return d.rawConn, nil +} + +var errDummy = fmt.Errorf("dummyConn err") + +func (d *dummyConn) Read(b []byte) (n int, err error) { + return 0, errDummy +} + +func (d *dummyConn) Write(b []byte) (n int, err error) { + return 0, errDummy +} + +func (d *dummyConn) Close() error { + d.rawConn.Close() + return nil +} + +func (d *dummyConn) LocalAddr() net.Addr { + return &net.TCPAddr{} +} + +func (d *dummyConn) RemoteAddr() net.Addr { + return &net.TCPAddr{} +} + +func (d *dummyConn) SetDeadline(t time.Time) error { + return nil +} + +func (d *dummyConn) SetReadDeadline(t time.Time) error { + return nil +} + +func (d *dummyConn) SetWriteDeadline(t time.Time) error { + return nil +} + +var _ syscall.RawConn = (*dummyRawConn)(nil) + +type dummyRawConn struct { + closed bool + mux sync.Mutex +} + +func (d *dummyRawConn) Control(f func(fd uintptr)) error { + return nil +} + +func (d *dummyRawConn) Read(f func(fd uintptr) (done bool)) error { + d.mux.Lock() + defer d.mux.Unlock() + if d.closed { + return fmt.Errorf("dummyRawConn closed") + } + return nil +} + +func (d *dummyRawConn) Write(f func(fd uintptr) (done bool)) error { + return nil +} +func (d *dummyRawConn) Close() { + d.mux.Lock() + d.closed = true + d.mux.Unlock() } diff --git a/internal/pool/pool.go b/internal/pool/pool.go index 44a4e779..3a6ecb27 100644 --- a/internal/pool/pool.go +++ b/internal/pool/pool.go @@ -542,7 +542,7 @@ func (p *ConnPool) reapStaleConn() *Conn { func (p *ConnPool) isStaleConn(cn *Conn) bool { if p.opt.IdleTimeout == 0 && p.opt.MaxConnAge == 0 { - return false + return connCheck(cn.netConn) != nil } now := time.Now() @@ -553,5 +553,5 @@ func (p *ConnPool) isStaleConn(cn *Conn) bool { return true } - return false + return connCheck(cn.netConn) != nil } 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/pool_test.go b/pool_test.go index e297b010..56d290ce 100644 --- a/pool_test.go +++ b/pool_test.go @@ -87,7 +87,7 @@ var _ = Describe("pool", func() { client.Pool().Put(ctx, cn) err = client.Ping(ctx).Err() - Expect(err).To(MatchError("bad connection")) + Expect(err).NotTo(HaveOccurred()) val, err := client.Ping(ctx).Result() Expect(err).NotTo(HaveOccurred()) diff --git a/sentinel_test.go b/sentinel_test.go index cc56cbab..dae28fea 100644 --- a/sentinel_test.go +++ b/sentinel_test.go @@ -192,7 +192,7 @@ var _ = Describe("NewFailoverClusterClient", func() { err = master.Shutdown(ctx).Err() Expect(err).NotTo(HaveOccurred()) Eventually(func() error { - return sentinelMaster.Ping(ctx).Err() + return master.Ping(ctx).Err() }, "15s", "100ms").Should(HaveOccurred()) // Check that client picked up new master. diff --git a/tx_test.go b/tx_test.go index 7deb2dfd..030a56cc 100644 --- a/tx_test.go +++ b/tx_test.go @@ -142,9 +142,6 @@ var _ = Describe("Tx", func() { return err } - err = do() - Expect(err).To(MatchError("bad connection")) - err = do() Expect(err).NotTo(HaveOccurred()) })