diff --git a/.github/wordlist.txt b/.github/wordlist.txt index 48d3fc48..52fdc1bc 100644 --- a/.github/wordlist.txt +++ b/.github/wordlist.txt @@ -2,6 +2,7 @@ ACLs autoload autoloader autoloading +analytics Autoloading backend backends @@ -13,6 +14,7 @@ customizable Customizable dataset de +DisableIdentity ElastiCache extensibility FPM @@ -43,6 +45,7 @@ RocksDB runtime SHA sharding +SETNAME SSL struct stunnel diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index e35ee85c..a139f5da 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -23,4 +23,4 @@ jobs: steps: - uses: actions/checkout@v4 - name: golangci-lint - uses: golangci/golangci-lint-action@v3 + uses: golangci/golangci-lint-action@v4 diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml index eebb3e67..6695abfe 100644 --- a/.github/workflows/release-drafter.yml +++ b/.github/workflows/release-drafter.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest steps: # Drafts your next Release notes as Pull Requests are merged into "master" - - uses: release-drafter/release-drafter@v5 + - uses: release-drafter/release-drafter@v6 with: # (Optional) specify config name to use, relative to .github/. Default: release-drafter.yml config-name: release-drafter-config.yml diff --git a/.github/workflows/spellcheck.yml b/.github/workflows/spellcheck.yml index a48781aa..f739a542 100644 --- a/.github/workflows/spellcheck.yml +++ b/.github/workflows/spellcheck.yml @@ -8,7 +8,7 @@ jobs: - name: Checkout uses: actions/checkout@v4 - name: Check Spelling - uses: rojopolis/spellcheck-github-actions@0.35.0 + uses: rojopolis/spellcheck-github-actions@0.36.0 with: config_path: .github/spellcheck-settings.yml task_name: Markdown diff --git a/.github/workflows/test-redis-enterprise.yml b/.github/workflows/test-redis-enterprise.yml index d14766fb..af66fd1f 100644 --- a/.github/workflows/test-redis-enterprise.yml +++ b/.github/workflows/test-redis-enterprise.yml @@ -16,7 +16,7 @@ jobs: fail-fast: false matrix: go-version: [1.21.x] - re-build: ["7.2.4-92"] + re-build: ["7.4.2-54"] steps: - name: Checkout code @@ -36,7 +36,7 @@ jobs: - name: Build cluster working-directory: redis-ee env: - IMAGE: "redislabs/redis-internal:${{ matrix.re-build }}" + IMAGE: "redislabs/redis:${{ matrix.re-build }}" RE_USERNAME: ${{ secrets.RE_USERNAME }} RE_PASS: ${{ secrets.RE_PASS }} RE_CLUSTER_NAME: ${{ secrets.RE_CLUSTER_NAME }} diff --git a/README.md b/README.md index 98edbe21..043d3f0e 100644 --- a/README.md +++ b/README.md @@ -51,8 +51,8 @@ key value NoSQL database that uses RocksDB as storage engine and is compatible w ## Features -- Redis 3 commands except QUIT, MONITOR, and SYNC. -- Automatic connection pooling with +- Redis commands except QUIT and SYNC. +- Automatic connection pooling. - [Pub/Sub](https://redis.uptrace.dev/guide/go-redis-pubsub.html). - [Pipelines and transactions](https://redis.uptrace.dev/guide/go-redis-pipelines.html). - [Scripting](https://redis.uptrace.dev/guide/lua-scripting.html). @@ -161,6 +161,30 @@ func ExampleClient() *redis.Client { ``` + +### Advanced Configuration + +go-redis supports extending the client identification phase to allow projects to send their own custom client identification. + +#### Default Client Identification + +By default, go-redis automatically sends the client library name and version during the connection process. This feature is available in redis-server as of version 7.2. As a result, the command is "fire and forget", meaning it should fail silently, in the case that the redis server does not support this feature. + +#### Disabling Identity Verification + +When connection identity verification is not required or needs to be explicitly disabled, a `DisableIndentity` configuration option exists. In V10 of this library, `DisableIndentity` will become `DisableIdentity` in order to fix the associated typo. + +To disable verification, set the `DisableIndentity` option to `true` in the Redis client options: + +```go +rdb := redis.NewClient(&redis.Options{ + Addr: "localhost:6379", + Password: "", + DB: 0, + DisableIndentity: true, // Disable set-info on connect +}) +``` + ## Contributing Please see [out contributing guidelines](CONTRIBUTING.md) to help us improve this library! diff --git a/bitmap_commands.go b/bitmap_commands.go index 550d7f52..d9fc50dc 100644 --- a/bitmap_commands.go +++ b/bitmap_commands.go @@ -2,6 +2,7 @@ package redis import ( "context" + "errors" ) type BitMapCmdable interface { @@ -37,15 +38,28 @@ func (c cmdable) SetBit(ctx context.Context, key string, offset int64, value int type BitCount struct { Start, End int64 + Unit string // BYTE(default) | BIT } +const BitCountIndexByte string = "BYTE" +const BitCountIndexBit string = "BIT" + func (c cmdable) BitCount(ctx context.Context, key string, bitCount *BitCount) *IntCmd { args := []interface{}{"bitcount", key} if bitCount != nil { + if bitCount.Unit == "" { + bitCount.Unit = "BYTE" + } + if bitCount.Unit != BitCountIndexByte && bitCount.Unit != BitCountIndexBit { + cmd := NewIntCmd(ctx) + cmd.SetErr(errors.New("redis: invalid bitcount index")) + return cmd + } args = append( args, bitCount.Start, bitCount.End, + string(bitCount.Unit), ) } cmd := NewIntCmd(ctx, args...) diff --git a/bitmap_commands_test.go b/bitmap_commands_test.go new file mode 100644 index 00000000..f3cc3205 --- /dev/null +++ b/bitmap_commands_test.go @@ -0,0 +1,98 @@ +package redis_test + +import ( + . "github.com/bsm/ginkgo/v2" + . "github.com/bsm/gomega" + "github.com/redis/go-redis/v9" +) + +type bitCountExpected struct { + Start int64 + End int64 + Expected int64 +} + +var _ = Describe("BitCountBite", func() { + var client *redis.Client + key := "bit_count_test" + + BeforeEach(func() { + client = redis.NewClient(redisOptions()) + Expect(client.FlushDB(ctx).Err()).NotTo(HaveOccurred()) + values := []int{0, 1, 0, 0, 1, 0, 1, 0, 1, 1} + for i, v := range values { + cmd := client.SetBit(ctx, key, int64(i), v) + Expect(cmd.Err()).NotTo(HaveOccurred()) + } + }) + + AfterEach(func() { + Expect(client.Close()).NotTo(HaveOccurred()) + }) + + It("bit count bite", func() { + var expected = []bitCountExpected{ + {0, 0, 0}, + {0, 1, 1}, + {0, 2, 1}, + {0, 3, 1}, + {0, 4, 2}, + {0, 5, 2}, + {0, 6, 3}, + {0, 7, 3}, + {0, 8, 4}, + {0, 9, 5}, + } + + for _, e := range expected { + cmd := client.BitCount(ctx, key, &redis.BitCount{Start: e.Start, End: e.End, Unit: redis.BitCountIndexBit}) + Expect(cmd.Err()).NotTo(HaveOccurred()) + Expect(cmd.Val()).To(Equal(e.Expected)) + } + }) +}) + +var _ = Describe("BitCountByte", func() { + var client *redis.Client + key := "bit_count_test" + + BeforeEach(func() { + client = redis.NewClient(redisOptions()) + Expect(client.FlushDB(ctx).Err()).NotTo(HaveOccurred()) + values := []int{0, 0, 0, 0, 0, 0, 0, 1, 1, 1} + for i, v := range values { + cmd := client.SetBit(ctx, key, int64(i), v) + Expect(cmd.Err()).NotTo(HaveOccurred()) + } + }) + + AfterEach(func() { + Expect(client.Close()).NotTo(HaveOccurred()) + }) + + It("bit count byte", func() { + var expected = []bitCountExpected{ + {0, 0, 1}, + {0, 1, 3}, + } + + for _, e := range expected { + cmd := client.BitCount(ctx, key, &redis.BitCount{Start: e.Start, End: e.End, Unit: redis.BitCountIndexByte}) + Expect(cmd.Err()).NotTo(HaveOccurred()) + Expect(cmd.Val()).To(Equal(e.Expected)) + } + }) + + It("bit count byte with no unit specified", func() { + var expected = []bitCountExpected{ + {0, 0, 1}, + {0, 1, 3}, + } + + for _, e := range expected { + cmd := client.BitCount(ctx, key, &redis.BitCount{Start: e.Start, End: e.End}) + Expect(cmd.Err()).NotTo(HaveOccurred()) + Expect(cmd.Val()).To(Equal(e.Expected)) + } + }) +}) diff --git a/command.go b/command.go index 06ed86ed..9fb9a831 100644 --- a/command.go +++ b/command.go @@ -5310,6 +5310,16 @@ type LibraryInfo struct { LibVer *string } +// WithLibraryName returns a valid LibraryInfo with library name only. +func WithLibraryName(libName string) LibraryInfo { + return LibraryInfo{LibName: &libName} +} + +// WithLibraryVersion returns a valid LibraryInfo with library version only. +func WithLibraryVersion(libVer string) LibraryInfo { + return LibraryInfo{LibVer: &libVer} +} + // ------------------------------------------- type InfoCmd struct { diff --git a/commands.go b/commands.go index 546ebafb..db595944 100644 --- a/commands.go +++ b/commands.go @@ -309,7 +309,7 @@ func (c statefulCmdable) ClientSetInfo(ctx context.Context, info LibraryInfo) *S var cmd *StatusCmd if info.LibName != nil { - libName := fmt.Sprintf("go-redis(%s,%s)", *info.LibName, runtime.Version()) + libName := fmt.Sprintf("go-redis(%s,%s)", *info.LibName, internal.ReplaceSpaces(runtime.Version())) cmd = NewStatusCmd(ctx, "client", "setinfo", "LIB-NAME", libName) } else { cmd = NewStatusCmd(ctx, "client", "setinfo", "LIB-VER", *info.LibVer) diff --git a/commands_test.go b/commands_test.go index 3d2ebf51..d30a9d8b 100644 --- a/commands_test.go +++ b/commands_test.go @@ -248,7 +248,7 @@ var _ = Describe("Commands", func() { // Test setting the libName libName := "go-redis" - libInfo := redis.LibraryInfo{LibName: &libName} + libInfo := redis.WithLibraryName(libName) setInfo := pipe.ClientSetInfo(ctx, libInfo) _, err := pipe.Exec(ctx) @@ -258,7 +258,7 @@ var _ = Describe("Commands", func() { // Test setting the libVer libVer := "vX.x" - libInfo = redis.LibraryInfo{LibVer: &libVer} + libInfo = redis.WithLibraryVersion(libVer) setInfo = pipe.ClientSetInfo(ctx, libInfo) _, err = pipe.Exec(ctx) @@ -676,7 +676,7 @@ var _ = Describe("Commands", func() { Expect(get.Val()).To(Equal("hello")) }) - It("should Object", func() { + It("should Object", Label("NonRedisEnterprise"), func() { start := time.Now() set := client.Set(ctx, "key", "hello", 0) Expect(set.Err()).NotTo(HaveOccurred()) @@ -686,6 +686,11 @@ var _ = Describe("Commands", func() { Expect(refCount.Err()).NotTo(HaveOccurred()) Expect(refCount.Val()).To(Equal(int64(1))) + client.ConfigSet(ctx, "maxmemory-policy", "volatile-lfu") + freq := client.ObjectFreq(ctx, "key") + Expect(freq.Err()).NotTo(HaveOccurred()) + client.ConfigSet(ctx, "maxmemory-policy", "noeviction") // default + err := client.ObjectEncoding(ctx, "key").Err() Expect(err).NotTo(HaveOccurred()) diff --git a/example/del-keys-without-ttl/go.mod b/example/del-keys-without-ttl/go.mod index c37ea4ad..02955cd6 100644 --- a/example/del-keys-without-ttl/go.mod +++ b/example/del-keys-without-ttl/go.mod @@ -5,7 +5,7 @@ go 1.18 replace github.com/redis/go-redis/v9 => ../.. require ( - github.com/redis/go-redis/v9 v9.4.0 + github.com/redis/go-redis/v9 v9.5.1 go.uber.org/zap v1.24.0 ) diff --git a/example/hll/go.mod b/example/hll/go.mod index 37b8b7ca..27c66c27 100644 --- a/example/hll/go.mod +++ b/example/hll/go.mod @@ -4,7 +4,7 @@ go 1.18 replace github.com/redis/go-redis/v9 => ../.. -require github.com/redis/go-redis/v9 v9.4.0 +require github.com/redis/go-redis/v9 v9.5.1 require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect diff --git a/example/lua-scripting/go.mod b/example/lua-scripting/go.mod index 52d39763..03402b00 100644 --- a/example/lua-scripting/go.mod +++ b/example/lua-scripting/go.mod @@ -4,7 +4,7 @@ go 1.18 replace github.com/redis/go-redis/v9 => ../.. -require github.com/redis/go-redis/v9 v9.4.0 +require github.com/redis/go-redis/v9 v9.5.1 require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect diff --git a/example/otel/go.mod b/example/otel/go.mod index 3b2a7a00..f36a69e3 100644 --- a/example/otel/go.mod +++ b/example/otel/go.mod @@ -9,8 +9,8 @@ replace github.com/redis/go-redis/extra/redisotel/v9 => ../../extra/redisotel replace github.com/redis/go-redis/extra/rediscmd/v9 => ../../extra/rediscmd require ( - github.com/redis/go-redis/extra/redisotel/v9 v9.4.0 - github.com/redis/go-redis/v9 v9.4.0 + github.com/redis/go-redis/extra/redisotel/v9 v9.5.1 + github.com/redis/go-redis/v9 v9.5.1 github.com/uptrace/uptrace-go v1.21.0 go.opentelemetry.io/otel v1.21.0 ) @@ -23,7 +23,7 @@ require ( github.com/go-logr/stdr v1.2.2 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect - github.com/redis/go-redis/extra/rediscmd/v9 v9.4.0 // indirect + github.com/redis/go-redis/extra/rediscmd/v9 v9.5.1 // indirect go.opentelemetry.io/contrib/instrumentation/runtime v0.46.1 // indirect go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.17.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.43.0 // indirect diff --git a/example/redis-bloom/go.mod b/example/redis-bloom/go.mod index d7af7cfa..f152ff26 100644 --- a/example/redis-bloom/go.mod +++ b/example/redis-bloom/go.mod @@ -4,7 +4,7 @@ go 1.18 replace github.com/redis/go-redis/v9 => ../.. -require github.com/redis/go-redis/v9 v9.4.0 +require github.com/redis/go-redis/v9 v9.5.1 require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect diff --git a/example/scan-struct/go.mod b/example/scan-struct/go.mod index 84d8e4a6..6bc8a69c 100644 --- a/example/scan-struct/go.mod +++ b/example/scan-struct/go.mod @@ -6,7 +6,7 @@ replace github.com/redis/go-redis/v9 => ../.. require ( github.com/davecgh/go-spew v1.1.1 - github.com/redis/go-redis/v9 v9.4.0 + github.com/redis/go-redis/v9 v9.5.1 ) require ( diff --git a/example/scan-struct/main.go b/example/scan-struct/main.go index 9d270b53..cc877b84 100644 --- a/example/scan-struct/main.go +++ b/example/scan-struct/main.go @@ -11,6 +11,7 @@ import ( type Model struct { Str1 string `redis:"str1"` Str2 string `redis:"str2"` + Bytes []byte `redis:"bytes"` Int int `redis:"int"` Bool bool `redis:"bool"` Ignored struct{} `redis:"-"` @@ -22,6 +23,7 @@ func main() { rdb := redis.NewClient(&redis.Options{ Addr: ":6379", }) + _ = rdb.FlushDB(ctx).Err() // Set some fields. if _, err := rdb.Pipelined(ctx, func(rdb redis.Pipeliner) error { @@ -29,6 +31,7 @@ func main() { rdb.HSet(ctx, "key", "str2", "world") rdb.HSet(ctx, "key", "int", 123) rdb.HSet(ctx, "key", "bool", 1) + rdb.HSet(ctx, "key", "bytes", []byte("this is bytes !")) return nil }); err != nil { panic(err) @@ -47,5 +50,28 @@ func main() { } spew.Dump(model1) + // Output: + // (main.Model) { + // Str1: (string) (len=5) "hello", + // Str2: (string) (len=5) "world", + // Bytes: ([]uint8) (len=15 cap=16) { + // 00000000 74 68 69 73 20 69 73 20 62 79 74 65 73 20 21 |this is bytes !| + // }, + // Int: (int) 123, + // Bool: (bool) true, + // Ignored: (struct {}) { + // } + // } + spew.Dump(model2) + // Output: + // (main.Model) { + // Str1: (string) (len=5) "hello", + // Str2: (string) "", + // Bytes: ([]uint8) , + // Int: (int) 123, + // Bool: (bool) false, + // Ignored: (struct {}) { + // } + // } } diff --git a/example_test.go b/example_test.go index 67559fce..62aa8cb5 100644 --- a/example_test.go +++ b/example_test.go @@ -154,7 +154,7 @@ func ExampleClient() { // missing_key does not exist } -func ExampleConn() { +func ExampleConn_name() { conn := rdb.Conn() err := conn.ClientSetName(ctx, "foobar").Err() @@ -175,6 +175,28 @@ func ExampleConn() { // Output: foobar } +func ExampleConn_client_setInfo_libraryVersion() { + conn := rdb.Conn() + + err := conn.ClientSetInfo(ctx, redis.WithLibraryVersion("1.2.3")).Err() + if err != nil { + panic(err) + } + + // Open other connections. + for i := 0; i < 10; i++ { + go rdb.Ping(ctx) + } + + s, err := conn.ClientInfo(ctx).Result() + if err != nil { + panic(err) + } + + fmt.Println(s.LibVer) + // Output: 1.2.3 +} + func ExampleClient_Set() { // Last argument is expiration. Zero means the key has no // expiration time. diff --git a/extra/rediscensus/go.mod b/extra/rediscensus/go.mod index adf5a9f6..96dc44af 100644 --- a/extra/rediscensus/go.mod +++ b/extra/rediscensus/go.mod @@ -8,7 +8,7 @@ replace github.com/redis/go-redis/extra/rediscmd/v9 => ../rediscmd require ( github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/redis/go-redis/extra/rediscmd/v9 v9.4.0 - github.com/redis/go-redis/v9 v9.4.0 + github.com/redis/go-redis/extra/rediscmd/v9 v9.5.1 + github.com/redis/go-redis/v9 v9.5.1 go.opencensus.io v0.24.0 ) diff --git a/extra/rediscmd/go.mod b/extra/rediscmd/go.mod index f27efd0b..eb38857d 100644 --- a/extra/rediscmd/go.mod +++ b/extra/rediscmd/go.mod @@ -7,5 +7,5 @@ replace github.com/redis/go-redis/v9 => ../.. require ( github.com/bsm/ginkgo/v2 v2.7.0 github.com/bsm/gomega v1.26.0 - github.com/redis/go-redis/v9 v9.4.0 + github.com/redis/go-redis/v9 v9.5.1 ) diff --git a/extra/redisotel/go.mod b/extra/redisotel/go.mod index 987953b5..a6f00357 100644 --- a/extra/redisotel/go.mod +++ b/extra/redisotel/go.mod @@ -7,8 +7,8 @@ replace github.com/redis/go-redis/v9 => ../.. replace github.com/redis/go-redis/extra/rediscmd/v9 => ../rediscmd require ( - github.com/redis/go-redis/extra/rediscmd/v9 v9.4.0 - github.com/redis/go-redis/v9 v9.4.0 + github.com/redis/go-redis/extra/rediscmd/v9 v9.5.1 + github.com/redis/go-redis/v9 v9.5.1 go.opentelemetry.io/otel v1.16.0 go.opentelemetry.io/otel/metric v1.16.0 go.opentelemetry.io/otel/sdk v1.16.0 diff --git a/extra/redisprometheus/go.mod b/extra/redisprometheus/go.mod index 141c94e1..8f8f1da7 100644 --- a/extra/redisprometheus/go.mod +++ b/extra/redisprometheus/go.mod @@ -6,7 +6,7 @@ replace github.com/redis/go-redis/v9 => ../.. require ( github.com/prometheus/client_golang v1.14.0 - github.com/redis/go-redis/v9 v9.4.0 + github.com/redis/go-redis/v9 v9.5.1 ) require ( diff --git a/generic_commands.go b/generic_commands.go index bf1fb47d..dc6c3fe0 100644 --- a/generic_commands.go +++ b/generic_commands.go @@ -19,6 +19,7 @@ type GenericCmdable interface { 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 + ObjectFreq(ctx context.Context, key string) *IntCmd ObjectRefCount(ctx context.Context, key string) *IntCmd ObjectEncoding(ctx context.Context, key string) *StringCmd ObjectIdleTime(ctx context.Context, key string) *DurationCmd @@ -159,6 +160,12 @@ func (c cmdable) Move(ctx context.Context, key string, db int) *BoolCmd { return cmd } +func (c cmdable) ObjectFreq(ctx context.Context, key string) *IntCmd { + cmd := NewIntCmd(ctx, "object", "freq", key) + _ = c(ctx, cmd) + return cmd +} + func (c cmdable) ObjectRefCount(ctx context.Context, key string) *IntCmd { cmd := NewIntCmd(ctx, "object", "refcount", key) _ = c(ctx, cmd) diff --git a/internal/pool/pool.go b/internal/pool/pool.go index 986c05d0..2125f3e1 100644 --- a/internal/pool/pool.go +++ b/internal/pool/pool.go @@ -168,9 +168,12 @@ func (p *ConnPool) newConn(ctx context.Context, pooled bool) (*Conn, error) { return nil, ErrClosed } + p.connsMu.Lock() if p.cfg.MaxActiveConns > 0 && p.poolSize >= p.cfg.MaxActiveConns { + p.connsMu.Unlock() return nil, ErrPoolExhausted } + p.connsMu.Unlock() cn, err := p.dialConn(ctx, pooled) if err != nil { @@ -180,6 +183,11 @@ func (p *ConnPool) newConn(ctx context.Context, pooled bool) (*Conn, error) { p.connsMu.Lock() defer p.connsMu.Unlock() + if p.cfg.MaxActiveConns > 0 && p.poolSize >= p.cfg.MaxActiveConns { + _ = cn.Close() + return nil, ErrPoolExhausted + } + p.conns = append(p.conns, cn) if pooled { // If pool is full remove the cn on next Put. diff --git a/internal/util.go b/internal/util.go index 77ca4ee1..ed81ad7a 100644 --- a/internal/util.go +++ b/internal/util.go @@ -2,6 +2,7 @@ package internal import ( "context" + "strings" "time" "github.com/redis/go-redis/v9/internal/util" @@ -44,3 +45,22 @@ func isLower(s string) bool { } return true } + +func ReplaceSpaces(s string) string { + // Pre-allocate a builder with the same length as s to minimize allocations. + // This is a basic optimization; adjust the initial size based on your use case. + var builder strings.Builder + builder.Grow(len(s)) + + for _, char := range s { + if char == ' ' { + // Replace space with a hyphen. + builder.WriteRune('-') + } else { + // Copy the character as-is. + builder.WriteRune(char) + } + } + + return builder.String() +} diff --git a/internal/util_test.go b/internal/util_test.go new file mode 100644 index 00000000..f090ebaa --- /dev/null +++ b/internal/util_test.go @@ -0,0 +1,53 @@ +package internal + +import ( + "strings" + "testing" + + . "github.com/bsm/ginkgo/v2" + . "github.com/bsm/gomega" +) + +func BenchmarkToLowerStd(b *testing.B) { + str := "AaBbCcDdEeFfGgHhIiJjKk" + for i := 0; i < b.N; i++ { + _ = strings.ToLower(str) + } +} + +// util.ToLower is 3x faster than strings.ToLower. +func BenchmarkToLowerInternal(b *testing.B) { + str := "AaBbCcDdEeFfGgHhIiJjKk" + for i := 0; i < b.N; i++ { + _ = ToLower(str) + } +} + +func TestToLower(t *testing.T) { + It("toLower", func() { + str := "AaBbCcDdEeFfGg" + Expect(ToLower(str)).To(Equal(strings.ToLower(str))) + + str = "ABCDE" + Expect(ToLower(str)).To(Equal(strings.ToLower(str))) + + str = "ABCDE" + Expect(ToLower(str)).To(Equal(strings.ToLower(str))) + + str = "abced" + Expect(ToLower(str)).To(Equal(strings.ToLower(str))) + }) +} + +func TestIsLower(t *testing.T) { + It("isLower", func() { + str := "AaBbCcDdEeFfGg" + Expect(isLower(str)).To(BeFalse()) + + str = "ABCDE" + Expect(isLower(str)).To(BeFalse()) + + str = "abcdefg" + Expect(isLower(str)).To(BeTrue()) + }) +} diff --git a/osscluster.go b/osscluster.go index 9e5eb046..17f98d9d 100644 --- a/osscluster.go +++ b/osscluster.go @@ -62,9 +62,10 @@ type ClusterOptions struct { OnConnect func(ctx context.Context, cn *Conn) error - Protocol int - Username string - Password string + Protocol int + Username string + Password string + CredentialsProvider func() (username string, password string) MaxRetries int MinRetryBackoff time.Duration @@ -271,9 +272,10 @@ func (opt *ClusterOptions) clientOptions() *Options { Dialer: opt.Dialer, OnConnect: opt.OnConnect, - Protocol: opt.Protocol, - Username: opt.Username, - Password: opt.Password, + Protocol: opt.Protocol, + Username: opt.Username, + Password: opt.Password, + CredentialsProvider: opt.CredentialsProvider, MaxRetries: opt.MaxRetries, MinRetryBackoff: opt.MinRetryBackoff, diff --git a/package.json b/package.json deleted file mode 100644 index 1a690047..00000000 --- a/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "redis", - "version": "9.4.0", - "main": "index.js", - "repository": "git@github.com:redis/go-redis.git", - "author": "Vladimir Mihailenco ", - "license": "BSD-2-clause" -} diff --git a/redis.go b/redis.go index 4dd862b8..d25a0d31 100644 --- a/redis.go +++ b/redis.go @@ -312,17 +312,7 @@ func (c *baseClient) initConn(ctx context.Context, cn *pool.Conn) error { // difficult to rely on error strings to determine all results. return err } - if !c.opt.DisableIndentity { - libName := "" - libVer := Version() - if c.opt.IdentitySuffix != "" { - libName = c.opt.IdentitySuffix - } - libInfo := LibraryInfo{LibName: &libName} - conn.ClientSetInfo(ctx, libInfo) - libInfo = LibraryInfo{LibVer: &libVer} - conn.ClientSetInfo(ctx, libInfo) - } + _, err := conn.Pipelined(ctx, func(pipe Pipeliner) error { if !auth && password != "" { if username != "" { @@ -350,6 +340,18 @@ func (c *baseClient) initConn(ctx context.Context, cn *pool.Conn) error { return err } + if !c.opt.DisableIndentity { + libName := "" + libVer := Version() + if c.opt.IdentitySuffix != "" { + libName = c.opt.IdentitySuffix + } + p := conn.Pipeline() + p.ClientSetInfo(ctx, WithLibraryName(libName)) + p.ClientSetInfo(ctx, WithLibraryVersion(libVer)) + _, _ = p.Exec(ctx) + } + if c.opt.OnConnect != nil { return c.opt.OnConnect(ctx, conn) } diff --git a/sentinel.go b/sentinel.go index 9ace0886..188f8849 100644 --- a/sentinel.go +++ b/sentinel.go @@ -153,6 +153,9 @@ func (opt *FailoverOptions) sentinelOptions(addr string) *Options { ConnMaxLifetime: opt.ConnMaxLifetime, TLSConfig: opt.TLSConfig, + + DisableIndentity: opt.DisableIndentity, + IdentitySuffix: opt.IdentitySuffix, } } @@ -190,6 +193,9 @@ func (opt *FailoverOptions) clusterOptions() *ClusterOptions { ConnMaxLifetime: opt.ConnMaxLifetime, TLSConfig: opt.TLSConfig, + + DisableIndentity: opt.DisableIndentity, + IdentitySuffix: opt.IdentitySuffix, } } diff --git a/sentinel_test.go b/sentinel_test.go index 4c013f05..8bc6c578 100644 --- a/sentinel_test.go +++ b/sentinel_test.go @@ -324,7 +324,7 @@ var _ = Describe("SentinelAclAuth", func() { 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", + "+command", "+hello", "+ping", "+client|setinfo", "+role", "+sentinel|get-master-addr-by-name", "+sentinel|master", "+sentinel|myid", "+sentinel|replicas", "+sentinel|sentinels") for _, process := range sentinels() { diff --git a/version.go b/version.go index 92f49820..e2c7f3e7 100644 --- a/version.go +++ b/version.go @@ -2,5 +2,5 @@ package redis // Version is the current release version. func Version() string { - return "9.4.0" + return "9.5.1" }