mirror of
https://github.com/redis/go-redis.git
synced 2025-04-16 09:23:06 +03:00
Merge remote-tracking branch 'origin/master' into ndyakov/token-based-auth
This commit is contained in:
commit
9ef438bd15
2
.github/actions/run-tests/action.yml
vendored
2
.github/actions/run-tests/action.yml
vendored
@ -25,7 +25,7 @@ runs:
|
||||
|
||||
# Mapping of redis version to redis testing containers
|
||||
declare -A redis_version_mapping=(
|
||||
["8.0-M03"]="8.0-M04-pre"
|
||||
["8.0-M05"]="8.0-M05-pre"
|
||||
["7.4.2"]="rs-7.4.0-v2"
|
||||
["7.2.7"]="rs-7.2.0-v14"
|
||||
)
|
||||
|
6
.github/workflows/build.yml
vendored
6
.github/workflows/build.yml
vendored
@ -18,7 +18,7 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
redis-version:
|
||||
- "8.0-M03" # 8.0 milestone 4
|
||||
- "8.0-M05" # 8.0 milestone 5
|
||||
- "7.4.2" # should use redis stack 7.4
|
||||
go-version:
|
||||
- "1.23.x"
|
||||
@ -43,7 +43,7 @@ jobs:
|
||||
|
||||
# Mapping of redis version to redis testing containers
|
||||
declare -A redis_version_mapping=(
|
||||
["8.0-M03"]="8.0-M04-pre"
|
||||
["8.0-M05"]="8.0-M05-pre"
|
||||
["7.4.2"]="rs-7.4.0-v2"
|
||||
)
|
||||
if [[ -v redis_version_mapping[$REDIS_VERSION] ]]; then
|
||||
@ -72,7 +72,7 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
redis-version:
|
||||
- "8.0-M03" # 8.0 milestone 4
|
||||
- "8.0-M05" # 8.0 milestone 5
|
||||
- "7.4.2" # should use redis stack 7.4
|
||||
- "7.2.7" # should redis stack 7.2
|
||||
go-version:
|
||||
|
5
.github/workflows/golangci-lint.yml
vendored
5
.github/workflows/golangci-lint.yml
vendored
@ -21,4 +21,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v6.5.0
|
||||
uses: golangci/golangci-lint-action@v6.5.2
|
||||
with:
|
||||
verify: false # disable verifying the configuration since golangci is currently introducing breaking changes in the configuration
|
||||
|
||||
|
2
Makefile
2
Makefile
@ -17,7 +17,7 @@ test.ci:
|
||||
(cd "$${dir}" && \
|
||||
go mod tidy -compat=1.18 && \
|
||||
go vet && \
|
||||
go test -coverprofile=coverage.txt -covermode=atomic ./... -race); \
|
||||
go test -v -coverprofile=coverage.txt -covermode=atomic ./... -race); \
|
||||
done
|
||||
cd internal/customvet && go build .
|
||||
go vet -vettool ./internal/customvet/customvet
|
||||
|
28
README.md
28
README.md
@ -6,13 +6,7 @@
|
||||
[](https://codecov.io/github/redis/go-redis)
|
||||
[](https://discord.gg/rWtp5Aj)
|
||||
|
||||
> go-redis is brought to you by :star: [**uptrace/uptrace**](https://github.com/uptrace/uptrace).
|
||||
> Uptrace is an open-source APM tool that supports distributed tracing, metrics, and logs. You can
|
||||
> use it to monitor applications and set up automatic alerts to receive notifications via email,
|
||||
> Slack, Telegram, and others.
|
||||
>
|
||||
> See [OpenTelemetry](https://github.com/redis/go-redis/tree/master/example/otel) example which
|
||||
> demonstrates how you can use Uptrace to monitor go-redis.
|
||||
> go-redis is the official Redis client library for the Go programming language. It offers a straightforward interface for interacting with Redis servers.
|
||||
|
||||
## Supported versions
|
||||
|
||||
@ -184,16 +178,18 @@ By default, go-redis automatically sends the client library name and version dur
|
||||
|
||||
#### 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.
|
||||
When connection identity verification is not required or needs to be explicitly disabled, a `DisableIdentity` configuration option exists.
|
||||
Initially there was a typo and the option was named `DisableIndentity` instead of `DisableIdentity`. The misspelled option is marked as Deprecated and will be removed in V10 of this library.
|
||||
Although both options will work at the moment, the correct option is `DisableIdentity`. The deprecated option will be removed in V10 of this library, so please use the correct option name to avoid any issues.
|
||||
|
||||
To disable verification, set the `DisableIndentity` option to `true` in the Redis client options:
|
||||
To disable verification, set the `DisableIdentity` 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
|
||||
DisableIdentity: true, // Disable set-info on connect
|
||||
})
|
||||
```
|
||||
|
||||
@ -215,6 +211,10 @@ res1, err := client.FTSearchWithArgs(ctx, "txt", "foo bar", &redis.FTSearchOptio
|
||||
val1 := client.FTSearchWithArgs(ctx, "txt", "foo bar", &redis.FTSearchOptions{}).RawVal()
|
||||
```
|
||||
|
||||
#### Redis-Search Default Dialect
|
||||
|
||||
In the Redis-Search module, **the default dialect is 2**. If needed, you can explicitly specify a different dialect using the appropriate configuration in your queries.
|
||||
|
||||
## Contributing
|
||||
|
||||
Please see [out contributing guidelines](CONTRIBUTING.md) to help us improve this library!
|
||||
@ -297,6 +297,14 @@ REDIS_PORT=9999 go test <your options>
|
||||
|
||||
## Contributors
|
||||
|
||||
> The go-redis project was originally initiated by :star: [**uptrace/uptrace**](https://github.com/uptrace/uptrace).
|
||||
> Uptrace is an open-source APM tool that supports distributed tracing, metrics, and logs. You can
|
||||
> use it to monitor applications and set up automatic alerts to receive notifications via email,
|
||||
> Slack, Telegram, and others.
|
||||
>
|
||||
> See [OpenTelemetry](https://github.com/redis/go-redis/tree/master/example/otel) example which
|
||||
> demonstrates how you can use Uptrace to monitor go-redis.
|
||||
|
||||
Thanks to all the people who already contributed!
|
||||
|
||||
<a href="https://github.com/redis/go-redis/graphs/contributors">
|
||||
|
@ -30,7 +30,7 @@ func NewClientStub(resp []byte) *ClientStub {
|
||||
Dialer: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
return stub.stubConn(initHello), nil
|
||||
},
|
||||
DisableIndentity: true,
|
||||
DisableIdentity: true,
|
||||
})
|
||||
return stub
|
||||
}
|
||||
@ -46,7 +46,7 @@ func NewClusterClientStub(resp []byte) *ClientStub {
|
||||
Dialer: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
return stub.stubConn(initHello), nil
|
||||
},
|
||||
DisableIndentity: true,
|
||||
DisableIdentity: true,
|
||||
|
||||
ClusterSlots: func(_ context.Context) ([]ClusterSlot, error) {
|
||||
return []ClusterSlot{
|
||||
|
143
commands_test.go
143
commands_test.go
@ -2659,7 +2659,6 @@ var _ = Describe("Commands", func() {
|
||||
Expect(res).To(Equal([]int64{1, 1, -2}))
|
||||
})
|
||||
|
||||
|
||||
It("should HPExpire", Label("hash-expiration", "NonRedisEnterprise"), func() {
|
||||
SkipBeforeRedisVersion(7.4, "doesn't work with older redis stack images")
|
||||
res, err := client.HPExpire(ctx, "no_such_key", 10*time.Second, "field1", "field2", "field3").Result()
|
||||
@ -2812,6 +2811,148 @@ var _ = Describe("Commands", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res[0]).To(BeNumerically("~", 10*time.Second.Milliseconds(), 1))
|
||||
})
|
||||
|
||||
It("should HGETDEL", Label("hash", "HGETDEL"), func() {
|
||||
SkipBeforeRedisVersion(7.9, "requires Redis 8.x")
|
||||
|
||||
err := client.HSet(ctx, "myhash", "f1", "val1", "f2", "val2", "f3", "val3").Err()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Execute HGETDEL on fields f1 and f2.
|
||||
res, err := client.HGetDel(ctx, "myhash", "f1", "f2").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
// Expect the returned values for f1 and f2.
|
||||
Expect(res).To(Equal([]string{"val1", "val2"}))
|
||||
|
||||
// Verify that f1 and f2 have been deleted, while f3 remains.
|
||||
remaining, err := client.HMGet(ctx, "myhash", "f1", "f2", "f3").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(remaining[0]).To(BeNil())
|
||||
Expect(remaining[1]).To(BeNil())
|
||||
Expect(remaining[2]).To(Equal("val3"))
|
||||
})
|
||||
|
||||
It("should return nil responses for HGETDEL on non-existent key", Label("hash", "HGETDEL"), func() {
|
||||
SkipBeforeRedisVersion(7.9, "requires Redis 8.x")
|
||||
// HGETDEL on a key that does not exist.
|
||||
res, err := client.HGetDel(ctx, "nonexistent", "f1", "f2").Result()
|
||||
Expect(err).To(BeNil())
|
||||
Expect(res).To(Equal([]string{"", ""}))
|
||||
})
|
||||
|
||||
// -----------------------------
|
||||
// HGETEX with various TTL options
|
||||
// -----------------------------
|
||||
It("should HGETEX with EX option", Label("hash", "HGETEX"), func() {
|
||||
SkipBeforeRedisVersion(7.9, "requires Redis 8.x")
|
||||
|
||||
err := client.HSet(ctx, "myhash", "f1", "val1", "f2", "val2").Err()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Call HGETEX with EX option and 60 seconds TTL.
|
||||
opt := redis.HGetEXOptions{
|
||||
ExpirationType: redis.HGetEXExpirationEX,
|
||||
ExpirationVal: 60,
|
||||
}
|
||||
res, err := client.HGetEXWithArgs(ctx, "myhash", &opt, "f1", "f2").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(Equal([]string{"val1", "val2"}))
|
||||
})
|
||||
|
||||
It("should HGETEX with PERSIST option", Label("hash", "HGETEX"), func() {
|
||||
SkipBeforeRedisVersion(7.9, "requires Redis 8.x")
|
||||
|
||||
err := client.HSet(ctx, "myhash", "f1", "val1", "f2", "val2").Err()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Call HGETEX with PERSIST (no TTL value needed).
|
||||
opt := redis.HGetEXOptions{ExpirationType: redis.HGetEXExpirationPERSIST}
|
||||
res, err := client.HGetEXWithArgs(ctx, "myhash", &opt, "f1", "f2").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(Equal([]string{"val1", "val2"}))
|
||||
})
|
||||
|
||||
It("should HGETEX with EXAT option", Label("hash", "HGETEX"), func() {
|
||||
SkipBeforeRedisVersion(7.9, "requires Redis 8.x")
|
||||
|
||||
err := client.HSet(ctx, "myhash", "f1", "val1", "f2", "val2").Err()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Set expiration at a specific Unix timestamp (60 seconds from now).
|
||||
expireAt := time.Now().Add(60 * time.Second).Unix()
|
||||
opt := redis.HGetEXOptions{
|
||||
ExpirationType: redis.HGetEXExpirationEXAT,
|
||||
ExpirationVal: expireAt,
|
||||
}
|
||||
res, err := client.HGetEXWithArgs(ctx, "myhash", &opt, "f1", "f2").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(Equal([]string{"val1", "val2"}))
|
||||
})
|
||||
|
||||
// -----------------------------
|
||||
// HSETEX with FNX/FXX options
|
||||
// -----------------------------
|
||||
It("should HSETEX with FNX condition", Label("hash", "HSETEX"), func() {
|
||||
SkipBeforeRedisVersion(7.9, "requires Redis 8.x")
|
||||
|
||||
opt := redis.HSetEXOptions{
|
||||
Condition: redis.HSetEXFNX,
|
||||
ExpirationType: redis.HSetEXExpirationEX,
|
||||
ExpirationVal: 60,
|
||||
}
|
||||
res, err := client.HSetEXWithArgs(ctx, "myhash", &opt, "f1", "val1").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(Equal(int64(1)))
|
||||
|
||||
opt = redis.HSetEXOptions{
|
||||
Condition: redis.HSetEXFNX,
|
||||
ExpirationType: redis.HSetEXExpirationEX,
|
||||
ExpirationVal: 60,
|
||||
}
|
||||
res, err = client.HSetEXWithArgs(ctx, "myhash", &opt, "f1", "val2").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(Equal(int64(0)))
|
||||
})
|
||||
|
||||
It("should HSETEX with FXX condition", Label("hash", "HSETEX"), func() {
|
||||
SkipBeforeRedisVersion(7.9, "requires Redis 8.x")
|
||||
|
||||
err := client.HSet(ctx, "myhash", "f2", "val1").Err()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
opt := redis.HSetEXOptions{
|
||||
Condition: redis.HSetEXFXX,
|
||||
ExpirationType: redis.HSetEXExpirationEX,
|
||||
ExpirationVal: 60,
|
||||
}
|
||||
res, err := client.HSetEXWithArgs(ctx, "myhash", &opt, "f2", "val2").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(Equal(int64(1)))
|
||||
opt = redis.HSetEXOptions{
|
||||
Condition: redis.HSetEXFXX,
|
||||
ExpirationType: redis.HSetEXExpirationEX,
|
||||
ExpirationVal: 60,
|
||||
}
|
||||
res, err = client.HSetEXWithArgs(ctx, "myhash", &opt, "f3", "val3").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(Equal(int64(0)))
|
||||
})
|
||||
|
||||
It("should HSETEX with multiple field operations", Label("hash", "HSETEX"), func() {
|
||||
SkipBeforeRedisVersion(7.9, "requires Redis 8.x")
|
||||
|
||||
opt := redis.HSetEXOptions{
|
||||
ExpirationType: redis.HSetEXExpirationEX,
|
||||
ExpirationVal: 60,
|
||||
}
|
||||
res, err := client.HSetEXWithArgs(ctx, "myhash", &opt, "f1", "val1", "f2", "val2").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(Equal(int64(1)))
|
||||
|
||||
values, err := client.HMGet(ctx, "myhash", "f1", "f2").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(values).To(Equal([]interface{}{"val1", "val2"}))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("hyperloglog", func() {
|
||||
|
@ -3,6 +3,7 @@
|
||||
services:
|
||||
redis:
|
||||
image: ${CLIENT_LIBS_TEST_IMAGE:-redislabs/client-libs-test:rs-7.4.0-v2}
|
||||
platform: linux/amd64
|
||||
container_name: redis-standalone
|
||||
environment:
|
||||
- TLS_ENABLED=yes
|
||||
@ -23,6 +24,7 @@ services:
|
||||
|
||||
osscluster:
|
||||
image: ${CLIENT_LIBS_TEST_IMAGE:-redislabs/client-libs-test:rs-7.4.0-v2}
|
||||
platform: linux/amd64
|
||||
container_name: redis-osscluster
|
||||
environment:
|
||||
- NODES=6
|
||||
@ -39,6 +41,7 @@ services:
|
||||
|
||||
sentinel-cluster:
|
||||
image: ${CLIENT_LIBS_TEST_IMAGE:-redislabs/client-libs-test:rs-7.4.0-v2}
|
||||
platform: linux/amd64
|
||||
container_name: redis-sentinel-cluster
|
||||
network_mode: "host"
|
||||
environment:
|
||||
@ -58,6 +61,7 @@ services:
|
||||
|
||||
sentinel:
|
||||
image: ${CLIENT_LIBS_TEST_IMAGE:-redislabs/client-libs-test:rs-7.4.0-v2}
|
||||
platform: linux/amd64
|
||||
container_name: redis-sentinel
|
||||
depends_on:
|
||||
- sentinel-cluster
|
||||
@ -81,6 +85,7 @@ services:
|
||||
|
||||
ring-cluster:
|
||||
image: ${CLIENT_LIBS_TEST_IMAGE:-redislabs/client-libs-test:rs-7.4.0-v2}
|
||||
platform: linux/amd64
|
||||
container_name: redis-ring-cluster
|
||||
environment:
|
||||
- NODES=3
|
||||
|
102
doctests/cmds_set_test.go
Normal file
102
doctests/cmds_set_test.go
Normal file
@ -0,0 +1,102 @@
|
||||
// EXAMPLE: cmds_set
|
||||
// HIDE_START
|
||||
package example_commands_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
// HIDE_END
|
||||
|
||||
func ExampleClient_sadd_cmd() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
rdb.Del(ctx, "myset")
|
||||
// REMOVE_END
|
||||
|
||||
// STEP_START sadd
|
||||
sAddResult1, err := rdb.SAdd(ctx, "myset", "Hello").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(sAddResult1) // >>> 1
|
||||
|
||||
sAddResult2, err := rdb.SAdd(ctx, "myset", "World").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(sAddResult2) // >>> 1
|
||||
|
||||
sAddResult3, err := rdb.SAdd(ctx, "myset", "World").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(sAddResult3) // >>> 0
|
||||
|
||||
sMembersResult, err := rdb.SMembers(ctx, "myset").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(sMembersResult) // >>> [Hello World]
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// 1
|
||||
// 1
|
||||
// 0
|
||||
// [Hello World]
|
||||
}
|
||||
|
||||
func ExampleClient_smembers_cmd() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
rdb.Del(ctx, "myset")
|
||||
// REMOVE_END
|
||||
|
||||
// STEP_START smembers
|
||||
sAddResult, err := rdb.SAdd(ctx, "myset", "Hello", "World").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(sAddResult) // >>> 2
|
||||
|
||||
sMembersResult, err := rdb.SMembers(ctx, "myset").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(sMembersResult) // >>> [Hello World]
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// 2
|
||||
// [Hello World]
|
||||
}
|
6
error.go
6
error.go
@ -53,6 +53,9 @@ func shouldRetry(err error, retryTimeout bool) bool {
|
||||
return true
|
||||
case nil, context.Canceled, context.DeadlineExceeded:
|
||||
return false
|
||||
case pool.ErrPoolTimeout:
|
||||
// connection pool timeout, increase retries. #3289
|
||||
return true
|
||||
}
|
||||
|
||||
if v, ok := err.(timeoutError); ok {
|
||||
@ -72,6 +75,9 @@ func shouldRetry(err error, retryTimeout bool) bool {
|
||||
if strings.HasPrefix(s, "READONLY ") {
|
||||
return true
|
||||
}
|
||||
if strings.HasPrefix(s, "MASTERDOWN ") {
|
||||
return true
|
||||
}
|
||||
if strings.HasPrefix(s, "CLUSTERDOWN ") {
|
||||
return true
|
||||
}
|
||||
|
65
error_test.go
Normal file
65
error_test.go
Normal file
@ -0,0 +1,65 @@
|
||||
package redis_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
. "github.com/bsm/ginkgo/v2"
|
||||
. "github.com/bsm/gomega"
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
type testTimeout struct {
|
||||
timeout bool
|
||||
}
|
||||
|
||||
func (t testTimeout) Timeout() bool {
|
||||
return t.timeout
|
||||
}
|
||||
|
||||
func (t testTimeout) Error() string {
|
||||
return "test timeout"
|
||||
}
|
||||
|
||||
var _ = Describe("error", func() {
|
||||
BeforeEach(func() {
|
||||
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
|
||||
})
|
||||
|
||||
It("should retry", func() {
|
||||
data := map[error]bool{
|
||||
io.EOF: true,
|
||||
io.ErrUnexpectedEOF: true,
|
||||
nil: false,
|
||||
context.Canceled: false,
|
||||
context.DeadlineExceeded: false,
|
||||
redis.ErrPoolTimeout: true,
|
||||
errors.New("ERR max number of clients reached"): true,
|
||||
errors.New("LOADING Redis is loading the dataset in memory"): true,
|
||||
errors.New("READONLY You can't write against a read only replica"): true,
|
||||
errors.New("CLUSTERDOWN The cluster is down"): true,
|
||||
errors.New("TRYAGAIN Command cannot be processed, please try again"): true,
|
||||
errors.New("other"): false,
|
||||
}
|
||||
|
||||
for err, expected := range data {
|
||||
Expect(redis.ShouldRetry(err, false)).To(Equal(expected))
|
||||
Expect(redis.ShouldRetry(err, true)).To(Equal(expected))
|
||||
}
|
||||
})
|
||||
|
||||
It("should retry timeout", func() {
|
||||
t1 := testTimeout{timeout: true}
|
||||
Expect(redis.ShouldRetry(t1, true)).To(Equal(true))
|
||||
Expect(redis.ShouldRetry(t1, false)).To(Equal(false))
|
||||
|
||||
t2 := testTimeout{timeout: false}
|
||||
Expect(redis.ShouldRetry(t2, true)).To(Equal(true))
|
||||
Expect(redis.ShouldRetry(t2, false)).To(Equal(true))
|
||||
})
|
||||
})
|
@ -5,7 +5,7 @@ go 1.18
|
||||
replace github.com/redis/go-redis/v9 => ../..
|
||||
|
||||
require (
|
||||
github.com/redis/go-redis/v9 v9.7.1
|
||||
github.com/redis/go-redis/v9 v9.7.3
|
||||
go.uber.org/zap v1.24.0
|
||||
)
|
||||
|
||||
|
@ -4,7 +4,7 @@ go 1.18
|
||||
|
||||
replace github.com/redis/go-redis/v9 => ../..
|
||||
|
||||
require github.com/redis/go-redis/v9 v9.7.1
|
||||
require github.com/redis/go-redis/v9 v9.7.3
|
||||
|
||||
require (
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
|
@ -1,7 +1,5 @@
|
||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
|
@ -4,7 +4,7 @@ go 1.18
|
||||
|
||||
replace github.com/redis/go-redis/v9 => ../..
|
||||
|
||||
require github.com/redis/go-redis/v9 v9.7.1
|
||||
require github.com/redis/go-redis/v9 v9.7.3
|
||||
|
||||
require (
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
|
@ -1,6 +1,8 @@
|
||||
module github.com/redis/go-redis/example/otel
|
||||
|
||||
go 1.19
|
||||
go 1.23.0
|
||||
|
||||
toolchain go1.24.1
|
||||
|
||||
replace github.com/redis/go-redis/v9 => ../..
|
||||
|
||||
@ -9,8 +11,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.7.1
|
||||
github.com/redis/go-redis/v9 v9.7.1
|
||||
github.com/redis/go-redis/extra/redisotel/v9 v9.7.3
|
||||
github.com/redis/go-redis/v9 v9.7.3
|
||||
github.com/uptrace/uptrace-go v1.21.0
|
||||
go.opentelemetry.io/otel v1.22.0
|
||||
)
|
||||
@ -23,7 +25,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.7.1 // indirect
|
||||
github.com/redis/go-redis/extra/rediscmd/v9 v9.7.3 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/runtime v0.46.1 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.44.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 // indirect
|
||||
@ -34,9 +36,9 @@ require (
|
||||
go.opentelemetry.io/otel/sdk/metric v1.21.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.22.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.0.0 // indirect
|
||||
golang.org/x/net v0.33.0 // indirect
|
||||
golang.org/x/sys v0.28.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
golang.org/x/net v0.36.0 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
golang.org/x/text v0.22.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20240108191215-35c7eff3a6b1 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240108191215-35c7eff3a6b1 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1 // indirect
|
||||
|
@ -1,10 +1,13 @@
|
||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
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/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
@ -17,10 +20,13 @@ github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU=
|
||||
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/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/uptrace/uptrace-go v1.21.0 h1:oJoUjhiVT7aiuoG6B3ClVHtJozLn3cK9hQt8U5dQO1M=
|
||||
github.com/uptrace/uptrace-go v1.21.0/go.mod h1:/aXAFGKOqeAFBqWa1xtzLnGX2xJm1GScqz9NJ0TJjLM=
|
||||
go.opentelemetry.io/contrib/instrumentation/runtime v0.46.1 h1:m9ReioVPIffxjJlGNRd0d5poy+9oTro3D+YbiEzUDOc=
|
||||
@ -46,12 +52,13 @@ go.opentelemetry.io/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40
|
||||
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
|
||||
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA=
|
||||
golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/genproto v0.0.0-20240108191215-35c7eff3a6b1 h1:/IWabOtPziuXTEtI1KYCpM6Ss7vaAkeMxk+uXV/xvZs=
|
||||
google.golang.org/genproto v0.0.0-20240108191215-35c7eff3a6b1/go.mod h1:+Rvu7ElI+aLzyDQhpHMFMMltsD6m7nqpuWDd2CwJw3k=
|
||||
@ -66,3 +73,4 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
@ -4,7 +4,7 @@ go 1.18
|
||||
|
||||
replace github.com/redis/go-redis/v9 => ../..
|
||||
|
||||
require github.com/redis/go-redis/v9 v9.7.1
|
||||
require github.com/redis/go-redis/v9 v9.7.3
|
||||
|
||||
require (
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
|
@ -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.7.1
|
||||
github.com/redis/go-redis/v9 v9.7.3
|
||||
)
|
||||
|
||||
require (
|
||||
|
@ -11,6 +11,8 @@ import (
|
||||
"github.com/redis/go-redis/v9/internal/pool"
|
||||
)
|
||||
|
||||
var ErrPoolTimeout = pool.ErrPoolTimeout
|
||||
|
||||
func (c *baseClient) Pool() pool.Pooler {
|
||||
return c.connPool
|
||||
}
|
||||
@ -102,3 +104,7 @@ func (c *Ring) ShardByName(name string) *ringShard {
|
||||
func (c *ModuleLoadexConfig) ToArgs() []interface{} {
|
||||
return c.toArgs()
|
||||
}
|
||||
|
||||
func ShouldRetry(err error, retryTimeout bool) bool {
|
||||
return shouldRetry(err, retryTimeout)
|
||||
}
|
||||
|
@ -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.7.1
|
||||
github.com/redis/go-redis/v9 v9.7.1
|
||||
github.com/redis/go-redis/extra/rediscmd/v9 v9.7.3
|
||||
github.com/redis/go-redis/v9 v9.7.3
|
||||
go.opencensus.io v0.24.0
|
||||
)
|
||||
|
||||
@ -18,4 +18,7 @@ require (
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
)
|
||||
|
||||
retract v9.5.3 // This version was accidentally released.
|
||||
retract (
|
||||
v9.5.3 // This version was accidentally released.
|
||||
v9.7.2 // This version was accidentally released.
|
||||
)
|
||||
|
@ -7,7 +7,7 @@ replace github.com/redis/go-redis/v9 => ../..
|
||||
require (
|
||||
github.com/bsm/ginkgo/v2 v2.12.0
|
||||
github.com/bsm/gomega v1.27.10
|
||||
github.com/redis/go-redis/v9 v9.7.1
|
||||
github.com/redis/go-redis/v9 v9.7.3
|
||||
)
|
||||
|
||||
require (
|
||||
@ -15,4 +15,7 @@ require (
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
)
|
||||
|
||||
retract v9.5.3 // This version was accidentally released.
|
||||
retract (
|
||||
v9.5.3 // This version was accidentally released.
|
||||
v9.7.2 // This version was accidentally released.
|
||||
)
|
||||
|
@ -17,7 +17,6 @@ func CmdString(cmd redis.Cmder) string {
|
||||
}
|
||||
|
||||
func CmdsString(cmds []redis.Cmder) (string, string) {
|
||||
const numCmdLimit = 100
|
||||
const numNameLimit = 10
|
||||
|
||||
seen := make(map[string]struct{}, numNameLimit)
|
||||
@ -26,10 +25,6 @@ func CmdsString(cmds []redis.Cmder) (string, string) {
|
||||
b := make([]byte, 0, 32*len(cmds))
|
||||
|
||||
for i, cmd := range cmds {
|
||||
if i > numCmdLimit {
|
||||
break
|
||||
}
|
||||
|
||||
if i > 0 {
|
||||
b = append(b, '\n')
|
||||
}
|
||||
@ -51,12 +46,7 @@ func CmdsString(cmds []redis.Cmder) (string, string) {
|
||||
}
|
||||
|
||||
func AppendCmd(b []byte, cmd redis.Cmder) []byte {
|
||||
const numArgLimit = 32
|
||||
|
||||
for i, arg := range cmd.Args() {
|
||||
if i > numArgLimit {
|
||||
break
|
||||
}
|
||||
if i > 0 {
|
||||
b = append(b, ' ')
|
||||
}
|
||||
@ -72,20 +62,12 @@ func AppendCmd(b []byte, cmd redis.Cmder) []byte {
|
||||
}
|
||||
|
||||
func appendArg(b []byte, v interface{}) []byte {
|
||||
const argLenLimit = 64
|
||||
|
||||
switch v := v.(type) {
|
||||
case nil:
|
||||
return append(b, "<nil>"...)
|
||||
case string:
|
||||
if len(v) > argLenLimit {
|
||||
v = v[:argLenLimit]
|
||||
}
|
||||
return appendUTF8String(b, Bytes(v))
|
||||
case []byte:
|
||||
if len(v) > argLenLimit {
|
||||
v = v[:argLenLimit]
|
||||
}
|
||||
return appendUTF8String(b, v)
|
||||
case int:
|
||||
return strconv.AppendInt(b, int64(v), 10)
|
||||
|
@ -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.7.1
|
||||
github.com/redis/go-redis/v9 v9.7.1
|
||||
github.com/redis/go-redis/extra/rediscmd/v9 v9.7.3
|
||||
github.com/redis/go-redis/v9 v9.7.3
|
||||
go.opentelemetry.io/otel v1.22.0
|
||||
go.opentelemetry.io/otel/metric v1.22.0
|
||||
go.opentelemetry.io/otel/sdk v1.22.0
|
||||
@ -23,4 +23,7 @@ require (
|
||||
golang.org/x/sys v0.16.0 // indirect
|
||||
)
|
||||
|
||||
retract v9.5.3 // This version was accidentally released.
|
||||
retract (
|
||||
v9.5.3 // This version was accidentally released.
|
||||
v9.7.2 // This version was accidentally released.
|
||||
)
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
@ -222,6 +223,169 @@ func TestTracingHook_ProcessPipelineHook(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTracingHook_ProcessHook_LongCommand(t *testing.T) {
|
||||
imsb := tracetest.NewInMemoryExporter()
|
||||
provider := sdktrace.NewTracerProvider(sdktrace.WithSyncer(imsb))
|
||||
hook := newTracingHook(
|
||||
"redis://localhost:6379",
|
||||
WithTracerProvider(provider),
|
||||
)
|
||||
longValue := strings.Repeat("a", 102400)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
cmd redis.Cmder
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "short command",
|
||||
cmd: redis.NewCmd(context.Background(), "SET", "key", "value"),
|
||||
expected: "SET key value",
|
||||
},
|
||||
{
|
||||
name: "set command with long key",
|
||||
cmd: redis.NewCmd(context.Background(), "SET", longValue, "value"),
|
||||
expected: "SET " + longValue + " value",
|
||||
},
|
||||
{
|
||||
name: "set command with long value",
|
||||
cmd: redis.NewCmd(context.Background(), "SET", "key", longValue),
|
||||
expected: "SET key " + longValue,
|
||||
},
|
||||
{
|
||||
name: "set command with long key and value",
|
||||
cmd: redis.NewCmd(context.Background(), "SET", longValue, longValue),
|
||||
expected: "SET " + longValue + " " + longValue,
|
||||
},
|
||||
{
|
||||
name: "short command with many arguments",
|
||||
cmd: redis.NewCmd(context.Background(), "MSET", "key1", "value1", "key2", "value2", "key3", "value3", "key4", "value4", "key5", "value5"),
|
||||
expected: "MSET key1 value1 key2 value2 key3 value3 key4 value4 key5 value5",
|
||||
},
|
||||
{
|
||||
name: "long command",
|
||||
cmd: redis.NewCmd(context.Background(), longValue, "key", "value"),
|
||||
expected: longValue + " key value",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
defer imsb.Reset()
|
||||
|
||||
processHook := hook.ProcessHook(func(ctx context.Context, cmd redis.Cmder) error {
|
||||
return nil
|
||||
})
|
||||
|
||||
if err := processHook(context.Background(), tt.cmd); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assertEqual(t, 1, len(imsb.GetSpans()))
|
||||
|
||||
spanData := imsb.GetSpans()[0]
|
||||
|
||||
var dbStatement string
|
||||
for _, attr := range spanData.Attributes {
|
||||
if attr.Key == semconv.DBStatementKey {
|
||||
dbStatement = attr.Value.AsString()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if dbStatement != tt.expected {
|
||||
t.Errorf("Expected DB statement: %q\nGot: %q", tt.expected, dbStatement)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTracingHook_ProcessPipelineHook_LongCommands(t *testing.T) {
|
||||
imsb := tracetest.NewInMemoryExporter()
|
||||
provider := sdktrace.NewTracerProvider(sdktrace.WithSyncer(imsb))
|
||||
hook := newTracingHook(
|
||||
"redis://localhost:6379",
|
||||
WithTracerProvider(provider),
|
||||
)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
cmds []redis.Cmder
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "multiple short commands",
|
||||
cmds: []redis.Cmder{
|
||||
redis.NewCmd(context.Background(), "SET", "key1", "value1"),
|
||||
redis.NewCmd(context.Background(), "SET", "key2", "value2"),
|
||||
},
|
||||
expected: "SET key1 value1\nSET key2 value2",
|
||||
},
|
||||
{
|
||||
name: "multiple short commands with long key",
|
||||
cmds: []redis.Cmder{
|
||||
redis.NewCmd(context.Background(), "SET", strings.Repeat("a", 102400), "value1"),
|
||||
redis.NewCmd(context.Background(), "SET", strings.Repeat("b", 102400), "value2"),
|
||||
},
|
||||
expected: "SET " + strings.Repeat("a", 102400) + " value1\nSET " + strings.Repeat("b", 102400) + " value2",
|
||||
},
|
||||
{
|
||||
name: "multiple short commands with long value",
|
||||
cmds: []redis.Cmder{
|
||||
redis.NewCmd(context.Background(), "SET", "key1", strings.Repeat("a", 102400)),
|
||||
redis.NewCmd(context.Background(), "SET", "key2", strings.Repeat("b", 102400)),
|
||||
},
|
||||
expected: "SET key1 " + strings.Repeat("a", 102400) + "\nSET key2 " + strings.Repeat("b", 102400),
|
||||
},
|
||||
{
|
||||
name: "multiple short commands with long key and value",
|
||||
cmds: []redis.Cmder{
|
||||
redis.NewCmd(context.Background(), "SET", strings.Repeat("a", 102400), strings.Repeat("b", 102400)),
|
||||
redis.NewCmd(context.Background(), "SET", strings.Repeat("c", 102400), strings.Repeat("d", 102400)),
|
||||
},
|
||||
expected: "SET " + strings.Repeat("a", 102400) + " " + strings.Repeat("b", 102400) + "\nSET " + strings.Repeat("c", 102400) + " " + strings.Repeat("d", 102400),
|
||||
},
|
||||
{
|
||||
name: "multiple long commands",
|
||||
cmds: []redis.Cmder{
|
||||
redis.NewCmd(context.Background(), strings.Repeat("a", 102400), "key1", "value1"),
|
||||
redis.NewCmd(context.Background(), strings.Repeat("a", 102400), "key2", "value2"),
|
||||
},
|
||||
expected: strings.Repeat("a", 102400) + " key1 value1\n" + strings.Repeat("a", 102400) + " key2 value2",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
defer imsb.Reset()
|
||||
|
||||
processHook := hook.ProcessPipelineHook(func(ctx context.Context, cmds []redis.Cmder) error {
|
||||
return nil
|
||||
})
|
||||
|
||||
if err := processHook(context.Background(), tt.cmds); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assertEqual(t, 1, len(imsb.GetSpans()))
|
||||
|
||||
spanData := imsb.GetSpans()[0]
|
||||
|
||||
var dbStatement string
|
||||
for _, attr := range spanData.Attributes {
|
||||
if attr.Key == semconv.DBStatementKey {
|
||||
dbStatement = attr.Value.AsString()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if dbStatement != tt.expected {
|
||||
t.Errorf("Expected DB statement:\n%q\nGot:\n%q", tt.expected, dbStatement)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func assertEqual(t *testing.T, expected, actual interface{}) {
|
||||
t.Helper()
|
||||
if expected != actual {
|
||||
|
@ -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.7.1
|
||||
github.com/redis/go-redis/v9 v9.7.3
|
||||
)
|
||||
|
||||
require (
|
||||
@ -22,4 +22,7 @@ require (
|
||||
google.golang.org/protobuf v1.33.0 // indirect
|
||||
)
|
||||
|
||||
retract v9.5.3 // This version was accidentally released.
|
||||
retract (
|
||||
v9.5.3 // This version was accidentally released.
|
||||
v9.7.2 // This version was accidentally released.
|
||||
)
|
||||
|
1
go.mod
1
go.mod
@ -10,6 +10,7 @@ require (
|
||||
)
|
||||
|
||||
retract (
|
||||
v9.7.2 // This version was accidentally released. Please use version 9.7.3 instead.
|
||||
v9.5.4 // This version was accidentally released. Please use version 9.6.0 instead.
|
||||
v9.5.3 // This version was accidentally released. Please use version 9.6.0 instead.
|
||||
)
|
||||
|
116
hash_commands.go
116
hash_commands.go
@ -10,13 +10,17 @@ type HashCmdable interface {
|
||||
HExists(ctx context.Context, key, field string) *BoolCmd
|
||||
HGet(ctx context.Context, key, field string) *StringCmd
|
||||
HGetAll(ctx context.Context, key string) *MapStringStringCmd
|
||||
HIncrBy(ctx context.Context, key, field string, incr int64) *IntCmd
|
||||
HGetDel(ctx context.Context, key string, fields ...string) *StringSliceCmd
|
||||
HGetEX(ctx context.Context, key string, fields ...string) *StringSliceCmd
|
||||
HGetEXWithArgs(ctx context.Context, key string, options *HGetEXOptions, fields ...string) *StringSliceCmd
|
||||
HIncrByFloat(ctx context.Context, key, field string, incr float64) *FloatCmd
|
||||
HKeys(ctx context.Context, key string) *StringSliceCmd
|
||||
HLen(ctx context.Context, key string) *IntCmd
|
||||
HMGet(ctx context.Context, key string, fields ...string) *SliceCmd
|
||||
HSet(ctx context.Context, key string, values ...interface{}) *IntCmd
|
||||
HMSet(ctx context.Context, key string, values ...interface{}) *BoolCmd
|
||||
HSetEX(ctx context.Context, key string, fieldsAndValues ...string) *IntCmd
|
||||
HSetEXWithArgs(ctx context.Context, key string, options *HSetEXOptions, fieldsAndValues ...string) *IntCmd
|
||||
HSetNX(ctx context.Context, key, field string, value interface{}) *BoolCmd
|
||||
HScan(ctx context.Context, key string, cursor uint64, match string, count int64) *ScanCmd
|
||||
HScanNoValues(ctx context.Context, key string, cursor uint64, match string, count int64) *ScanCmd
|
||||
@ -454,3 +458,113 @@ func (c cmdable) HPTTL(ctx context.Context, key string, fields ...string) *IntSl
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) HGetDel(ctx context.Context, key string, fields ...string) *StringSliceCmd {
|
||||
args := []interface{}{"HGETDEL", key, "FIELDS", len(fields)}
|
||||
for _, field := range fields {
|
||||
args = append(args, field)
|
||||
}
|
||||
cmd := NewStringSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) HGetEX(ctx context.Context, key string, fields ...string) *StringSliceCmd {
|
||||
args := []interface{}{"HGETEX", key, "FIELDS", len(fields)}
|
||||
for _, field := range fields {
|
||||
args = append(args, field)
|
||||
}
|
||||
cmd := NewStringSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// ExpirationType represents an expiration option for the HGETEX command.
|
||||
type HGetEXExpirationType string
|
||||
|
||||
const (
|
||||
HGetEXExpirationEX HGetEXExpirationType = "EX"
|
||||
HGetEXExpirationPX HGetEXExpirationType = "PX"
|
||||
HGetEXExpirationEXAT HGetEXExpirationType = "EXAT"
|
||||
HGetEXExpirationPXAT HGetEXExpirationType = "PXAT"
|
||||
HGetEXExpirationPERSIST HGetEXExpirationType = "PERSIST"
|
||||
)
|
||||
|
||||
type HGetEXOptions struct {
|
||||
ExpirationType HGetEXExpirationType
|
||||
ExpirationVal int64
|
||||
}
|
||||
|
||||
func (c cmdable) HGetEXWithArgs(ctx context.Context, key string, options *HGetEXOptions, fields ...string) *StringSliceCmd {
|
||||
args := []interface{}{"HGETEX", key}
|
||||
if options.ExpirationType != "" {
|
||||
args = append(args, string(options.ExpirationType))
|
||||
if options.ExpirationType != HGetEXExpirationPERSIST {
|
||||
args = append(args, options.ExpirationVal)
|
||||
}
|
||||
}
|
||||
|
||||
args = append(args, "FIELDS", len(fields))
|
||||
for _, field := range fields {
|
||||
args = append(args, field)
|
||||
}
|
||||
|
||||
cmd := NewStringSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
type HSetEXCondition string
|
||||
|
||||
const (
|
||||
HSetEXFNX HSetEXCondition = "FNX" // Only set the fields if none of them already exist.
|
||||
HSetEXFXX HSetEXCondition = "FXX" // Only set the fields if all already exist.
|
||||
)
|
||||
|
||||
type HSetEXExpirationType string
|
||||
|
||||
const (
|
||||
HSetEXExpirationEX HSetEXExpirationType = "EX"
|
||||
HSetEXExpirationPX HSetEXExpirationType = "PX"
|
||||
HSetEXExpirationEXAT HSetEXExpirationType = "EXAT"
|
||||
HSetEXExpirationPXAT HSetEXExpirationType = "PXAT"
|
||||
HSetEXExpirationKEEPTTL HSetEXExpirationType = "KEEPTTL"
|
||||
)
|
||||
|
||||
type HSetEXOptions struct {
|
||||
Condition HSetEXCondition
|
||||
ExpirationType HSetEXExpirationType
|
||||
ExpirationVal int64
|
||||
}
|
||||
|
||||
func (c cmdable) HSetEX(ctx context.Context, key string, fieldsAndValues ...string) *IntCmd {
|
||||
args := []interface{}{"HSETEX", key, "FIELDS", len(fieldsAndValues) / 2}
|
||||
for _, field := range fieldsAndValues {
|
||||
args = append(args, field)
|
||||
}
|
||||
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) HSetEXWithArgs(ctx context.Context, key string, options *HSetEXOptions, fieldsAndValues ...string) *IntCmd {
|
||||
args := []interface{}{"HSETEX", key}
|
||||
if options.Condition != "" {
|
||||
args = append(args, string(options.Condition))
|
||||
}
|
||||
if options.ExpirationType != "" {
|
||||
args = append(args, string(options.ExpirationType))
|
||||
if options.ExpirationType != HSetEXExpirationKEEPTTL {
|
||||
args = append(args, options.ExpirationVal)
|
||||
}
|
||||
}
|
||||
args = append(args, "FIELDS", len(fieldsAndValues)/2)
|
||||
for _, field := range fieldsAndValues {
|
||||
args = append(args, field)
|
||||
}
|
||||
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ func BenchmarkPoolGetPut(b *testing.B) {
|
||||
Dialer: dummyDialer,
|
||||
PoolSize: bm.poolSize,
|
||||
PoolTimeout: time.Second,
|
||||
DialTimeout: 1 * time.Second,
|
||||
ConnMaxIdleTime: time.Hour,
|
||||
})
|
||||
|
||||
@ -76,6 +77,7 @@ func BenchmarkPoolGetRemove(b *testing.B) {
|
||||
Dialer: dummyDialer,
|
||||
PoolSize: bm.poolSize,
|
||||
PoolTimeout: time.Second,
|
||||
DialTimeout: 1 * time.Second,
|
||||
ConnMaxIdleTime: time.Hour,
|
||||
})
|
||||
|
||||
|
@ -62,6 +62,7 @@ type Options struct {
|
||||
|
||||
PoolFIFO bool
|
||||
PoolSize int
|
||||
DialTimeout time.Duration
|
||||
PoolTimeout time.Duration
|
||||
MinIdleConns int
|
||||
MaxIdleConns int
|
||||
@ -140,7 +141,10 @@ func (p *ConnPool) checkMinIdleConns() {
|
||||
}
|
||||
|
||||
func (p *ConnPool) addIdleConn() error {
|
||||
cn, err := p.dialConn(context.TODO(), true)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), p.cfg.DialTimeout)
|
||||
defer cancel()
|
||||
|
||||
cn, err := p.dialConn(ctx, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -230,15 +234,19 @@ func (p *ConnPool) tryDial() {
|
||||
return
|
||||
}
|
||||
|
||||
conn, err := p.cfg.Dialer(context.Background())
|
||||
ctx, cancel := context.WithTimeout(context.Background(), p.cfg.DialTimeout)
|
||||
|
||||
conn, err := p.cfg.Dialer(ctx)
|
||||
if err != nil {
|
||||
p.setLastDialError(err)
|
||||
time.Sleep(time.Second)
|
||||
cancel()
|
||||
continue
|
||||
}
|
||||
|
||||
atomic.StoreUint32(&p.dialErrorsNum, 0)
|
||||
_ = conn.Close()
|
||||
cancel()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ var _ = Describe("ConnPool", func() {
|
||||
Dialer: dummyDialer,
|
||||
PoolSize: 10,
|
||||
PoolTimeout: time.Hour,
|
||||
DialTimeout: 1 * time.Second,
|
||||
ConnMaxIdleTime: time.Millisecond,
|
||||
})
|
||||
})
|
||||
@ -46,6 +47,7 @@ var _ = Describe("ConnPool", func() {
|
||||
},
|
||||
PoolSize: 10,
|
||||
PoolTimeout: time.Hour,
|
||||
DialTimeout: 1 * time.Second,
|
||||
ConnMaxIdleTime: time.Millisecond,
|
||||
MinIdleConns: minIdleConns,
|
||||
})
|
||||
@ -129,6 +131,7 @@ var _ = Describe("MinIdleConns", func() {
|
||||
PoolSize: poolSize,
|
||||
MinIdleConns: minIdleConns,
|
||||
PoolTimeout: 100 * time.Millisecond,
|
||||
DialTimeout: 1 * time.Second,
|
||||
ConnMaxIdleTime: -1,
|
||||
})
|
||||
Eventually(func() int {
|
||||
@ -306,6 +309,7 @@ var _ = Describe("race", func() {
|
||||
Dialer: dummyDialer,
|
||||
PoolSize: 10,
|
||||
PoolTimeout: time.Minute,
|
||||
DialTimeout: 1 * time.Second,
|
||||
ConnMaxIdleTime: time.Millisecond,
|
||||
})
|
||||
|
||||
@ -336,6 +340,7 @@ var _ = Describe("race", func() {
|
||||
PoolSize: 1000,
|
||||
MinIdleConns: 50,
|
||||
PoolTimeout: 3 * time.Second,
|
||||
DialTimeout: 1 * time.Second,
|
||||
}
|
||||
p := pool.NewConnPool(opt)
|
||||
|
||||
|
14
options.go
14
options.go
@ -45,7 +45,7 @@ type Options struct {
|
||||
// Network and Addr options.
|
||||
Dialer func(ctx context.Context, network, addr string) (net.Conn, error)
|
||||
|
||||
// OnConnect Hook that is called when new connection is established.
|
||||
// Hook that is called when new connection is established.
|
||||
OnConnect func(ctx context.Context, cn *Conn) error
|
||||
|
||||
// Protocol 2 or 3. Use the version to negotiate RESP version with redis-server.
|
||||
@ -188,9 +188,19 @@ type Options struct {
|
||||
// readOnly enables read only queries on slave/follower nodes.
|
||||
readOnly bool
|
||||
|
||||
// DisableIndentity set-lib on connect. Default is false.
|
||||
// DisableIndentity - Disable set-lib on connect.
|
||||
//
|
||||
// default: false
|
||||
//
|
||||
// Deprecated: Use DisableIdentity instead.
|
||||
DisableIndentity bool
|
||||
|
||||
// DisableIdentity is used to disable CLIENT SETINFO command on connect.
|
||||
//
|
||||
// default: false
|
||||
DisableIdentity bool
|
||||
|
||||
// Add suffix to client name. Default is empty.
|
||||
// IdentitySuffix - add suffix to client name.
|
||||
IdentitySuffix string
|
||||
|
||||
|
@ -90,8 +90,19 @@ type ClusterOptions struct {
|
||||
ConnMaxIdleTime time.Duration
|
||||
ConnMaxLifetime time.Duration
|
||||
|
||||
TLSConfig *tls.Config
|
||||
DisableIndentity bool // Disable set-lib on connect. Default is false.
|
||||
TLSConfig *tls.Config
|
||||
|
||||
// DisableIndentity - Disable set-lib on connect.
|
||||
//
|
||||
// default: false
|
||||
//
|
||||
// Deprecated: Use DisableIdentity instead.
|
||||
DisableIndentity bool
|
||||
|
||||
// DisableIdentity is used to disable CLIENT SETINFO command on connect.
|
||||
//
|
||||
// default: false
|
||||
DisableIdentity bool
|
||||
|
||||
IdentitySuffix string // Add suffix to client name. Default is empty.
|
||||
|
||||
@ -303,7 +314,8 @@ func (opt *ClusterOptions) clientOptions() *Options {
|
||||
MaxActiveConns: opt.MaxActiveConns,
|
||||
ConnMaxIdleTime: opt.ConnMaxIdleTime,
|
||||
ConnMaxLifetime: opt.ConnMaxLifetime,
|
||||
DisableIndentity: opt.DisableIndentity,
|
||||
DisableIdentity: opt.DisableIdentity,
|
||||
DisableIndentity: opt.DisableIdentity,
|
||||
IdentitySuffix: opt.IdentitySuffix,
|
||||
TLSConfig: opt.TLSConfig,
|
||||
// If ClusterSlots is populated, then we probably have an artificial
|
||||
|
10
redis.go
10
redis.go
@ -322,7 +322,7 @@ func (c *baseClient) initConn(ctx context.Context, cn *pool.Conn) error {
|
||||
|
||||
// for redis-server versions that do not support the HELLO command,
|
||||
// RESP2 will continue to be used.
|
||||
if err = conn.Hello(ctx, protocol, username, password, "").Err(); err == nil {
|
||||
if err = conn.Hello(ctx, protocol, username, password, c.opt.ClientName).Err(); err == nil {
|
||||
authenticated = true
|
||||
} else if !isRedisError(err) {
|
||||
// When the server responds with the RESP protocol and the result is not a normal
|
||||
@ -360,7 +360,7 @@ func (c *baseClient) initConn(ctx context.Context, cn *pool.Conn) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if !c.opt.DisableIndentity {
|
||||
if !c.opt.DisableIdentity && !c.opt.DisableIndentity {
|
||||
libName := ""
|
||||
libVer := Version()
|
||||
if c.opt.IdentitySuffix != "" {
|
||||
@ -369,7 +369,11 @@ func (c *baseClient) initConn(ctx context.Context, cn *pool.Conn) error {
|
||||
p := conn.Pipeline()
|
||||
p.ClientSetInfo(ctx, WithLibraryName(libName))
|
||||
p.ClientSetInfo(ctx, WithLibraryVersion(libVer))
|
||||
_, _ = p.Exec(ctx)
|
||||
// Handle network errors (e.g. timeouts) in CLIENT SETINFO to avoid
|
||||
// out of order responses later on.
|
||||
if _, err = p.Exec(ctx); err != nil && !isRedisError(err) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if c.opt.OnConnect != nil {
|
||||
|
@ -186,6 +186,32 @@ var _ = Describe("Client", func() {
|
||||
Expect(val).Should(ContainSubstring("name=hi"))
|
||||
})
|
||||
|
||||
It("should attempt to set client name in HELLO", func() {
|
||||
opt := redisOptions()
|
||||
opt.ClientName = "hi"
|
||||
db := redis.NewClient(opt)
|
||||
|
||||
defer func() {
|
||||
Expect(db.Close()).NotTo(HaveOccurred())
|
||||
}()
|
||||
|
||||
// Client name should be already set on any successfully initialized connection
|
||||
name, err := db.ClientGetName(ctx).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(name).Should(Equal("hi"))
|
||||
|
||||
// HELLO should be able to explicitly overwrite the client name
|
||||
conn := db.Conn()
|
||||
hello, err := conn.Hello(ctx, 3, "", "", "hi2").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(hello["proto"]).Should(Equal(int64(3)))
|
||||
name, err = conn.ClientGetName(ctx).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(name).Should(Equal("hi2"))
|
||||
err = conn.Close()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("should client PROTO 2", func() {
|
||||
opt := redisOptions()
|
||||
opt.Protocol = 2
|
||||
@ -370,6 +396,13 @@ var _ = Describe("Client timeout", func() {
|
||||
})
|
||||
|
||||
testTimeout := func() {
|
||||
It("SETINFO timeouts", func() {
|
||||
conn := client.Conn()
|
||||
err := conn.Ping(ctx).Err()
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.(net.Error).Timeout()).To(BeTrue())
|
||||
})
|
||||
|
||||
It("Ping timeouts", func() {
|
||||
err := client.Ping(ctx).Err()
|
||||
Expect(err).To(HaveOccurred())
|
||||
|
20
ring.go
20
ring.go
@ -98,9 +98,19 @@ type RingOptions struct {
|
||||
TLSConfig *tls.Config
|
||||
Limiter Limiter
|
||||
|
||||
// DisableIndentity - Disable set-lib on connect.
|
||||
//
|
||||
// default: false
|
||||
//
|
||||
// Deprecated: Use DisableIdentity instead.
|
||||
DisableIndentity bool
|
||||
IdentitySuffix string
|
||||
UnstableResp3 bool
|
||||
|
||||
// DisableIdentity is used to disable CLIENT SETINFO command on connect.
|
||||
//
|
||||
// default: false
|
||||
DisableIdentity bool
|
||||
IdentitySuffix string
|
||||
UnstableResp3 bool
|
||||
}
|
||||
|
||||
func (opt *RingOptions) init() {
|
||||
@ -167,9 +177,11 @@ func (opt *RingOptions) clientOptions() *Options {
|
||||
TLSConfig: opt.TLSConfig,
|
||||
Limiter: opt.Limiter,
|
||||
|
||||
DisableIdentity: opt.DisableIdentity,
|
||||
DisableIndentity: opt.DisableIndentity,
|
||||
IdentitySuffix: opt.IdentitySuffix,
|
||||
UnstableResp3: opt.UnstableResp3,
|
||||
|
||||
IdentitySuffix: opt.IdentitySuffix,
|
||||
UnstableResp3: opt.UnstableResp3,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -604,6 +604,8 @@ func FTAggregateQuery(query string, options *FTAggregateOptions) AggregateQuery
|
||||
|
||||
if options.DialectVersion > 0 {
|
||||
queryArgs = append(queryArgs, "DIALECT", options.DialectVersion)
|
||||
} else {
|
||||
queryArgs = append(queryArgs, "DIALECT", 2)
|
||||
}
|
||||
}
|
||||
return queryArgs
|
||||
@ -801,6 +803,8 @@ func (c cmdable) FTAggregateWithArgs(ctx context.Context, index string, query st
|
||||
}
|
||||
if options.DialectVersion > 0 {
|
||||
args = append(args, "DIALECT", options.DialectVersion)
|
||||
} else {
|
||||
args = append(args, "DIALECT", 2)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1174,6 +1178,8 @@ func (c cmdable) FTExplainWithArgs(ctx context.Context, index string, query stri
|
||||
args := []interface{}{"FT.EXPLAIN", index, query}
|
||||
if options.Dialect != "" {
|
||||
args = append(args, "DIALECT", options.Dialect)
|
||||
} else {
|
||||
args = append(args, "DIALECT", 2)
|
||||
}
|
||||
cmd := NewStringCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
@ -1471,6 +1477,8 @@ func (c cmdable) FTSpellCheckWithArgs(ctx context.Context, index string, query s
|
||||
}
|
||||
if options.Dialect > 0 {
|
||||
args = append(args, "DIALECT", options.Dialect)
|
||||
} else {
|
||||
args = append(args, "DIALECT", 2)
|
||||
}
|
||||
}
|
||||
cmd := newFTSpellCheckCmd(ctx, args...)
|
||||
@ -1840,6 +1848,8 @@ func FTSearchQuery(query string, options *FTSearchOptions) SearchQuery {
|
||||
}
|
||||
if options.DialectVersion > 0 {
|
||||
queryArgs = append(queryArgs, "DIALECT", options.DialectVersion)
|
||||
} else {
|
||||
queryArgs = append(queryArgs, "DIALECT", 2)
|
||||
}
|
||||
}
|
||||
return queryArgs
|
||||
@ -1955,6 +1965,8 @@ func (c cmdable) FTSearchWithArgs(ctx context.Context, index string, query strin
|
||||
}
|
||||
if options.DialectVersion > 0 {
|
||||
args = append(args, "DIALECT", options.DialectVersion)
|
||||
} else {
|
||||
args = append(args, "DIALECT", 2)
|
||||
}
|
||||
}
|
||||
cmd := newFTSearchCmd(ctx, options, args...)
|
||||
|
106
search_test.go
106
search_test.go
@ -1143,6 +1143,55 @@ var _ = Describe("RediSearch commands Resp 2", Label("search"), func() {
|
||||
Expect(res.Docs[0].Fields["__v_score"]).To(BeEquivalentTo("0"))
|
||||
})
|
||||
|
||||
It("should FTCreate VECTOR with dialect 1 ", Label("search", "ftcreate"), func() {
|
||||
hnswOptions := &redis.FTHNSWOptions{Type: "FLOAT32", Dim: 2, DistanceMetric: "L2"}
|
||||
val, err := client.FTCreate(ctx, "idx1",
|
||||
&redis.FTCreateOptions{},
|
||||
&redis.FieldSchema{FieldName: "v", FieldType: redis.SearchFieldTypeVector, VectorArgs: &redis.FTVectorArgs{HNSWOptions: hnswOptions}}).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(val).To(BeEquivalentTo("OK"))
|
||||
WaitForIndexing(client, "idx1")
|
||||
|
||||
client.HSet(ctx, "a", "v", "aaaaaaaa")
|
||||
client.HSet(ctx, "b", "v", "aaaabaaa")
|
||||
client.HSet(ctx, "c", "v", "aaaaabaa")
|
||||
|
||||
searchOptions := &redis.FTSearchOptions{
|
||||
Return: []redis.FTSearchReturn{{FieldName: "v"}},
|
||||
SortBy: []redis.FTSearchSortBy{{FieldName: "v", Asc: true}},
|
||||
Limit: 10,
|
||||
DialectVersion: 1,
|
||||
}
|
||||
res, err := client.FTSearchWithArgs(ctx, "idx1", "*", searchOptions).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res.Docs[0].ID).To(BeEquivalentTo("a"))
|
||||
Expect(res.Docs[0].Fields["v"]).To(BeEquivalentTo("aaaaaaaa"))
|
||||
})
|
||||
|
||||
It("should FTCreate VECTOR with default dialect", Label("search", "ftcreate"), func() {
|
||||
hnswOptions := &redis.FTHNSWOptions{Type: "FLOAT32", Dim: 2, DistanceMetric: "L2"}
|
||||
val, err := client.FTCreate(ctx, "idx1",
|
||||
&redis.FTCreateOptions{},
|
||||
&redis.FieldSchema{FieldName: "v", FieldType: redis.SearchFieldTypeVector, VectorArgs: &redis.FTVectorArgs{HNSWOptions: hnswOptions}}).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(val).To(BeEquivalentTo("OK"))
|
||||
WaitForIndexing(client, "idx1")
|
||||
|
||||
client.HSet(ctx, "a", "v", "aaaaaaaa")
|
||||
client.HSet(ctx, "b", "v", "aaaabaaa")
|
||||
client.HSet(ctx, "c", "v", "aaaaabaa")
|
||||
|
||||
searchOptions := &redis.FTSearchOptions{
|
||||
Return: []redis.FTSearchReturn{{FieldName: "__v_score"}},
|
||||
SortBy: []redis.FTSearchSortBy{{FieldName: "__v_score", Asc: true}},
|
||||
Params: map[string]interface{}{"vec": "aaaaaaaa"},
|
||||
}
|
||||
res, err := client.FTSearchWithArgs(ctx, "idx1", "*=>[KNN 2 @v $vec]", searchOptions).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res.Docs[0].ID).To(BeEquivalentTo("a"))
|
||||
Expect(res.Docs[0].Fields["__v_score"]).To(BeEquivalentTo("0"))
|
||||
})
|
||||
|
||||
It("should FTCreate and FTSearch text params", Label("search", "ftcreate", "ftsearch"), func() {
|
||||
val, err := client.FTCreate(ctx, "idx1", &redis.FTCreateOptions{}, &redis.FieldSchema{FieldName: "name", FieldType: redis.SearchFieldTypeText}).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
@ -1577,6 +1626,63 @@ var _ = Describe("RediSearch commands Resp 2", Label("search"), func() {
|
||||
Expect(res.Docs[0].ID).To(BeEquivalentTo("property:1"))
|
||||
Expect(res.Docs[1].ID).To(BeEquivalentTo("property:2"))
|
||||
})
|
||||
|
||||
It("should FTCreate VECTOR with int8 and uint8 types", Label("search", "ftcreate"), func() {
|
||||
SkipBeforeRedisVersion(7.9, "doesn't work with older redis")
|
||||
// Define INT8 vector field
|
||||
hnswOptionsInt8 := &redis.FTHNSWOptions{
|
||||
Type: "INT8",
|
||||
Dim: 2,
|
||||
DistanceMetric: "L2",
|
||||
}
|
||||
|
||||
// Define UINT8 vector field
|
||||
hnswOptionsUint8 := &redis.FTHNSWOptions{
|
||||
Type: "UINT8",
|
||||
Dim: 2,
|
||||
DistanceMetric: "L2",
|
||||
}
|
||||
|
||||
// Create index with INT8 and UINT8 vector fields
|
||||
val, err := client.FTCreate(ctx, "idx1",
|
||||
&redis.FTCreateOptions{},
|
||||
&redis.FieldSchema{FieldName: "int8_vector", FieldType: redis.SearchFieldTypeVector, VectorArgs: &redis.FTVectorArgs{HNSWOptions: hnswOptionsInt8}},
|
||||
&redis.FieldSchema{FieldName: "uint8_vector", FieldType: redis.SearchFieldTypeVector, VectorArgs: &redis.FTVectorArgs{HNSWOptions: hnswOptionsUint8}},
|
||||
).Result()
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(val).To(BeEquivalentTo("OK"))
|
||||
WaitForIndexing(client, "idx1")
|
||||
|
||||
// Insert vectors in int8 and uint8 format
|
||||
client.HSet(ctx, "doc1", "int8_vector", "\x01\x02", "uint8_vector", "\x01\x02")
|
||||
client.HSet(ctx, "doc2", "int8_vector", "\x03\x04", "uint8_vector", "\x03\x04")
|
||||
|
||||
// Perform KNN search on INT8 vector
|
||||
searchOptionsInt8 := &redis.FTSearchOptions{
|
||||
Return: []redis.FTSearchReturn{{FieldName: "int8_vector"}},
|
||||
SortBy: []redis.FTSearchSortBy{{FieldName: "int8_vector", Asc: true}},
|
||||
DialectVersion: 2,
|
||||
Params: map[string]interface{}{"vec": "\x01\x02"},
|
||||
}
|
||||
|
||||
resInt8, err := client.FTSearchWithArgs(ctx, "idx1", "*=>[KNN 1 @int8_vector $vec]", searchOptionsInt8).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(resInt8.Docs[0].ID).To(BeEquivalentTo("doc1"))
|
||||
|
||||
// Perform KNN search on UINT8 vector
|
||||
searchOptionsUint8 := &redis.FTSearchOptions{
|
||||
Return: []redis.FTSearchReturn{{FieldName: "uint8_vector"}},
|
||||
SortBy: []redis.FTSearchSortBy{{FieldName: "uint8_vector", Asc: true}},
|
||||
DialectVersion: 2,
|
||||
Params: map[string]interface{}{"vec": "\x01\x02"},
|
||||
}
|
||||
|
||||
resUint8, err := client.FTSearchWithArgs(ctx, "idx1", "*=>[KNN 1 @uint8_vector $vec]", searchOptionsUint8).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(resUint8.Docs[0].ID).To(BeEquivalentTo("doc1"))
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
func _assert_geosearch_result(result *redis.FTSearchResult, expectedDocIDs []string) {
|
||||
|
31
sentinel.go
31
sentinel.go
@ -80,9 +80,20 @@ type FailoverOptions struct {
|
||||
|
||||
TLSConfig *tls.Config
|
||||
|
||||
// DisableIndentity - Disable set-lib on connect.
|
||||
//
|
||||
// default: false
|
||||
//
|
||||
// Deprecated: Use DisableIdentity instead.
|
||||
DisableIndentity bool
|
||||
IdentitySuffix string
|
||||
UnstableResp3 bool
|
||||
|
||||
// DisableIdentity is used to disable CLIENT SETINFO command on connect.
|
||||
//
|
||||
// default: false
|
||||
DisableIdentity bool
|
||||
|
||||
IdentitySuffix string
|
||||
UnstableResp3 bool
|
||||
}
|
||||
|
||||
func (opt *FailoverOptions) clientOptions() *Options {
|
||||
@ -118,9 +129,11 @@ func (opt *FailoverOptions) clientOptions() *Options {
|
||||
|
||||
TLSConfig: opt.TLSConfig,
|
||||
|
||||
DisableIdentity: opt.DisableIdentity,
|
||||
DisableIndentity: opt.DisableIndentity,
|
||||
IdentitySuffix: opt.IdentitySuffix,
|
||||
UnstableResp3: opt.UnstableResp3,
|
||||
|
||||
IdentitySuffix: opt.IdentitySuffix,
|
||||
UnstableResp3: opt.UnstableResp3,
|
||||
}
|
||||
}
|
||||
|
||||
@ -156,9 +169,11 @@ func (opt *FailoverOptions) sentinelOptions(addr string) *Options {
|
||||
|
||||
TLSConfig: opt.TLSConfig,
|
||||
|
||||
DisableIdentity: opt.DisableIdentity,
|
||||
DisableIndentity: opt.DisableIndentity,
|
||||
IdentitySuffix: opt.IdentitySuffix,
|
||||
UnstableResp3: opt.UnstableResp3,
|
||||
|
||||
IdentitySuffix: opt.IdentitySuffix,
|
||||
UnstableResp3: opt.UnstableResp3,
|
||||
}
|
||||
}
|
||||
|
||||
@ -197,8 +212,10 @@ func (opt *FailoverOptions) clusterOptions() *ClusterOptions {
|
||||
|
||||
TLSConfig: opt.TLSConfig,
|
||||
|
||||
DisableIdentity: opt.DisableIdentity,
|
||||
DisableIndentity: opt.DisableIndentity,
|
||||
IdentitySuffix: opt.IdentitySuffix,
|
||||
|
||||
IdentitySuffix: opt.IdentitySuffix,
|
||||
}
|
||||
}
|
||||
|
||||
|
48
universal.go
48
universal.go
@ -61,14 +61,24 @@ type UniversalOptions struct {
|
||||
RouteByLatency bool
|
||||
RouteRandomly bool
|
||||
|
||||
// The sentinel master name.
|
||||
// Only failover clients.
|
||||
|
||||
// MasterName is the sentinel master name.
|
||||
// Only for failover clients.
|
||||
MasterName string
|
||||
|
||||
// DisableIndentity - Disable set-lib on connect.
|
||||
//
|
||||
// default: false
|
||||
//
|
||||
// Deprecated: Use DisableIdentity instead.
|
||||
DisableIndentity bool
|
||||
IdentitySuffix string
|
||||
UnstableResp3 bool
|
||||
|
||||
// DisableIdentity is used to disable CLIENT SETINFO command on connect.
|
||||
//
|
||||
// default: false
|
||||
DisableIdentity bool
|
||||
|
||||
IdentitySuffix string
|
||||
UnstableResp3 bool
|
||||
|
||||
// IsClusterMode can be used when only one Addrs is provided (e.g. Elasticache supports setting up cluster mode with configuration endpoint).
|
||||
IsClusterMode bool
|
||||
@ -116,6 +126,7 @@ func (o *UniversalOptions) Cluster() *ClusterOptions {
|
||||
|
||||
TLSConfig: o.TLSConfig,
|
||||
|
||||
DisableIdentity: o.DisableIdentity,
|
||||
DisableIndentity: o.DisableIndentity,
|
||||
IdentitySuffix: o.IdentitySuffix,
|
||||
UnstableResp3: o.UnstableResp3,
|
||||
@ -143,6 +154,9 @@ func (o *UniversalOptions) Failover() *FailoverOptions {
|
||||
SentinelUsername: o.SentinelUsername,
|
||||
SentinelPassword: o.SentinelPassword,
|
||||
|
||||
RouteByLatency: o.RouteByLatency,
|
||||
RouteRandomly: o.RouteRandomly,
|
||||
|
||||
MaxRetries: o.MaxRetries,
|
||||
MinRetryBackoff: o.MinRetryBackoff,
|
||||
MaxRetryBackoff: o.MaxRetryBackoff,
|
||||
@ -163,8 +177,9 @@ func (o *UniversalOptions) Failover() *FailoverOptions {
|
||||
|
||||
TLSConfig: o.TLSConfig,
|
||||
|
||||
ReplicaOnly: o.ReadOnly,
|
||||
ReplicaOnly: o.ReadOnly,
|
||||
|
||||
DisableIdentity: o.DisableIdentity,
|
||||
DisableIndentity: o.DisableIndentity,
|
||||
IdentitySuffix: o.IdentitySuffix,
|
||||
UnstableResp3: o.UnstableResp3,
|
||||
@ -209,6 +224,7 @@ func (o *UniversalOptions) Simple() *Options {
|
||||
|
||||
TLSConfig: o.TLSConfig,
|
||||
|
||||
DisableIdentity: o.DisableIdentity,
|
||||
DisableIndentity: o.DisableIndentity,
|
||||
IdentitySuffix: o.IdentitySuffix,
|
||||
UnstableResp3: o.UnstableResp3,
|
||||
@ -243,14 +259,22 @@ var (
|
||||
// NewUniversalClient returns a new multi client. The type of the returned client depends
|
||||
// on the following conditions:
|
||||
//
|
||||
// 1. If the MasterName option is specified, a sentinel-backed FailoverClient is returned.
|
||||
// 2. if the number of Addrs is two or more, a ClusterClient is returned.
|
||||
// 3. Otherwise, a single-node Client is returned.
|
||||
// 1. If the MasterName option is specified with RouteByLatency, RouteRandomly or IsClusterMode,
|
||||
// a FailoverClusterClient is returned.
|
||||
// 2. If the MasterName option is specified without RouteByLatency, RouteRandomly or IsClusterMode,
|
||||
// a sentinel-backed FailoverClient is returned.
|
||||
// 3. If the number of Addrs is two or more, or IsClusterMode option is specified,
|
||||
// a ClusterClient is returned.
|
||||
// 4. Otherwise, a single-node Client is returned.
|
||||
func NewUniversalClient(opts *UniversalOptions) UniversalClient {
|
||||
if opts.MasterName != "" {
|
||||
switch {
|
||||
case opts.MasterName != "" && (opts.RouteByLatency || opts.RouteRandomly || opts.IsClusterMode):
|
||||
return NewFailoverClusterClient(opts.Failover())
|
||||
case opts.MasterName != "":
|
||||
return NewFailoverClient(opts.Failover())
|
||||
} else if len(opts.Addrs) > 1 || opts.IsClusterMode {
|
||||
case len(opts.Addrs) > 1 || opts.IsClusterMode:
|
||||
return NewClusterClient(opts.Cluster())
|
||||
default:
|
||||
return NewClient(opts.Simple())
|
||||
}
|
||||
return NewClient(opts.Simple())
|
||||
}
|
||||
|
@ -24,6 +24,16 @@ var _ = Describe("UniversalClient", func() {
|
||||
Expect(client.Ping(ctx).Err()).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("should connect to failover cluster", Label("NonRedisEnterprise"), func() {
|
||||
client = redis.NewUniversalClient(&redis.UniversalOptions{
|
||||
MasterName: sentinelName,
|
||||
RouteRandomly: true,
|
||||
Addrs: sentinelAddrs,
|
||||
})
|
||||
_, ok := client.(*redis.ClusterClient)
|
||||
Expect(ok).To(BeTrue(), "expected a ClusterClient")
|
||||
})
|
||||
|
||||
It("should connect to simple servers", func() {
|
||||
client = redis.NewUniversalClient(&redis.UniversalOptions{
|
||||
Addrs: []string{redisAddr},
|
||||
@ -79,6 +89,7 @@ var _ = Describe("UniversalClient", func() {
|
||||
err = client.Set(ctx, "somekey", "somevalue", 0).Err()
|
||||
Expect(err).To(HaveOccurred())
|
||||
})
|
||||
|
||||
It("should connect to clusters if IsClusterMode is set even if only a single address is provided", Label("NonRedisEnterprise"), func() {
|
||||
client = redis.NewUniversalClient(&redis.UniversalOptions{
|
||||
Addrs: []string{cluster.addrs()[0]},
|
||||
@ -96,4 +107,3 @@ var _ = Describe("UniversalClient", func() {
|
||||
Expect(client.ClusterSlots(ctx).Val()).To(HaveLen(3))
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -2,5 +2,5 @@ package redis
|
||||
|
||||
// Version is the current release version.
|
||||
func Version() string {
|
||||
return "9.7.1"
|
||||
return "9.7.3"
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user