- update redis

master v1.0.78
李光春 1 year ago
parent 851a91a9d1
commit cb86530fd4

@ -11,7 +11,6 @@ require (
github.com/go-playground/locales v0.14.1
github.com/go-playground/universal-translator v0.18.0
github.com/go-playground/validator/v10 v10.11.1
github.com/go-redis/redis/v9 v9.0.0-rc.2
github.com/go-sql-driver/mysql v1.7.0
github.com/lib/pq v1.10.7
github.com/mitchellh/mapstructure v1.5.0
@ -19,6 +18,7 @@ require (
github.com/natefinch/lumberjack v2.0.0+incompatible
github.com/oschwald/geoip2-golang v1.8.0
github.com/qiniu/go-sdk/v7 v7.14.0
github.com/redis/go-redis/v9 v9.0.0-rc.4
github.com/robfig/cron/v3 v3.0.1
github.com/saracen/go7z v0.0.0-20191010121135-9c09b6bd7fda
github.com/shirou/gopsutil v3.21.11+incompatible

@ -108,8 +108,6 @@ github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl
github.com/go-playground/validator/v10 v10.8.0/go.mod h1:9JhgTzTaE31GZDpH/HSvHiRJrJ3iKAgqqH0Bl/Ocjdk=
github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ=
github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
github.com/go-redis/redis/v9 v9.0.0-rc.2 h1:IN1eI8AvJJeWHjMW/hlFAv2sAfvTun2DVksDDJ3a6a0=
github.com/go-redis/redis/v9 v9.0.0-rc.2/go.mod h1:cgBknjwcBJa2prbnuHH/4k/Mlj4r0pWNV2HBanHujfY=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
@ -351,7 +349,7 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.24.1 h1:KORJXNNTzJXzu4ScJWssJfJMnJ+2QJqhoQSRwNlze9E=
github.com/onsi/gomega v1.25.0 h1:Vw7br2PCDYijJHSfBOWhov+8cAnUf8MfMaIOV323l6Y=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
@ -403,6 +401,8 @@ github.com/qiniu/go-sdk/v7 v7.14.0 h1:6icihMTKHoKMmeU1mqtIoHUv7c1LrLjYm8wTQaYDqm
github.com/qiniu/go-sdk/v7 v7.14.0/go.mod h1:btsaOc8CA3hdVloULfFdDgDc+g4f3TDZEFsDY0BLE+w=
github.com/qiniu/x v1.10.5/go.mod h1:03Ni9tj+N2h2aKnAz+6N0Xfl8FwMEDRC2PAlxekASDs=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/redis/go-redis/v9 v9.0.0-rc.4 h1:JUhsiZMTZknz3vn50zSVlkwcSeTGPd51lMO3IKUrWpY=
github.com/redis/go-redis/v9 v9.0.0-rc.4/go.mod h1:Vo3EsyWnicKnSKCA7HhgnvnyA74wOA69Cd2Meli5mmA=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=

@ -1,5 +1,5 @@
package go_library
func Version() string {
return "1.0.77"
return "1.0.78"
}

@ -4,7 +4,7 @@ import (
"context"
"errors"
"fmt"
"github.com/go-redis/redis/v9"
"github.com/redis/go-redis/v9"
"time"
)

@ -2,7 +2,7 @@ package dorm
import (
"context"
"github.com/go-redis/redis/v9"
"github.com/redis/go-redis/v9"
"time"
)

@ -2,7 +2,7 @@ package dorm
import (
"context"
"github.com/go-redis/redis/v9"
"github.com/redis/go-redis/v9"
)
// Subscribe 订阅channel

@ -1,6 +1,6 @@
package dorm
import "github.com/go-redis/redis/v9"
import "github.com/redis/go-redis/v9"
// GetDb 获取驱动
func (r *RedisClient) GetDb() *redis.Client {

@ -2,7 +2,7 @@ package dorm
import (
"context"
"github.com/go-redis/redis/v9"
"github.com/redis/go-redis/v9"
)
type HashOperation struct {

@ -2,7 +2,7 @@ package dorm
import (
"context"
"github.com/go-redis/redis/v9"
"github.com/redis/go-redis/v9"
)
type ListOperation struct {

@ -4,7 +4,7 @@ import (
"context"
"errors"
"fmt"
"github.com/go-redis/redis/v9"
"github.com/redis/go-redis/v9"
"time"
)

@ -2,7 +2,7 @@ package dorm
import (
"context"
"github.com/go-redis/redis/v9"
"github.com/redis/go-redis/v9"
"time"
)

@ -2,7 +2,7 @@ package dorm
import (
"context"
"github.com/go-redis/redis/v9"
"github.com/redis/go-redis/v9"
"time"
)

@ -4,7 +4,7 @@ import (
"context"
"github.com/dtapps/go-library"
"github.com/dtapps/go-library/utils/goip"
"github.com/go-redis/redis/v9"
"github.com/redis/go-redis/v9"
"github.com/shirou/gopsutil/cpu"
"github.com/shirou/gopsutil/host"
"log"

@ -1,7 +1,7 @@
package gojobs
import (
"github.com/go-redis/redis/v9"
"github.com/redis/go-redis/v9"
"go.mongodb.org/mongo-driver/mongo"
"gorm.io/gorm"
)

@ -2,7 +2,7 @@ package gojobs
import (
"context"
"github.com/go-redis/redis/v9"
"github.com/redis/go-redis/v9"
)
// Publish 发布

@ -1,55 +0,0 @@
# [9.0.0-rc.2](https://github.com/go-redis/redis/compare/v9.0.0-rc.1...v9.0.0-rc.2) (2022-11-26)
### Bug Fixes
* capture error correctly in withConn ([d1bfaba](https://github.com/go-redis/redis/commit/d1bfaba549fe380d269c26cea0a0183ed1520a85))
* fixes ring.SetAddrs and rebalance race ([#2283](https://github.com/go-redis/redis/issues/2283)) ([d83436b](https://github.com/go-redis/redis/commit/d83436b321cd9ed52ba33c3edbe8f63bb0444c59))
* read in route_randomly query param correctly ([f236053](https://github.com/go-redis/redis/commit/f236053735d10aec5e6e31fc3ced1b2e53292554))
* reduce `SetAddrs` shards lock contention ([6c05a9f](https://github.com/go-redis/redis/commit/6c05a9f6b17f8e32593d3f7d594f82ba3dbcafb1)), closes [/github.com/go-redis/redis/pull/2190#discussion_r953040289](https://github.com//github.com/go-redis/redis/pull/2190/issues/discussion_r953040289) [#2077](https://github.com/go-redis/redis/issues/2077)
* wrap cmds in Conn.TxPipeline ([5053db2](https://github.com/go-redis/redis/commit/5053db2f9c8b3ca25f497a75f70012c7ad6cd775))
### Features
* add HasErrorPrefix ([d3d8002](https://github.com/go-redis/redis/commit/d3d8002e894a1eab5bab2c9fff13439527e330d8))
* add support for SINTERCARD command ([bc51c61](https://github.com/go-redis/redis/commit/bc51c61a458d1bc4fb4424c7c3e912325ef980cc))
## v9 UNRELEASED
### Added
- Added support for [RESP3](https://github.com/antirez/RESP3/blob/master/spec.md) protocol.
Contributed by @monkey92t who has done a lot of work recently.
- Added `ContextTimeoutEnabled` option that controls whether the client respects context timeouts
and deadlines. See
[Redis Timeouts](https://redis.uptrace.dev/guide/go-redis-debugging.html#timeouts) for details.
- Added `ParseClusterURL` to parse URLs into `ClusterOptions`, for example,
`redis://user:password@localhost:6789?dial_timeout=3&read_timeout=6s&addr=localhost:6790&addr=localhost:6791`.
- Added metrics instrumentation using `redisotel.IstrumentMetrics`. See
[documentation](https://redis.uptrace.dev/guide/go-redis-monitoring.html)
### Changed
- Removed asynchronous cancellation based on the context timeout. It was racy in v8 and is
completely gone in v9.
- Reworked hook interface and added `DialHook`.
- Replaced `redisotel.NewTracingHook` with `redisotel.InstrumentTracing`. See
[example](example/otel) and
[documentation](https://redis.uptrace.dev/guide/go-redis-monitoring.html).
- Replaced `*redis.Z` with `redis.Z` since it is small enough to be passed as value without making
an allocation.
- Renamed the option `MaxConnAge` to `ConnMaxLifetime`.
- Renamed the option `IdleTimeout` to `ConnMaxIdleTime`.
- Removed connection reaper in favor of `MaxIdleConns`.
- Removed `WithContext` since `context.Context` can be passed directly as an arg.
- Removed `Pipeline.Close` since there is no real need to explicitly manage pipeline resources and
it can be safely reused via `sync.Pool` etc. `Pipeline.Discard` is still available if you want to
reset commands for some reason.
### Fixed
- Improved and fixed pipeline retries.
- As usual, added more commands and fixed some bugs.

@ -0,0 +1,71 @@
# [9.0.0-rc.4](https://github.com/redis/go-redis/compare/v9.0.0-rc.3...v9.0.0-rc.4) (2023-01-24)
# [9.0.0-rc.3](https://github.com/redis/go-redis/compare/v9.0.0-rc.2...v9.0.0-rc.3) (2023-01-24)
### Bug Fixes
* 386 platform test ([701b1d0](https://github.com/redis/go-redis/commit/701b1d0a8bc497c8dc55fb61bda05afde2dd073b))
* change serialize key "key" to "redis" ([913936b](https://github.com/redis/go-redis/commit/913936b4cd9ae131e4671d0960bf1f9e46e6b171))
* fix the withHook func ([0ed4a44](https://github.com/redis/go-redis/commit/0ed4a4420fddcbe897b3884ef637ece53ccc55b8))
* read cursor as uint64 ([b88bd93](https://github.com/redis/go-redis/commit/b88bd93662f55ff2d0b2353f5f79e7065464f982))
* **redisotel:** correct metrics.DialHook attrs ([#2331](https://github.com/redis/go-redis/issues/2331)) ([7c4b924](https://github.com/redis/go-redis/commit/7c4b92435024eef4429a30146fad28ec98085c5b))
* remove comment ([4ce9046](https://github.com/redis/go-redis/commit/4ce90461a5572395f0bffcf1e0eb5f17ae31ce11))
* remove mutex from pipeline ([6525bbb](https://github.com/redis/go-redis/commit/6525bbbaa157eaea40e363c462057a3ad29536a9))
* tags for structToMap "json" -> "key" ([07e15d2](https://github.com/redis/go-redis/commit/07e15d2876ccc88afcd0f344a3eed6a050ff1921))
* test code ([1fdcbf8](https://github.com/redis/go-redis/commit/1fdcbf86bbb390e4e689a35a391a4a4b3917216d))
### Features
* add ClientName option ([a872c35](https://github.com/redis/go-redis/commit/a872c35b1a9cbd19904010c105281ad15ab687ab))
* add SORT_RO command ([ca063fd](https://github.com/redis/go-redis/commit/ca063fd0adf0974504f4e9d7352e1b4d7b14cb61))
* add zintercard cmd ([bb65dcd](https://github.com/redis/go-redis/commit/bb65dcdf0903459ed341c87de34ad689632dceff))
* appendArgs adds to read the structure field and supplements the test ([0064199](https://github.com/redis/go-redis/commit/0064199323e408f0dafcd033460acb94a9ad9f4f))
* enable struct on HSet ([bf334e7](https://github.com/redis/go-redis/commit/bf334e773819574a898717f5a709e15cecaa43ff))
* hook mode is changed to FIFO ([97697f4](https://github.com/redis/go-redis/commit/97697f488fe5179542d07af72e031939fd854a99))
* **redisotel:** add code attributes ([3892986](https://github.com/redis/go-redis/commit/3892986f01959e1e71aee8710d9719400e0b1205))
* **scan:** add Scanner interface ([#2317](https://github.com/redis/go-redis/issues/2317)) ([a4336cb](https://github.com/redis/go-redis/commit/a4336cbd43a1e620cb8967bca27a678b9445bef8))
## v9 UNRELEASED
### Added
- Added support for [RESP3](https://github.com/antirez/RESP3/blob/master/spec.md) protocol. It was
contributed by @monkey92t who has done the majority of work in this release.
- Added `ContextTimeoutEnabled` option that controls whether the client respects context timeouts
and deadlines. See
[Redis Timeouts](https://redis.uptrace.dev/guide/go-redis-debugging.html#timeouts) for details.
- Added `ParseClusterURL` to parse URLs into `ClusterOptions`, for example,
`redis://user:password@localhost:6789?dial_timeout=3&read_timeout=6s&addr=localhost:6790&addr=localhost:6791`.
- Added metrics instrumentation using `redisotel.IstrumentMetrics`. See
[documentation](https://redis.uptrace.dev/guide/go-redis-monitoring.html)
- Added `redis.HasErrorPrefix` to help working with errors.
### Changed
- Removed asynchronous cancellation based on the context timeout. It was racy in v8 and is
completely gone in v9.
- Reworked hook interface and added `DialHook`.
- Replaced `redisotel.NewTracingHook` with `redisotel.InstrumentTracing`. See
[example](example/otel) and
[documentation](https://redis.uptrace.dev/guide/go-redis-monitoring.html).
- Replaced `*redis.Z` with `redis.Z` since it is small enough to be passed as value without making
an allocation.
- Renamed the option `MaxConnAge` to `ConnMaxLifetime`.
- Renamed the option `IdleTimeout` to `ConnMaxIdleTime`.
- Removed connection reaper in favor of `MaxIdleConns`.
- Removed `WithContext` since `context.Context` can be passed directly as an arg.
- Removed `Pipeline.Close` since there is no real need to explicitly manage pipeline resources and
it can be safely reused via `sync.Pool` etc. `Pipeline.Discard` is still available if you want to
reset commands for some reason.
- Changed Pipelines to not be thread-safe any more.
### Fixed
- Improved and fixed pipeline retries.
- As usually, added support for more commands and fixed some bugs.

@ -1,4 +1,4 @@
Copyright (c) 2013 The github.com/go-redis/redis Authors.
Copyright (c) 2013 The github.com/redis/go-redis Authors.
All rights reserved.
Redistribution and use in source and binary forms, with or without

@ -18,19 +18,19 @@ bench: testdeps
testdata/redis:
mkdir -p $@
wget -qO- https://download.redis.io/releases/redis-7.0.0.tar.gz | tar xvz --strip-components=1 -C $@
wget -qO- https://download.redis.io/releases/redis-7.0.7.tar.gz | tar xvz --strip-components=1 -C $@
testdata/redis/src/redis-server: testdata/redis
cd $< && make all
fmt:
gofmt -w -s ./
goimports -w -local github.com/go-redis/redis ./
goimports -w -local github.com/redis/go-redis ./
go_mod_tidy:
set -e; for dir in $(PACKAGE_DIRS); do \
echo "go mod tidy in $${dir}"; \
(cd "$${dir}" && \
go get -u ./... && \
go mod tidy -compat=1.17); \
go mod tidy -compat=1.18); \
done

@ -1,7 +1,7 @@
# Redis client for Go
[![build workflow](https://github.com/go-redis/redis/actions/workflows/build.yml/badge.svg)](https://github.com/go-redis/redis/actions)
[![PkgGoDev](https://pkg.go.dev/badge/github.com/go-redis/redis/v8)](https://pkg.go.dev/github.com/go-redis/redis/v8?tab=doc)
[![build workflow](https://github.com/redis/go-redis/actions/workflows/build.yml/badge.svg)](https://github.com/redis/go-redis/actions)
[![PkgGoDev](https://pkg.go.dev/badge/github.com/redis/go-redis/v9)](https://pkg.go.dev/github.com/redis/go-redis/v9?tab=doc)
[![Documentation](https://img.shields.io/badge/redis-documentation-informational)](https://redis.uptrace.dev/)
[![Chat](https://discordapp.com/api/guilds/752070105847955518/widget.png)](https://discord.gg/rWtp5Aj)
@ -16,10 +16,10 @@
## Resources
- [Documentation](https://redis.uptrace.dev)
- [Discussions](https://github.com/go-redis/redis/discussions)
- [Discussions](https://github.com/redis/go-redis/discussions)
- [Chat](https://discord.gg/rWtp5Aj)
- [Reference](https://pkg.go.dev/github.com/go-redis/redis/v8?tab=doc)
- [Examples](https://pkg.go.dev/github.com/go-redis/redis/v8?tab=doc#pkg-examples)
- [Reference](https://pkg.go.dev/github.com/redis/go-redis/v9)
- [Examples](https://pkg.go.dev/github.com/redis/go-redis/v9#pkg-examples)
## Ecosystem
@ -53,16 +53,10 @@ module:
go mod init github.com/my/repo
```
If you are using **Redis 6**, install go-redis/**v8**:
Then install go-redis/**v9**:
```shell
go get github.com/go-redis/redis/v8
```
If you are using **Redis 7**, install go-redis/**v9**:
```shell
go get github.com/go-redis/redis/v9
go get github.com/redis/go-redis/v9
```
## Quickstart
@ -70,7 +64,7 @@ go get github.com/go-redis/redis/v9
```go
import (
"context"
"github.com/go-redis/redis/v8"
"github.com/redis/go-redis/v9"
"fmt"
)
@ -180,6 +174,6 @@ go test
Thanks to all the people who already contributed!
<a href="https://github.com/go-redis/redis/graphs/contributors">
<img src="https://contributors-img.web.app/image?repo=go-redis/redis" />
<a href="https://github.com/redis/go-redis/graphs/contributors">
<img src="https://contributors-img.web.app/image?repo=redis/go-redis" />
</a>

@ -14,11 +14,11 @@ import (
"sync/atomic"
"time"
"github.com/go-redis/redis/v9/internal"
"github.com/go-redis/redis/v9/internal/hashtag"
"github.com/go-redis/redis/v9/internal/pool"
"github.com/go-redis/redis/v9/internal/proto"
"github.com/go-redis/redis/v9/internal/rand"
"github.com/redis/go-redis/v9/internal"
"github.com/redis/go-redis/v9/internal/hashtag"
"github.com/redis/go-redis/v9/internal/pool"
"github.com/redis/go-redis/v9/internal/proto"
"github.com/redis/go-redis/v9/internal/rand"
)
var errClusterNoNodes = fmt.Errorf("redis: cluster has no nodes")
@ -29,6 +29,9 @@ type ClusterOptions struct {
// A seed list of host:port addresses of cluster nodes.
Addrs []string
// ClientName will execute the `CLIENT SETNAME ClientName` command for each conn.
ClientName string
// NewClient creates a cluster node client with provided name and options.
NewClient func(opt *Options) *Client
@ -133,34 +136,39 @@ func (opt *ClusterOptions) init() {
// ParseClusterURL parses a URL into ClusterOptions that can be used to connect to Redis.
// The URL must be in the form:
// redis://<user>:<password>@<host>:<port>
// or
// rediss://<user>:<password>@<host>:<port>
//
// redis://<user>:<password>@<host>:<port>
// or
// rediss://<user>:<password>@<host>:<port>
//
// To add additional addresses, specify the query parameter, "addr" one or more times. e.g:
// redis://<user>:<password>@<host>:<port>?addr=<host2>:<port2>&addr=<host3>:<port3>
// or
// rediss://<user>:<password>@<host>:<port>?addr=<host2>:<port2>&addr=<host3>:<port3>
//
// redis://<user>:<password>@<host>:<port>?addr=<host2>:<port2>&addr=<host3>:<port3>
// or
// rediss://<user>:<password>@<host>:<port>?addr=<host2>:<port2>&addr=<host3>:<port3>
//
// Most Option fields can be set using query parameters, with the following restrictions:
// - field names are mapped using snake-case conversion: to set MaxRetries, use max_retries
// - only scalar type fields are supported (bool, int, time.Duration)
// - for time.Duration fields, values must be a valid input for time.ParseDuration();
// additionally a plain integer as value (i.e. without unit) is intepreted as seconds
// - to disable a duration field, use value less than or equal to 0; to use the default
// value, leave the value blank or remove the parameter
// - only the last value is interpreted if a parameter is given multiple times
// - fields "network", "addr", "username" and "password" can only be set using other
// URL attributes (scheme, host, userinfo, resp.), query paremeters using these
// names will be treated as unknown parameters
// - unknown parameter names will result in an error
// - field names are mapped using snake-case conversion: to set MaxRetries, use max_retries
// - only scalar type fields are supported (bool, int, time.Duration)
// - for time.Duration fields, values must be a valid input for time.ParseDuration();
// additionally a plain integer as value (i.e. without unit) is intepreted as seconds
// - to disable a duration field, use value less than or equal to 0; to use the default
// value, leave the value blank or remove the parameter
// - only the last value is interpreted if a parameter is given multiple times
// - fields "network", "addr", "username" and "password" can only be set using other
// URL attributes (scheme, host, userinfo, resp.), query paremeters using these
// names will be treated as unknown parameters
// - unknown parameter names will result in an error
//
// Example:
// redis://user:password@localhost:6789?dial_timeout=3&read_timeout=6s&addr=localhost:6790&addr=localhost:6791
// is equivalent to:
// &ClusterOptions{
// Addr: ["localhost:6789", "localhost:6790", "localhost:6791"]
// DialTimeout: 3 * time.Second, // no time unit = seconds
// ReadTimeout: 6 * time.Second,
// }
//
// redis://user:password@localhost:6789?dial_timeout=3&read_timeout=6s&addr=localhost:6790&addr=localhost:6791
// is equivalent to:
// &ClusterOptions{
// Addr: ["localhost:6789", "localhost:6790", "localhost:6791"]
// DialTimeout: 3 * time.Second, // no time unit = seconds
// ReadTimeout: 6 * time.Second,
// }
func ParseClusterURL(redisURL string) (*ClusterOptions, error) {
o := &ClusterOptions{}
@ -208,6 +216,7 @@ func setupClusterConn(u *url.URL, host string, o *ClusterOptions) (*ClusterOptio
func setupClusterQueryParams(u *url.URL, o *ClusterOptions) (*ClusterOptions, error) {
q := queryOptions{q: u.Query()}
o.ClientName = q.string("client_name")
o.MaxRedirects = q.int("max_redirects")
o.ReadOnly = q.bool("read_only")
o.RouteByLatency = q.bool("route_by_latency")
@ -250,8 +259,9 @@ func setupClusterQueryParams(u *url.URL, o *ClusterOptions) (*ClusterOptions, er
func (opt *ClusterOptions) clientOptions() *Options {
return &Options{
Dialer: opt.Dialer,
OnConnect: opt.OnConnect,
ClientName: opt.ClientName,
Dialer: opt.Dialer,
OnConnect: opt.OnConnect,
Username: opt.Username,
Password: opt.Password,
@ -828,7 +838,7 @@ type ClusterClient struct {
state *clusterStateHolder
cmdsInfoCache *cmdsInfoCache
cmdable
hooks
hooksMixin
}
// NewClusterClient returns a Redis Cluster client as described in
@ -845,9 +855,12 @@ func NewClusterClient(opt *ClusterOptions) *ClusterClient {
c.cmdsInfoCache = newCmdsInfoCache(c.cmdsInfo)
c.cmdable = c.Process
c.hooks.setProcess(c.process)
c.hooks.setProcessPipeline(c.processPipeline)
c.hooks.setProcessTxPipeline(c.processTxPipeline)
c.initHooks(hooks{
dial: nil,
process: c.process,
pipeline: c.processPipeline,
txPipeline: c.processTxPipeline,
})
return c
}
@ -871,7 +884,7 @@ func (c *ClusterClient) Close() error {
return c.nodes.Close()
}
// Do creates a Cmd from the args and processes the cmd.
// Do create a Cmd from the args and processes the cmd.
func (c *ClusterClient) Do(ctx context.Context, args ...interface{}) *Cmd {
cmd := NewCmd(ctx, args...)
_ = c.Process(ctx, cmd)
@ -879,7 +892,7 @@ func (c *ClusterClient) Do(ctx context.Context, args ...interface{}) *Cmd {
}
func (c *ClusterClient) Process(ctx context.Context, cmd Cmder) error {
err := c.hooks.process(ctx, cmd)
err := c.processHook(ctx, cmd)
cmd.SetErr(err)
return err
}
@ -1177,7 +1190,7 @@ func (c *ClusterClient) loadState(ctx context.Context) (*clusterState, error) {
func (c *ClusterClient) Pipeline() Pipeliner {
pipe := Pipeline{
exec: pipelineExecer(c.hooks.processPipeline),
exec: pipelineExecer(c.processPipelineHook),
}
pipe.init()
return &pipe
@ -1266,7 +1279,7 @@ func (c *ClusterClient) cmdsAreReadOnly(ctx context.Context, cmds []Cmder) bool
func (c *ClusterClient) processPipelineNode(
ctx context.Context, node *clusterNode, cmds []Cmder, failedCmds *cmdsMap,
) {
_ = node.Client.hooks.withProcessPipelineHook(ctx, cmds, func(ctx context.Context, cmds []Cmder) error {
_ = node.Client.withProcessPipelineHook(ctx, cmds, func(ctx context.Context, cmds []Cmder) error {
cn, err := node.Client.getConn(ctx)
if err != nil {
_ = c.mapCmdsByNode(ctx, failedCmds, cmds)
@ -1370,7 +1383,7 @@ func (c *ClusterClient) TxPipeline() Pipeliner {
pipe := Pipeline{
exec: func(ctx context.Context, cmds []Cmder) error {
cmds = wrapMultiExec(ctx, cmds)
return c.hooks.processTxPipeline(ctx, cmds)
return c.processTxPipelineHook(ctx, cmds)
},
}
pipe.init()
@ -1443,7 +1456,7 @@ func (c *ClusterClient) processTxPipelineNode(
ctx context.Context, node *clusterNode, cmds []Cmder, failedCmds *cmdsMap,
) {
cmds = wrapMultiExec(ctx, cmds)
_ = node.Client.hooks.withProcessPipelineHook(ctx, cmds, func(ctx context.Context, cmds []Cmder) error {
_ = node.Client.withProcessPipelineHook(ctx, cmds, func(ctx context.Context, cmds []Cmder) error {
cn, err := node.Client.getConn(ctx)
if err != nil {
_ = c.mapCmdsByNode(ctx, failedCmds, cmds)

@ -8,7 +8,7 @@ import (
func (c *ClusterClient) DBSize(ctx context.Context) *IntCmd {
cmd := NewIntCmd(ctx, "dbsize")
_ = c.hooks.withProcessHook(ctx, cmd, func(ctx context.Context, _ Cmder) error {
_ = c.withProcessHook(ctx, cmd, func(ctx context.Context, _ Cmder) error {
var size int64
err := c.ForEachMaster(ctx, func(ctx context.Context, master *Client) error {
n, err := master.DBSize(ctx).Result()
@ -30,8 +30,8 @@ func (c *ClusterClient) DBSize(ctx context.Context) *IntCmd {
func (c *ClusterClient) ScriptLoad(ctx context.Context, script string) *StringCmd {
cmd := NewStringCmd(ctx, "script", "load", script)
_ = c.hooks.withProcessHook(ctx, cmd, func(ctx context.Context, _ Cmder) error {
mu := &sync.Mutex{}
_ = c.withProcessHook(ctx, cmd, func(ctx context.Context, _ Cmder) error {
var mu sync.Mutex
err := c.ForEachShard(ctx, func(ctx context.Context, shard *Client) error {
val, err := shard.ScriptLoad(ctx, script).Result()
if err != nil {
@ -56,7 +56,7 @@ func (c *ClusterClient) ScriptLoad(ctx context.Context, script string) *StringCm
func (c *ClusterClient) ScriptFlush(ctx context.Context) *StatusCmd {
cmd := NewStatusCmd(ctx, "script", "flush")
_ = c.hooks.withProcessHook(ctx, cmd, func(ctx context.Context, _ Cmder) error {
_ = c.withProcessHook(ctx, cmd, func(ctx context.Context, _ Cmder) error {
err := c.ForEachShard(ctx, func(ctx context.Context, shard *Client) error {
return shard.ScriptFlush(ctx).Err()
})
@ -82,7 +82,7 @@ func (c *ClusterClient) ScriptExists(ctx context.Context, hashes ...string) *Boo
result[i] = true
}
_ = c.hooks.withProcessHook(ctx, cmd, func(ctx context.Context, _ Cmder) error {
_ = c.withProcessHook(ctx, cmd, func(ctx context.Context, _ Cmder) error {
var mu sync.Mutex
err := c.ForEachShard(ctx, func(ctx context.Context, shard *Client) error {
val, err := shard.ScriptExists(ctx, hashes...).Result()

@ -7,10 +7,10 @@ import (
"strconv"
"time"
"github.com/go-redis/redis/v9/internal"
"github.com/go-redis/redis/v9/internal/hscan"
"github.com/go-redis/redis/v9/internal/proto"
"github.com/go-redis/redis/v9/internal/util"
"github.com/redis/go-redis/v9/internal"
"github.com/redis/go-redis/v9/internal/hscan"
"github.com/redis/go-redis/v9/internal/proto"
"github.com/redis/go-redis/v9/internal/util"
)
type Cmder interface {
@ -1110,15 +1110,16 @@ func (cmd *KeyValueSliceCmd) String() string {
}
// Many commands will respond to two formats:
// 1) 1) "one"
// 2) (double) 1
// 2) 1) "two"
// 2) (double) 2
// 1. 1) "one"
// 2. (double) 1
// 2. 1) "two"
// 2. (double) 2
//
// OR:
// 1) "two"
// 2) (double) 2
// 3) "one"
// 4) (double) 1
// 1. "two"
// 2. (double) 2
// 3. "one"
// 4. (double) 1
func (cmd *KeyValueSliceCmd) readReply(rd *proto.Reader) error { // nolint:dupl
n, err := rd.ReadArrayLen()
if err != nil {
@ -2693,11 +2694,11 @@ func (cmd *ScanCmd) readReply(rd *proto.Reader) error {
return err
}
cursor, err := rd.ReadInt()
cursor, err := rd.ReadUint()
if err != nil {
return err
}
cmd.cursor = uint64(cursor)
cmd.cursor = cursor
n, err := rd.ReadArrayLen()
if err != nil {

@ -4,9 +4,11 @@ import (
"context"
"errors"
"io"
"reflect"
"strings"
"time"
"github.com/go-redis/redis/v9/internal"
"github.com/redis/go-redis/v9/internal"
)
// KeepTTL is a Redis KEEPTTL option to keep existing TTL, it requires your redis-server version >= 6.0,
@ -74,10 +76,46 @@ func appendArg(dst []interface{}, arg interface{}) []interface{} {
}
return dst
default:
// scan struct field
v := reflect.ValueOf(arg)
if v.Type().Kind() == reflect.Ptr {
if v.IsNil() {
// error: arg is not a valid object
return dst
}
v = v.Elem()
}
if v.Type().Kind() == reflect.Struct {
return appendStructField(dst, v)
}
return append(dst, arg)
}
}
// appendStructField appends the field and value held by the structure v to dst, and returns the appended dst.
func appendStructField(dst []interface{}, v reflect.Value) []interface{} {
typ := v.Type()
for i := 0; i < typ.NumField(); i++ {
tag := typ.Field(i).Tag.Get("redis")
if tag == "" || tag == "-" {
continue
}
tag = strings.Split(tag, ",")[0]
if tag == "" {
continue
}
field := v.Field(i)
if field.CanInterface() {
dst = append(dst, tag, field.Interface())
}
}
return dst
}
type Cmdable interface {
Pipeline() Pipeliner
Pipelined(ctx context.Context, fn func(Pipeliner) error) ([]Cmder, error)
@ -116,6 +154,7 @@ type Cmdable interface {
Restore(ctx context.Context, key string, ttl time.Duration, value string) *StatusCmd
RestoreReplace(ctx context.Context, key string, ttl time.Duration, value string) *StatusCmd
Sort(ctx context.Context, key string, sort *Sort) *StringSliceCmd
SortRO(ctx context.Context, key string, sort *Sort) *StringSliceCmd
SortStore(ctx context.Context, key, store string, sort *Sort) *IntCmd
SortInterfaces(ctx context.Context, key string, sort *Sort) *SliceCmd
Touch(ctx context.Context, keys ...string) *IntCmd
@ -268,6 +307,7 @@ type Cmdable interface {
ZIncrBy(ctx context.Context, key string, increment float64, member string) *FloatCmd
ZInter(ctx context.Context, store *ZStore) *StringSliceCmd
ZInterWithScores(ctx context.Context, store *ZStore) *ZSliceCmd
ZInterCard(ctx context.Context, limit int64, keys ...string) *IntCmd
ZInterStore(ctx context.Context, destination string, store *ZStore) *IntCmd
ZMScore(ctx context.Context, key string, members ...string) *FloatSliceCmd
ZPopMax(ctx context.Context, key string, count ...int64) *ZSliceCmd
@ -709,8 +749,9 @@ type Sort struct {
Alpha bool
}
func (sort *Sort) args(key string) []interface{} {
args := []interface{}{"sort", key}
func (sort *Sort) args(command, key string) []interface{} {
args := []interface{}{command, key}
if sort.By != "" {
args = append(args, "by", sort.By)
}
@ -729,14 +770,20 @@ func (sort *Sort) args(key string) []interface{} {
return args
}
func (c cmdable) SortRO(ctx context.Context, key string, sort *Sort) *StringSliceCmd {
cmd := NewStringSliceCmd(ctx, sort.args("sort_ro", key)...)
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) Sort(ctx context.Context, key string, sort *Sort) *StringSliceCmd {
cmd := NewStringSliceCmd(ctx, sort.args(key)...)
cmd := NewStringSliceCmd(ctx, sort.args("sort", key)...)
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) SortStore(ctx context.Context, key, store string, sort *Sort) *IntCmd {
args := sort.args(key)
args := sort.args("sort", key)
if store != "" {
args = append(args, "store", store)
}
@ -746,7 +793,7 @@ func (c cmdable) SortStore(ctx context.Context, key, store string, sort *Sort) *
}
func (c cmdable) SortInterfaces(ctx context.Context, key string, sort *Sort) *SliceCmd {
cmd := NewSliceCmd(ctx, sort.args(key)...)
cmd := NewSliceCmd(ctx, sort.args("sort", key)...)
_ = c(ctx, cmd)
return cmd
}
@ -871,6 +918,7 @@ func (c cmdable) MGet(ctx context.Context, keys ...string) *SliceCmd {
// - MSet("key1", "value1", "key2", "value2")
// - MSet([]string{"key1", "value1", "key2", "value2"})
// - MSet(map[string]interface{}{"key1": "value1", "key2": "value2"})
// - MSet(struct), For struct types, see HSet description.
func (c cmdable) MSet(ctx context.Context, values ...interface{}) *StatusCmd {
args := make([]interface{}, 1, 1+len(values))
args[0] = "mset"
@ -884,6 +932,7 @@ func (c cmdable) MSet(ctx context.Context, values ...interface{}) *StatusCmd {
// - MSetNX("key1", "value1", "key2", "value2")
// - MSetNX([]string{"key1", "value1", "key2", "value2"})
// - MSetNX(map[string]interface{}{"key1": "value1", "key2": "value2"})
// - MSetNX(struct), For struct types, see HSet description.
func (c cmdable) MSetNX(ctx context.Context, values ...interface{}) *BoolCmd {
args := make([]interface{}, 1, 1+len(values))
args[0] = "msetnx"
@ -1286,10 +1335,25 @@ func (c cmdable) HMGet(ctx context.Context, key string, fields ...string) *Slice
}
// HSet accepts values in following formats:
//
// - HSet("myhash", "key1", "value1", "key2", "value2")
//
// - HSet("myhash", []string{"key1", "value1", "key2", "value2"})
//
// - HSet("myhash", map[string]interface{}{"key1": "value1", "key2": "value2"})
//
// Playing struct With "redis" tag.
// type MyHash struct { Key1 string `redis:"key1"`; Key2 int `redis:"key2"` }
//
// - HSet("myhash", MyHash{"value1", "value2"})
//
// For struct, can be a structure pointer type, we only parse the field whose tag is redis.
// if you don't want the field to be read, you can use the `redis:"-"` flag to ignore it,
// or you don't need to set the redis tag.
// For the type of structure field, we only support simple data types:
// string, int/uint(8,16,32,64), float(32,64), time.Time(to RFC3339Nano), time.Duration(to Nanoseconds ),
// if you are other more complex or custom data types, please implement the encoding.BinaryMarshaler interface.
//
// Note that it requires Redis v4 for multiple field/value pairs support.
func (c cmdable) HSet(ctx context.Context, key string, values ...interface{}) *IntCmd {
args := make([]interface{}, 2, 2+len(values))
@ -2346,6 +2410,22 @@ func (c cmdable) ZInterWithScores(ctx context.Context, store *ZStore) *ZSliceCmd
return cmd
}
func (c cmdable) ZInterCard(ctx context.Context, limit int64, keys ...string) *IntCmd {
args := make([]interface{}, 4+len(keys))
args[0] = "zintercard"
numkeys := int64(0)
for i, key := range keys {
args[2+i] = key
numkeys++
}
args[1] = numkeys
args[2+numkeys] = "limit"
args[3+numkeys] = limit
cmd := NewIntCmd(ctx, args...)
_ = c(ctx, cmd)
return cmd
}
func (c cmdable) ZMScore(ctx context.Context, key string, members ...string) *FloatSliceCmd {
args := make([]interface{}, 2+len(members))
args[0] = "zmscore"

@ -6,8 +6,8 @@ import (
"net"
"strings"
"github.com/go-redis/redis/v9/internal/pool"
"github.com/go-redis/redis/v9/internal/proto"
"github.com/redis/go-redis/v9/internal/pool"
"github.com/redis/go-redis/v9/internal/proto"
)
// ErrClosed performs any operation on the closed client will return this error.

@ -5,7 +5,7 @@ import (
"strconv"
"time"
"github.com/go-redis/redis/v9/internal/util"
"github.com/redis/go-redis/v9/internal/util"
)
func AppendArg(b []byte, v interface{}) []byte {

@ -3,7 +3,7 @@ package hashtag
import (
"strings"
"github.com/go-redis/redis/v9/internal/rand"
"github.com/redis/go-redis/v9/internal/rand"
)
const slotNumber = 16384

@ -10,6 +10,12 @@ import (
// decoderFunc represents decoding functions for default built-in types.
type decoderFunc func(reflect.Value, string) error
// Scanner is the interface implemented by themselves,
// which will override the decoding behavior of decoderFunc.
type Scanner interface {
ScanRedis(s string) error
}
var (
// List of built-in decoders indexed by their numeric constant values (eg: reflect.Bool = 1).
decoders = []decoderFunc{

@ -84,7 +84,29 @@ func (s StructValue) Scan(key string, value string) error {
if !ok {
return nil
}
if err := field.fn(s.value.Field(field.index), value); err != nil {
v := s.value.Field(field.index)
isPtr := v.Kind() == reflect.Pointer
if isPtr && v.IsNil() {
v.Set(reflect.New(v.Type().Elem()))
}
if !isPtr && v.Type().Name() != "" && v.CanAddr() {
v = v.Addr()
isPtr = true
}
if isPtr && v.Type().NumMethod() > 0 && v.CanInterface() {
if scan, ok := v.Interface().(Scanner); ok {
return scan.ScanRedis(value)
}
}
if isPtr {
v = v.Elem()
}
if err := field.fn(v, value); err != nil {
t := s.value.Type()
return fmt.Errorf("cannot scan redis.result %s into struct field %s.%s of type %s, error-%s",
value, t.Name(), t.Field(field.index).Name, t.Field(field.index).Type, err.Error())

@ -3,7 +3,7 @@ package internal
import (
"time"
"github.com/go-redis/redis/v9/internal/rand"
"github.com/redis/go-redis/v9/internal/rand"
)
func RetryBackoff(retry int, minBackoff, maxBackoff time.Duration) time.Duration {

@ -32,7 +32,9 @@ type Once struct {
// Do calls the function f if and only if Do has not been invoked
// without error for this instance of Once. In other words, given
// var once Once
//
// var once Once
//
// if once.Do(f) is called multiple times, only the first call will
// invoke f, even if f has a different value in each invocation unless
// f returns an error. A new instance of Once is required for each
@ -41,7 +43,8 @@ type Once struct {
// Do is intended for initialization that must be run exactly once. Since f
// is niladic, it may be necessary to use a function literal to capture the
// arguments to a function to be invoked by Do:
// err := config.once.Do(func() error { return config.init(filename) })
//
// err := config.once.Do(func() error { return config.init(filename) })
func (o *Once) Do(f func() error) error {
if atomic.LoadUint32(&o.done) == 1 {
return nil

@ -7,7 +7,7 @@ import (
"sync/atomic"
"time"
"github.com/go-redis/redis/v9/internal/proto"
"github.com/redis/go-redis/v9/internal/proto"
)
var noDeadline = time.Time{}

@ -8,7 +8,7 @@ import (
"sync/atomic"
"time"
"github.com/go-redis/redis/v9/internal"
"github.com/redis/go-redis/v9/internal"
)
var (

@ -9,7 +9,7 @@ import (
"math/big"
"strconv"
"github.com/go-redis/redis/v9/internal/util"
"github.com/redis/go-redis/v9/internal/util"
)
// redis resp protocol data type.
@ -319,6 +319,33 @@ func (r *Reader) ReadInt() (int64, error) {
return 0, fmt.Errorf("redis: can't parse int reply: %.100q", line)
}
func (r *Reader) ReadUint() (uint64, error) {
line, err := r.ReadLine()
if err != nil {
return 0, err
}
switch line[0] {
case RespInt, RespStatus:
return util.ParseUint(line[1:], 10, 64)
case RespString:
s, err := r.readStringReply(line)
if err != nil {
return 0, err
}
return util.ParseUint([]byte(s), 10, 64)
case RespBigInt:
b, err := r.readBigInt(line)
if err != nil {
return 0, err
}
if !b.IsUint64() {
return 0, fmt.Errorf("bigInt(%s) value out of range", b.String())
}
return b.Uint64(), nil
}
return 0, fmt.Errorf("redis: can't parse uint reply: %.100q", line)
}
func (r *Reader) ReadFloat() (float64, error) {
line, err := r.ReadLine()
if err != nil {

@ -7,10 +7,11 @@ import (
"reflect"
"time"
"github.com/go-redis/redis/v9/internal/util"
"github.com/redis/go-redis/v9/internal/util"
)
// Scan parses bytes `b` to `v` with appropriate type.
//
//nolint:gocyclo
func Scan(b []byte, v interface{}) error {
switch v := v.(type) {

@ -8,7 +8,7 @@ import (
"strconv"
"time"
"github.com/go-redis/redis/v9/internal/util"
"github.com/redis/go-redis/v9/internal/util"
)
type writer interface {

@ -4,7 +4,7 @@ import (
"context"
"time"
"github.com/go-redis/redis/v9/internal/util"
"github.com/redis/go-redis/v9/internal/util"
)
func Sleep(ctx context.Context, dur time.Duration) error {

@ -13,7 +13,7 @@ import (
"strings"
"time"
"github.com/go-redis/redis/v9/internal/pool"
"github.com/redis/go-redis/v9/internal/pool"
)
// Limiter is the interface of a rate limiter or a circuit breaker.
@ -27,7 +27,7 @@ type Limiter interface {
ReportResult(result error)
}
// Options keeps the settings to setup redis connection.
// Options keeps the settings to set up redis connection.
type Options struct {
// The network type, either tcp or unix.
// Default is tcp.
@ -35,6 +35,9 @@ type Options struct {
// host:port address.
Addr string
// ClientName will execute the `CLIENT SETNAME ClientName` command for each conn.
ClientName string
// Dialer creates new network connection and has priority over
// Network and Addr options.
Dialer func(ctx context.Context, network, addr string) (net.Conn, error)
@ -220,32 +223,38 @@ func NewDialer(opt *Options) func(context.Context, string, string) (net.Conn, er
// Scheme is required.
// There are two connection types: by tcp socket and by unix socket.
// Tcp connection:
// redis://<user>:<password>@<host>:<port>/<db_number>
//
// redis://<user>:<password>@<host>:<port>/<db_number>
//
// Unix connection:
// unix://<user>:<password>@</path/to/redis.sock>?db=<db_number>
//
// unix://<user>:<password>@</path/to/redis.sock>?db=<db_number>
//
// Most Option fields can be set using query parameters, with the following restrictions:
// - field names are mapped using snake-case conversion: to set MaxRetries, use max_retries
// - only scalar type fields are supported (bool, int, time.Duration)
// - for time.Duration fields, values must be a valid input for time.ParseDuration();
// additionally a plain integer as value (i.e. without unit) is intepreted as seconds
// - to disable a duration field, use value less than or equal to 0; to use the default
// value, leave the value blank or remove the parameter
// - only the last value is interpreted if a parameter is given multiple times
// - fields "network", "addr", "username" and "password" can only be set using other
// URL attributes (scheme, host, userinfo, resp.), query paremeters using these
// names will be treated as unknown parameters
// - unknown parameter names will result in an error
// - field names are mapped using snake-case conversion: to set MaxRetries, use max_retries
// - only scalar type fields are supported (bool, int, time.Duration)
// - for time.Duration fields, values must be a valid input for time.ParseDuration();
// additionally a plain integer as value (i.e. without unit) is intepreted as seconds
// - to disable a duration field, use value less than or equal to 0; to use the default
// value, leave the value blank or remove the parameter
// - only the last value is interpreted if a parameter is given multiple times
// - fields "network", "addr", "username" and "password" can only be set using other
// URL attributes (scheme, host, userinfo, resp.), query paremeters using these
// names will be treated as unknown parameters
// - unknown parameter names will result in an error
//
// Examples:
// redis://user:password@localhost:6789/3?dial_timeout=3&db=1&read_timeout=6s&max_retries=2
// is equivalent to:
// &Options{
// Network: "tcp",
// Addr: "localhost:6789",
// DB: 1, // path "/3" was overridden by "&db=1"
// DialTimeout: 3 * time.Second, // no time unit = seconds
// ReadTimeout: 6 * time.Second,
// MaxRetries: 2,
// }
//
// redis://user:password@localhost:6789/3?dial_timeout=3&db=1&read_timeout=6s&max_retries=2
// is equivalent to:
// &Options{
// Network: "tcp",
// Addr: "localhost:6789",
// DB: 1, // path "/3" was overridden by "&db=1"
// DialTimeout: 3 * time.Second, // no time unit = seconds
// ReadTimeout: 6 * time.Second,
// MaxRetries: 2,
// }
func ParseURL(redisURL string) (*Options, error) {
u, err := url.Parse(redisURL)
if err != nil {
@ -426,6 +435,7 @@ func setupConnParams(u *url.URL, o *Options) (*Options, error) {
o.DB = db
}
o.ClientName = q.string("client_name")
o.MaxRetries = q.int("max_retries")
o.MinRetryBackoff = q.duration("min_retry_backoff")
o.MaxRetryBackoff = q.duration("max_retry_backoff")

@ -1,8 +1,8 @@
{
"name": "redis",
"version": "9.0.0-rc.2",
"version": "9.0.0-rc.4",
"main": "index.js",
"repository": "git@github.com:go-redis/redis.git",
"repository": "git@github.com:redis/go-redis.git",
"author": "Vladimir Mihailenco <vladimir.webdev@gmail.com>",
"license": "BSD-2-clause"
}

@ -2,7 +2,6 @@ package redis
import (
"context"
"sync"
)
type pipelineExecer func(context.Context, []Cmder) error
@ -32,15 +31,13 @@ type Pipeliner interface {
var _ Pipeliner = (*Pipeline)(nil)
// Pipeline implements pipelining as described in
// http://redis.io/topics/pipelining. It's safe for concurrent use
// by multiple goroutines.
// http://redis.io/topics/pipelining.
// Please note: it is not safe for concurrent use by multiple goroutines.
type Pipeline struct {
cmdable
statefulCmdable
exec pipelineExecer
mu sync.Mutex
cmds []Cmder
}
@ -51,10 +48,7 @@ func (c *Pipeline) init() {
// Len returns the number of queued commands.
func (c *Pipeline) Len() int {
c.mu.Lock()
ln := len(c.cmds)
c.mu.Unlock()
return ln
return len(c.cmds)
}
// Do queues the custom command for later execution.
@ -66,17 +60,13 @@ func (c *Pipeline) Do(ctx context.Context, args ...interface{}) *Cmd {
// Process queues the cmd for later execution.
func (c *Pipeline) Process(ctx context.Context, cmd Cmder) error {
c.mu.Lock()
c.cmds = append(c.cmds, cmd)
c.mu.Unlock()
return nil
}
// Discard resets the pipeline and discards queued commands.
func (c *Pipeline) Discard() {
c.mu.Lock()
c.cmds = c.cmds[:0]
c.mu.Unlock()
}
// Exec executes all previously queued commands using one
@ -85,9 +75,6 @@ func (c *Pipeline) Discard() {
// Exec always returns list of commands and error of the first failed
// command if any.
func (c *Pipeline) Exec(ctx context.Context) ([]Cmder, error) {
c.mu.Lock()
defer c.mu.Unlock()
if len(c.cmds) == 0 {
return nil, nil
}

@ -7,9 +7,9 @@ import (
"sync"
"time"
"github.com/go-redis/redis/v9/internal"
"github.com/go-redis/redis/v9/internal/pool"
"github.com/go-redis/redis/v9/internal/proto"
"github.com/redis/go-redis/v9/internal"
"github.com/redis/go-redis/v9/internal/pool"
"github.com/redis/go-redis/v9/internal/proto"
)
// PubSub implements Pub/Sub commands as described in

@ -9,11 +9,15 @@ import (
"sync/atomic"
"time"
"github.com/go-redis/redis/v9/internal"
"github.com/go-redis/redis/v9/internal/pool"
"github.com/go-redis/redis/v9/internal/proto"
"github.com/redis/go-redis/v9/internal"
"github.com/redis/go-redis/v9/internal/hscan"
"github.com/redis/go-redis/v9/internal/pool"
"github.com/redis/go-redis/v9/internal/proto"
)
// Scanner internal/hscan.Scanner exposed interface.
type Scanner = hscan.Scanner
// Nil reply returned by Redis when key does not exist.
const Nil = proto.Nil
@ -25,9 +29,9 @@ func SetLogger(logger internal.Logging) {
//------------------------------------------------------------------------------
type Hook interface {
DialHook(hook DialHook) DialHook
ProcessHook(hook ProcessHook) ProcessHook
ProcessPipelineHook(hook ProcessPipelineHook) ProcessPipelineHook
DialHook(next DialHook) DialHook
ProcessHook(next ProcessHook) ProcessHook
ProcessPipelineHook(next ProcessPipelineHook) ProcessPipelineHook
}
type (
@ -36,99 +40,145 @@ type (
ProcessPipelineHook func(ctx context.Context, cmds []Cmder) error
)
type hooks struct {
slice []Hook
dialHook DialHook
processHook ProcessHook
processPipelineHook ProcessPipelineHook
processTxPipelineHook ProcessPipelineHook
type hooksMixin struct {
slice []Hook
initial hooks
current hooks
}
func (hs *hooks) AddHook(hook Hook) {
hs.slice = append(hs.slice, hook)
hs.dialHook = hook.DialHook(hs.dialHook)
hs.processHook = hook.ProcessHook(hs.processHook)
hs.processPipelineHook = hook.ProcessPipelineHook(hs.processPipelineHook)
hs.processTxPipelineHook = hook.ProcessPipelineHook(hs.processTxPipelineHook)
func (hs *hooksMixin) initHooks(hooks hooks) {
hs.initial = hooks
hs.chain()
}
func (hs *hooks) clone() hooks {
clone := *hs
l := len(clone.slice)
clone.slice = clone.slice[:l:l]
return clone
type hooks struct {
dial DialHook
process ProcessHook
pipeline ProcessPipelineHook
txPipeline ProcessPipelineHook
}
func (hs *hooks) setDial(dial DialHook) {
hs.dialHook = dial
for _, h := range hs.slice {
if wrapped := h.DialHook(hs.dialHook); wrapped != nil {
hs.dialHook = wrapped
}
func (h *hooks) setDefaults() {
if h.dial == nil {
h.dial = func(ctx context.Context, network, addr string) (net.Conn, error) { return nil, nil }
}
if h.process == nil {
h.process = func(ctx context.Context, cmd Cmder) error { return nil }
}
if h.pipeline == nil {
h.pipeline = func(ctx context.Context, cmds []Cmder) error { return nil }
}
if h.txPipeline == nil {
h.txPipeline = func(ctx context.Context, cmds []Cmder) error { return nil }
}
}
func (hs *hooks) setProcess(process ProcessHook) {
hs.processHook = process
for _, h := range hs.slice {
if wrapped := h.ProcessHook(hs.processHook); wrapped != nil {
hs.processHook = wrapped
}
}
// AddHook is to add a hook to the queue.
// Hook is a function executed during network connection, command execution, and pipeline,
// it is a first-in-first-out stack queue (FIFO).
// You need to execute the next hook in each hook, unless you want to terminate the execution of the command.
// For example, you added hook-1, hook-2:
//
// client.AddHook(hook-1, hook-2)
//
// hook-1:
//
// func (Hook1) ProcessHook(next redis.ProcessHook) redis.ProcessHook {
// return func(ctx context.Context, cmd Cmder) error {
// print("hook-1 start")
// next(ctx, cmd)
// print("hook-1 end")
// return nil
// }
// }
//
// hook-2:
//
// func (Hook2) ProcessHook(next redis.ProcessHook) redis.ProcessHook {
// return func(ctx context.Context, cmd redis.Cmder) error {
// print("hook-2 start")
// next(ctx, cmd)
// print("hook-2 end")
// return nil
// }
// }
//
// The execution sequence is:
//
// hook-1 start -> hook-2 start -> exec redis cmd -> hook-2 end -> hook-1 end
//
// Please note: "next(ctx, cmd)" is very important, it will call the next hook,
// if "next(ctx, cmd)" is not executed, the redis command will not be executed.
func (hs *hooksMixin) AddHook(hook Hook) {
hs.slice = append(hs.slice, hook)
hs.chain()
}
func (hs *hooks) setProcessPipeline(processPipeline ProcessPipelineHook) {
hs.processPipelineHook = processPipeline
for _, h := range hs.slice {
if wrapped := h.ProcessPipelineHook(hs.processPipelineHook); wrapped != nil {
hs.processPipelineHook = wrapped
func (hs *hooksMixin) chain() {
hs.initial.setDefaults()
hs.current.dial = hs.initial.dial
hs.current.process = hs.initial.process
hs.current.pipeline = hs.initial.pipeline
hs.current.txPipeline = hs.initial.txPipeline
for i := len(hs.slice) - 1; i >= 0; i-- {
if wrapped := hs.slice[i].DialHook(hs.current.dial); wrapped != nil {
hs.current.dial = wrapped
}
if wrapped := hs.slice[i].ProcessHook(hs.current.process); wrapped != nil {
hs.current.process = wrapped
}
if wrapped := hs.slice[i].ProcessPipelineHook(hs.current.pipeline); wrapped != nil {
hs.current.pipeline = wrapped
}
if wrapped := hs.slice[i].ProcessPipelineHook(hs.current.txPipeline); wrapped != nil {
hs.current.txPipeline = wrapped
}
}
}
func (hs *hooks) setProcessTxPipeline(processTxPipeline ProcessPipelineHook) {
hs.processTxPipelineHook = processTxPipeline
for _, h := range hs.slice {
if wrapped := h.ProcessPipelineHook(hs.processTxPipelineHook); wrapped != nil {
hs.processTxPipelineHook = wrapped
}
}
func (hs *hooksMixin) clone() hooksMixin {
clone := *hs
l := len(clone.slice)
clone.slice = clone.slice[:l:l]
return clone
}
func (hs *hooks) withProcessHook(ctx context.Context, cmd Cmder, hook ProcessHook) error {
for _, h := range hs.slice {
if wrapped := h.ProcessHook(hook); wrapped != nil {
func (hs *hooksMixin) withProcessHook(ctx context.Context, cmd Cmder, hook ProcessHook) error {
for i := len(hs.slice) - 1; i >= 0; i-- {
if wrapped := hs.slice[i].ProcessHook(hook); wrapped != nil {
hook = wrapped
}
}
return hook(ctx, cmd)
}
func (hs *hooks) withProcessPipelineHook(
func (hs *hooksMixin) withProcessPipelineHook(
ctx context.Context, cmds []Cmder, hook ProcessPipelineHook,
) error {
for _, h := range hs.slice {
if wrapped := h.ProcessPipelineHook(hook); wrapped != nil {
for i := len(hs.slice) - 1; i >= 0; i-- {
if wrapped := hs.slice[i].ProcessPipelineHook(hook); wrapped != nil {
hook = wrapped
}
}
return hook(ctx, cmds)
}
func (hs *hooks) dial(ctx context.Context, network, addr string) (net.Conn, error) {
return hs.dialHook(ctx, network, addr)
func (hs *hooksMixin) dialHook(ctx context.Context, network, addr string) (net.Conn, error) {
return hs.current.dial(ctx, network, addr)
}
func (hs *hooks) process(ctx context.Context, cmd Cmder) error {
return hs.processHook(ctx, cmd)
func (hs *hooksMixin) processHook(ctx context.Context, cmd Cmder) error {
return hs.current.process(ctx, cmd)
}
func (hs *hooks) processPipeline(ctx context.Context, cmds []Cmder) error {
return hs.processPipelineHook(ctx, cmds)
func (hs *hooksMixin) processPipelineHook(ctx context.Context, cmds []Cmder) error {
return hs.current.pipeline(ctx, cmds)
}
func (hs *hooks) processTxPipeline(ctx context.Context, cmds []Cmder) error {
return hs.processTxPipelineHook(ctx, cmds)
func (hs *hooksMixin) processTxPipelineHook(ctx context.Context, cmds []Cmder) error {
return hs.current.txPipeline(ctx, cmds)
}
//------------------------------------------------------------------------------
@ -256,6 +306,10 @@ func (c *baseClient) initConn(ctx context.Context, cn *pool.Conn) error {
pipe.ReadOnly(ctx)
}
if c.opt.ClientName != "" {
pipe.ClientSetName(ctx, c.opt.ClientName)
}
return nil
})
if err != nil {
@ -530,7 +584,7 @@ func (c *baseClient) context(ctx context.Context) context.Context {
type Client struct {
*baseClient
cmdable
hooks
hooksMixin
}
// NewClient returns a client to the Redis Server specified by Options.
@ -543,17 +597,19 @@ func NewClient(opt *Options) *Client {
},
}
c.init()
c.connPool = newConnPool(opt, c.hooks.dial)
c.connPool = newConnPool(opt, c.dialHook)
return &c
}
func (c *Client) init() {
c.cmdable = c.Process
c.hooks.setDial(c.baseClient.dial)
c.hooks.setProcess(c.baseClient.process)
c.hooks.setProcessPipeline(c.baseClient.processPipeline)
c.hooks.setProcessTxPipeline(c.baseClient.processTxPipeline)
c.initHooks(hooks{
dial: c.baseClient.dial,
process: c.baseClient.process,
pipeline: c.baseClient.processPipeline,
txPipeline: c.baseClient.processTxPipeline,
})
}
func (c *Client) WithTimeout(timeout time.Duration) *Client {
@ -567,7 +623,7 @@ func (c *Client) Conn() *Conn {
return newConn(c.opt, pool.NewStickyConnPool(c.connPool))
}
// Do creates a Cmd from the args and processes the cmd.
// Do create a Cmd from the args and processes the cmd.
func (c *Client) Do(ctx context.Context, args ...interface{}) *Cmd {
cmd := NewCmd(ctx, args...)
_ = c.Process(ctx, cmd)
@ -575,7 +631,7 @@ func (c *Client) Do(ctx context.Context, args ...interface{}) *Cmd {
}
func (c *Client) Process(ctx context.Context, cmd Cmder) error {
err := c.hooks.process(ctx, cmd)
err := c.processHook(ctx, cmd)
cmd.SetErr(err)
return err
}
@ -599,7 +655,7 @@ func (c *Client) Pipelined(ctx context.Context, fn func(Pipeliner) error) ([]Cmd
func (c *Client) Pipeline() Pipeliner {
pipe := Pipeline{
exec: pipelineExecer(c.hooks.processPipeline),
exec: pipelineExecer(c.processPipelineHook),
}
pipe.init()
return &pipe
@ -614,7 +670,7 @@ func (c *Client) TxPipeline() Pipeliner {
pipe := Pipeline{
exec: func(ctx context.Context, cmds []Cmder) error {
cmds = wrapMultiExec(ctx, cmds)
return c.hooks.processTxPipeline(ctx, cmds)
return c.processTxPipelineHook(ctx, cmds)
},
}
pipe.init()
@ -640,26 +696,26 @@ func (c *Client) pubSub() *PubSub {
// subscription may not be active immediately. To force the connection to wait,
// you may call the Receive() method on the returned *PubSub like so:
//
// sub := client.Subscribe(queryResp)
// iface, err := sub.Receive()
// if err != nil {
// // handle error
// }
// sub := client.Subscribe(queryResp)
// iface, err := sub.Receive()
// if err != nil {
// // handle error
// }
//
// // Should be *Subscription, but others are possible if other actions have been
// // taken on sub since it was created.
// switch iface.(type) {
// case *Subscription:
// // subscribe succeeded
// case *Message:
// // received first message
// case *Pong:
// // pong received
// default:
// // handle error
// }
// // Should be *Subscription, but others are possible if other actions have been
// // taken on sub since it was created.
// switch iface.(type) {
// case *Subscription:
// // subscribe succeeded
// case *Message:
// // received first message
// case *Pong:
// // pong received
// default:
// // handle error
// }
//
// ch := sub.Channel()
// ch := sub.Channel()
func (c *Client) Subscribe(ctx context.Context, channels ...string) *PubSub {
pubsub := c.pubSub()
if len(channels) > 0 {
@ -697,7 +753,7 @@ type Conn struct {
baseClient
cmdable
statefulCmdable
hooks
hooksMixin
}
func newConn(opt *Options, connPool pool.Pooler) *Conn {
@ -710,17 +766,18 @@ func newConn(opt *Options, connPool pool.Pooler) *Conn {
c.cmdable = c.Process
c.statefulCmdable = c.Process
c.hooks.setDial(c.baseClient.dial)
c.hooks.setProcess(c.baseClient.process)
c.hooks.setProcessPipeline(c.baseClient.processPipeline)
c.hooks.setProcessTxPipeline(c.baseClient.processTxPipeline)
c.initHooks(hooks{
dial: c.baseClient.dial,
process: c.baseClient.process,
pipeline: c.baseClient.processPipeline,
txPipeline: c.baseClient.processTxPipeline,
})
return &c
}
func (c *Conn) Process(ctx context.Context, cmd Cmder) error {
err := c.hooks.process(ctx, cmd)
err := c.processHook(ctx, cmd)
cmd.SetErr(err)
return err
}
@ -731,7 +788,7 @@ func (c *Conn) Pipelined(ctx context.Context, fn func(Pipeliner) error) ([]Cmder
func (c *Conn) Pipeline() Pipeliner {
pipe := Pipeline{
exec: c.hooks.processPipeline,
exec: c.processPipelineHook,
}
pipe.init()
return &pipe
@ -746,7 +803,7 @@ func (c *Conn) TxPipeline() Pipeliner {
pipe := Pipeline{
exec: func(ctx context.Context, cmds []Cmder) error {
cmds = wrapMultiExec(ctx, cmds)
return c.hooks.processTxPipeline(ctx, cmds)
return c.processTxPipelineHook(ctx, cmds)
},
}
pipe.init()

@ -14,10 +14,10 @@ import (
"github.com/cespare/xxhash/v2"
rendezvous "github.com/dgryski/go-rendezvous" //nolint
"github.com/go-redis/redis/v9/internal"
"github.com/go-redis/redis/v9/internal/hashtag"
"github.com/go-redis/redis/v9/internal/pool"
"github.com/go-redis/redis/v9/internal/rand"
"github.com/redis/go-redis/v9/internal"
"github.com/redis/go-redis/v9/internal/hashtag"
"github.com/redis/go-redis/v9/internal/pool"
"github.com/redis/go-redis/v9/internal/rand"
)
var errRingShardsDown = errors.New("redis: all ring shards are down")
@ -51,6 +51,9 @@ type RingOptions struct {
// NewClient creates a shard client with provided options.
NewClient func(opt *Options) *Client
// ClientName will execute the `CLIENT SETNAME ClientName` command for each conn.
ClientName string
// Frequency of PING commands sent to check shards availability.
// Shard is considered down after 3 subsequent failed checks.
HeartbeatFrequency time.Duration
@ -129,8 +132,9 @@ func (opt *RingOptions) init() {
func (opt *RingOptions) clientOptions() *Options {
return &Options{
Dialer: opt.Dialer,
OnConnect: opt.OnConnect,
ClientName: opt.ClientName,
Dialer: opt.Dialer,
OnConnect: opt.OnConnect,
Username: opt.Username,
Password: opt.Password,
@ -483,7 +487,7 @@ func (c *ringSharding) Close() error {
// Otherwise you should use Redis Cluster.
type Ring struct {
cmdable
hooks
hooksMixin
opt *RingOptions
sharding *ringSharding
@ -505,12 +509,14 @@ func NewRing(opt *RingOptions) *Ring {
ring.cmdsInfoCache = newCmdsInfoCache(ring.cmdsInfo)
ring.cmdable = ring.Process
ring.hooks.setProcess(ring.process)
ring.hooks.setProcessPipeline(func(ctx context.Context, cmds []Cmder) error {
return ring.generalProcessPipeline(ctx, cmds, false)
})
ring.hooks.setProcessTxPipeline(func(ctx context.Context, cmds []Cmder) error {
return ring.generalProcessPipeline(ctx, cmds, true)
ring.initHooks(hooks{
process: ring.process,
pipeline: func(ctx context.Context, cmds []Cmder) error {
return ring.generalProcessPipeline(ctx, cmds, false)
},
txPipeline: func(ctx context.Context, cmds []Cmder) error {
return ring.generalProcessPipeline(ctx, cmds, true)
},
})
go ring.sharding.Heartbeat(hbCtx, opt.HeartbeatFrequency)
@ -522,7 +528,7 @@ func (c *Ring) SetAddrs(addrs map[string]string) {
c.sharding.SetAddrs(addrs)
}
// Do creates a Cmd from the args and processes the cmd.
// Do create a Cmd from the args and processes the cmd.
func (c *Ring) Do(ctx context.Context, args ...interface{}) *Cmd {
cmd := NewCmd(ctx, args...)
_ = c.Process(ctx, cmd)
@ -530,7 +536,7 @@ func (c *Ring) Do(ctx context.Context, args ...interface{}) *Cmd {
}
func (c *Ring) Process(ctx context.Context, cmd Cmder) error {
err := c.hooks.process(ctx, cmd)
err := c.processHook(ctx, cmd)
cmd.SetErr(err)
return err
}
@ -713,7 +719,7 @@ func (c *Ring) Pipelined(ctx context.Context, fn func(Pipeliner) error) ([]Cmder
func (c *Ring) Pipeline() Pipeliner {
pipe := Pipeline{
exec: pipelineExecer(c.hooks.processPipeline),
exec: pipelineExecer(c.processPipelineHook),
}
pipe.init()
return &pipe
@ -727,7 +733,7 @@ func (c *Ring) TxPipeline() Pipeliner {
pipe := Pipeline{
exec: func(ctx context.Context, cmds []Cmder) error {
cmds = wrapMultiExec(ctx, cmds)
return c.hooks.processTxPipeline(ctx, cmds)
return c.processTxPipelineHook(ctx, cmds)
},
}
pipe.init()
@ -768,9 +774,9 @@ func (c *Ring) generalProcessPipeline(
if tx {
cmds = wrapMultiExec(ctx, cmds)
_ = shard.Client.hooks.processTxPipeline(ctx, cmds)
_ = shard.Client.processTxPipelineHook(ctx, cmds)
} else {
_ = shard.Client.hooks.processPipeline(ctx, cmds)
_ = shard.Client.processPipelineHook(ctx, cmds)
}
}(hash, cmds)
}

@ -9,9 +9,9 @@ import (
"sync"
"time"
"github.com/go-redis/redis/v9/internal"
"github.com/go-redis/redis/v9/internal/pool"
"github.com/go-redis/redis/v9/internal/rand"
"github.com/redis/go-redis/v9/internal"
"github.com/redis/go-redis/v9/internal/pool"
"github.com/redis/go-redis/v9/internal/rand"
)
//------------------------------------------------------------------------------
@ -24,6 +24,9 @@ type FailoverOptions struct {
// A seed list of host:port addresses of sentinel nodes.
SentinelAddrs []string
// ClientName will execute the `CLIENT SETNAME ClientName` command for each conn.
ClientName string
// If specified with SentinelPassword, enables ACL-based authentication (via
// AUTH <user> <pass>).
SentinelUsername string
@ -78,7 +81,8 @@ type FailoverOptions struct {
func (opt *FailoverOptions) clientOptions() *Options {
return &Options{
Addr: "FailoverClient",
Addr: "FailoverClient",
ClientName: opt.ClientName,
Dialer: opt.Dialer,
OnConnect: opt.OnConnect,
@ -110,7 +114,8 @@ func (opt *FailoverOptions) clientOptions() *Options {
func (opt *FailoverOptions) sentinelOptions(addr string) *Options {
return &Options{
Addr: addr,
Addr: addr,
ClientName: opt.ClientName,
Dialer: opt.Dialer,
OnConnect: opt.OnConnect,
@ -141,6 +146,8 @@ func (opt *FailoverOptions) sentinelOptions(addr string) *Options {
func (opt *FailoverOptions) clusterOptions() *ClusterOptions {
return &ClusterOptions{
ClientName: opt.ClientName,
Dialer: opt.Dialer,
OnConnect: opt.OnConnect,
@ -207,7 +214,7 @@ func NewFailoverClient(failoverOpt *FailoverOptions) *Client {
}
rdb.init()
connPool = newConnPool(opt, rdb.hooks.dial)
connPool = newConnPool(opt, rdb.dialHook)
rdb.connPool = connPool
rdb.onClose = failover.Close
@ -260,7 +267,7 @@ func masterReplicaDialer(
// SentinelClient is a client for a Redis Sentinel.
type SentinelClient struct {
*baseClient
hooks
hooksMixin
}
func NewSentinelClient(opt *Options) *SentinelClient {
@ -271,15 +278,17 @@ func NewSentinelClient(opt *Options) *SentinelClient {
},
}
c.hooks.setDial(c.baseClient.dial)
c.hooks.setProcess(c.baseClient.process)
c.connPool = newConnPool(opt, c.hooks.dial)
c.initHooks(hooks{
dial: c.baseClient.dial,
process: c.baseClient.process,
})
c.connPool = newConnPool(opt, c.dialHook)
return c
}
func (c *SentinelClient) Process(ctx context.Context, cmd Cmder) error {
err := c.hooks.process(ctx, cmd)
err := c.processHook(ctx, cmd)
cmd.SetErr(err)
return err
}

@ -3,8 +3,8 @@ package redis
import (
"context"
"github.com/go-redis/redis/v9/internal/pool"
"github.com/go-redis/redis/v9/internal/proto"
"github.com/redis/go-redis/v9/internal/pool"
"github.com/redis/go-redis/v9/internal/proto"
)
// TxFailedErr transaction redis failed.
@ -19,7 +19,7 @@ type Tx struct {
baseClient
cmdable
statefulCmdable
hooks
hooksMixin
}
func (c *Client) newTx() *Tx {
@ -28,7 +28,7 @@ func (c *Client) newTx() *Tx {
opt: c.opt,
connPool: pool.NewStickyConnPool(c.connPool),
},
hooks: c.hooks.clone(),
hooksMixin: c.hooksMixin.clone(),
}
tx.init()
return &tx
@ -38,14 +38,16 @@ func (c *Tx) init() {
c.cmdable = c.Process
c.statefulCmdable = c.Process
c.hooks.setDial(c.baseClient.dial)
c.hooks.setProcess(c.baseClient.process)
c.hooks.setProcessPipeline(c.baseClient.processPipeline)
c.hooks.setProcessTxPipeline(c.baseClient.processTxPipeline)
c.initHooks(hooks{
dial: c.baseClient.dial,
process: c.baseClient.process,
pipeline: c.baseClient.processPipeline,
txPipeline: c.baseClient.processTxPipeline,
})
}
func (c *Tx) Process(ctx context.Context, cmd Cmder) error {
err := c.hooks.process(ctx, cmd)
err := c.processHook(ctx, cmd)
cmd.SetErr(err)
return err
}
@ -100,7 +102,7 @@ func (c *Tx) Unwatch(ctx context.Context, keys ...string) *StatusCmd {
func (c *Tx) Pipeline() Pipeliner {
pipe := Pipeline{
exec: func(ctx context.Context, cmds []Cmder) error {
return c.hooks.processPipeline(ctx, cmds)
return c.processPipelineHook(ctx, cmds)
},
}
pipe.init()
@ -130,7 +132,7 @@ func (c *Tx) TxPipeline() Pipeliner {
pipe := Pipeline{
exec: func(ctx context.Context, cmds []Cmder) error {
cmds = wrapMultiExec(ctx, cmds)
return c.hooks.processTxPipeline(ctx, cmds)
return c.processTxPipelineHook(ctx, cmds)
},
}
pipe.init()

@ -14,6 +14,9 @@ type UniversalOptions struct {
// of cluster/sentinel nodes.
Addrs []string
// ClientName will execute the `CLIENT SETNAME ClientName` command for each conn.
ClientName string
// Database to be selected after connecting to the server.
// Only single-node and failover clients.
DB int
@ -69,9 +72,10 @@ func (o *UniversalOptions) Cluster() *ClusterOptions {
}
return &ClusterOptions{
Addrs: o.Addrs,
Dialer: o.Dialer,
OnConnect: o.OnConnect,
Addrs: o.Addrs,
ClientName: o.ClientName,
Dialer: o.Dialer,
OnConnect: o.OnConnect,
Username: o.Username,
Password: o.Password,
@ -112,6 +116,7 @@ func (o *UniversalOptions) Failover() *FailoverOptions {
return &FailoverOptions{
SentinelAddrs: o.Addrs,
MasterName: o.MasterName,
ClientName: o.ClientName,
Dialer: o.Dialer,
OnConnect: o.OnConnect,
@ -151,9 +156,10 @@ func (o *UniversalOptions) Simple() *Options {
}
return &Options{
Addr: addr,
Dialer: o.Dialer,
OnConnect: o.OnConnect,
Addr: addr,
ClientName: o.ClientName,
Dialer: o.Dialer,
OnConnect: o.OnConnect,
DB: o.DB,
Username: o.Username,

@ -2,5 +2,5 @@ package redis
// Version is the current release version.
func Version() string {
return "9.0.0-rc.2"
return "9.0.0-rc.4"
}

20
vendor/modules.txt vendored

@ -60,16 +60,6 @@ github.com/go-playground/universal-translator
github.com/go-playground/validator/v10
github.com/go-playground/validator/v10/translations/en
github.com/go-playground/validator/v10/translations/zh
# github.com/go-redis/redis/v9 v9.0.0-rc.2
## explicit; go 1.17
github.com/go-redis/redis/v9
github.com/go-redis/redis/v9/internal
github.com/go-redis/redis/v9/internal/hashtag
github.com/go-redis/redis/v9/internal/hscan
github.com/go-redis/redis/v9/internal/pool
github.com/go-redis/redis/v9/internal/proto
github.com/go-redis/redis/v9/internal/rand
github.com/go-redis/redis/v9/internal/util
# github.com/go-sql-driver/mysql v1.7.0
## explicit; go 1.13
github.com/go-sql-driver/mysql
@ -190,6 +180,16 @@ github.com/qiniu/go-sdk/v7/internal/hostprovider
github.com/qiniu/go-sdk/v7/internal/log
github.com/qiniu/go-sdk/v7/reqid
github.com/qiniu/go-sdk/v7/storage
# github.com/redis/go-redis/v9 v9.0.0-rc.4
## explicit; go 1.17
github.com/redis/go-redis/v9
github.com/redis/go-redis/v9/internal
github.com/redis/go-redis/v9/internal/hashtag
github.com/redis/go-redis/v9/internal/hscan
github.com/redis/go-redis/v9/internal/pool
github.com/redis/go-redis/v9/internal/proto
github.com/redis/go-redis/v9/internal/rand
github.com/redis/go-redis/v9/internal/util
# github.com/robfig/cron/v3 v3.0.1
## explicit; go 1.12
github.com/robfig/cron/v3

Loading…
Cancel
Save