mirror of
https://github.com/redis/go-redis.git
synced 2025-06-06 17:40:59 +03:00
Merge branch 'master' into ndyakov/token-based-auth
This commit is contained in:
commit
8f05aef18e
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-RC2"]="8.0-RC2-pre"
|
["8.0.1"]="8.0.1-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"
|
||||||
)
|
)
|
||||||
|
10
.github/workflows/build.yml
vendored
10
.github/workflows/build.yml
vendored
@ -2,9 +2,9 @@ name: Go
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [master, v9, v9.7]
|
branches: [master, v9, v9.7, v9.8]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [master, v9, v9.7]
|
branches: [master, v9, v9.7, v9.8]
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
@ -18,7 +18,7 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
redis-version:
|
redis-version:
|
||||||
- "8.0-RC2" # 8.0 RC2
|
- "8.0.1" # 8.0.1
|
||||||
- "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-RC2"]="8.0-RC2-pre"
|
["8.0.1"]="8.0.1-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-RC2" # 8.0 RC2
|
- "8.0.1" # 8.0.1
|
||||||
- "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/codeql-analysis.yml
vendored
5
.github/workflows/codeql-analysis.yml
vendored
@ -13,10 +13,9 @@ name: "CodeQL"
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ master ]
|
branches: [master, v9, v9.7, v9.8]
|
||||||
pull_request:
|
pull_request:
|
||||||
# The branches below must be a subset of the branches above
|
branches: [master, v9, v9.7, v9.8]
|
||||||
branches: [ master ]
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
analyze:
|
analyze:
|
||||||
|
3
.github/workflows/golangci-lint.yml
vendored
3
.github/workflows/golangci-lint.yml
vendored
@ -8,6 +8,7 @@ on:
|
|||||||
- master
|
- master
|
||||||
- main
|
- main
|
||||||
- v9
|
- v9
|
||||||
|
- v9.8
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
@ -21,7 +22,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
uses: golangci/golangci-lint-action@v7.0.0
|
uses: golangci/golangci-lint-action@v8.0.0
|
||||||
with:
|
with:
|
||||||
verify: true
|
verify: true
|
||||||
|
|
||||||
|
2
.github/workflows/test-redis-enterprise.yml
vendored
2
.github/workflows/test-redis-enterprise.yml
vendored
@ -2,7 +2,7 @@ name: RE Tests
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [master]
|
branches: [master, v9, v9.7, v9.8]
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
|
80
RELEASE-NOTES.md
Normal file
80
RELEASE-NOTES.md
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
# Release Notes
|
||||||
|
|
||||||
|
# 9.8.0 (2025-04-30)
|
||||||
|
|
||||||
|
## 🚀 Highlights
|
||||||
|
- **Redis 8 Support**: Full compatibility with Redis 8.0, including testing and CI integration
|
||||||
|
- **Enhanced Hash Operations**: Added support for new hash commands (`HGETDEL`, `HGETEX`, `HSETEX`) and `HSTRLEN` command
|
||||||
|
- **Search Improvements**: Enabled Search DIALECT 2 by default and added `CountOnly` argument for `FT.Search`
|
||||||
|
|
||||||
|
## ✨ New Features
|
||||||
|
- Added support for new hash commands: `HGETDEL`, `HGETEX`, `HSETEX` ([#3305](https://github.com/redis/go-redis/pull/3305))
|
||||||
|
- Added `HSTRLEN` command for hash operations ([#2843](https://github.com/redis/go-redis/pull/2843))
|
||||||
|
- Added `Do` method for raw query by single connection from `pool.Conn()` ([#3182](https://github.com/redis/go-redis/pull/3182))
|
||||||
|
- Prevent false-positive marshaling by treating zero time.Time as empty in isEmptyValue ([#3273](https://github.com/redis/go-redis/pull/3273))
|
||||||
|
- Added FailoverClusterClient support for Universal client ([#2794](https://github.com/redis/go-redis/pull/2794))
|
||||||
|
- Added support for cluster mode with `IsClusterMode` config parameter ([#3255](https://github.com/redis/go-redis/pull/3255))
|
||||||
|
- Added client name support in `HELLO` RESP handshake ([#3294](https://github.com/redis/go-redis/pull/3294))
|
||||||
|
- **Enabled Search DIALECT 2 by default** ([#3213](https://github.com/redis/go-redis/pull/3213))
|
||||||
|
- Added read-only option for failover configurations ([#3281](https://github.com/redis/go-redis/pull/3281))
|
||||||
|
- Added `CountOnly` argument for `FT.Search` to use `LIMIT 0 0` ([#3338](https://github.com/redis/go-redis/pull/3338))
|
||||||
|
- Added `DB` option support in `NewFailoverClusterClient` ([#3342](https://github.com/redis/go-redis/pull/3342))
|
||||||
|
- Added `nil` check for the options when creating a client ([#3363](https://github.com/redis/go-redis/pull/3363))
|
||||||
|
|
||||||
|
## 🐛 Bug Fixes
|
||||||
|
- Fixed `PubSub` concurrency safety issues ([#3360](https://github.com/redis/go-redis/pull/3360))
|
||||||
|
- Fixed panic caused when argument is `nil` ([#3353](https://github.com/redis/go-redis/pull/3353))
|
||||||
|
- Improved error handling when fetching master node from sentinels ([#3349](https://github.com/redis/go-redis/pull/3349))
|
||||||
|
- Fixed connection pool timeout issues and increased retries ([#3298](https://github.com/redis/go-redis/pull/3298))
|
||||||
|
- Fixed context cancellation error leading to connection spikes on Primary instances ([#3190](https://github.com/redis/go-redis/pull/3190))
|
||||||
|
- Fixed RedisCluster client to consider `MASTERDOWN` a retriable error ([#3164](https://github.com/redis/go-redis/pull/3164))
|
||||||
|
- Fixed tracing to show complete commands instead of truncated versions ([#3290](https://github.com/redis/go-redis/pull/3290))
|
||||||
|
- Fixed OpenTelemetry instrumentation to prevent multiple span reporting ([#3168](https://github.com/redis/go-redis/pull/3168))
|
||||||
|
- Fixed `FT.Search` Limit argument and added `CountOnly` argument for limit 0 0 ([#3338](https://github.com/redis/go-redis/pull/3338))
|
||||||
|
- Fixed missing command in interface ([#3344](https://github.com/redis/go-redis/pull/3344))
|
||||||
|
- Fixed slot calculation for `COUNTKEYSINSLOT` command ([#3327](https://github.com/redis/go-redis/pull/3327))
|
||||||
|
- Updated PubSub implementation with correct context ([#3329](https://github.com/redis/go-redis/pull/3329))
|
||||||
|
|
||||||
|
## 📚 Documentation
|
||||||
|
- Added hash search examples ([#3357](https://github.com/redis/go-redis/pull/3357))
|
||||||
|
- Fixed documentation comments ([#3351](https://github.com/redis/go-redis/pull/3351))
|
||||||
|
- Added `CountOnly` search example ([#3345](https://github.com/redis/go-redis/pull/3345))
|
||||||
|
- Added examples for list commands: `LLEN`, `LPOP`, `LPUSH`, `LRANGE`, `RPOP`, `RPUSH` ([#3234](https://github.com/redis/go-redis/pull/3234))
|
||||||
|
- Added `SADD` and `SMEMBERS` command examples ([#3242](https://github.com/redis/go-redis/pull/3242))
|
||||||
|
- Updated `README.md` to use Redis Discord guild ([#3331](https://github.com/redis/go-redis/pull/3331))
|
||||||
|
- Updated `HExpire` command documentation ([#3355](https://github.com/redis/go-redis/pull/3355))
|
||||||
|
- Featured OpenTelemetry instrumentation more prominently ([#3316](https://github.com/redis/go-redis/pull/3316))
|
||||||
|
- Updated `README.md` with additional information ([#310ce55](https://github.com/redis/go-redis/commit/310ce55))
|
||||||
|
|
||||||
|
## ⚡ Performance and Reliability
|
||||||
|
- Bound connection pool background dials to configured dial timeout ([#3089](https://github.com/redis/go-redis/pull/3089))
|
||||||
|
- Ensured context isn't exhausted via concurrent query ([#3334](https://github.com/redis/go-redis/pull/3334))
|
||||||
|
|
||||||
|
## 🔧 Dependencies and Infrastructure
|
||||||
|
- Updated testing image to Redis 8.0-RC2 ([#3361](https://github.com/redis/go-redis/pull/3361))
|
||||||
|
- Enabled CI for Redis CE 8.0 ([#3274](https://github.com/redis/go-redis/pull/3274))
|
||||||
|
- Updated various dependencies:
|
||||||
|
- Bumped golangci/golangci-lint-action from 6.5.0 to 7.0.0 ([#3354](https://github.com/redis/go-redis/pull/3354))
|
||||||
|
- Bumped rojopolis/spellcheck-github-actions ([#3336](https://github.com/redis/go-redis/pull/3336))
|
||||||
|
- Bumped golang.org/x/net in example/otel ([#3308](https://github.com/redis/go-redis/pull/3308))
|
||||||
|
- Migrated golangci-lint configuration to v2 format ([#3354](https://github.com/redis/go-redis/pull/3354))
|
||||||
|
|
||||||
|
## ⚠️ Breaking Changes
|
||||||
|
- **Enabled Search DIALECT 2 by default** ([#3213](https://github.com/redis/go-redis/pull/3213))
|
||||||
|
- Dropped RedisGears (Triggers and Functions) support ([#3321](https://github.com/redis/go-redis/pull/3321))
|
||||||
|
- Dropped FT.PROFILE command that was never enabled ([#3323](https://github.com/redis/go-redis/pull/3323))
|
||||||
|
|
||||||
|
## 🔒 Security
|
||||||
|
- Fixed network error handling on SETINFO (CVE-2025-29923) ([#3295](https://github.com/redis/go-redis/pull/3295))
|
||||||
|
|
||||||
|
## 🧪 Testing
|
||||||
|
- Added integration tests for Redis 8 behavior changes in Redis Search ([#3337](https://github.com/redis/go-redis/pull/3337))
|
||||||
|
- Added vector types INT8 and UINT8 tests ([#3299](https://github.com/redis/go-redis/pull/3299))
|
||||||
|
- Added test codes for search_commands.go ([#3285](https://github.com/redis/go-redis/pull/3285))
|
||||||
|
- Fixed example test sorting ([#3292](https://github.com/redis/go-redis/pull/3292))
|
||||||
|
|
||||||
|
## 👥 Contributors
|
||||||
|
|
||||||
|
We would like to thank all the contributors who made this release possible:
|
||||||
|
|
||||||
|
[@alexander-menshchikov](https://github.com/alexander-menshchikov), [@EXPEbdodla](https://github.com/EXPEbdodla), [@afti](https://github.com/afti), [@dmaier-redislabs](https://github.com/dmaier-redislabs), [@four_leaf_clover](https://github.com/four_leaf_clover), [@alohaglenn](https://github.com/alohaglenn), [@gh73962](https://github.com/gh73962), [@justinmir](https://github.com/justinmir), [@LINKIWI](https://github.com/LINKIWI), [@liushuangbill](https://github.com/liushuangbill), [@golang88](https://github.com/golang88), [@gnpaone](https://github.com/gnpaone), [@ndyakov](https://github.com/ndyakov), [@nikolaydubina](https://github.com/nikolaydubina), [@oleglacto](https://github.com/oleglacto), [@andy-stark-redis](https://github.com/andy-stark-redis), [@rodneyosodo](https://github.com/rodneyosodo), [@dependabot](https://github.com/dependabot), [@rfyiamcool](https://github.com/rfyiamcool), [@frankxjkuang](https://github.com/frankxjkuang), [@fukua95](https://github.com/fukua95), [@soleymani-milad](https://github.com/soleymani-milad), [@ofekshenawa](https://github.com/ofekshenawa), [@khasanovbi](https://github.com/khasanovbi)
|
@ -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.8.0-beta.1
|
github.com/redis/go-redis/v9 v9.8.0
|
||||||
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.8.0-beta.1
|
require github.com/redis/go-redis/v9 v9.8.0
|
||||||
|
|
||||||
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.8.0-beta.1
|
github.com/redis/go-redis/v9 v9.8.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
@ -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.8.0-beta.1
|
require github.com/redis/go-redis/v9 v9.8.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
|
@ -11,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.8.0-beta.1
|
github.com/redis/go-redis/extra/redisotel/v9 v9.8.0
|
||||||
github.com/redis/go-redis/v9 v9.8.0-beta.1
|
github.com/redis/go-redis/v9 v9.8.0
|
||||||
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
|
||||||
)
|
)
|
||||||
@ -25,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.8.0-beta.1 // indirect
|
github.com/redis/go-redis/extra/rediscmd/v9 v9.8.0 // 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
|
||||||
|
@ -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.8.0-beta.1
|
require github.com/redis/go-redis/v9 v9.8.0
|
||||||
|
|
||||||
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.8.0-beta.1
|
github.com/redis/go-redis/v9 v9.8.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
@ -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.8.0-beta.1
|
github.com/redis/go-redis/extra/rediscmd/v9 v9.8.0
|
||||||
github.com/redis/go-redis/v9 v9.8.0-beta.1
|
github.com/redis/go-redis/v9 v9.8.0
|
||||||
go.opencensus.io v0.24.0
|
go.opencensus.io v0.24.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -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.8.0-beta.1
|
github.com/redis/go-redis/v9 v9.8.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
@ -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.8.0-beta.1
|
github.com/redis/go-redis/extra/rediscmd/v9 v9.8.0
|
||||||
github.com/redis/go-redis/v9 v9.8.0-beta.1
|
github.com/redis/go-redis/v9 v9.8.0
|
||||||
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
|
||||||
|
@ -127,6 +127,22 @@ func reportPoolStats(rdb *redis.Client, conf *config) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hits, err := conf.meter.Int64ObservableUpDownCounter(
|
||||||
|
"db.client.connections.hits",
|
||||||
|
metric.WithDescription("The number of times free connection was found in the pool"),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
misses, err := conf.meter.Int64ObservableUpDownCounter(
|
||||||
|
"db.client.connections.misses",
|
||||||
|
metric.WithDescription("The number of times free connection was not found in the pool"),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
redisConf := rdb.Options()
|
redisConf := rdb.Options()
|
||||||
_, err = conf.meter.RegisterCallback(
|
_, err = conf.meter.RegisterCallback(
|
||||||
func(ctx context.Context, o metric.Observer) error {
|
func(ctx context.Context, o metric.Observer) error {
|
||||||
@ -140,6 +156,8 @@ func reportPoolStats(rdb *redis.Client, conf *config) error {
|
|||||||
o.ObserveInt64(usage, int64(stats.TotalConns-stats.IdleConns), metric.WithAttributes(usedAttrs...))
|
o.ObserveInt64(usage, int64(stats.TotalConns-stats.IdleConns), metric.WithAttributes(usedAttrs...))
|
||||||
|
|
||||||
o.ObserveInt64(timeouts, int64(stats.Timeouts), metric.WithAttributes(labels...))
|
o.ObserveInt64(timeouts, int64(stats.Timeouts), metric.WithAttributes(labels...))
|
||||||
|
o.ObserveInt64(hits, int64(stats.Hits), metric.WithAttributes(labels...))
|
||||||
|
o.ObserveInt64(misses, int64(stats.Misses), metric.WithAttributes(labels...))
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
idleMax,
|
idleMax,
|
||||||
@ -147,6 +165,8 @@ func reportPoolStats(rdb *redis.Client, conf *config) error {
|
|||||||
connsMax,
|
connsMax,
|
||||||
usage,
|
usage,
|
||||||
timeouts,
|
timeouts,
|
||||||
|
hits,
|
||||||
|
misses,
|
||||||
)
|
)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
@ -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.8.0-beta.1
|
github.com/redis/go-redis/v9 v9.8.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
11
helper/helper.go
Normal file
11
helper/helper.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package helper
|
||||||
|
|
||||||
|
import "github.com/redis/go-redis/v9/internal/util"
|
||||||
|
|
||||||
|
func ParseFloat(s string) (float64, error) {
|
||||||
|
return util.ParseStringToFloat(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MustParseFloat(s string) float64 {
|
||||||
|
return util.MustParseFloat(s)
|
||||||
|
}
|
@ -33,9 +33,11 @@ var timers = sync.Pool{
|
|||||||
|
|
||||||
// Stats contains pool state information and accumulated stats.
|
// Stats contains pool state information and accumulated stats.
|
||||||
type Stats struct {
|
type Stats struct {
|
||||||
Hits uint32 // number of times free connection was found in the pool
|
Hits uint32 // number of times free connection was found in the pool
|
||||||
Misses uint32 // number of times free connection was NOT found in the pool
|
Misses uint32 // number of times free connection was NOT found in the pool
|
||||||
Timeouts uint32 // number of times a wait timeout occurred
|
Timeouts uint32 // number of times a wait timeout occurred
|
||||||
|
WaitCount uint32 // number of times a connection was waited
|
||||||
|
WaitDurationNs int64 // total time spent for waiting a connection in nanoseconds
|
||||||
|
|
||||||
TotalConns uint32 // number of total connections in the pool
|
TotalConns uint32 // number of total connections in the pool
|
||||||
IdleConns uint32 // number of idle connections in the pool
|
IdleConns uint32 // number of idle connections in the pool
|
||||||
@ -90,7 +92,8 @@ type ConnPool struct {
|
|||||||
poolSize int
|
poolSize int
|
||||||
idleConnsLen int
|
idleConnsLen int
|
||||||
|
|
||||||
stats Stats
|
stats Stats
|
||||||
|
waitDurationNs atomic.Int64
|
||||||
|
|
||||||
_closed uint32 // atomic
|
_closed uint32 // atomic
|
||||||
}
|
}
|
||||||
@ -320,6 +323,7 @@ func (p *ConnPool) waitTurn(ctx context.Context) error {
|
|||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
timer := timers.Get().(*time.Timer)
|
timer := timers.Get().(*time.Timer)
|
||||||
timer.Reset(p.cfg.PoolTimeout)
|
timer.Reset(p.cfg.PoolTimeout)
|
||||||
|
|
||||||
@ -331,6 +335,8 @@ func (p *ConnPool) waitTurn(ctx context.Context) error {
|
|||||||
timers.Put(timer)
|
timers.Put(timer)
|
||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
case p.queue <- struct{}{}:
|
case p.queue <- struct{}{}:
|
||||||
|
p.waitDurationNs.Add(time.Since(start).Nanoseconds())
|
||||||
|
atomic.AddUint32(&p.stats.WaitCount, 1)
|
||||||
if !timer.Stop() {
|
if !timer.Stop() {
|
||||||
<-timer.C
|
<-timer.C
|
||||||
}
|
}
|
||||||
@ -457,9 +463,11 @@ func (p *ConnPool) IdleLen() int {
|
|||||||
|
|
||||||
func (p *ConnPool) Stats() *Stats {
|
func (p *ConnPool) Stats() *Stats {
|
||||||
return &Stats{
|
return &Stats{
|
||||||
Hits: atomic.LoadUint32(&p.stats.Hits),
|
Hits: atomic.LoadUint32(&p.stats.Hits),
|
||||||
Misses: atomic.LoadUint32(&p.stats.Misses),
|
Misses: atomic.LoadUint32(&p.stats.Misses),
|
||||||
Timeouts: atomic.LoadUint32(&p.stats.Timeouts),
|
Timeouts: atomic.LoadUint32(&p.stats.Timeouts),
|
||||||
|
WaitCount: atomic.LoadUint32(&p.stats.WaitCount),
|
||||||
|
WaitDurationNs: p.waitDurationNs.Load(),
|
||||||
|
|
||||||
TotalConns: uint32(p.Len()),
|
TotalConns: uint32(p.Len()),
|
||||||
IdleConns: uint32(p.IdleLen()),
|
IdleConns: uint32(p.IdleLen()),
|
||||||
|
@ -59,12 +59,14 @@ var _ = Describe("ConnPool", func() {
|
|||||||
time.Sleep(time.Second)
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
Expect(connPool.Stats()).To(Equal(&pool.Stats{
|
Expect(connPool.Stats()).To(Equal(&pool.Stats{
|
||||||
Hits: 0,
|
Hits: 0,
|
||||||
Misses: 0,
|
Misses: 0,
|
||||||
Timeouts: 0,
|
Timeouts: 0,
|
||||||
TotalConns: 0,
|
WaitCount: 0,
|
||||||
IdleConns: 0,
|
WaitDurationNs: 0,
|
||||||
StaleConns: 0,
|
TotalConns: 0,
|
||||||
|
IdleConns: 0,
|
||||||
|
StaleConns: 0,
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -358,4 +360,31 @@ var _ = Describe("race", func() {
|
|||||||
Expect(stats.IdleConns).To(Equal(uint32(0)))
|
Expect(stats.IdleConns).To(Equal(uint32(0)))
|
||||||
Expect(stats.TotalConns).To(Equal(uint32(opt.PoolSize)))
|
Expect(stats.TotalConns).To(Equal(uint32(opt.PoolSize)))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
It("wait", func() {
|
||||||
|
opt := &pool.Options{
|
||||||
|
Dialer: func(ctx context.Context) (net.Conn, error) {
|
||||||
|
return &net.TCPConn{}, nil
|
||||||
|
},
|
||||||
|
PoolSize: 1,
|
||||||
|
PoolTimeout: 3 * time.Second,
|
||||||
|
}
|
||||||
|
p := pool.NewConnPool(opt)
|
||||||
|
|
||||||
|
wait := make(chan struct{})
|
||||||
|
conn, _ := p.Get(ctx)
|
||||||
|
go func() {
|
||||||
|
_, _ = p.Get(ctx)
|
||||||
|
wait <- struct{}{}
|
||||||
|
}()
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
p.Put(ctx, conn)
|
||||||
|
<-wait
|
||||||
|
|
||||||
|
stats := p.Stats()
|
||||||
|
Expect(stats.IdleConns).To(Equal(uint32(0)))
|
||||||
|
Expect(stats.TotalConns).To(Equal(uint32(1)))
|
||||||
|
Expect(stats.WaitCount).To(Equal(uint32(1)))
|
||||||
|
Expect(stats.WaitDurationNs).To(BeNumerically("~", time.Second.Nanoseconds(), 100*time.Millisecond.Nanoseconds()))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
30
internal/util/convert.go
Normal file
30
internal/util/convert.go
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParseFloat parses a Redis RESP3 float reply into a Go float64,
|
||||||
|
// handling "inf", "-inf", "nan" per Redis conventions.
|
||||||
|
func ParseStringToFloat(s string) (float64, error) {
|
||||||
|
switch s {
|
||||||
|
case "inf":
|
||||||
|
return math.Inf(1), nil
|
||||||
|
case "-inf":
|
||||||
|
return math.Inf(-1), nil
|
||||||
|
case "nan", "-nan":
|
||||||
|
return math.NaN(), nil
|
||||||
|
}
|
||||||
|
return strconv.ParseFloat(s, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustParseFloat is like ParseFloat but panics on parse errors.
|
||||||
|
func MustParseFloat(s string) float64 {
|
||||||
|
f, err := ParseStringToFloat(s)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("redis: failed to parse float %q: %v", s, err))
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
40
internal/util/convert_test.go
Normal file
40
internal/util/convert_test.go
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseStringToFloat(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
in string
|
||||||
|
want float64
|
||||||
|
ok bool
|
||||||
|
}{
|
||||||
|
{"1.23", 1.23, true},
|
||||||
|
{"inf", math.Inf(1), true},
|
||||||
|
{"-inf", math.Inf(-1), true},
|
||||||
|
{"nan", math.NaN(), true},
|
||||||
|
{"oops", 0, false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
got, err := ParseStringToFloat(tc.in)
|
||||||
|
if tc.ok {
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ParseFloat(%q) error: %v", tc.in, err)
|
||||||
|
}
|
||||||
|
if math.IsNaN(tc.want) {
|
||||||
|
if !math.IsNaN(got) {
|
||||||
|
t.Errorf("ParseFloat(%q) = %v; want NaN", tc.in, got)
|
||||||
|
}
|
||||||
|
} else if got != tc.want {
|
||||||
|
t.Errorf("ParseFloat(%q) = %v; want %v", tc.in, got, tc.want)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("ParseFloat(%q) expected error, got nil", tc.in)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -100,7 +100,8 @@ var _ = BeforeSuite(func() {
|
|||||||
|
|
||||||
fmt.Printf("RECluster: %v\n", RECluster)
|
fmt.Printf("RECluster: %v\n", RECluster)
|
||||||
fmt.Printf("RCEDocker: %v\n", RCEDocker)
|
fmt.Printf("RCEDocker: %v\n", RCEDocker)
|
||||||
fmt.Printf("REDIS_VERSION: %v\n", RedisVersion)
|
fmt.Printf("REDIS_VERSION: %.1f\n", RedisVersion)
|
||||||
|
fmt.Printf("CLIENT_LIBS_TEST_IMAGE: %v\n", os.Getenv("CLIENT_LIBS_TEST_IMAGE"))
|
||||||
|
|
||||||
if RedisVersion < 7.0 || RedisVersion > 9 {
|
if RedisVersion < 7.0 || RedisVersion > 9 {
|
||||||
panic("incorrect or not supported redis version")
|
panic("incorrect or not supported redis version")
|
||||||
|
@ -927,6 +927,9 @@ type ClusterClient struct {
|
|||||||
// NewClusterClient returns a Redis Cluster client as described in
|
// NewClusterClient returns a Redis Cluster client as described in
|
||||||
// http://redis.io/topics/cluster-spec.
|
// http://redis.io/topics/cluster-spec.
|
||||||
func NewClusterClient(opt *ClusterOptions) *ClusterClient {
|
func NewClusterClient(opt *ClusterOptions) *ClusterClient {
|
||||||
|
if opt == nil {
|
||||||
|
panic("redis: NewClusterClient nil options")
|
||||||
|
}
|
||||||
opt.init()
|
opt.init()
|
||||||
|
|
||||||
c := &ClusterClient{
|
c := &ClusterClient{
|
||||||
|
3
redis.go
3
redis.go
@ -736,6 +736,9 @@ type Client struct {
|
|||||||
|
|
||||||
// NewClient returns a client to the Redis Server specified by Options.
|
// NewClient returns a client to the Redis Server specified by Options.
|
||||||
func NewClient(opt *Options) *Client {
|
func NewClient(opt *Options) *Client {
|
||||||
|
if opt == nil {
|
||||||
|
panic("redis: NewClient nil options")
|
||||||
|
}
|
||||||
opt.init()
|
opt.init()
|
||||||
|
|
||||||
c := Client{
|
c := Client{
|
||||||
|
@ -896,3 +896,55 @@ func (m *mockStreamingProvider) Subscribe(listener auth.CredentialsListener) (au
|
|||||||
return
|
return
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ = Describe("Client creation", func() {
|
||||||
|
Context("simple client with nil options", func() {
|
||||||
|
It("panics", func() {
|
||||||
|
Expect(func() {
|
||||||
|
redis.NewClient(nil)
|
||||||
|
}).To(Panic())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("cluster client with nil options", func() {
|
||||||
|
It("panics", func() {
|
||||||
|
Expect(func() {
|
||||||
|
redis.NewClusterClient(nil)
|
||||||
|
}).To(Panic())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("ring client with nil options", func() {
|
||||||
|
It("panics", func() {
|
||||||
|
Expect(func() {
|
||||||
|
redis.NewRing(nil)
|
||||||
|
}).To(Panic())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("universal client with nil options", func() {
|
||||||
|
It("panics", func() {
|
||||||
|
Expect(func() {
|
||||||
|
redis.NewUniversalClient(nil)
|
||||||
|
}).To(Panic())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("failover client with nil options", func() {
|
||||||
|
It("panics", func() {
|
||||||
|
Expect(func() {
|
||||||
|
redis.NewFailoverClient(nil)
|
||||||
|
}).To(Panic())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("failover cluster client with nil options", func() {
|
||||||
|
It("panics", func() {
|
||||||
|
Expect(func() {
|
||||||
|
redis.NewFailoverClusterClient(nil)
|
||||||
|
}).To(Panic())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("sentinel client with nil options", func() {
|
||||||
|
It("panics", func() {
|
||||||
|
Expect(func() {
|
||||||
|
redis.NewSentinelClient(nil)
|
||||||
|
}).To(Panic())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
3
ring.go
3
ring.go
@ -523,6 +523,9 @@ type Ring struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewRing(opt *RingOptions) *Ring {
|
func NewRing(opt *RingOptions) *Ring {
|
||||||
|
if opt == nil {
|
||||||
|
panic("redis: NewRing nil options")
|
||||||
|
}
|
||||||
opt.init()
|
opt.init()
|
||||||
|
|
||||||
hbCtx, hbCancel := context.WithCancel(context.Background())
|
hbCtx, hbCancel := context.WithCancel(context.Background())
|
||||||
|
@ -1,15 +1,18 @@
|
|||||||
package redis_test
|
package redis_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"math"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
. "github.com/bsm/ginkgo/v2"
|
. "github.com/bsm/ginkgo/v2"
|
||||||
. "github.com/bsm/gomega"
|
. "github.com/bsm/gomega"
|
||||||
"github.com/redis/go-redis/v9"
|
"github.com/redis/go-redis/v9"
|
||||||
|
"github.com/redis/go-redis/v9/helper"
|
||||||
)
|
)
|
||||||
|
|
||||||
func WaitForIndexing(c *redis.Client, index string) {
|
func WaitForIndexing(c *redis.Client, index string) {
|
||||||
@ -27,6 +30,14 @@ func WaitForIndexing(c *redis.Client, index string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func encodeFloat32Vector(vec []float32) []byte {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
for _, v := range vec {
|
||||||
|
binary.Write(buf, binary.LittleEndian, v)
|
||||||
|
}
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
var _ = Describe("RediSearch commands Resp 2", Label("search"), func() {
|
var _ = Describe("RediSearch commands Resp 2", Label("search"), func() {
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
var client *redis.Client
|
var client *redis.Client
|
||||||
@ -693,9 +704,9 @@ var _ = Describe("RediSearch commands Resp 2", Label("search"), func() {
|
|||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
Expect(res).ToNot(BeNil())
|
Expect(res).ToNot(BeNil())
|
||||||
Expect(len(res.Rows)).To(BeEquivalentTo(2))
|
Expect(len(res.Rows)).To(BeEquivalentTo(2))
|
||||||
score1, err := strconv.ParseFloat(fmt.Sprintf("%s", res.Rows[0].Fields["__score"]), 64)
|
score1, err := helper.ParseFloat(fmt.Sprintf("%s", res.Rows[0].Fields["__score"]))
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
score2, err := strconv.ParseFloat(fmt.Sprintf("%s", res.Rows[1].Fields["__score"]), 64)
|
score2, err := helper.ParseFloat(fmt.Sprintf("%s", res.Rows[1].Fields["__score"]))
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
Expect(score1).To(BeNumerically(">", score2))
|
Expect(score1).To(BeNumerically(">", score2))
|
||||||
|
|
||||||
@ -712,9 +723,9 @@ var _ = Describe("RediSearch commands Resp 2", Label("search"), func() {
|
|||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
Expect(resDM).ToNot(BeNil())
|
Expect(resDM).ToNot(BeNil())
|
||||||
Expect(len(resDM.Rows)).To(BeEquivalentTo(2))
|
Expect(len(resDM.Rows)).To(BeEquivalentTo(2))
|
||||||
score1DM, err := strconv.ParseFloat(fmt.Sprintf("%s", resDM.Rows[0].Fields["__score"]), 64)
|
score1DM, err := helper.ParseFloat(fmt.Sprintf("%s", resDM.Rows[0].Fields["__score"]))
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
score2DM, err := strconv.ParseFloat(fmt.Sprintf("%s", resDM.Rows[1].Fields["__score"]), 64)
|
score2DM, err := helper.ParseFloat(fmt.Sprintf("%s", resDM.Rows[1].Fields["__score"]))
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
Expect(score1DM).To(BeNumerically(">", score2DM))
|
Expect(score1DM).To(BeNumerically(">", score2DM))
|
||||||
|
|
||||||
@ -1684,6 +1695,56 @@ var _ = Describe("RediSearch commands Resp 2", Label("search"), func() {
|
|||||||
Expect(resUint8.Docs[0].ID).To(BeEquivalentTo("doc1"))
|
Expect(resUint8.Docs[0].ID).To(BeEquivalentTo("doc1"))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
It("should return special float scores in FT.SEARCH vecsim", Label("search", "ftsearch", "vecsim"), func() {
|
||||||
|
SkipBeforeRedisVersion(7.4, "doesn't work with older redis stack images")
|
||||||
|
|
||||||
|
vecField := &redis.FTFlatOptions{
|
||||||
|
Type: "FLOAT32",
|
||||||
|
Dim: 2,
|
||||||
|
DistanceMetric: "IP",
|
||||||
|
}
|
||||||
|
_, err := client.FTCreate(ctx, "idx_vec",
|
||||||
|
&redis.FTCreateOptions{OnHash: true, Prefix: []interface{}{"doc:"}},
|
||||||
|
&redis.FieldSchema{FieldName: "vector", FieldType: redis.SearchFieldTypeVector, VectorArgs: &redis.FTVectorArgs{FlatOptions: vecField}}).Result()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
WaitForIndexing(client, "idx_vec")
|
||||||
|
|
||||||
|
bigPos := []float32{1e38, 1e38}
|
||||||
|
bigNeg := []float32{-1e38, -1e38}
|
||||||
|
nanVec := []float32{float32(math.NaN()), 0}
|
||||||
|
negNanVec := []float32{float32(math.Copysign(math.NaN(), -1)), 0}
|
||||||
|
|
||||||
|
client.HSet(ctx, "doc:1", "vector", encodeFloat32Vector(bigPos))
|
||||||
|
client.HSet(ctx, "doc:2", "vector", encodeFloat32Vector(bigNeg))
|
||||||
|
client.HSet(ctx, "doc:3", "vector", encodeFloat32Vector(nanVec))
|
||||||
|
client.HSet(ctx, "doc:4", "vector", encodeFloat32Vector(negNanVec))
|
||||||
|
|
||||||
|
searchOptions := &redis.FTSearchOptions{WithScores: true, Params: map[string]interface{}{"vec": encodeFloat32Vector(bigPos)}}
|
||||||
|
res, err := client.FTSearchWithArgs(ctx, "idx_vec", "*=>[KNN 4 @vector $vec]", searchOptions).Result()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(res.Total).To(BeEquivalentTo(4))
|
||||||
|
|
||||||
|
var scores []float64
|
||||||
|
for _, row := range res.Docs {
|
||||||
|
raw := fmt.Sprintf("%v", row.Fields["__vector_score"])
|
||||||
|
f, err := helper.ParseFloat(raw)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
scores = append(scores, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
Expect(scores).To(ContainElement(BeNumerically("==", math.Inf(1))))
|
||||||
|
Expect(scores).To(ContainElement(BeNumerically("==", math.Inf(-1))))
|
||||||
|
|
||||||
|
// For NaN values, use a custom check since NaN != NaN in floating point math
|
||||||
|
nanCount := 0
|
||||||
|
for _, score := range scores {
|
||||||
|
if math.IsNaN(score) {
|
||||||
|
nanCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expect(nanCount).To(Equal(2))
|
||||||
|
})
|
||||||
|
|
||||||
It("should fail when using a non-zero offset with a zero limit", Label("search", "ftsearch"), func() {
|
It("should fail when using a non-zero offset with a zero limit", Label("search", "ftsearch"), func() {
|
||||||
SkipBeforeRedisVersion(7.9, "requires Redis 8.x")
|
SkipBeforeRedisVersion(7.9, "requires Redis 8.x")
|
||||||
val, err := client.FTCreate(ctx, "testIdx", &redis.FTCreateOptions{}, &redis.FieldSchema{
|
val, err := client.FTCreate(ctx, "testIdx", &redis.FTCreateOptions{}, &redis.FieldSchema{
|
||||||
@ -1871,17 +1932,20 @@ var _ = Describe("RediSearch commands Resp 2", Label("search"), func() {
|
|||||||
Expect(val).To(BeEquivalentTo("OK"))
|
Expect(val).To(BeEquivalentTo("OK"))
|
||||||
WaitForIndexing(client, "aggTimeoutHeavy")
|
WaitForIndexing(client, "aggTimeoutHeavy")
|
||||||
|
|
||||||
const totalDocs = 10000
|
const totalDocs = 100000
|
||||||
for i := 0; i < totalDocs; i++ {
|
for i := 0; i < totalDocs; i++ {
|
||||||
key := fmt.Sprintf("doc%d", i)
|
key := fmt.Sprintf("doc%d", i)
|
||||||
_, err := client.HSet(ctx, key, "n", i).Result()
|
_, err := client.HSet(ctx, key, "n", i).Result()
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
}
|
}
|
||||||
|
// default behaviour was changed in 8.0.1, set to fail to validate the timeout was triggered
|
||||||
|
err = client.ConfigSet(ctx, "search-on-timeout", "fail").Err()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
options := &redis.FTAggregateOptions{
|
options := &redis.FTAggregateOptions{
|
||||||
SortBy: []redis.FTAggregateSortBy{{FieldName: "@n", Desc: true}},
|
SortBy: []redis.FTAggregateSortBy{{FieldName: "@n", Desc: true}},
|
||||||
LimitOffset: 0,
|
LimitOffset: 0,
|
||||||
Limit: 100,
|
Limit: 100000,
|
||||||
Timeout: 1, // 1 ms timeout, expected to trigger a timeout error.
|
Timeout: 1, // 1 ms timeout, expected to trigger a timeout error.
|
||||||
}
|
}
|
||||||
_, err = client.FTAggregateWithArgs(ctx, "aggTimeoutHeavy", "*", options).Result()
|
_, err = client.FTAggregateWithArgs(ctx, "aggTimeoutHeavy", "*", options).Result()
|
||||||
|
11
sentinel.go
11
sentinel.go
@ -224,6 +224,10 @@ func (opt *FailoverOptions) clusterOptions() *ClusterOptions {
|
|||||||
// for automatic failover. It's safe for concurrent use by multiple
|
// for automatic failover. It's safe for concurrent use by multiple
|
||||||
// goroutines.
|
// goroutines.
|
||||||
func NewFailoverClient(failoverOpt *FailoverOptions) *Client {
|
func NewFailoverClient(failoverOpt *FailoverOptions) *Client {
|
||||||
|
if failoverOpt == nil {
|
||||||
|
panic("redis: NewFailoverClient nil options")
|
||||||
|
}
|
||||||
|
|
||||||
if failoverOpt.RouteByLatency {
|
if failoverOpt.RouteByLatency {
|
||||||
panic("to route commands by latency, use NewFailoverClusterClient")
|
panic("to route commands by latency, use NewFailoverClusterClient")
|
||||||
}
|
}
|
||||||
@ -312,6 +316,9 @@ type SentinelClient struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewSentinelClient(opt *Options) *SentinelClient {
|
func NewSentinelClient(opt *Options) *SentinelClient {
|
||||||
|
if opt == nil {
|
||||||
|
panic("redis: NewSentinelClient nil options")
|
||||||
|
}
|
||||||
opt.init()
|
opt.init()
|
||||||
c := &SentinelClient{
|
c := &SentinelClient{
|
||||||
baseClient: &baseClient{
|
baseClient: &baseClient{
|
||||||
@ -827,6 +834,10 @@ func contains(slice []string, str string) bool {
|
|||||||
// NewFailoverClusterClient returns a client that supports routing read-only commands
|
// NewFailoverClusterClient returns a client that supports routing read-only commands
|
||||||
// to a replica node.
|
// to a replica node.
|
||||||
func NewFailoverClusterClient(failoverOpt *FailoverOptions) *ClusterClient {
|
func NewFailoverClusterClient(failoverOpt *FailoverOptions) *ClusterClient {
|
||||||
|
if failoverOpt == nil {
|
||||||
|
panic("redis: NewFailoverClusterClient nil options")
|
||||||
|
}
|
||||||
|
|
||||||
sentinelAddrs := make([]string, len(failoverOpt.SentinelAddrs))
|
sentinelAddrs := make([]string, len(failoverOpt.SentinelAddrs))
|
||||||
copy(sentinelAddrs, failoverOpt.SentinelAddrs)
|
copy(sentinelAddrs, failoverOpt.SentinelAddrs)
|
||||||
|
|
||||||
|
@ -267,6 +267,10 @@ var (
|
|||||||
// a ClusterClient is returned.
|
// a ClusterClient is returned.
|
||||||
// 4. Otherwise, a single-node Client is returned.
|
// 4. Otherwise, a single-node Client is returned.
|
||||||
func NewUniversalClient(opts *UniversalOptions) UniversalClient {
|
func NewUniversalClient(opts *UniversalOptions) UniversalClient {
|
||||||
|
if opts == nil {
|
||||||
|
panic("redis: NewUniversalClient nil options")
|
||||||
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case opts.MasterName != "" && (opts.RouteByLatency || opts.RouteRandomly || opts.IsClusterMode):
|
case opts.MasterName != "" && (opts.RouteByLatency || opts.RouteRandomly || opts.IsClusterMode):
|
||||||
return NewFailoverClusterClient(opts.Failover())
|
return NewFailoverClusterClient(opts.Failover())
|
||||||
|
@ -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.8.0-beta.1"
|
return "9.8.0"
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user