mirror of
https://github.com/redis/go-redis.git
synced 2025-04-17 20:17:02 +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
|
# Mapping of redis version to redis testing containers
|
||||||
declare -A redis_version_mapping=(
|
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.4.2"]="rs-7.4.0-v2"
|
||||||
["7.2.7"]="rs-7.2.0-v14"
|
["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
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
redis-version:
|
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.4.2" # should use redis stack 7.4
|
||||||
go-version:
|
go-version:
|
||||||
- "1.23.x"
|
- "1.23.x"
|
||||||
@ -43,7 +43,7 @@ jobs:
|
|||||||
|
|
||||||
# Mapping of redis version to redis testing containers
|
# Mapping of redis version to redis testing containers
|
||||||
declare -A redis_version_mapping=(
|
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.4.2"]="rs-7.4.0-v2"
|
||||||
)
|
)
|
||||||
if [[ -v redis_version_mapping[$REDIS_VERSION] ]]; then
|
if [[ -v redis_version_mapping[$REDIS_VERSION] ]]; then
|
||||||
@ -72,7 +72,7 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
redis-version:
|
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.4.2" # should use redis stack 7.4
|
||||||
- "7.2.7" # should redis stack 7.2
|
- "7.2.7" # should redis stack 7.2
|
||||||
go-version:
|
go-version:
|
||||||
|
5
.github/workflows/golangci-lint.yml
vendored
5
.github/workflows/golangci-lint.yml
vendored
@ -21,4 +21,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: golangci-lint
|
- 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}" && \
|
(cd "$${dir}" && \
|
||||||
go mod tidy -compat=1.18 && \
|
go mod tidy -compat=1.18 && \
|
||||||
go vet && \
|
go vet && \
|
||||||
go test -coverprofile=coverage.txt -covermode=atomic ./... -race); \
|
go test -v -coverprofile=coverage.txt -covermode=atomic ./... -race); \
|
||||||
done
|
done
|
||||||
cd internal/customvet && go build .
|
cd internal/customvet && go build .
|
||||||
go vet -vettool ./internal/customvet/customvet
|
go vet -vettool ./internal/customvet/customvet
|
||||||
|
28
README.md
28
README.md
@ -6,13 +6,7 @@
|
|||||||
[](https://codecov.io/github/redis/go-redis)
|
[](https://codecov.io/github/redis/go-redis)
|
||||||
[](https://discord.gg/rWtp5Aj)
|
[](https://discord.gg/rWtp5Aj)
|
||||||
|
|
||||||
> go-redis is brought to you by :star: [**uptrace/uptrace**](https://github.com/uptrace/uptrace).
|
> go-redis is the official Redis client library for the Go programming language. It offers a straightforward interface for interacting with Redis servers.
|
||||||
> 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.
|
|
||||||
|
|
||||||
## Supported versions
|
## Supported versions
|
||||||
|
|
||||||
@ -184,16 +178,18 @@ By default, go-redis automatically sends the client library name and version dur
|
|||||||
|
|
||||||
#### Disabling Identity Verification
|
#### 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
|
```go
|
||||||
rdb := redis.NewClient(&redis.Options{
|
rdb := redis.NewClient(&redis.Options{
|
||||||
Addr: "localhost:6379",
|
Addr: "localhost:6379",
|
||||||
Password: "",
|
Password: "",
|
||||||
DB: 0,
|
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()
|
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
|
## Contributing
|
||||||
|
|
||||||
Please see [out contributing guidelines](CONTRIBUTING.md) to help us improve this library!
|
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
|
## 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!
|
Thanks to all the people who already contributed!
|
||||||
|
|
||||||
<a href="https://github.com/redis/go-redis/graphs/contributors">
|
<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) {
|
Dialer: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
return stub.stubConn(initHello), nil
|
return stub.stubConn(initHello), nil
|
||||||
},
|
},
|
||||||
DisableIndentity: true,
|
DisableIdentity: true,
|
||||||
})
|
})
|
||||||
return stub
|
return stub
|
||||||
}
|
}
|
||||||
@ -46,7 +46,7 @@ func NewClusterClientStub(resp []byte) *ClientStub {
|
|||||||
Dialer: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
Dialer: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
return stub.stubConn(initHello), nil
|
return stub.stubConn(initHello), nil
|
||||||
},
|
},
|
||||||
DisableIndentity: true,
|
DisableIdentity: true,
|
||||||
|
|
||||||
ClusterSlots: func(_ context.Context) ([]ClusterSlot, error) {
|
ClusterSlots: func(_ context.Context) ([]ClusterSlot, error) {
|
||||||
return []ClusterSlot{
|
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}))
|
Expect(res).To(Equal([]int64{1, 1, -2}))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
It("should HPExpire", Label("hash-expiration", "NonRedisEnterprise"), func() {
|
It("should HPExpire", Label("hash-expiration", "NonRedisEnterprise"), func() {
|
||||||
SkipBeforeRedisVersion(7.4, "doesn't work with older redis stack images")
|
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()
|
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(err).NotTo(HaveOccurred())
|
||||||
Expect(res[0]).To(BeNumerically("~", 10*time.Second.Milliseconds(), 1))
|
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() {
|
Describe("hyperloglog", func() {
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
services:
|
services:
|
||||||
redis:
|
redis:
|
||||||
image: ${CLIENT_LIBS_TEST_IMAGE:-redislabs/client-libs-test:rs-7.4.0-v2}
|
image: ${CLIENT_LIBS_TEST_IMAGE:-redislabs/client-libs-test:rs-7.4.0-v2}
|
||||||
|
platform: linux/amd64
|
||||||
container_name: redis-standalone
|
container_name: redis-standalone
|
||||||
environment:
|
environment:
|
||||||
- TLS_ENABLED=yes
|
- TLS_ENABLED=yes
|
||||||
@ -23,6 +24,7 @@ services:
|
|||||||
|
|
||||||
osscluster:
|
osscluster:
|
||||||
image: ${CLIENT_LIBS_TEST_IMAGE:-redislabs/client-libs-test:rs-7.4.0-v2}
|
image: ${CLIENT_LIBS_TEST_IMAGE:-redislabs/client-libs-test:rs-7.4.0-v2}
|
||||||
|
platform: linux/amd64
|
||||||
container_name: redis-osscluster
|
container_name: redis-osscluster
|
||||||
environment:
|
environment:
|
||||||
- NODES=6
|
- NODES=6
|
||||||
@ -39,6 +41,7 @@ services:
|
|||||||
|
|
||||||
sentinel-cluster:
|
sentinel-cluster:
|
||||||
image: ${CLIENT_LIBS_TEST_IMAGE:-redislabs/client-libs-test:rs-7.4.0-v2}
|
image: ${CLIENT_LIBS_TEST_IMAGE:-redislabs/client-libs-test:rs-7.4.0-v2}
|
||||||
|
platform: linux/amd64
|
||||||
container_name: redis-sentinel-cluster
|
container_name: redis-sentinel-cluster
|
||||||
network_mode: "host"
|
network_mode: "host"
|
||||||
environment:
|
environment:
|
||||||
@ -58,6 +61,7 @@ services:
|
|||||||
|
|
||||||
sentinel:
|
sentinel:
|
||||||
image: ${CLIENT_LIBS_TEST_IMAGE:-redislabs/client-libs-test:rs-7.4.0-v2}
|
image: ${CLIENT_LIBS_TEST_IMAGE:-redislabs/client-libs-test:rs-7.4.0-v2}
|
||||||
|
platform: linux/amd64
|
||||||
container_name: redis-sentinel
|
container_name: redis-sentinel
|
||||||
depends_on:
|
depends_on:
|
||||||
- sentinel-cluster
|
- sentinel-cluster
|
||||||
@ -81,6 +85,7 @@ services:
|
|||||||
|
|
||||||
ring-cluster:
|
ring-cluster:
|
||||||
image: ${CLIENT_LIBS_TEST_IMAGE:-redislabs/client-libs-test:rs-7.4.0-v2}
|
image: ${CLIENT_LIBS_TEST_IMAGE:-redislabs/client-libs-test:rs-7.4.0-v2}
|
||||||
|
platform: linux/amd64
|
||||||
container_name: redis-ring-cluster
|
container_name: redis-ring-cluster
|
||||||
environment:
|
environment:
|
||||||
- NODES=3
|
- 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
|
return true
|
||||||
case nil, context.Canceled, context.DeadlineExceeded:
|
case nil, context.Canceled, context.DeadlineExceeded:
|
||||||
return false
|
return false
|
||||||
|
case pool.ErrPoolTimeout:
|
||||||
|
// connection pool timeout, increase retries. #3289
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if v, ok := err.(timeoutError); ok {
|
if v, ok := err.(timeoutError); ok {
|
||||||
@ -72,6 +75,9 @@ func shouldRetry(err error, retryTimeout bool) bool {
|
|||||||
if strings.HasPrefix(s, "READONLY ") {
|
if strings.HasPrefix(s, "READONLY ") {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
if strings.HasPrefix(s, "MASTERDOWN ") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
if strings.HasPrefix(s, "CLUSTERDOWN ") {
|
if strings.HasPrefix(s, "CLUSTERDOWN ") {
|
||||||
return true
|
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 => ../..
|
replace github.com/redis/go-redis/v9 => ../..
|
||||||
|
|
||||||
require (
|
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
|
go.uber.org/zap v1.24.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ go 1.18
|
|||||||
|
|
||||||
replace github.com/redis/go-redis/v9 => ../..
|
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 (
|
require (
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
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/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
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 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
@ -4,7 +4,7 @@ go 1.18
|
|||||||
|
|
||||||
replace github.com/redis/go-redis/v9 => ../..
|
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 (
|
require (
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
module github.com/redis/go-redis/example/otel
|
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 => ../..
|
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
|
replace github.com/redis/go-redis/extra/rediscmd/v9 => ../../extra/rediscmd
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/redis/go-redis/extra/redisotel/v9 v9.7.1
|
github.com/redis/go-redis/extra/redisotel/v9 v9.7.3
|
||||||
github.com/redis/go-redis/v9 v9.7.1
|
github.com/redis/go-redis/v9 v9.7.3
|
||||||
github.com/uptrace/uptrace-go v1.21.0
|
github.com/uptrace/uptrace-go v1.21.0
|
||||||
go.opentelemetry.io/otel v1.22.0
|
go.opentelemetry.io/otel v1.22.0
|
||||||
)
|
)
|
||||||
@ -23,7 +25,7 @@ require (
|
|||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/golang/protobuf v1.5.3 // indirect
|
github.com/golang/protobuf v1.5.3 // indirect
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // 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/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/otlpmetric/otlpmetricgrpc v0.44.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.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/sdk/metric v1.21.0 // indirect
|
||||||
go.opentelemetry.io/otel/trace v1.22.0 // indirect
|
go.opentelemetry.io/otel/trace v1.22.0 // indirect
|
||||||
go.opentelemetry.io/proto/otlp v1.0.0 // indirect
|
go.opentelemetry.io/proto/otlp v1.0.0 // indirect
|
||||||
golang.org/x/net v0.33.0 // indirect
|
golang.org/x/net v0.36.0 // indirect
|
||||||
golang.org/x/sys v0.28.0 // indirect
|
golang.org/x/sys v0.30.0 // indirect
|
||||||
golang.org/x/text v0.21.0 // indirect
|
golang.org/x/text v0.22.0 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20240108191215-35c7eff3a6b1 // 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/api v0.0.0-20240108191215-35c7eff3a6b1 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc 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 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 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 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
|
||||||
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
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 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
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 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 h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
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=
|
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/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.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 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 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU=
|
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 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 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 h1:oJoUjhiVT7aiuoG6B3ClVHtJozLn3cK9hQt8U5dQO1M=
|
||||||
github.com/uptrace/uptrace-go v1.21.0/go.mod h1:/aXAFGKOqeAFBqWa1xtzLnGX2xJm1GScqz9NJ0TJjLM=
|
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=
|
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 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
|
||||||
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
|
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
|
||||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA=
|
||||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I=
|
||||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
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=
|
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 h1:/IWabOtPziuXTEtI1KYCpM6Ss7vaAkeMxk+uXV/xvZs=
|
||||||
google.golang.org/genproto v0.0.0-20240108191215-35c7eff3a6b1/go.mod h1:+Rvu7ElI+aLzyDQhpHMFMMltsD6m7nqpuWDd2CwJw3k=
|
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 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
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 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 => ../..
|
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 (
|
require (
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
|
@ -6,7 +6,7 @@ replace github.com/redis/go-redis/v9 => ../..
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/davecgh/go-spew v1.1.1
|
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 (
|
require (
|
||||||
|
@ -11,6 +11,8 @@ import (
|
|||||||
"github.com/redis/go-redis/v9/internal/pool"
|
"github.com/redis/go-redis/v9/internal/pool"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var ErrPoolTimeout = pool.ErrPoolTimeout
|
||||||
|
|
||||||
func (c *baseClient) Pool() pool.Pooler {
|
func (c *baseClient) Pool() pool.Pooler {
|
||||||
return c.connPool
|
return c.connPool
|
||||||
}
|
}
|
||||||
@ -102,3 +104,7 @@ func (c *Ring) ShardByName(name string) *ringShard {
|
|||||||
func (c *ModuleLoadexConfig) ToArgs() []interface{} {
|
func (c *ModuleLoadexConfig) ToArgs() []interface{} {
|
||||||
return c.toArgs()
|
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
|
replace github.com/redis/go-redis/extra/rediscmd/v9 => ../rediscmd
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/redis/go-redis/extra/rediscmd/v9 v9.7.1
|
github.com/redis/go-redis/extra/rediscmd/v9 v9.7.3
|
||||||
github.com/redis/go-redis/v9 v9.7.1
|
github.com/redis/go-redis/v9 v9.7.3
|
||||||
go.opencensus.io v0.24.0
|
go.opencensus.io v0.24.0
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -18,4 +18,7 @@ require (
|
|||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
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 (
|
require (
|
||||||
github.com/bsm/ginkgo/v2 v2.12.0
|
github.com/bsm/ginkgo/v2 v2.12.0
|
||||||
github.com/bsm/gomega v1.27.10
|
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 (
|
require (
|
||||||
@ -15,4 +15,7 @@ require (
|
|||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
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) {
|
func CmdsString(cmds []redis.Cmder) (string, string) {
|
||||||
const numCmdLimit = 100
|
|
||||||
const numNameLimit = 10
|
const numNameLimit = 10
|
||||||
|
|
||||||
seen := make(map[string]struct{}, numNameLimit)
|
seen := make(map[string]struct{}, numNameLimit)
|
||||||
@ -26,10 +25,6 @@ func CmdsString(cmds []redis.Cmder) (string, string) {
|
|||||||
b := make([]byte, 0, 32*len(cmds))
|
b := make([]byte, 0, 32*len(cmds))
|
||||||
|
|
||||||
for i, cmd := range cmds {
|
for i, cmd := range cmds {
|
||||||
if i > numCmdLimit {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
b = append(b, '\n')
|
b = append(b, '\n')
|
||||||
}
|
}
|
||||||
@ -51,12 +46,7 @@ func CmdsString(cmds []redis.Cmder) (string, string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func AppendCmd(b []byte, cmd redis.Cmder) []byte {
|
func AppendCmd(b []byte, cmd redis.Cmder) []byte {
|
||||||
const numArgLimit = 32
|
|
||||||
|
|
||||||
for i, arg := range cmd.Args() {
|
for i, arg := range cmd.Args() {
|
||||||
if i > numArgLimit {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
b = append(b, ' ')
|
b = append(b, ' ')
|
||||||
}
|
}
|
||||||
@ -72,20 +62,12 @@ func AppendCmd(b []byte, cmd redis.Cmder) []byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func appendArg(b []byte, v interface{}) []byte {
|
func appendArg(b []byte, v interface{}) []byte {
|
||||||
const argLenLimit = 64
|
|
||||||
|
|
||||||
switch v := v.(type) {
|
switch v := v.(type) {
|
||||||
case nil:
|
case nil:
|
||||||
return append(b, "<nil>"...)
|
return append(b, "<nil>"...)
|
||||||
case string:
|
case string:
|
||||||
if len(v) > argLenLimit {
|
|
||||||
v = v[:argLenLimit]
|
|
||||||
}
|
|
||||||
return appendUTF8String(b, Bytes(v))
|
return appendUTF8String(b, Bytes(v))
|
||||||
case []byte:
|
case []byte:
|
||||||
if len(v) > argLenLimit {
|
|
||||||
v = v[:argLenLimit]
|
|
||||||
}
|
|
||||||
return appendUTF8String(b, v)
|
return appendUTF8String(b, v)
|
||||||
case int:
|
case int:
|
||||||
return strconv.AppendInt(b, int64(v), 10)
|
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
|
replace github.com/redis/go-redis/extra/rediscmd/v9 => ../rediscmd
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/redis/go-redis/extra/rediscmd/v9 v9.7.1
|
github.com/redis/go-redis/extra/rediscmd/v9 v9.7.3
|
||||||
github.com/redis/go-redis/v9 v9.7.1
|
github.com/redis/go-redis/v9 v9.7.3
|
||||||
go.opentelemetry.io/otel v1.22.0
|
go.opentelemetry.io/otel v1.22.0
|
||||||
go.opentelemetry.io/otel/metric v1.22.0
|
go.opentelemetry.io/otel/metric v1.22.0
|
||||||
go.opentelemetry.io/otel/sdk v1.22.0
|
go.opentelemetry.io/otel/sdk v1.22.0
|
||||||
@ -23,4 +23,7 @@ require (
|
|||||||
golang.org/x/sys v0.16.0 // indirect
|
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"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"go.opentelemetry.io/otel/attribute"
|
"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{}) {
|
func assertEqual(t *testing.T, expected, actual interface{}) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
if expected != actual {
|
if expected != actual {
|
||||||
|
@ -6,7 +6,7 @@ replace github.com/redis/go-redis/v9 => ../..
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/prometheus/client_golang v1.14.0
|
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 (
|
require (
|
||||||
@ -22,4 +22,7 @@ require (
|
|||||||
google.golang.org/protobuf v1.33.0 // indirect
|
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 (
|
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.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.
|
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
|
HExists(ctx context.Context, key, field string) *BoolCmd
|
||||||
HGet(ctx context.Context, key, field string) *StringCmd
|
HGet(ctx context.Context, key, field string) *StringCmd
|
||||||
HGetAll(ctx context.Context, key string) *MapStringStringCmd
|
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
|
HIncrByFloat(ctx context.Context, key, field string, incr float64) *FloatCmd
|
||||||
HKeys(ctx context.Context, key string) *StringSliceCmd
|
HKeys(ctx context.Context, key string) *StringSliceCmd
|
||||||
HLen(ctx context.Context, key string) *IntCmd
|
HLen(ctx context.Context, key string) *IntCmd
|
||||||
HMGet(ctx context.Context, key string, fields ...string) *SliceCmd
|
HMGet(ctx context.Context, key string, fields ...string) *SliceCmd
|
||||||
HSet(ctx context.Context, key string, values ...interface{}) *IntCmd
|
HSet(ctx context.Context, key string, values ...interface{}) *IntCmd
|
||||||
HMSet(ctx context.Context, key string, values ...interface{}) *BoolCmd
|
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
|
HSetNX(ctx context.Context, key, field string, value interface{}) *BoolCmd
|
||||||
HScan(ctx context.Context, key string, cursor uint64, match string, count int64) *ScanCmd
|
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
|
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)
|
_ = c(ctx, cmd)
|
||||||
return 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,
|
Dialer: dummyDialer,
|
||||||
PoolSize: bm.poolSize,
|
PoolSize: bm.poolSize,
|
||||||
PoolTimeout: time.Second,
|
PoolTimeout: time.Second,
|
||||||
|
DialTimeout: 1 * time.Second,
|
||||||
ConnMaxIdleTime: time.Hour,
|
ConnMaxIdleTime: time.Hour,
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -76,6 +77,7 @@ func BenchmarkPoolGetRemove(b *testing.B) {
|
|||||||
Dialer: dummyDialer,
|
Dialer: dummyDialer,
|
||||||
PoolSize: bm.poolSize,
|
PoolSize: bm.poolSize,
|
||||||
PoolTimeout: time.Second,
|
PoolTimeout: time.Second,
|
||||||
|
DialTimeout: 1 * time.Second,
|
||||||
ConnMaxIdleTime: time.Hour,
|
ConnMaxIdleTime: time.Hour,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -62,6 +62,7 @@ type Options struct {
|
|||||||
|
|
||||||
PoolFIFO bool
|
PoolFIFO bool
|
||||||
PoolSize int
|
PoolSize int
|
||||||
|
DialTimeout time.Duration
|
||||||
PoolTimeout time.Duration
|
PoolTimeout time.Duration
|
||||||
MinIdleConns int
|
MinIdleConns int
|
||||||
MaxIdleConns int
|
MaxIdleConns int
|
||||||
@ -140,7 +141,10 @@ func (p *ConnPool) checkMinIdleConns() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *ConnPool) addIdleConn() error {
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -230,15 +234,19 @@ func (p *ConnPool) tryDial() {
|
|||||||
return
|
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 {
|
if err != nil {
|
||||||
p.setLastDialError(err)
|
p.setLastDialError(err)
|
||||||
time.Sleep(time.Second)
|
time.Sleep(time.Second)
|
||||||
|
cancel()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
atomic.StoreUint32(&p.dialErrorsNum, 0)
|
atomic.StoreUint32(&p.dialErrorsNum, 0)
|
||||||
_ = conn.Close()
|
_ = conn.Close()
|
||||||
|
cancel()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ var _ = Describe("ConnPool", func() {
|
|||||||
Dialer: dummyDialer,
|
Dialer: dummyDialer,
|
||||||
PoolSize: 10,
|
PoolSize: 10,
|
||||||
PoolTimeout: time.Hour,
|
PoolTimeout: time.Hour,
|
||||||
|
DialTimeout: 1 * time.Second,
|
||||||
ConnMaxIdleTime: time.Millisecond,
|
ConnMaxIdleTime: time.Millisecond,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -46,6 +47,7 @@ var _ = Describe("ConnPool", func() {
|
|||||||
},
|
},
|
||||||
PoolSize: 10,
|
PoolSize: 10,
|
||||||
PoolTimeout: time.Hour,
|
PoolTimeout: time.Hour,
|
||||||
|
DialTimeout: 1 * time.Second,
|
||||||
ConnMaxIdleTime: time.Millisecond,
|
ConnMaxIdleTime: time.Millisecond,
|
||||||
MinIdleConns: minIdleConns,
|
MinIdleConns: minIdleConns,
|
||||||
})
|
})
|
||||||
@ -129,6 +131,7 @@ var _ = Describe("MinIdleConns", func() {
|
|||||||
PoolSize: poolSize,
|
PoolSize: poolSize,
|
||||||
MinIdleConns: minIdleConns,
|
MinIdleConns: minIdleConns,
|
||||||
PoolTimeout: 100 * time.Millisecond,
|
PoolTimeout: 100 * time.Millisecond,
|
||||||
|
DialTimeout: 1 * time.Second,
|
||||||
ConnMaxIdleTime: -1,
|
ConnMaxIdleTime: -1,
|
||||||
})
|
})
|
||||||
Eventually(func() int {
|
Eventually(func() int {
|
||||||
@ -306,6 +309,7 @@ var _ = Describe("race", func() {
|
|||||||
Dialer: dummyDialer,
|
Dialer: dummyDialer,
|
||||||
PoolSize: 10,
|
PoolSize: 10,
|
||||||
PoolTimeout: time.Minute,
|
PoolTimeout: time.Minute,
|
||||||
|
DialTimeout: 1 * time.Second,
|
||||||
ConnMaxIdleTime: time.Millisecond,
|
ConnMaxIdleTime: time.Millisecond,
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -336,6 +340,7 @@ var _ = Describe("race", func() {
|
|||||||
PoolSize: 1000,
|
PoolSize: 1000,
|
||||||
MinIdleConns: 50,
|
MinIdleConns: 50,
|
||||||
PoolTimeout: 3 * time.Second,
|
PoolTimeout: 3 * time.Second,
|
||||||
|
DialTimeout: 1 * time.Second,
|
||||||
}
|
}
|
||||||
p := pool.NewConnPool(opt)
|
p := pool.NewConnPool(opt)
|
||||||
|
|
||||||
|
14
options.go
14
options.go
@ -45,7 +45,7 @@ type Options struct {
|
|||||||
// Network and Addr options.
|
// Network and Addr options.
|
||||||
Dialer func(ctx context.Context, network, addr string) (net.Conn, error)
|
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
|
OnConnect func(ctx context.Context, cn *Conn) error
|
||||||
|
|
||||||
// Protocol 2 or 3. Use the version to negotiate RESP version with redis-server.
|
// 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 enables read only queries on slave/follower nodes.
|
||||||
readOnly bool
|
readOnly bool
|
||||||
|
|
||||||
// DisableIndentity set-lib on connect. Default is false.
|
// DisableIndentity - Disable set-lib on connect.
|
||||||
|
//
|
||||||
|
// default: false
|
||||||
|
//
|
||||||
|
// Deprecated: Use DisableIdentity instead.
|
||||||
DisableIndentity bool
|
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 - add suffix to client name.
|
||||||
IdentitySuffix string
|
IdentitySuffix string
|
||||||
|
|
||||||
|
@ -90,8 +90,19 @@ type ClusterOptions struct {
|
|||||||
ConnMaxIdleTime time.Duration
|
ConnMaxIdleTime time.Duration
|
||||||
ConnMaxLifetime time.Duration
|
ConnMaxLifetime time.Duration
|
||||||
|
|
||||||
TLSConfig *tls.Config
|
TLSConfig *tls.Config
|
||||||
DisableIndentity bool // Disable 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
|
||||||
|
|
||||||
IdentitySuffix string // Add suffix to client name. Default is empty.
|
IdentitySuffix string // Add suffix to client name. Default is empty.
|
||||||
|
|
||||||
@ -303,7 +314,8 @@ func (opt *ClusterOptions) clientOptions() *Options {
|
|||||||
MaxActiveConns: opt.MaxActiveConns,
|
MaxActiveConns: opt.MaxActiveConns,
|
||||||
ConnMaxIdleTime: opt.ConnMaxIdleTime,
|
ConnMaxIdleTime: opt.ConnMaxIdleTime,
|
||||||
ConnMaxLifetime: opt.ConnMaxLifetime,
|
ConnMaxLifetime: opt.ConnMaxLifetime,
|
||||||
DisableIndentity: opt.DisableIndentity,
|
DisableIdentity: opt.DisableIdentity,
|
||||||
|
DisableIndentity: opt.DisableIdentity,
|
||||||
IdentitySuffix: opt.IdentitySuffix,
|
IdentitySuffix: opt.IdentitySuffix,
|
||||||
TLSConfig: opt.TLSConfig,
|
TLSConfig: opt.TLSConfig,
|
||||||
// If ClusterSlots is populated, then we probably have an artificial
|
// 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,
|
// for redis-server versions that do not support the HELLO command,
|
||||||
// RESP2 will continue to be used.
|
// 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
|
authenticated = true
|
||||||
} else if !isRedisError(err) {
|
} else if !isRedisError(err) {
|
||||||
// When the server responds with the RESP protocol and the result is not a normal
|
// 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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !c.opt.DisableIndentity {
|
if !c.opt.DisableIdentity && !c.opt.DisableIndentity {
|
||||||
libName := ""
|
libName := ""
|
||||||
libVer := Version()
|
libVer := Version()
|
||||||
if c.opt.IdentitySuffix != "" {
|
if c.opt.IdentitySuffix != "" {
|
||||||
@ -369,7 +369,11 @@ func (c *baseClient) initConn(ctx context.Context, cn *pool.Conn) error {
|
|||||||
p := conn.Pipeline()
|
p := conn.Pipeline()
|
||||||
p.ClientSetInfo(ctx, WithLibraryName(libName))
|
p.ClientSetInfo(ctx, WithLibraryName(libName))
|
||||||
p.ClientSetInfo(ctx, WithLibraryVersion(libVer))
|
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 {
|
if c.opt.OnConnect != nil {
|
||||||
|
@ -186,6 +186,32 @@ var _ = Describe("Client", func() {
|
|||||||
Expect(val).Should(ContainSubstring("name=hi"))
|
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() {
|
It("should client PROTO 2", func() {
|
||||||
opt := redisOptions()
|
opt := redisOptions()
|
||||||
opt.Protocol = 2
|
opt.Protocol = 2
|
||||||
@ -370,6 +396,13 @@ var _ = Describe("Client timeout", func() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
testTimeout := 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() {
|
It("Ping timeouts", func() {
|
||||||
err := client.Ping(ctx).Err()
|
err := client.Ping(ctx).Err()
|
||||||
Expect(err).To(HaveOccurred())
|
Expect(err).To(HaveOccurred())
|
||||||
|
20
ring.go
20
ring.go
@ -98,9 +98,19 @@ type RingOptions struct {
|
|||||||
TLSConfig *tls.Config
|
TLSConfig *tls.Config
|
||||||
Limiter Limiter
|
Limiter Limiter
|
||||||
|
|
||||||
|
// DisableIndentity - Disable set-lib on connect.
|
||||||
|
//
|
||||||
|
// default: false
|
||||||
|
//
|
||||||
|
// Deprecated: Use DisableIdentity instead.
|
||||||
DisableIndentity bool
|
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() {
|
func (opt *RingOptions) init() {
|
||||||
@ -167,9 +177,11 @@ func (opt *RingOptions) clientOptions() *Options {
|
|||||||
TLSConfig: opt.TLSConfig,
|
TLSConfig: opt.TLSConfig,
|
||||||
Limiter: opt.Limiter,
|
Limiter: opt.Limiter,
|
||||||
|
|
||||||
|
DisableIdentity: opt.DisableIdentity,
|
||||||
DisableIndentity: opt.DisableIndentity,
|
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 {
|
if options.DialectVersion > 0 {
|
||||||
queryArgs = append(queryArgs, "DIALECT", options.DialectVersion)
|
queryArgs = append(queryArgs, "DIALECT", options.DialectVersion)
|
||||||
|
} else {
|
||||||
|
queryArgs = append(queryArgs, "DIALECT", 2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return queryArgs
|
return queryArgs
|
||||||
@ -801,6 +803,8 @@ func (c cmdable) FTAggregateWithArgs(ctx context.Context, index string, query st
|
|||||||
}
|
}
|
||||||
if options.DialectVersion > 0 {
|
if options.DialectVersion > 0 {
|
||||||
args = append(args, "DIALECT", options.DialectVersion)
|
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}
|
args := []interface{}{"FT.EXPLAIN", index, query}
|
||||||
if options.Dialect != "" {
|
if options.Dialect != "" {
|
||||||
args = append(args, "DIALECT", options.Dialect)
|
args = append(args, "DIALECT", options.Dialect)
|
||||||
|
} else {
|
||||||
|
args = append(args, "DIALECT", 2)
|
||||||
}
|
}
|
||||||
cmd := NewStringCmd(ctx, args...)
|
cmd := NewStringCmd(ctx, args...)
|
||||||
_ = c(ctx, cmd)
|
_ = c(ctx, cmd)
|
||||||
@ -1471,6 +1477,8 @@ func (c cmdable) FTSpellCheckWithArgs(ctx context.Context, index string, query s
|
|||||||
}
|
}
|
||||||
if options.Dialect > 0 {
|
if options.Dialect > 0 {
|
||||||
args = append(args, "DIALECT", options.Dialect)
|
args = append(args, "DIALECT", options.Dialect)
|
||||||
|
} else {
|
||||||
|
args = append(args, "DIALECT", 2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cmd := newFTSpellCheckCmd(ctx, args...)
|
cmd := newFTSpellCheckCmd(ctx, args...)
|
||||||
@ -1840,6 +1848,8 @@ func FTSearchQuery(query string, options *FTSearchOptions) SearchQuery {
|
|||||||
}
|
}
|
||||||
if options.DialectVersion > 0 {
|
if options.DialectVersion > 0 {
|
||||||
queryArgs = append(queryArgs, "DIALECT", options.DialectVersion)
|
queryArgs = append(queryArgs, "DIALECT", options.DialectVersion)
|
||||||
|
} else {
|
||||||
|
queryArgs = append(queryArgs, "DIALECT", 2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return queryArgs
|
return queryArgs
|
||||||
@ -1955,6 +1965,8 @@ func (c cmdable) FTSearchWithArgs(ctx context.Context, index string, query strin
|
|||||||
}
|
}
|
||||||
if options.DialectVersion > 0 {
|
if options.DialectVersion > 0 {
|
||||||
args = append(args, "DIALECT", options.DialectVersion)
|
args = append(args, "DIALECT", options.DialectVersion)
|
||||||
|
} else {
|
||||||
|
args = append(args, "DIALECT", 2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cmd := newFTSearchCmd(ctx, options, args...)
|
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"))
|
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() {
|
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()
|
val, err := client.FTCreate(ctx, "idx1", &redis.FTCreateOptions{}, &redis.FieldSchema{FieldName: "name", FieldType: redis.SearchFieldTypeText}).Result()
|
||||||
Expect(err).NotTo(HaveOccurred())
|
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[0].ID).To(BeEquivalentTo("property:1"))
|
||||||
Expect(res.Docs[1].ID).To(BeEquivalentTo("property:2"))
|
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) {
|
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
|
TLSConfig *tls.Config
|
||||||
|
|
||||||
|
// DisableIndentity - Disable set-lib on connect.
|
||||||
|
//
|
||||||
|
// default: false
|
||||||
|
//
|
||||||
|
// Deprecated: Use DisableIdentity instead.
|
||||||
DisableIndentity bool
|
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 {
|
func (opt *FailoverOptions) clientOptions() *Options {
|
||||||
@ -118,9 +129,11 @@ func (opt *FailoverOptions) clientOptions() *Options {
|
|||||||
|
|
||||||
TLSConfig: opt.TLSConfig,
|
TLSConfig: opt.TLSConfig,
|
||||||
|
|
||||||
|
DisableIdentity: opt.DisableIdentity,
|
||||||
DisableIndentity: opt.DisableIndentity,
|
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,
|
TLSConfig: opt.TLSConfig,
|
||||||
|
|
||||||
|
DisableIdentity: opt.DisableIdentity,
|
||||||
DisableIndentity: opt.DisableIndentity,
|
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,
|
TLSConfig: opt.TLSConfig,
|
||||||
|
|
||||||
|
DisableIdentity: opt.DisableIdentity,
|
||||||
DisableIndentity: opt.DisableIndentity,
|
DisableIndentity: opt.DisableIndentity,
|
||||||
IdentitySuffix: opt.IdentitySuffix,
|
|
||||||
|
IdentitySuffix: opt.IdentitySuffix,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
48
universal.go
48
universal.go
@ -61,14 +61,24 @@ type UniversalOptions struct {
|
|||||||
RouteByLatency bool
|
RouteByLatency bool
|
||||||
RouteRandomly bool
|
RouteRandomly bool
|
||||||
|
|
||||||
// The sentinel master name.
|
// MasterName is the sentinel master name.
|
||||||
// Only failover clients.
|
// Only for failover clients.
|
||||||
|
|
||||||
MasterName string
|
MasterName string
|
||||||
|
|
||||||
|
// DisableIndentity - Disable set-lib on connect.
|
||||||
|
//
|
||||||
|
// default: false
|
||||||
|
//
|
||||||
|
// Deprecated: Use DisableIdentity instead.
|
||||||
DisableIndentity bool
|
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 can be used when only one Addrs is provided (e.g. Elasticache supports setting up cluster mode with configuration endpoint).
|
||||||
IsClusterMode bool
|
IsClusterMode bool
|
||||||
@ -116,6 +126,7 @@ func (o *UniversalOptions) Cluster() *ClusterOptions {
|
|||||||
|
|
||||||
TLSConfig: o.TLSConfig,
|
TLSConfig: o.TLSConfig,
|
||||||
|
|
||||||
|
DisableIdentity: o.DisableIdentity,
|
||||||
DisableIndentity: o.DisableIndentity,
|
DisableIndentity: o.DisableIndentity,
|
||||||
IdentitySuffix: o.IdentitySuffix,
|
IdentitySuffix: o.IdentitySuffix,
|
||||||
UnstableResp3: o.UnstableResp3,
|
UnstableResp3: o.UnstableResp3,
|
||||||
@ -143,6 +154,9 @@ func (o *UniversalOptions) Failover() *FailoverOptions {
|
|||||||
SentinelUsername: o.SentinelUsername,
|
SentinelUsername: o.SentinelUsername,
|
||||||
SentinelPassword: o.SentinelPassword,
|
SentinelPassword: o.SentinelPassword,
|
||||||
|
|
||||||
|
RouteByLatency: o.RouteByLatency,
|
||||||
|
RouteRandomly: o.RouteRandomly,
|
||||||
|
|
||||||
MaxRetries: o.MaxRetries,
|
MaxRetries: o.MaxRetries,
|
||||||
MinRetryBackoff: o.MinRetryBackoff,
|
MinRetryBackoff: o.MinRetryBackoff,
|
||||||
MaxRetryBackoff: o.MaxRetryBackoff,
|
MaxRetryBackoff: o.MaxRetryBackoff,
|
||||||
@ -163,8 +177,9 @@ func (o *UniversalOptions) Failover() *FailoverOptions {
|
|||||||
|
|
||||||
TLSConfig: o.TLSConfig,
|
TLSConfig: o.TLSConfig,
|
||||||
|
|
||||||
ReplicaOnly: o.ReadOnly,
|
ReplicaOnly: o.ReadOnly,
|
||||||
|
|
||||||
|
DisableIdentity: o.DisableIdentity,
|
||||||
DisableIndentity: o.DisableIndentity,
|
DisableIndentity: o.DisableIndentity,
|
||||||
IdentitySuffix: o.IdentitySuffix,
|
IdentitySuffix: o.IdentitySuffix,
|
||||||
UnstableResp3: o.UnstableResp3,
|
UnstableResp3: o.UnstableResp3,
|
||||||
@ -209,6 +224,7 @@ func (o *UniversalOptions) Simple() *Options {
|
|||||||
|
|
||||||
TLSConfig: o.TLSConfig,
|
TLSConfig: o.TLSConfig,
|
||||||
|
|
||||||
|
DisableIdentity: o.DisableIdentity,
|
||||||
DisableIndentity: o.DisableIndentity,
|
DisableIndentity: o.DisableIndentity,
|
||||||
IdentitySuffix: o.IdentitySuffix,
|
IdentitySuffix: o.IdentitySuffix,
|
||||||
UnstableResp3: o.UnstableResp3,
|
UnstableResp3: o.UnstableResp3,
|
||||||
@ -243,14 +259,22 @@ var (
|
|||||||
// NewUniversalClient returns a new multi client. The type of the returned client depends
|
// NewUniversalClient returns a new multi client. The type of the returned client depends
|
||||||
// on the following conditions:
|
// on the following conditions:
|
||||||
//
|
//
|
||||||
// 1. If the MasterName option is specified, a sentinel-backed FailoverClient is returned.
|
// 1. If the MasterName option is specified with RouteByLatency, RouteRandomly or IsClusterMode,
|
||||||
// 2. if the number of Addrs is two or more, a ClusterClient is returned.
|
// a FailoverClusterClient is returned.
|
||||||
// 3. Otherwise, a single-node Client 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 {
|
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())
|
return NewFailoverClient(opts.Failover())
|
||||||
} else if len(opts.Addrs) > 1 || opts.IsClusterMode {
|
case len(opts.Addrs) > 1 || opts.IsClusterMode:
|
||||||
return NewClusterClient(opts.Cluster())
|
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())
|
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() {
|
It("should connect to simple servers", func() {
|
||||||
client = redis.NewUniversalClient(&redis.UniversalOptions{
|
client = redis.NewUniversalClient(&redis.UniversalOptions{
|
||||||
Addrs: []string{redisAddr},
|
Addrs: []string{redisAddr},
|
||||||
@ -79,6 +89,7 @@ var _ = Describe("UniversalClient", func() {
|
|||||||
err = client.Set(ctx, "somekey", "somevalue", 0).Err()
|
err = client.Set(ctx, "somekey", "somevalue", 0).Err()
|
||||||
Expect(err).To(HaveOccurred())
|
Expect(err).To(HaveOccurred())
|
||||||
})
|
})
|
||||||
|
|
||||||
It("should connect to clusters if IsClusterMode is set even if only a single address is provided", Label("NonRedisEnterprise"), func() {
|
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{
|
client = redis.NewUniversalClient(&redis.UniversalOptions{
|
||||||
Addrs: []string{cluster.addrs()[0]},
|
Addrs: []string{cluster.addrs()[0]},
|
||||||
@ -96,4 +107,3 @@ var _ = Describe("UniversalClient", func() {
|
|||||||
Expect(client.ClusterSlots(ctx).Val()).To(HaveLen(3))
|
Expect(client.ClusterSlots(ctx).Val()).To(HaveLen(3))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -2,5 +2,5 @@ package redis
|
|||||||
|
|
||||||
// Version is the current release version.
|
// Version is the current release version.
|
||||||
func Version() string {
|
func Version() string {
|
||||||
return "9.7.1"
|
return "9.7.3"
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user