diff --git a/go.mod b/go.mod
index 78853690fd0..746c3f48b43 100644
--- a/go.mod
+++ b/go.mod
@@ -26,7 +26,7 @@ require (
github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.0.2
github.com/redis/go-redis/extra/redisotel/v9 v9.0.5
- github.com/redis/go-redis/v9 v9.1.0
+ github.com/redis/go-redis/v9 v9.18.0
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.8.0
github.com/stretchr/testify v1.11.1
@@ -57,6 +57,7 @@ require (
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect
+ go.uber.org/atomic v1.11.0 // indirect
golang.org/x/time v0.12.0 // indirect
)
diff --git a/go.sum b/go.sum
index 6c4a1b13348..de7b2c3269f 100644
--- a/go.sum
+++ b/go.sum
@@ -57,10 +57,11 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r
github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70=
github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk=
github.com/bsm/ginkgo/v2 v2.7.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w=
-github.com/bsm/ginkgo/v2 v2.9.5 h1:rtVBYPs3+TC5iLUVOis1B9tjLTup7Cj5IfzosKtvTJ0=
-github.com/bsm/ginkgo/v2 v2.9.5/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
-github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y=
+github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
+github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.26.0/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
+github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
+github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
@@ -154,6 +155,8 @@ github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRt
github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k=
github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co=
github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0=
+github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
+github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
@@ -207,8 +210,8 @@ github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5/go.mod h1:fyalQWdtzDBECAQFBJu
github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 h1:EfpWLLCyXw8PSM2/XNJLjI3Pb27yVE+gIAfeqp8LUCc=
github.com/redis/go-redis/extra/redisotel/v9 v9.0.5/go.mod h1:WZjPDy7VNzn77AAfnAfVjZNvfJTYfPetfZk5yoSTLaQ=
github.com/redis/go-redis/v9 v9.0.5/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk=
-github.com/redis/go-redis/v9 v9.1.0 h1:137FnGdk+EQdCbye1FW+qOEcY5S+SpY9T0NiuqvtfMY=
-github.com/redis/go-redis/v9 v9.1.0/go.mod h1:urWj3He21Dj5k4TK1y59xH8Uj6ATueP8AH1cY3lZl4c=
+github.com/redis/go-redis/v9 v9.18.0 h1:pMkxYPkEbMPwRdenAzUNyFNrDgHx9U+DrBabWNfSRQs=
+github.com/redis/go-redis/v9 v9.18.0/go.mod h1:k3ufPphLU5YXwNTUcCRXGxUoF1fqxnhFQmscfkCoDA0=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
@@ -231,6 +234,8 @@ github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM=
github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
+github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
+github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/detectors/gcp v1.36.0 h1:F7q2tNlCaHY9nMKHR6XH9/qkp8FktLnIcy6jJNyOCQw=
@@ -269,6 +274,8 @@ go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKr
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
+go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
+go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
diff --git a/vendor/github.com/redis/go-redis/v9/.gitignore b/vendor/github.com/redis/go-redis/v9/.gitignore
index 64a7cb51225..93affec7fab 100644
--- a/vendor/github.com/redis/go-redis/v9/.gitignore
+++ b/vendor/github.com/redis/go-redis/v9/.gitignore
@@ -2,3 +2,18 @@
testdata/*
.idea/
.DS_Store
+*.tar.gz
+*.dic
+redis8tests.sh
+coverage.txt
+**/coverage.txt
+.vscode
+tmp/*
+*.test
+extra/redisotel-native/metrics-collector-app/
+# maintenanceNotifications upgrade documentation (temporary)
+maintenanceNotifications/docs/
+
+# Docker-generated files (TLS certificates, cluster data, etc.)
+dockers/*/tls/
+dockers/osscluster-tls/
diff --git a/vendor/github.com/redis/go-redis/v9/.golangci.yml b/vendor/github.com/redis/go-redis/v9/.golangci.yml
index de514554a9c..dd13c2c2938 100644
--- a/vendor/github.com/redis/go-redis/v9/.golangci.yml
+++ b/vendor/github.com/redis/go-redis/v9/.golangci.yml
@@ -1,4 +1,36 @@
+version: "2"
run:
- concurrency: 8
- deadline: 5m
+ timeout: 5m
tests: false
+linters:
+ settings:
+ staticcheck:
+ checks:
+ - all
+ # Incorrect or missing package comment.
+ # https://staticcheck.dev/docs/checks/#ST1000
+ - -ST1000
+ # Omit embedded fields from selector expression.
+ # https://staticcheck.dev/docs/checks/#QF1008
+ - -QF1008
+ - -ST1003
+ exclusions:
+ generated: lax
+ presets:
+ - comments
+ - common-false-positives
+ - legacy
+ - std-error-handling
+ paths:
+ - third_party$
+ - builtin$
+ - examples$
+formatters:
+ enable:
+ - gofmt
+ exclusions:
+ generated: lax
+ paths:
+ - third_party$
+ - builtin$
+ - examples$
diff --git a/vendor/github.com/redis/go-redis/v9/CHANGELOG.md b/vendor/github.com/redis/go-redis/v9/CHANGELOG.md
deleted file mode 100644
index 297438a9fc4..00000000000
--- a/vendor/github.com/redis/go-redis/v9/CHANGELOG.md
+++ /dev/null
@@ -1,124 +0,0 @@
-## [9.0.5](https://github.com/redis/go-redis/compare/v9.0.4...v9.0.5) (2023-05-29)
-
-
-### Features
-
-* Add ACL LOG ([#2536](https://github.com/redis/go-redis/issues/2536)) ([31ba855](https://github.com/redis/go-redis/commit/31ba855ddebc38fbcc69a75d9d4fb769417cf602))
-* add field protocol to setupClusterQueryParams ([#2600](https://github.com/redis/go-redis/issues/2600)) ([840c25c](https://github.com/redis/go-redis/commit/840c25cb6f320501886a82a5e75f47b491e46fbe))
-* add protocol option ([#2598](https://github.com/redis/go-redis/issues/2598)) ([3917988](https://github.com/redis/go-redis/commit/391798880cfb915c4660f6c3ba63e0c1a459e2af))
-
-
-
-## [9.0.4](https://github.com/redis/go-redis/compare/v9.0.3...v9.0.4) (2023-05-01)
-
-
-### Bug Fixes
-
-* reader float parser ([#2513](https://github.com/redis/go-redis/issues/2513)) ([46f2450](https://github.com/redis/go-redis/commit/46f245075e6e3a8bd8471f9ca67ea95fd675e241))
-
-
-### Features
-
-* add client info command ([#2483](https://github.com/redis/go-redis/issues/2483)) ([b8c7317](https://github.com/redis/go-redis/commit/b8c7317cc6af444603731f7017c602347c0ba61e))
-* no longer verify HELLO error messages ([#2515](https://github.com/redis/go-redis/issues/2515)) ([7b4f217](https://github.com/redis/go-redis/commit/7b4f2179cb5dba3d3c6b0c6f10db52b837c912c8))
-* read the structure to increase the judgment of the omitempty op… ([#2529](https://github.com/redis/go-redis/issues/2529)) ([37c057b](https://github.com/redis/go-redis/commit/37c057b8e597c5e8a0e372337f6a8ad27f6030af))
-
-
-
-## [9.0.3](https://github.com/redis/go-redis/compare/v9.0.2...v9.0.3) (2023-04-02)
-
-### New Features
-
-- feat(scan): scan time.Time sets the default decoding (#2413)
-- Add support for CLUSTER LINKS command (#2504)
-- Add support for acl dryrun command (#2502)
-- Add support for COMMAND GETKEYS & COMMAND GETKEYSANDFLAGS (#2500)
-- Add support for LCS Command (#2480)
-- Add support for BZMPOP (#2456)
-- Adding support for ZMPOP command (#2408)
-- Add support for LMPOP (#2440)
-- feat: remove pool unused fields (#2438)
-- Expiretime and PExpireTime (#2426)
-- Implement `FUNCTION` group of commands (#2475)
-- feat(zadd): add ZAddLT and ZAddGT (#2429)
-- Add: Support for COMMAND LIST command (#2491)
-- Add support for BLMPOP (#2442)
-- feat: check pipeline.Do to prevent confusion with Exec (#2517)
-- Function stats, function kill, fcall and fcall_ro (#2486)
-- feat: Add support for CLUSTER SHARDS command (#2507)
-- feat(cmd): support for adding byte,bit parameters to the bitpos command (#2498)
-
-### Fixed
-
-- fix: eval api cmd.SetFirstKeyPos (#2501)
-- fix: limit the number of connections created (#2441)
-- fixed #2462 v9 continue support dragonfly, it's Hello command return "NOAUTH Authentication required" error (#2479)
-- Fix for internal/hscan/structmap.go:89:23: undefined: reflect.Pointer (#2458)
-- fix: group lag can be null (#2448)
-
-### Maintenance
-
-- Updating to the latest version of redis (#2508)
-- Allowing for running tests on a port other than the fixed 6380 (#2466)
-- redis 7.0.8 in tests (#2450)
-- docs: Update redisotel example for v9 (#2425)
-- chore: update go mod, Upgrade golang.org/x/net version to 0.7.0 (#2476)
-- chore: add Chinese translation (#2436)
-- chore(deps): bump github.com/bsm/gomega from 1.20.0 to 1.26.0 (#2421)
-- chore(deps): bump github.com/bsm/ginkgo/v2 from 2.5.0 to 2.7.0 (#2420)
-- chore(deps): bump actions/setup-go from 3 to 4 (#2495)
-- docs: add instructions for the HSet api (#2503)
-- docs: add reading lag field comment (#2451)
-- test: update go mod before testing(go mod tidy) (#2423)
-- docs: fix comment typo (#2505)
-- test: remove testify (#2463)
-- refactor: change ListElementCmd to KeyValuesCmd. (#2443)
-- fix(appendArg): appendArg case special type (#2489)
-
-## [9.0.2](https://github.com/redis/go-redis/compare/v9.0.1...v9.0.2) (2023-02-01)
-
-### Features
-
-* upgrade OpenTelemetry, use the new metrics API. ([#2410](https://github.com/redis/go-redis/issues/2410)) ([e29e42c](https://github.com/redis/go-redis/commit/e29e42cde2755ab910d04185025dc43ce6f59c65))
-
-## v9 2023-01-30
-
-### Breaking
-
-- Changed Pipelines to not be thread-safe any more.
-
-### 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.
-
-### Fixed
-
-- Improved and fixed pipeline retries.
-- As usually, added support for more commands and fixed some bugs.
diff --git a/vendor/github.com/redis/go-redis/v9/CONTRIBUTING.md b/vendor/github.com/redis/go-redis/v9/CONTRIBUTING.md
new file mode 100644
index 00000000000..8c68c522e5f
--- /dev/null
+++ b/vendor/github.com/redis/go-redis/v9/CONTRIBUTING.md
@@ -0,0 +1,118 @@
+# Contributing
+
+## Introduction
+
+We appreciate your interest in considering contributing to go-redis.
+Community contributions mean a lot to us.
+
+## Contributions we need
+
+You may already know how you'd like to contribute, whether it's a fix for a bug you
+encountered, or a new feature your team wants to use.
+
+If you don't know where to start, consider improving
+documentation, bug triaging, and writing tutorials are all examples of
+helpful contributions that mean less work for you.
+
+## Your First Contribution
+
+Unsure where to begin contributing? You can start by looking through
+[help-wanted
+issues](https://github.com/redis/go-redis/issues?q=is%3Aopen+is%3Aissue+label%3ahelp-wanted).
+
+Never contributed to open source before? Here are a couple of friendly
+tutorials:
+
+-
+-
+
+## Getting Started
+
+Here's how to get started with your code contribution:
+
+1. Create your own fork of go-redis
+2. Do the changes in your fork
+3. If you need a development environment, run `make docker.start`.
+
+> Note: this clones and builds the docker containers specified in `docker-compose.yml`, to understand more about
+> the infrastructure that will be started you can check the `docker-compose.yml`. You also have the possiblity
+> to specify the redis image that will be pulled with the env variable `CLIENT_LIBS_TEST_IMAGE`.
+> By default the docker image that will be pulled and started is `redislabs/client-libs-test:8.2.1-pre`.
+> If you want to test with newer Redis version, using a newer version of `redislabs/client-libs-test` should work out of the box.
+
+4. While developing, make sure the tests pass by running `make test` (if you have the docker containers running, `make test.ci` may be sufficient).
+> Note: `make test` will try to start all containers, run the tests with `make test.ci` and then stop all containers.
+5. If you like the change and think the project could use it, send a
+ pull request
+
+To see what else is part of the automation, run `invoke -l`
+
+
+## Testing
+
+### Setting up Docker
+To run the tests, you need to have Docker installed and running. If you are using a host OS that does not support
+docker host networks out of the box (e.g. Windows, OSX), you need to set up a docker desktop and enable docker host networks.
+
+### Running tests
+Call `make test` to run all tests.
+
+Continuous Integration uses these same wrappers to run all of these
+tests against multiple versions of redis. Feel free to test your
+changes against all the go versions supported, as declared by the
+[build.yml](./.github/workflows/build.yml) file.
+
+### Troubleshooting
+
+If you get any errors when running `make test`, make sure
+that you are using supported versions of Docker and go.
+
+## How to Report a Bug
+
+### Security Vulnerabilities
+
+**NOTE**: If you find a security vulnerability, do NOT open an issue.
+Email [Redis Open Source ()](mailto:oss@redis.com) instead.
+
+In order to determine whether you are dealing with a security issue, ask
+yourself these two questions:
+
+- Can I access something that's not mine, or something I shouldn't
+ have access to?
+- Can I disable something for other people?
+
+If the answer to either of those two questions are *yes*, then you're
+probably dealing with a security issue. Note that even if you answer
+*no* to both questions, you may still be dealing with a security
+issue, so if you're unsure, just email [us](mailto:oss@redis.com).
+
+### Everything Else
+
+When filing an issue, make sure to answer these five questions:
+
+1. What version of go-redis are you using?
+2. What version of redis are you using?
+3. What did you do?
+4. What did you expect to see?
+5. What did you see instead?
+
+## Suggest a feature or enhancement
+
+If you'd like to contribute a new feature, make sure you check our
+issue list to see if someone has already proposed it. Work may already
+be underway on the feature you want or we may have rejected a
+feature like it already.
+
+If you don't see anything, open a new issue that describes the feature
+you would like and how it should work.
+
+## Code review process
+
+The core team regularly looks at pull requests. We will provide
+feedback as soon as possible. After receiving our feedback, please respond
+within two weeks. After that time, we may close your PR if it isn't
+showing any activity.
+
+## Support
+
+Maintainers can provide limited support to contributors on discord: https://discord.gg/W4txy5AeKM
diff --git a/vendor/github.com/redis/go-redis/v9/Makefile b/vendor/github.com/redis/go-redis/v9/Makefile
index b59c3955464..370f3880c5d 100644
--- a/vendor/github.com/redis/go-redis/v9/Makefile
+++ b/vendor/github.com/redis/go-redis/v9/Makefile
@@ -1,35 +1,116 @@
GO_MOD_DIRS := $(shell find . -type f -name 'go.mod' -exec dirname {} \; | sort)
+REDIS_VERSION ?= 8.6
+RE_CLUSTER ?= false
+RCE_DOCKER ?= true
+CLIENT_LIBS_TEST_IMAGE ?= redislabs/client-libs-test:custom-21860421418-debian-amd64
-test: testdeps
+docker.start:
+ export RE_CLUSTER=$(RE_CLUSTER) && \
+ export RCE_DOCKER=$(RCE_DOCKER) && \
+ export REDIS_VERSION=$(REDIS_VERSION) && \
+ export CLIENT_LIBS_TEST_IMAGE=$(CLIENT_LIBS_TEST_IMAGE) && \
+ docker compose --profile all up -d --quiet-pull
+
+docker.stop:
+ docker compose --profile all down
+
+docker.e2e.start:
+ @echo "Starting Redis and cae-resp-proxy for E2E tests..."
+ docker compose --profile e2e up -d --quiet-pull
+ @echo "Waiting for services to be ready..."
+ @sleep 3
+ @echo "Services ready!"
+
+docker.e2e.stop:
+ @echo "Stopping E2E services..."
+ docker compose --profile e2e down
+
+test:
+ $(MAKE) docker.start
+ @if [ -z "$(REDIS_VERSION)" ]; then \
+ echo "REDIS_VERSION not set, running all tests"; \
+ $(MAKE) test.ci; \
+ else \
+ MAJOR_VERSION=$$(echo "$(REDIS_VERSION)" | cut -d. -f1); \
+ if [ "$$MAJOR_VERSION" -ge 8 ]; then \
+ echo "REDIS_VERSION $(REDIS_VERSION) >= 8, running all tests"; \
+ $(MAKE) test.ci; \
+ else \
+ echo "REDIS_VERSION $(REDIS_VERSION) < 8, skipping vector_sets tests"; \
+ $(MAKE) test.ci.skip-vectorsets; \
+ fi; \
+ fi
+ $(MAKE) docker.stop
+
+test.ci:
set -e; for dir in $(GO_MOD_DIRS); do \
echo "go test in $${dir}"; \
(cd "$${dir}" && \
+ export RE_CLUSTER=$(RE_CLUSTER) && \
+ export RCE_DOCKER=$(RCE_DOCKER) && \
+ export REDIS_VERSION=$(REDIS_VERSION) && \
go mod tidy -compat=1.18 && \
- go test && \
- go test ./... -short -race && \
- go test ./... -run=NONE -bench=. -benchmem && \
- env GOOS=linux GOARCH=386 go test && \
- go vet); \
+ go vet && \
+ go test -v -coverprofile=coverage.txt -covermode=atomic ./... -race -skip Example); \
done
cd internal/customvet && go build .
go vet -vettool ./internal/customvet/customvet
-testdeps: testdata/redis/src/redis-server
+test.ci.skip-vectorsets:
+ set -e; for dir in $(GO_MOD_DIRS); do \
+ echo "go test in $${dir} (skipping vector sets)"; \
+ (cd "$${dir}" && \
+ export RE_CLUSTER=$(RE_CLUSTER) && \
+ export RCE_DOCKER=$(RCE_DOCKER) && \
+ export REDIS_VERSION=$(REDIS_VERSION) && \
+ go mod tidy -compat=1.18 && \
+ go vet && \
+ go test -v -coverprofile=coverage.txt -covermode=atomic ./... -race \
+ -run '^(?!.*(?:VectorSet|vectorset|ExampleClient_vectorset)).*$$' -skip Example); \
+ done
+ cd internal/customvet && go build .
+ go vet -vettool ./internal/customvet/customvet
+
+bench:
+ export RE_CLUSTER=$(RE_CLUSTER) && \
+ export RCE_DOCKER=$(RCE_DOCKER) && \
+ export REDIS_VERSION=$(REDIS_VERSION) && \
+ go test ./... -test.run=NONE -test.bench=. -test.benchmem -skip Example
+
+test.e2e:
+ @echo "Running E2E tests with auto-start proxy..."
+ $(MAKE) docker.e2e.start
+ @echo "Running tests..."
+ @E2E_SCENARIO_TESTS=true go test -v ./maintnotifications/e2e/ -timeout 30m || ($(MAKE) docker.e2e.stop && exit 1)
+ $(MAKE) docker.e2e.stop
+ @echo "E2E tests completed!"
-bench: testdeps
- go test ./... -test.run=NONE -test.bench=. -test.benchmem
+test.e2e.docker:
+ @echo "Running Docker-compatible E2E tests..."
+ $(MAKE) docker.e2e.start
+ @echo "Running unified injector tests..."
+ @E2E_SCENARIO_TESTS=true go test -v -run "TestUnifiedInjector|TestCreateTestFaultInjectorLogic|TestFaultInjectorClientCreation" ./maintnotifications/e2e/ -timeout 10m || ($(MAKE) docker.e2e.stop && exit 1)
+ $(MAKE) docker.e2e.stop
+ @echo "Docker E2E tests completed!"
-.PHONY: all test testdeps bench
+test.e2e.logic:
+ @echo "Running E2E logic tests (no proxy required)..."
+ @E2E_SCENARIO_TESTS=true \
+ REDIS_ENDPOINTS_CONFIG_PATH=/tmp/test_endpoints_verify.json \
+ FAULT_INJECTION_API_URL=http://localhost:8080 \
+ go test -v -run "TestCreateTestFaultInjectorLogic|TestFaultInjectorClientCreation" ./maintnotifications/e2e/
+ @echo "Logic tests completed!"
-testdata/redis:
- mkdir -p $@
- wget -qO- https://download.redis.io/releases/redis-7.2-rc3.tar.gz | tar xvz --strip-components=1 -C $@
+.PHONY: all test test.ci test.ci.skip-vectorsets bench fmt test.e2e test.e2e.logic docker.e2e.start docker.e2e.stop
-testdata/redis/src/redis-server: testdata/redis
- cd $< && make all
+build:
+ export RE_CLUSTER=$(RE_CLUSTER) && \
+ export RCE_DOCKER=$(RCE_DOCKER) && \
+ export REDIS_VERSION=$(REDIS_VERSION) && \
+ go build .
fmt:
- gofmt -w -s ./
+ gofumpt -w ./
goimports -w -local github.com/redis/go-redis ./
go_mod_tidy:
diff --git a/vendor/github.com/redis/go-redis/v9/README.md b/vendor/github.com/redis/go-redis/v9/README.md
index 3486e8e5a76..160714ab099 100644
--- a/vendor/github.com/redis/go-redis/v9/README.md
+++ b/vendor/github.com/redis/go-redis/v9/README.md
@@ -2,51 +2,79 @@
[](https://github.com/redis/go-redis/actions)
[](https://pkg.go.dev/github.com/redis/go-redis/v9?tab=doc)
-[](https://redis.uptrace.dev/)
-[](https://discord.gg/rWtp5Aj)
+[](https://redis.io/docs/latest/develop/clients/go/)
+[](https://goreportcard.com/report/github.com/redis/go-redis/v9)
+[](https://codecov.io/github/redis/go-redis)
-> go-redis is brought to you by :star: [**uptrace/uptrace**](https://github.com/uptrace/uptrace).
-> Uptrace is an open-source APM tool that supports distributed tracing, metrics, and logs. You can
-> use it to monitor applications and set up automatic alerts to receive notifications via email,
-> Slack, Telegram, and others.
->
-> See [OpenTelemetry](example/otel) example which demonstrates how you can use Uptrace to monitor
-> go-redis.
+[](https://discord.gg/W4txy5AeKM)
+[](https://www.twitch.tv/redisinc)
+[](https://www.youtube.com/redisinc)
+[](https://twitter.com/redisinc)
+[](https://stackoverflow.com/questions/tagged/go-redis)
-## Documentation
+> go-redis is the official Redis client library for the Go programming language. It offers a straightforward interface for interacting with Redis servers.
+
+## Supported versions
+
+In `go-redis` we are aiming to support the last three releases of Redis. Currently, this means we do support:
+- [Redis 8.0](https://raw.githubusercontent.com/redis/redis/8.0/00-RELEASENOTES) - using Redis CE 8.0
+- [Redis 8.2](https://raw.githubusercontent.com/redis/redis/8.2/00-RELEASENOTES) - using Redis CE 8.2
+- [Redis 8.4](https://raw.githubusercontent.com/redis/redis/8.4/00-RELEASENOTES) - using Redis CE 8.4
+
+Although the `go.mod` states it requires at minimum `go 1.21`, our CI is configured to run the tests against all three
+versions of Redis and multiple versions of Go ([1.21](https://go.dev/doc/devel/release#go1.21.0),
+[1.23](https://go.dev/doc/devel/release#go1.23.0), oldstable, and stable). We observe that some modules related test may not pass with
+Redis Stack 7.2 and some commands are changed with Redis CE 8.0.
+Although it is not officially supported, `go-redis/v9` should be able to work with any Redis 7.0+.
+Please do refer to the documentation and the tests if you experience any issues.
+
+## How do I Redis?
+
+[Learn for free at Redis University](https://university.redis.com/)
+
+[Build faster with the Redis Launchpad](https://launchpad.redis.com/)
+
+[Try the Redis Cloud](https://redis.com/try-free/)
+
+[Dive in developer tutorials](https://developer.redis.com/)
+
+[Join the Redis community](https://redis.com/community/)
+
+[Work at Redis](https://redis.com/company/careers/jobs/)
-- [English](https://redis.uptrace.dev)
-- [简体中文](https://redis.uptrace.dev/zh/)
## Resources
- [Discussions](https://github.com/redis/go-redis/discussions)
-- [Chat](https://discord.gg/rWtp5Aj)
+- [Chat](https://discord.gg/W4txy5AeKM)
- [Reference](https://pkg.go.dev/github.com/redis/go-redis/v9)
- [Examples](https://pkg.go.dev/github.com/redis/go-redis/v9#pkg-examples)
+## old documentation
+
+- [English](https://redis.uptrace.dev)
+- [简体中文](https://redis.uptrace.dev/zh/)
+
## Ecosystem
-- [Redis Mock](https://github.com/go-redis/redismock)
+- [Entra ID (Azure AD)](https://github.com/redis/go-redis-entraid)
- [Distributed Locks](https://github.com/bsm/redislock)
- [Redis Cache](https://github.com/go-redis/cache)
- [Rate limiting](https://github.com/go-redis/redis_rate)
-This client also works with [Kvrocks](https://github.com/apache/incubator-kvrocks), a distributed
-key value NoSQL database that uses RocksDB as storage engine and is compatible with Redis protocol.
-
## Features
-- Redis 3 commands except QUIT, MONITOR, and SYNC.
-- Automatic connection pooling with
+- Redis commands except QUIT and SYNC.
+- Automatic connection pooling.
+- [StreamingCredentialsProvider (e.g. entra id, oauth)](#1-streaming-credentials-provider-highest-priority) (experimental)
- [Pub/Sub](https://redis.uptrace.dev/guide/go-redis-pubsub.html).
- [Pipelines and transactions](https://redis.uptrace.dev/guide/go-redis-pipelines.html).
- [Scripting](https://redis.uptrace.dev/guide/lua-scripting.html).
- [Redis Sentinel](https://redis.uptrace.dev/guide/go-redis-sentinel.html).
- [Redis Cluster](https://redis.uptrace.dev/guide/go-redis-cluster.html).
-- [Redis Ring](https://redis.uptrace.dev/guide/ring.html).
- [Redis Performance Monitoring](https://redis.uptrace.dev/guide/redis-performance-monitoring.html).
- [Redis Probabilistic [RedisStack]](https://redis.io/docs/data-types/probabilistic/)
+- [Customizable read and write buffers size.](#custom-buffer-sizes)
## Installation
@@ -69,8 +97,9 @@ go get github.com/redis/go-redis/v9
```go
import (
"context"
- "github.com/redis/go-redis/v9"
"fmt"
+
+ "github.com/redis/go-redis/v9"
)
var ctx = context.Background()
@@ -81,6 +110,7 @@ func ExampleClient() {
Password: "", // no password set
DB: 0, // use default DB
})
+ defer rdb.Close()
err := rdb.Set(ctx, "key", "value", 0).Err()
if err != nil {
@@ -106,40 +136,261 @@ func ExampleClient() {
}
```
-The above can be modified to specify the version of the RESP protocol by adding the `protocol` option to the `Options` struct:
+### Authentication
+
+The Redis client supports multiple ways to provide authentication credentials, with a clear priority order. Here are the available options:
+
+#### 1. Streaming Credentials Provider (Highest Priority) - Experimental feature
+
+The streaming credentials provider allows for dynamic credential updates during the connection lifetime. This is particularly useful for managed identity services and token-based authentication.
```go
- rdb := redis.NewClient(&redis.Options{
- Addr: "localhost:6379",
- Password: "", // no password set
- DB: 0, // use default DB
- Protocol: 3, // specify 2 for RESP 2 or 3 for RESP 3
- })
+type StreamingCredentialsProvider interface {
+ Subscribe(listener CredentialsListener) (Credentials, UnsubscribeFunc, error)
+}
+type CredentialsListener interface {
+ OnNext(credentials Credentials) // Called when credentials are updated
+ OnError(err error) // Called when an error occurs
+}
+
+type Credentials interface {
+ BasicAuth() (username string, password string)
+ RawCredentials() string
+}
```
-### Connecting via a redis url
+Example usage:
+```go
+rdb := redis.NewClient(&redis.Options{
+ Addr: "localhost:6379",
+ StreamingCredentialsProvider: &MyCredentialsProvider{},
+})
+```
-go-redis also supports connecting via the [redis uri specification](https://github.com/redis/redis-specifications/tree/master/uri/redis.txt). The example below demonstrates how the connection can easily be configured using a string, adhering to this specification.
+**Note:** The streaming credentials provider can be used with [go-redis-entraid](https://github.com/redis/go-redis-entraid) to enable Entra ID (formerly Azure AD) authentication. This allows for seamless integration with Azure's managed identity services and token-based authentication.
+Example with Entra ID:
```go
import (
- "context"
"github.com/redis/go-redis/v9"
- "fmt"
+ "github.com/redis/go-redis-entraid"
)
-var ctx = context.Background()
+// Create an Entra ID credentials provider
+provider := entraid.NewDefaultAzureIdentityProvider()
+
+// Configure Redis client with Entra ID authentication
+rdb := redis.NewClient(&redis.Options{
+ Addr: "your-redis-server.redis.cache.windows.net:6380",
+ StreamingCredentialsProvider: provider,
+ TLSConfig: &tls.Config{
+ MinVersion: tls.VersionTLS12,
+ },
+})
+```
-func ExampleClient() {
- url := "redis://localhost:6379?password=hello&protocol=3"
+#### 2. Context-based Credentials Provider
+
+The context-based provider allows credentials to be determined at the time of each operation, using the context.
+
+```go
+rdb := redis.NewClient(&redis.Options{
+ Addr: "localhost:6379",
+ CredentialsProviderContext: func(ctx context.Context) (string, string, error) {
+ // Return username, password, and any error
+ return "user", "pass", nil
+ },
+})
+```
+
+#### 3. Regular Credentials Provider
+
+A simple function-based provider that returns static credentials.
+
+```go
+rdb := redis.NewClient(&redis.Options{
+ Addr: "localhost:6379",
+ CredentialsProvider: func() (string, string) {
+ // Return username and password
+ return "user", "pass"
+ },
+})
+```
+
+#### 4. Username/Password Fields (Lowest Priority)
+
+The most basic way to provide credentials is through the `Username` and `Password` fields in the options.
+
+```go
+rdb := redis.NewClient(&redis.Options{
+ Addr: "localhost:6379",
+ Username: "user",
+ Password: "pass",
+})
+```
+
+#### Priority Order
+
+The client will use credentials in the following priority order:
+1. Streaming Credentials Provider (if set)
+2. Context-based Credentials Provider (if set)
+3. Regular Credentials Provider (if set)
+4. Username/Password fields (if set)
+
+If none of these are set, the client will attempt to connect without authentication.
+
+### Protocol Version
+
+The client supports both RESP2 and RESP3 protocols. You can specify the protocol version in the options:
+
+```go
+rdb := redis.NewClient(&redis.Options{
+ Addr: "localhost:6379",
+ Password: "", // no password set
+ DB: 0, // use default DB
+ Protocol: 3, // specify 2 for RESP 2 or 3 for RESP 3
+})
+```
+
+### Connecting via a redis url
+
+go-redis also supports connecting via the
+[redis uri specification](https://github.com/redis/redis-specifications/tree/master/uri/redis.txt).
+The example below demonstrates how the connection can easily be configured using a string, adhering
+to this specification.
+
+```go
+import (
+ "github.com/redis/go-redis/v9"
+)
+
+func ExampleClient() *redis.Client {
+ url := "redis://user:password@localhost:6379/0?protocol=3"
opts, err := redis.ParseURL(url)
if err != nil {
panic(err)
}
- rdb := redis.NewClient(opts)
+
+ return redis.NewClient(opts)
+}
+
+```
+
+### Instrument with OpenTelemetry
+
+```go
+import (
+ "github.com/redis/go-redis/v9"
+ "github.com/redis/go-redis/extra/redisotel/v9"
+ "errors"
+)
+
+func main() {
+ ...
+ rdb := redis.NewClient(&redis.Options{...})
+
+ if err := errors.Join(redisotel.InstrumentTracing(rdb), redisotel.InstrumentMetrics(rdb)); err != nil {
+ log.Fatal(err)
+ }
```
+
+### Buffer Size Configuration
+
+go-redis uses 32KiB read and write buffers by default for optimal performance. For high-throughput applications or large pipelines, you can customize buffer sizes:
+
+```go
+rdb := redis.NewClient(&redis.Options{
+ Addr: "localhost:6379",
+ ReadBufferSize: 1024 * 1024, // 1MiB read buffer
+ WriteBufferSize: 1024 * 1024, // 1MiB write buffer
+})
+```
+
+### Advanced Configuration
+
+go-redis supports extending the client identification phase to allow projects to send their own custom client identification.
+
+#### Default Client Identification
+
+By default, go-redis automatically sends the client library name and version during the connection process. This feature is available in redis-server as of version 7.2. As a result, the command is "fire and forget", meaning it should fail silently, in the case that the redis server does not support this feature.
+
+#### Disabling Identity Verification
+
+When connection identity verification is not required or needs to be explicitly disabled, a `DisableIdentity` configuration option exists.
+Initially there was a typo and the option was named `DisableIndentity` instead of `DisableIdentity`. The misspelled option is marked as Deprecated and will be removed in V10 of this library.
+Although both options will work at the moment, the correct option is `DisableIdentity`. The deprecated option will be removed in V10 of this library, so please use the correct option name to avoid any issues.
+
+To disable verification, set the `DisableIdentity` option to `true` in the Redis client options:
+
+```go
+rdb := redis.NewClient(&redis.Options{
+ Addr: "localhost:6379",
+ Password: "",
+ DB: 0,
+ DisableIdentity: true, // Disable set-info on connect
+})
+```
+
+#### Unstable RESP3 Structures for RediSearch Commands
+When integrating Redis with application functionalities using RESP3, it's important to note that some response structures aren't final yet. This is especially true for more complex structures like search and query results. We recommend using RESP2 when using the search and query capabilities, but we plan to stabilize the RESP3-based API-s in the coming versions. You can find more guidance in the upcoming release notes.
+
+To enable unstable RESP3, set the option in your client configuration:
+
+```go
+redis.NewClient(&redis.Options{
+ UnstableResp3: true,
+ })
+```
+**Note:** When UnstableResp3 mode is enabled, it's necessary to use RawResult() and RawVal() to retrieve a raw data.
+ Since, raw response is the only option for unstable search commands Val() and Result() calls wouldn't have any affect on them:
+
+```go
+res1, err := client.FTSearchWithArgs(ctx, "txt", "foo bar", &redis.FTSearchOptions{}).RawResult()
+val1 := client.FTSearchWithArgs(ctx, "txt", "foo bar", &redis.FTSearchOptions{}).RawVal()
+```
+
+#### Redis-Search Default Dialect
+
+In the Redis-Search module, **the default dialect is 2**. If needed, you can explicitly specify a different dialect using the appropriate configuration in your queries.
+
+**Important**: Be aware that the query dialect may impact the results returned. If needed, you can revert to a different dialect version by passing the desired dialect in the arguments of the command you want to execute.
+For example:
+```
+ res2, err := rdb.FTSearchWithArgs(ctx,
+ "idx:bicycle",
+ "@pickup_zone:[CONTAINS $bike]",
+ &redis.FTSearchOptions{
+ Params: map[string]interface{}{
+ "bike": "POINT(-0.1278 51.5074)",
+ },
+ DialectVersion: 3,
+ },
+ ).Result()
+```
+You can find further details in the [query dialect documentation](https://redis.io/docs/latest/develop/interact/search-and-query/advanced-concepts/dialects/).
+
+#### Custom buffer sizes
+Prior to v9.12, the buffer size was the default go value of 4096 bytes. Starting from v9.12,
+go-redis uses 32KiB read and write buffers by default for optimal performance.
+For high-throughput applications or large pipelines, you can customize buffer sizes:
+
+```go
+rdb := redis.NewClient(&redis.Options{
+ Addr: "localhost:6379",
+ ReadBufferSize: 1024 * 1024, // 1MiB read buffer
+ WriteBufferSize: 1024 * 1024, // 1MiB write buffer
+})
+```
+
+**Important**: If you experience any issues with the default buffer sizes, please try setting them to the go default of 4096 bytes.
+
+## Contributing
+We welcome contributions to the go-redis library! If you have a bug fix, feature request, or improvement, please open an issue or pull request on GitHub.
+We appreciate your help in making go-redis better for everyone.
+If you are interested in contributing to the go-redis library, please check out our [contributing guidelines](CONTRIBUTING.md) for more information on how to get started.
+
## Look and feel
Some corner cases:
@@ -175,37 +426,150 @@ vals, err := rdb.Eval(ctx, "return {KEYS[1],ARGV[1]}", []string{"key"}, "hello")
res, err := rdb.Do(ctx, "set", "key", "value").Result()
```
-## Run the test
-
-go-redis will start a redis-server and run the test cases.
+## Typed Errors
-The paths of redis-server bin file and redis config file are defined in `main_test.go`:
+go-redis provides typed error checking functions for common Redis errors:
```go
-var (
- redisServerBin, _ = filepath.Abs(filepath.Join("testdata", "redis", "src", "redis-server"))
- redisServerConf, _ = filepath.Abs(filepath.Join("testdata", "redis", "redis.conf"))
-)
+// Cluster and replication errors
+redis.IsLoadingError(err) // Redis is loading the dataset
+redis.IsReadOnlyError(err) // Write to read-only replica
+redis.IsClusterDownError(err) // Cluster is down
+redis.IsTryAgainError(err) // Command should be retried
+redis.IsMasterDownError(err) // Master is down
+redis.IsMovedError(err) // Returns (address, true) if key moved
+redis.IsAskError(err) // Returns (address, true) if key being migrated
+
+// Connection and resource errors
+redis.IsMaxClientsError(err) // Maximum clients reached
+redis.IsAuthError(err) // Authentication failed (NOAUTH, WRONGPASS, unauthenticated)
+redis.IsPermissionError(err) // Permission denied (NOPERM)
+redis.IsOOMError(err) // Out of memory (OOM)
+
+// Transaction errors
+redis.IsExecAbortError(err) // Transaction aborted (EXECABORT)
```
-For local testing, you can change the variables to refer to your local files, or create a soft link
-to the corresponding folder for redis-server and copy the config file to `testdata/redis/`:
+### Error Wrapping in Hooks
-```shell
-ln -s /usr/bin/redis-server ./go-redis/testdata/redis/src
-cp ./go-redis/testdata/redis.conf ./go-redis/testdata/redis/
+When wrapping errors in hooks, use custom error types with `Unwrap()` method (preferred) or `fmt.Errorf` with `%w`. Always call `cmd.SetErr()` to preserve error type information:
+
+```go
+// Custom error type (preferred)
+type AppError struct {
+ Code string
+ RequestID string
+ Err error
+}
+
+func (e *AppError) Error() string {
+ return fmt.Sprintf("[%s] request_id=%s: %v", e.Code, e.RequestID, e.Err)
+}
+
+func (e *AppError) Unwrap() error {
+ return e.Err
+}
+
+// Hook implementation
+func (h MyHook) ProcessHook(next redis.ProcessHook) redis.ProcessHook {
+ return func(ctx context.Context, cmd redis.Cmder) error {
+ err := next(ctx, cmd)
+ if err != nil {
+ // Wrap with custom error type
+ wrappedErr := &AppError{
+ Code: "REDIS_ERROR",
+ RequestID: getRequestID(ctx),
+ Err: err,
+ }
+ cmd.SetErr(wrappedErr)
+ return wrappedErr // Return wrapped error to preserve it
+ }
+ return nil
+ }
+}
+
+// Typed error detection works through wrappers
+if redis.IsLoadingError(err) {
+ // Retry logic
+}
+
+// Extract custom error if needed
+var appErr *AppError
+if errors.As(err, &appErr) {
+ log.Printf("Request: %s", appErr.RequestID)
+}
+```
+
+Alternatively, use `fmt.Errorf` with `%w`:
+```go
+wrappedErr := fmt.Errorf("context: %w", err)
+cmd.SetErr(wrappedErr)
```
-Lastly, run:
+### Pipeline Hook Example
-```shell
-go test
+For pipeline operations, use `ProcessPipelineHook`:
+
+```go
+type PipelineLoggingHook struct{}
+
+func (h PipelineLoggingHook) DialHook(next redis.DialHook) redis.DialHook {
+ return next
+}
+
+func (h PipelineLoggingHook) ProcessHook(next redis.ProcessHook) redis.ProcessHook {
+ return next
+}
+
+func (h PipelineLoggingHook) ProcessPipelineHook(next redis.ProcessPipelineHook) redis.ProcessPipelineHook {
+ return func(ctx context.Context, cmds []redis.Cmder) error {
+ start := time.Now()
+
+ // Execute the pipeline
+ err := next(ctx, cmds)
+
+ duration := time.Since(start)
+ log.Printf("Pipeline executed %d commands in %v", len(cmds), duration)
+
+ // Process individual command errors
+ // Note: Individual command errors are already set on each cmd by the pipeline execution
+ for _, cmd := range cmds {
+ if cmdErr := cmd.Err(); cmdErr != nil {
+ // Check for specific error types using typed error functions
+ if redis.IsAuthError(cmdErr) {
+ log.Printf("Auth error in pipeline command %s: %v", cmd.Name(), cmdErr)
+ } else if redis.IsPermissionError(cmdErr) {
+ log.Printf("Permission error in pipeline command %s: %v", cmd.Name(), cmdErr)
+ }
+
+ // Optionally wrap individual command errors to add context
+ // The wrapped error preserves type information through errors.As()
+ wrappedErr := fmt.Errorf("pipeline cmd %s failed: %w", cmd.Name(), cmdErr)
+ cmd.SetErr(wrappedErr)
+ }
+ }
+
+ // Return the pipeline-level error (connection errors, etc.)
+ // You can wrap it if needed, or return it as-is
+ return err
+ }
+}
+
+// Register the hook
+rdb.AddHook(PipelineLoggingHook{})
+
+// Use pipeline - errors are still properly typed
+pipe := rdb.Pipeline()
+pipe.Set(ctx, "key1", "value1", 0)
+pipe.Get(ctx, "key2")
+_, err := pipe.Exec(ctx)
```
-Another option is to run your specific tests with an already running redis. The example below, tests against a redis running on port 9999.:
+## Run the test
+Recommended to use Docker, just need to run:
```shell
-REDIS_PORT=9999 go test
+make test
```
## See also
@@ -217,6 +581,14 @@ REDIS_PORT=9999 go test
## Contributors
+> The go-redis project was originally initiated by :star: [**uptrace/uptrace**](https://github.com/uptrace/uptrace).
+> Uptrace is an open-source APM tool that supports distributed tracing, metrics, and logs. You can
+> use it to monitor applications and set up automatic alerts to receive notifications via email,
+> Slack, Telegram, and others.
+>
+> See [OpenTelemetry](https://github.com/redis/go-redis/tree/master/example/otel) example which
+> demonstrates how you can use Uptrace to monitor go-redis.
+
Thanks to all the people who already contributed!
diff --git a/vendor/github.com/redis/go-redis/v9/RELEASE-NOTES.md b/vendor/github.com/redis/go-redis/v9/RELEASE-NOTES.md
new file mode 100644
index 00000000000..7b705ee68b5
--- /dev/null
+++ b/vendor/github.com/redis/go-redis/v9/RELEASE-NOTES.md
@@ -0,0 +1,859 @@
+# Release Notes
+
+# 9.18.0 (2026-02-16)
+
+## 🚀 Highlights
+
+### Redis 8.6 Support
+
+Added support for Redis 8.6, including new commands and features for streams idempotent production and HOTKEYS.
+
+### Smart Client Handoff (Maintenance Notifications) for Cluster
+
+This release introduces comprehensive support for Redis Cluster maintenance notifications via SMIGRATING/SMIGRATED push notifications. The client now automatically handles slot migrations by:
+- **Relaxing timeouts during migration** (SMIGRATING) to prevent false failures
+- **Triggering lazy cluster state reloads** upon completion (SMIGRATED)
+- Enabling seamless operations during Redis Enterprise maintenance windows
+
+([#3643](https://github.com/redis/go-redis/pull/3643)) by [@ndyakov](https://github.com/ndyakov)
+
+### OpenTelemetry Native Metrics Support
+
+Added comprehensive OpenTelemetry metrics support following the [OpenTelemetry Database Client Semantic Conventions](https://opentelemetry.io/docs/specs/semconv/database/database-metrics/). The implementation uses a Bridge Pattern to keep the core library dependency-free while providing optional metrics instrumentation through the new `extra/redisotel-native` package.
+
+**Metric groups include:**
+- Command metrics: Operation duration with retry tracking
+- Connection basic: Connection count and creation time
+- Resiliency: Errors, handoffs, timeout relaxation
+- Connection advanced: Wait time and use time
+- Pubsub metrics: Published and received messages
+- Stream metrics: Processing duration and maintenance notifications
+
+([#3637](https://github.com/redis/go-redis/pull/3637)) by [@ofekshenawa](https://github.com/ofekshenawa)
+
+## ✨ New Features
+
+- **HOTKEYS Commands**: Added support for Redis HOTKEYS feature for identifying hot keys based on CPU consumption and network utilization ([#3695](https://github.com/redis/go-redis/pull/3695)) by [@ofekshenawa](https://github.com/ofekshenawa)
+- **Streams Idempotent Production**: Added support for Redis 8.6+ Streams Idempotent Production with `ProducerID`, `IdempotentID`, `IdempotentAuto` in `XAddArgs` and new `XCFGSET` command ([#3693](https://github.com/redis/go-redis/pull/3693)) by [@ofekshenawa](https://github.com/ofekshenawa)
+- **NaN Values for TimeSeries**: Added support for NaN (Not a Number) values in Redis time series commands ([#3687](https://github.com/redis/go-redis/pull/3687)) by [@ofekshenawa](https://github.com/ofekshenawa)
+- **DialerRetries Options**: Added `DialerRetries` and `DialerRetryTimeout` to `ClusterOptions`, `RingOptions`, and `FailoverOptions` ([#3686](https://github.com/redis/go-redis/pull/3686)) by [@naveenchander30](https://github.com/naveenchander30)
+- **ConnMaxLifetimeJitter**: Added jitter configuration to distribute connection expiration times and prevent thundering herd ([#3666](https://github.com/redis/go-redis/pull/3666)) by [@cyningsun](https://github.com/cyningsun)
+- **Digest Helper Functions**: Added `DigestString` and `DigestBytes` helper functions for client-side xxh3 hashing compatible with Redis DIGEST command ([#3679](https://github.com/redis/go-redis/pull/3679)) by [@ofekshenawa](https://github.com/ofekshenawa)
+- **SMIGRATED New Format**: Updated SMIGRATED parser to support new format and remember original host:port ([#3697](https://github.com/redis/go-redis/pull/3697)) by [@ndyakov](https://github.com/ndyakov)
+- **Cluster State Reload Interval**: Added cluster state reload interval option for maintenance notifications ([#3663](https://github.com/redis/go-redis/pull/3663)) by [@ndyakov](https://github.com/ndyakov)
+
+## 🐛 Bug Fixes
+
+- **PubSub nil pointer dereference**: Fixed nil pointer dereference in PubSub after `WithTimeout()` - `pubSubPool` is now properly cloned ([#3710](https://github.com/redis/go-redis/pull/3710)) by [@Copilot](https://github.com/apps/copilot-swe-agent)
+- **MaintNotificationsConfig nil check**: Guard against nil `MaintNotificationsConfig` in `initConn` ([#3707](https://github.com/redis/go-redis/pull/3707)) by [@veeceey](https://github.com/veeceey)
+- **wantConnQueue zombie elements**: Fixed zombie `wantConn` elements accumulation in `wantConnQueue` ([#3680](https://github.com/redis/go-redis/pull/3680)) by [@cyningsun](https://github.com/cyningsun)
+- **XADD/XTRIM approx flag**: Fixed XADD and XTRIM to use `=` when approx is false ([#3684](https://github.com/redis/go-redis/pull/3684)) by [@ndyakov](https://github.com/ndyakov)
+- **Sentinel timeout retry**: When connection to a sentinel times out, attempt to connect to other sentinels ([#3654](https://github.com/redis/go-redis/pull/3654)) by [@cxljs](https://github.com/cxljs)
+
+## ⚡ Performance
+
+- **Fuzz test optimization**: Eliminated repeated string conversions, used functional approach for cleaner operation selection ([#3692](https://github.com/redis/go-redis/pull/3692)) by [@feiguoL](https://github.com/feiguoL)
+- **Pre-allocate capacity**: Pre-allocate slice capacity to prevent multiple capacity expansions ([#3689](https://github.com/redis/go-redis/pull/3689)) by [@feelshu](https://github.com/feelshu)
+
+## 🧪 Testing
+
+- **Comprehensive TLS tests**: Added comprehensive TLS tests and example for standalone, cluster, and certificate authentication ([#3681](https://github.com/redis/go-redis/pull/3681)) by [@ndyakov](https://github.com/ndyakov)
+- **Redis 8.6**: Updated CI to use Redis 8.6-pre ([#3685](https://github.com/redis/go-redis/pull/3685)) by [@ndyakov](https://github.com/ndyakov)
+
+## 🧰 Maintenance
+
+- **Deprecation warnings**: Added deprecation warnings for commands based on Redis documentation ([#3673](https://github.com/redis/go-redis/pull/3673)) by [@ndyakov](https://github.com/ndyakov)
+- **Use errors.Join()**: Replaced custom error join function with standard library `errors.Join()` ([#3653](https://github.com/redis/go-redis/pull/3653)) by [@cxljs](https://github.com/cxljs)
+- **Use Go 1.21 min/max**: Use Go 1.21's built-in min/max functions ([#3656](https://github.com/redis/go-redis/pull/3656)) by [@cxljs](https://github.com/cxljs)
+- **Proper formatting**: Code formatting improvements ([#3670](https://github.com/redis/go-redis/pull/3670)) by [@12ya](https://github.com/12ya)
+- **Set commands documentation**: Added comprehensive documentation to all set command methods ([#3642](https://github.com/redis/go-redis/pull/3642)) by [@iamamirsalehi](https://github.com/iamamirsalehi)
+- **MaxActiveConns docs**: Added default value documentation for `MaxActiveConns` ([#3674](https://github.com/redis/go-redis/pull/3674)) by [@codykaup](https://github.com/codykaup)
+- **README example update**: Updated README example ([#3657](https://github.com/redis/go-redis/pull/3657)) by [@cxljs](https://github.com/cxljs)
+- **Cluster maintnotif example**: Added example application for cluster maintenance notifications ([#3651](https://github.com/redis/go-redis/pull/3651)) by [@ndyakov](https://github.com/ndyakov)
+
+## 👥 Contributors
+
+We'd like to thank all the contributors who worked on this release!
+
+[@12ya](https://github.com/12ya), [@Copilot](https://github.com/apps/copilot-swe-agent), [@codykaup](https://github.com/codykaup), [@cxljs](https://github.com/cxljs), [@cyningsun](https://github.com/cyningsun), [@feelshu](https://github.com/feelshu), [@feiguoL](https://github.com/feiguoL), [@iamamirsalehi](https://github.com/iamamirsalehi), [@naveenchander30](https://github.com/naveenchander30), [@ndyakov](https://github.com/ndyakov), [@ofekshenawa](https://github.com/ofekshenawa), [@veeceey](https://github.com/veeceey)
+
+---
+
+**Full Changelog**: https://github.com/redis/go-redis/compare/v9.17.0...v9.18.0
+
+# 9.18.0-beta.2 (2025-12-09)
+
+## 🚀 Highlights
+
+### Go Version Update
+
+This release updates the minimum required Go version to 1.21. This is part of a gradual migration strategy where the minimum supported Go version will be three versions behind the latest release. With each new Go version release, we will bump the minimum version by one, ensuring compatibility while staying current with the Go ecosystem.
+
+### Stability Improvements
+
+This release includes several important stability fixes:
+- Fixed a critical panic in the handoff worker manager that could occur when handling nil errors
+- Improved test reliability for Smart Client Handoff functionality
+- Fixed logging format issues that could cause runtime errors
+
+## ✨ New Features
+
+- OpenTelemetry metrics improvements for nil response handling ([#3638](https://github.com/redis/go-redis/pull/3638)) by [@fengve](https://github.com/fengve)
+
+## 🐛 Bug Fixes
+
+- Fixed panic on nil error in handoffWorkerManager closeConnFromRequest ([#3633](https://github.com/redis/go-redis/pull/3633)) by [@ccoVeille](https://github.com/ccoVeille)
+- Fixed bad sprintf syntax in logging ([#3632](https://github.com/redis/go-redis/pull/3632)) by [@ccoVeille](https://github.com/ccoVeille)
+
+## 🧰 Maintenance
+
+- Updated minimum Go version to 1.21 ([#3640](https://github.com/redis/go-redis/pull/3640)) by [@ndyakov](https://github.com/ndyakov)
+- Use Go 1.20 idiomatic string<->byte conversion ([#3435](https://github.com/redis/go-redis/pull/3435)) by [@justinhwang](https://github.com/justinhwang)
+- Reduce flakiness of Smart Client Handoff test ([#3641](https://github.com/redis/go-redis/pull/3641)) by [@kiryazovi-redis](https://github.com/kiryazovi-redis)
+- Revert PR #3634 (Observability metrics phase1) ([#3635](https://github.com/redis/go-redis/pull/3635)) by [@ofekshenawa](https://github.com/ofekshenawa)
+
+## 👥 Contributors
+
+We'd like to thank all the contributors who worked on this release!
+
+[@justinhwang](https://github.com/justinhwang), [@ndyakov](https://github.com/ndyakov), [@kiryazovi-redis](https://github.com/kiryazovi-redis), [@fengve](https://github.com/fengve), [@ccoVeille](https://github.com/ccoVeille), [@ofekshenawa](https://github.com/ofekshenawa)
+
+---
+
+**Full Changelog**: https://github.com/redis/go-redis/compare/v9.18.0-beta.1...v9.18.0-beta.2
+
+# 9.18.0-beta.1 (2025-12-01)
+
+## 🚀 Highlights
+
+### Request and Response Policy Based Routing in Cluster Mode
+
+This beta release introduces comprehensive support for Redis COMMAND-based request and response policy routing for cluster clients. This feature enables intelligent command routing and response aggregation based on Redis command metadata.
+
+**Key Features:**
+- **Command Policy Loader**: Automatically parses and caches COMMAND metadata with routing/aggregation hints
+- **Enhanced Routing Engine**: Supports all request policies including:
+ - `default(keyless)` - Commands without keys
+ - `default(hashslot)` - Commands with hash slot routing
+ - `all_shards` - Commands that need to run on all shards
+ - `all_nodes` - Commands that need to run on all nodes
+ - `multi_shard` - Commands that span multiple shards
+ - `special` - Commands with custom routing logic
+- **Response Aggregator**: Intelligently combines multi-shard replies based on response policies:
+ - `all_succeeded` - All shards must succeed
+ - `one_succeeded` - At least one shard must succeed
+ - `agg_sum` - Aggregate numeric responses
+ - `special` - Custom aggregation logic (e.g., FT.CURSOR)
+- **Raw Command Support**: Policies are enforced on `Client.Do(ctx, args...)`
+
+This feature is particularly useful for Redis Stack commands like RediSearch that need to operate across multiple shards in a cluster.
+
+### Connection Pool Improvements
+
+Fixed a critical defect in the connection pool's turn management mechanism that could lead to connection leaks under certain conditions. The fix ensures proper 1:1 correspondence between turns and connections.
+
+## ✨ New Features
+
+- Request and Response Policy Based Routing in Cluster Mode ([#3422](https://github.com/redis/go-redis/pull/3422)) by [@ofekshenawa](https://github.com/ofekshenawa)
+
+## 🐛 Bug Fixes
+
+- Fixed connection pool turn management to prevent connection leaks ([#3626](https://github.com/redis/go-redis/pull/3626)) by [@cyningsun](https://github.com/cyningsun)
+
+## 🧰 Maintenance
+
+- chore(deps): bump rojopolis/spellcheck-github-actions from 0.54.0 to 0.55.0 ([#3627](https://github.com/redis/go-redis/pull/3627))
+
+## 👥 Contributors
+
+We'd like to thank all the contributors who worked on this release!
+
+[@cyningsun](https://github.com/cyningsun), [@ofekshenawa](https://github.com/ofekshenawa), [@ndyakov](https://github.com/ndyakov)
+
+---
+
+**Full Changelog**: https://github.com/redis/go-redis/compare/v9.17.1...v9.18.0-beta.1
+
+# 9.17.1 (2025-11-25)
+
+## 🐛 Bug Fixes
+
+- add wait to keyless commands list ([#3615](https://github.com/redis/go-redis/pull/3615)) by [@marcoferrer](https://github.com/marcoferrer)
+- fix(time): remove cached time optimization ([#3611](https://github.com/redis/go-redis/pull/3611)) by [@ndyakov](https://github.com/ndyakov)
+
+## 🧰 Maintenance
+
+- chore(deps): bump golangci/golangci-lint-action from 9.0.0 to 9.1.0 ([#3609](https://github.com/redis/go-redis/pull/3609))
+- chore(deps): bump actions/checkout from 5 to 6 ([#3610](https://github.com/redis/go-redis/pull/3610))
+- chore(script): fix help call in tag.sh ([#3606](https://github.com/redis/go-redis/pull/3606)) by [@ndyakov](https://github.com/ndyakov)
+
+## Contributors
+We'd like to thank all the contributors who worked on this release!
+
+[@marcoferrer](https://github.com/marcoferrer) and [@ndyakov](https://github.com/ndyakov)
+
+---
+
+**Full Changelog**: https://github.com/redis/go-redis/compare/v9.17.0...v9.17.1
+
+# 9.17.0 (2025-11-19)
+
+## 🚀 Highlights
+
+### Redis 8.4 Support
+Added support for Redis 8.4, including new commands and features ([#3572](https://github.com/redis/go-redis/pull/3572))
+
+### Typed Errors
+Introduced typed errors for better error handling using `errors.As` instead of string checks. Errors can now be wrapped and set to commands in hooks without breaking library functionality ([#3602](https://github.com/redis/go-redis/pull/3602))
+
+### New Commands
+- **CAS/CAD Commands**: Added support for Compare-And-Set/Compare-And-Delete operations with conditional matching (`IFEQ`, `IFNE`, `IFDEQ`, `IFDNE`) ([#3583](https://github.com/redis/go-redis/pull/3583), [#3595](https://github.com/redis/go-redis/pull/3595))
+- **MSETEX**: Atomically set multiple key-value pairs with expiration options and conditional modes ([#3580](https://github.com/redis/go-redis/pull/3580))
+- **XReadGroup CLAIM**: Consume both incoming and idle pending entries from streams in a single call ([#3578](https://github.com/redis/go-redis/pull/3578))
+- **ACL Commands**: Added `ACLGenPass`, `ACLUsers`, and `ACLWhoAmI` ([#3576](https://github.com/redis/go-redis/pull/3576))
+- **SLOWLOG Commands**: Added `SLOWLOG LEN` and `SLOWLOG RESET` ([#3585](https://github.com/redis/go-redis/pull/3585))
+- **LATENCY Commands**: Added `LATENCY LATEST` and `LATENCY RESET` ([#3584](https://github.com/redis/go-redis/pull/3584))
+
+### Search & Vector Improvements
+- **Hybrid Search**: Added **EXPERIMENTAL** support for the new `FT.HYBRID` command ([#3573](https://github.com/redis/go-redis/pull/3573))
+- **Vector Range**: Added `VRANGE` command for vector sets ([#3543](https://github.com/redis/go-redis/pull/3543))
+- **FT.INFO Enhancements**: Added vector-specific attributes in FT.INFO response ([#3596](https://github.com/redis/go-redis/pull/3596))
+
+### Connection Pool Improvements
+- **Improved Connection Success Rate**: Implemented FIFO queue-based fairness and context pattern for connection creation to prevent premature cancellation under high concurrency ([#3518](https://github.com/redis/go-redis/pull/3518))
+- **Connection State Machine**: Resolved race conditions and improved pool performance with proper state tracking ([#3559](https://github.com/redis/go-redis/pull/3559))
+- **Pool Performance**: Significant performance improvements with faster semaphores, lockless hook manager, and reduced allocations (47-67% faster Get/Put operations) ([#3565](https://github.com/redis/go-redis/pull/3565))
+
+### Metrics & Observability
+- **Canceled Metric Attribute**: Added 'canceled' metrics attribute to distinguish context cancellation errors from other errors ([#3566](https://github.com/redis/go-redis/pull/3566))
+
+## ✨ New Features
+
+- Typed errors with wrapping support ([#3602](https://github.com/redis/go-redis/pull/3602)) by [@ndyakov](https://github.com/ndyakov)
+- CAS/CAD commands (marked as experimental) ([#3583](https://github.com/redis/go-redis/pull/3583), [#3595](https://github.com/redis/go-redis/pull/3595)) by [@ndyakov](https://github.com/ndyakov), [@htemelski-redis](https://github.com/htemelski-redis)
+- MSETEX command support ([#3580](https://github.com/redis/go-redis/pull/3580)) by [@ofekshenawa](https://github.com/ofekshenawa)
+- XReadGroup CLAIM argument ([#3578](https://github.com/redis/go-redis/pull/3578)) by [@ofekshenawa](https://github.com/ofekshenawa)
+- ACL commands: GenPass, Users, WhoAmI ([#3576](https://github.com/redis/go-redis/pull/3576)) by [@destinyoooo](https://github.com/destinyoooo)
+- SLOWLOG commands: LEN, RESET ([#3585](https://github.com/redis/go-redis/pull/3585)) by [@destinyoooo](https://github.com/destinyoooo)
+- LATENCY commands: LATEST, RESET ([#3584](https://github.com/redis/go-redis/pull/3584)) by [@destinyoooo](https://github.com/destinyoooo)
+- Hybrid search command (FT.HYBRID) ([#3573](https://github.com/redis/go-redis/pull/3573)) by [@htemelski-redis](https://github.com/htemelski-redis)
+- Vector range command (VRANGE) ([#3543](https://github.com/redis/go-redis/pull/3543)) by [@cxljs](https://github.com/cxljs)
+- Vector-specific attributes in FT.INFO ([#3596](https://github.com/redis/go-redis/pull/3596)) by [@ndyakov](https://github.com/ndyakov)
+- Improved connection pool success rate with FIFO queue ([#3518](https://github.com/redis/go-redis/pull/3518)) by [@cyningsun](https://github.com/cyningsun)
+- Canceled metrics attribute for context errors ([#3566](https://github.com/redis/go-redis/pull/3566)) by [@pvragov](https://github.com/pvragov)
+
+## 🐛 Bug Fixes
+
+- Fixed Failover Client MaintNotificationsConfig ([#3600](https://github.com/redis/go-redis/pull/3600)) by [@ajax16384](https://github.com/ajax16384)
+- Fixed ACLGenPass function to use the bit parameter ([#3597](https://github.com/redis/go-redis/pull/3597)) by [@destinyoooo](https://github.com/destinyoooo)
+- Return error instead of panic from commands ([#3568](https://github.com/redis/go-redis/pull/3568)) by [@dragneelfps](https://github.com/dragneelfps)
+- Safety harness in `joinErrors` to prevent panic ([#3577](https://github.com/redis/go-redis/pull/3577)) by [@manisharma](https://github.com/manisharma)
+
+## ⚡ Performance
+
+- Connection state machine with race condition fixes ([#3559](https://github.com/redis/go-redis/pull/3559)) by [@ndyakov](https://github.com/ndyakov)
+- Pool performance improvements: 47-67% faster Get/Put, 33% less memory, 50% fewer allocations ([#3565](https://github.com/redis/go-redis/pull/3565)) by [@ndyakov](https://github.com/ndyakov)
+
+## 🧪 Testing & Infrastructure
+
+- Updated to Redis 8.4.0 image ([#3603](https://github.com/redis/go-redis/pull/3603)) by [@ndyakov](https://github.com/ndyakov)
+- Added Redis 8.4-RC1-pre to CI ([#3572](https://github.com/redis/go-redis/pull/3572)) by [@ndyakov](https://github.com/ndyakov)
+- Refactored tests for idiomatic Go ([#3561](https://github.com/redis/go-redis/pull/3561), [#3562](https://github.com/redis/go-redis/pull/3562), [#3563](https://github.com/redis/go-redis/pull/3563)) by [@12ya](https://github.com/12ya)
+
+## 👥 Contributors
+
+We'd like to thank all the contributors who worked on this release!
+
+[@12ya](https://github.com/12ya), [@ajax16384](https://github.com/ajax16384), [@cxljs](https://github.com/cxljs), [@cyningsun](https://github.com/cyningsun), [@destinyoooo](https://github.com/destinyoooo), [@dragneelfps](https://github.com/dragneelfps), [@htemelski-redis](https://github.com/htemelski-redis), [@manisharma](https://github.com/manisharma), [@ndyakov](https://github.com/ndyakov), [@ofekshenawa](https://github.com/ofekshenawa), [@pvragov](https://github.com/pvragov)
+
+---
+
+**Full Changelog**: https://github.com/redis/go-redis/compare/v9.16.0...v9.17.0
+
+# 9.16.0 (2025-10-23)
+
+## 🚀 Highlights
+
+### Maintenance Notifications Support
+
+This release introduces comprehensive support for Redis maintenance notifications, enabling applications to handle server maintenance events gracefully. The new `maintnotifications` package provides:
+
+- **RESP3 Push Notifications**: Full support for Redis RESP3 protocol push notifications
+- **Connection Handoff**: Automatic connection migration during server maintenance with configurable retry policies and circuit breakers
+- **Graceful Degradation**: Configurable timeout relaxation during maintenance windows to prevent false failures
+- **Event-Driven Architecture**: Background workers with on-demand scaling for efficient handoff processing
+- **Production-Ready**: Comprehensive E2E testing framework and monitoring capabilities
+
+For detailed usage examples and configuration options, see the [maintenance notifications documentation](maintnotifications/README.md).
+
+## ✨ New Features
+
+- **Trace Filtering**: Add support for filtering traces for specific commands, including pipeline operations and dial operations ([#3519](https://github.com/redis/go-redis/pull/3519), [#3550](https://github.com/redis/go-redis/pull/3550))
+ - New `TraceCmdFilter` option to selectively trace commands
+ - Reduces overhead by excluding high-frequency or low-value commands from traces
+
+## 🐛 Bug Fixes
+
+- **Pipeline Error Handling**: Fix issue where pipeline repeatedly sets the same error ([#3525](https://github.com/redis/go-redis/pull/3525))
+- **Connection Pool**: Ensure re-authentication does not interfere with connection handoff operations ([#3547](https://github.com/redis/go-redis/pull/3547))
+
+## 🔧 Improvements
+
+- **Hash Commands**: Update hash command implementations ([#3523](https://github.com/redis/go-redis/pull/3523))
+- **OpenTelemetry**: Use `metric.WithAttributeSet` to avoid unnecessary attribute copying in redisotel ([#3552](https://github.com/redis/go-redis/pull/3552))
+
+## 📚 Documentation
+
+- **Cluster Client**: Add explanation for why `MaxRetries` is disabled for `ClusterClient` ([#3551](https://github.com/redis/go-redis/pull/3551))
+
+## 🧪 Testing & Infrastructure
+
+- **E2E Testing**: Upgrade E2E testing framework with improved reliability and coverage ([#3541](https://github.com/redis/go-redis/pull/3541))
+- **Release Process**: Improved resiliency of the release process ([#3530](https://github.com/redis/go-redis/pull/3530))
+
+## 📦 Dependencies
+
+- Bump `rojopolis/spellcheck-github-actions` from 0.51.0 to 0.52.0 ([#3520](https://github.com/redis/go-redis/pull/3520))
+- Bump `github/codeql-action` from 3 to 4 ([#3544](https://github.com/redis/go-redis/pull/3544))
+
+## 👥 Contributors
+
+We'd like to thank all the contributors who worked on this release!
+
+[@ndyakov](https://github.com/ndyakov), [@htemelski-redis](https://github.com/htemelski-redis), [@Sovietaced](https://github.com/Sovietaced), [@Udhayarajan](https://github.com/Udhayarajan), [@boekkooi-impossiblecloud](https://github.com/boekkooi-impossiblecloud), [@Pika-Gopher](https://github.com/Pika-Gopher), [@cxljs](https://github.com/cxljs), [@huiyifyj](https://github.com/huiyifyj), [@omid-h70](https://github.com/omid-h70)
+
+---
+
+**Full Changelog**: https://github.com/redis/go-redis/compare/v9.14.0...v9.16.0
+
+
+# 9.15.0 was accidentally released. Please use version 9.16.0 instead.
+
+# 9.15.0-beta.3 (2025-09-26)
+
+## Highlights
+This beta release includes a pre-production version of processing push notifications and hitless upgrades.
+
+# Changes
+
+- chore: Update hash_commands.go ([#3523](https://github.com/redis/go-redis/pull/3523))
+
+## 🚀 New Features
+
+- feat: RESP3 notifications support & Hitless notifications handling ([#3418](https://github.com/redis/go-redis/pull/3418))
+
+## 🐛 Bug Fixes
+
+- fix: pipeline repeatedly sets the error ([#3525](https://github.com/redis/go-redis/pull/3525))
+
+## 🧰 Maintenance
+
+- chore(deps): bump rojopolis/spellcheck-github-actions from 0.51.0 to 0.52.0 ([#3520](https://github.com/redis/go-redis/pull/3520))
+- feat(e2e-testing): maintnotifications e2e and refactor ([#3526](https://github.com/redis/go-redis/pull/3526))
+- feat(tag.sh): Improved resiliency of the release process ([#3530](https://github.com/redis/go-redis/pull/3530))
+
+## Contributors
+We'd like to thank all the contributors who worked on this release!
+
+[@cxljs](https://github.com/cxljs), [@ndyakov](https://github.com/ndyakov), [@htemelski-redis](https://github.com/htemelski-redis), and [@omid-h70](https://github.com/omid-h70)
+
+
+# 9.15.0-beta.1 (2025-09-10)
+
+## Highlights
+This beta release includes a pre-production version of processing push notifications and hitless upgrades.
+
+### Hitless Upgrades
+Hitless upgrades is a major new feature that allows for zero-downtime upgrades in Redis clusters.
+You can find more information in the [Hitless Upgrades documentation](https://github.com/redis/go-redis/tree/master/hitless).
+
+# Changes
+
+## 🚀 New Features
+- [CAE-1088] & [CAE-1072] feat: RESP3 notifications support & Hitless notifications handling ([#3418](https://github.com/redis/go-redis/pull/3418))
+
+## Contributors
+We'd like to thank all the contributors who worked on this release!
+
+[@ndyakov](https://github.com/ndyakov), [@htemelski-redis](https://github.com/htemelski-redis), [@ofekshenawa](https://github.com/ofekshenawa)
+
+
+# 9.14.0 (2025-09-10)
+
+## Highlights
+- Added batch process method to the pipeline ([#3510](https://github.com/redis/go-redis/pull/3510))
+
+# Changes
+
+## 🚀 New Features
+
+- Added batch process method to the pipeline ([#3510](https://github.com/redis/go-redis/pull/3510))
+
+## 🐛 Bug Fixes
+
+- fix: SetErr on Cmd if the command cannot be queued correctly in multi/exec ([#3509](https://github.com/redis/go-redis/pull/3509))
+
+## 🧰 Maintenance
+
+- Updates release drafter config to exclude dependabot ([#3511](https://github.com/redis/go-redis/pull/3511))
+- chore(deps): bump actions/setup-go from 5 to 6 ([#3504](https://github.com/redis/go-redis/pull/3504))
+
+## Contributors
+We'd like to thank all the contributors who worked on this release!
+
+[@elena-kolevska](https://github.com/elena-kolevksa), [@htemelski-redis](https://github.com/htemelski-redis) and [@ndyakov](https://github.com/ndyakov)
+
+
+# 9.13.0 (2025-09-03)
+
+## Highlights
+- Pipeliner expose queued commands ([#3496](https://github.com/redis/go-redis/pull/3496))
+- Ensure that JSON.GET returns Nil response ([#3470](https://github.com/redis/go-redis/pull/3470))
+- Fixes on Read and Write buffer sizes and UniversalOptions
+
+## Changes
+- Pipeliner expose queued commands ([#3496](https://github.com/redis/go-redis/pull/3496))
+- fix(test): fix a timing issue in pubsub test ([#3498](https://github.com/redis/go-redis/pull/3498))
+- Allow users to enable read-write splitting in failover mode. ([#3482](https://github.com/redis/go-redis/pull/3482))
+- Set the read/write buffer size of the sentinel client to 4KiB ([#3476](https://github.com/redis/go-redis/pull/3476))
+
+## 🚀 New Features
+
+- fix(otel): register wait metrics ([#3499](https://github.com/redis/go-redis/pull/3499))
+- Support subscriptions against cluster slave nodes ([#3480](https://github.com/redis/go-redis/pull/3480))
+- Add wait metrics to otel ([#3493](https://github.com/redis/go-redis/pull/3493))
+- Clean failing timeout implementation ([#3472](https://github.com/redis/go-redis/pull/3472))
+
+## 🐛 Bug Fixes
+
+- Do not assume that all non-IP hosts are loopbacks ([#3085](https://github.com/redis/go-redis/pull/3085))
+- Ensure that JSON.GET returns Nil response ([#3470](https://github.com/redis/go-redis/pull/3470))
+
+## 🧰 Maintenance
+
+- fix(otel): register wait metrics ([#3499](https://github.com/redis/go-redis/pull/3499))
+- fix(make test): Add default env in makefile ([#3491](https://github.com/redis/go-redis/pull/3491))
+- Update the introduction to running tests in README.md ([#3495](https://github.com/redis/go-redis/pull/3495))
+- test: Add comprehensive edge case tests for IncrByFloat command ([#3477](https://github.com/redis/go-redis/pull/3477))
+- Set the default read/write buffer size of Redis connection to 32KiB ([#3483](https://github.com/redis/go-redis/pull/3483))
+- Bumps test image to 8.2.1-pre ([#3478](https://github.com/redis/go-redis/pull/3478))
+- fix UniversalOptions miss ReadBufferSize and WriteBufferSize options ([#3485](https://github.com/redis/go-redis/pull/3485))
+- chore(deps): bump actions/checkout from 4 to 5 ([#3484](https://github.com/redis/go-redis/pull/3484))
+- Removes dry run for stale issues policy ([#3471](https://github.com/redis/go-redis/pull/3471))
+- Update otel metrics URL ([#3474](https://github.com/redis/go-redis/pull/3474))
+
+## Contributors
+We'd like to thank all the contributors who worked on this release!
+
+[@LINKIWI](https://github.com/LINKIWI), [@cxljs](https://github.com/cxljs), [@cybersmeashish](https://github.com/cybersmeashish), [@elena-kolevska](https://github.com/elena-kolevska), [@htemelski-redis](https://github.com/htemelski-redis), [@mwhooker](https://github.com/mwhooker), [@ndyakov](https://github.com/ndyakov), [@ofekshenawa](https://github.com/ofekshenawa), [@suever](https://github.com/suever)
+
+
+# 9.12.1 (2025-08-11)
+## 🚀 Highlights
+In the last version (9.12.0) the client introduced bigger write and read buffer sized. The default value we set was 512KiB.
+However, users reported that this is too big for most use cases and can lead to high memory usage.
+In this version the default value is changed to 256KiB. The `README.md` was updated to reflect the
+correct default value and include a note that the default value can be changed.
+
+## 🐛 Bug Fixes
+
+- fix(options): Add buffer sizes to failover. Update README ([#3468](https://github.com/redis/go-redis/pull/3468))
+
+## 🧰 Maintenance
+
+- fix(options): Add buffer sizes to failover. Update README ([#3468](https://github.com/redis/go-redis/pull/3468))
+- chore: update & fix otel example ([#3466](https://github.com/redis/go-redis/pull/3466))
+
+## Contributors
+We'd like to thank all the contributors who worked on this release!
+
+[@ndyakov](https://github.com/ndyakov) and [@vmihailenco](https://github.com/vmihailenco)
+
+# 9.12.0 (2025-08-05)
+
+## 🚀 Highlights
+
+- This release includes support for [Redis 8.2](https://redis.io/docs/latest/operate/oss_and_stack/stack-with-enterprise/release-notes/redisce/redisos-8.2-release-notes/).
+- Introduces an experimental Query Builders for `FTSearch`, `FTAggregate` and other search commands.
+- Adds support for `EPSILON` option in `FT.VSIM`.
+- Includes bug fixes and improvements contributed by the community related to ring and [redisotel](https://github.com/redis/go-redis/tree/master/extra/redisotel).
+
+## Changes
+- Improve stale issue workflow ([#3458](https://github.com/redis/go-redis/pull/3458))
+- chore(ci): Add 8.2 rc2 pre build for CI ([#3459](https://github.com/redis/go-redis/pull/3459))
+- Added new stream commands ([#3450](https://github.com/redis/go-redis/pull/3450))
+- feat: Add "skip_verify" to Sentinel ([#3428](https://github.com/redis/go-redis/pull/3428))
+- fix: `errors.Join` requires Go 1.20 or later ([#3442](https://github.com/redis/go-redis/pull/3442))
+- DOC-4344 document quickstart examples ([#3426](https://github.com/redis/go-redis/pull/3426))
+- feat(bitop): add support for the new bitop operations ([#3409](https://github.com/redis/go-redis/pull/3409))
+
+## 🚀 New Features
+
+- feat: recover addIdleConn may occur panic ([#2445](https://github.com/redis/go-redis/pull/2445))
+- feat(ring): specify custom health check func via HeartbeatFn option ([#2940](https://github.com/redis/go-redis/pull/2940))
+- Add Query Builder for RediSearch commands ([#3436](https://github.com/redis/go-redis/pull/3436))
+- add configurable buffer sizes for Redis connections ([#3453](https://github.com/redis/go-redis/pull/3453))
+- Add VAMANA vector type to RediSearch ([#3449](https://github.com/redis/go-redis/pull/3449))
+- VSIM add `EPSILON` option ([#3454](https://github.com/redis/go-redis/pull/3454))
+- Add closing support to otel metrics instrumentation ([#3444](https://github.com/redis/go-redis/pull/3444))
+
+## 🐛 Bug Fixes
+
+- fix(redisotel): fix buggy append in reportPoolStats ([#3122](https://github.com/redis/go-redis/pull/3122))
+- fix(search): return results even if doc is empty ([#3457](https://github.com/redis/go-redis/pull/3457))
+- [ISSUE-3402]: Ring.Pipelined return dial timeout error ([#3403](https://github.com/redis/go-redis/pull/3403))
+
+## 🧰 Maintenance
+
+- Merges stale issues jobs into one job with two steps ([#3463](https://github.com/redis/go-redis/pull/3463))
+- improve code readability ([#3446](https://github.com/redis/go-redis/pull/3446))
+- chore(release): 9.12.0-beta.1 ([#3460](https://github.com/redis/go-redis/pull/3460))
+- DOC-5472 time series doc examples ([#3443](https://github.com/redis/go-redis/pull/3443))
+- Add VAMANA compression algorithm tests ([#3461](https://github.com/redis/go-redis/pull/3461))
+- bumped redis 8.2 version used in the CI/CD ([#3451](https://github.com/redis/go-redis/pull/3451))
+
+## Contributors
+We'd like to thank all the contributors who worked on this release!
+
+[@andy-stark-redis](https://github.com/andy-stark-redis), [@cxljs](https://github.com/cxljs), [@elena-kolevska](https://github.com/elena-kolevska), [@htemelski-redis](https://github.com/htemelski-redis), [@jouir](https://github.com/jouir), [@monkey92t](https://github.com/monkey92t), [@ndyakov](https://github.com/ndyakov), [@ofekshenawa](https://github.com/ofekshenawa), [@rokn](https://github.com/rokn), [@smnvdev](https://github.com/smnvdev), [@strobil](https://github.com/strobil) and [@wzy9607](https://github.com/wzy9607)
+
+## New Contributors
+* [@htemelski-redis](https://github.com/htemelski-redis) made their first contribution in [#3409](https://github.com/redis/go-redis/pull/3409)
+* [@smnvdev](https://github.com/smnvdev) made their first contribution in [#3403](https://github.com/redis/go-redis/pull/3403)
+* [@rokn](https://github.com/rokn) made their first contribution in [#3444](https://github.com/redis/go-redis/pull/3444)
+
+# 9.11.0 (2025-06-24)
+
+## 🚀 Highlights
+
+Fixes TxPipeline to work correctly in cluster scenarios, allowing execution of commands
+only in the same slot.
+
+# Changes
+
+## 🚀 New Features
+
+- Set cluster slot for `scan` commands, rather than random ([#2623](https://github.com/redis/go-redis/pull/2623))
+- Add CredentialsProvider field to UniversalOptions ([#2927](https://github.com/redis/go-redis/pull/2927))
+- feat(redisotel): add WithCallerEnabled option ([#3415](https://github.com/redis/go-redis/pull/3415))
+
+## 🐛 Bug Fixes
+
+- fix(txpipeline): keyless commands should take the slot of the keyed ([#3411](https://github.com/redis/go-redis/pull/3411))
+- fix(loading): cache the loaded flag for slave nodes ([#3410](https://github.com/redis/go-redis/pull/3410))
+- fix(txpipeline): should return error on multi/exec on multiple slots ([#3408](https://github.com/redis/go-redis/pull/3408))
+- fix: check if the shard exists to avoid returning nil ([#3396](https://github.com/redis/go-redis/pull/3396))
+
+## 🧰 Maintenance
+
+- feat: optimize connection pool waitTurn ([#3412](https://github.com/redis/go-redis/pull/3412))
+- chore(ci): update CI redis builds ([#3407](https://github.com/redis/go-redis/pull/3407))
+- chore: remove a redundant method from `Ring`, `Client` and `ClusterClient` ([#3401](https://github.com/redis/go-redis/pull/3401))
+- test: refactor TestBasicCredentials using table-driven tests ([#3406](https://github.com/redis/go-redis/pull/3406))
+- perf: reduce unnecessary memory allocation operations ([#3399](https://github.com/redis/go-redis/pull/3399))
+- fix: insert entry during iterating over a map ([#3398](https://github.com/redis/go-redis/pull/3398))
+- DOC-5229 probabilistic data type examples ([#3413](https://github.com/redis/go-redis/pull/3413))
+- chore(deps): bump rojopolis/spellcheck-github-actions from 0.49.0 to 0.51.0 ([#3414](https://github.com/redis/go-redis/pull/3414))
+
+## Contributors
+We'd like to thank all the contributors who worked on this release!
+
+[@andy-stark-redis](https://github.com/andy-stark-redis), [@boekkooi-impossiblecloud](https://github.com/boekkooi-impossiblecloud), [@cxljs](https://github.com/cxljs), [@dcherubini](https://github.com/dcherubini), [@dependabot[bot]](https://github.com/apps/dependabot), [@iamamirsalehi](https://github.com/iamamirsalehi), [@ndyakov](https://github.com/ndyakov), [@pete-woods](https://github.com/pete-woods), [@twz915](https://github.com/twz915) and [dependabot[bot]](https://github.com/apps/dependabot)
+
+# 9.10.0 (2025-06-06)
+
+## 🚀 Highlights
+
+`go-redis` now supports [vector sets](https://redis.io/docs/latest/develop/data-types/vector-sets/). This data type is marked
+as "in preview" in Redis and its support in `go-redis` is marked as experimental. You can find examples in the documentation and
+in the `doctests` folder.
+
+# Changes
+
+## 🚀 New Features
+
+- feat: support vectorset ([#3375](https://github.com/redis/go-redis/pull/3375))
+
+## 🧰 Maintenance
+
+- Add the missing NewFloatSliceResult for testing ([#3393](https://github.com/redis/go-redis/pull/3393))
+- DOC-5078 vector set examples ([#3394](https://github.com/redis/go-redis/pull/3394))
+
+## Contributors
+We'd like to thank all the contributors who worked on this release!
+
+[@AndBobsYourUncle](https://github.com/AndBobsYourUncle), [@andy-stark-redis](https://github.com/andy-stark-redis), [@fukua95](https://github.com/fukua95) and [@ndyakov](https://github.com/ndyakov)
+
+
+
+# 9.9.0 (2025-05-27)
+
+## 🚀 Highlights
+- **Token-based Authentication**: Added `StreamingCredentialsProvider` for dynamic credential updates (experimental)
+ - Can be used with [go-redis-entraid](https://github.com/redis/go-redis-entraid) for Azure AD authentication
+- **Connection Statistics**: Added connection waiting statistics for better monitoring
+- **Failover Improvements**: Added `ParseFailoverURL` for easier failover configuration
+- **Ring Client Enhancements**: Added shard access methods for better Pub/Sub management
+
+## ✨ New Features
+- Added `StreamingCredentialsProvider` for token-based authentication ([#3320](https://github.com/redis/go-redis/pull/3320))
+ - Supports dynamic credential updates
+ - Includes connection close hooks
+ - Note: Currently marked as experimental
+- Added `ParseFailoverURL` for parsing failover URLs ([#3362](https://github.com/redis/go-redis/pull/3362))
+- Added connection waiting statistics ([#2804](https://github.com/redis/go-redis/pull/2804))
+- Added new utility functions:
+ - `ParseFloat` and `MustParseFloat` in public utils package ([#3371](https://github.com/redis/go-redis/pull/3371))
+ - Unit tests for `Atoi`, `ParseInt`, `ParseUint`, and `ParseFloat` ([#3377](https://github.com/redis/go-redis/pull/3377))
+- Added Ring client shard access methods:
+ - `GetShardClients()` to retrieve all active shard clients
+ - `GetShardClientForKey(key string)` to get the shard client for a specific key ([#3388](https://github.com/redis/go-redis/pull/3388))
+
+## 🐛 Bug Fixes
+- Fixed routing reads to loading slave nodes ([#3370](https://github.com/redis/go-redis/pull/3370))
+- Added support for nil lag in XINFO GROUPS ([#3369](https://github.com/redis/go-redis/pull/3369))
+- Fixed pool acquisition timeout issues ([#3381](https://github.com/redis/go-redis/pull/3381))
+- Optimized unnecessary copy operations ([#3376](https://github.com/redis/go-redis/pull/3376))
+
+## 📚 Documentation
+- Updated documentation for XINFO GROUPS with nil lag support ([#3369](https://github.com/redis/go-redis/pull/3369))
+- Added package-level comments for new features
+
+## ⚡ Performance and Reliability
+- Optimized `ReplaceSpaces` function ([#3383](https://github.com/redis/go-redis/pull/3383))
+- Set default value for `Options.Protocol` in `init()` ([#3387](https://github.com/redis/go-redis/pull/3387))
+- Exported pool errors for public consumption ([#3380](https://github.com/redis/go-redis/pull/3380))
+
+## 🔧 Dependencies and Infrastructure
+- Updated Redis CI to version 8.0.1 ([#3372](https://github.com/redis/go-redis/pull/3372))
+- Updated spellcheck GitHub Actions ([#3389](https://github.com/redis/go-redis/pull/3389))
+- Removed unused parameters ([#3382](https://github.com/redis/go-redis/pull/3382), [#3384](https://github.com/redis/go-redis/pull/3384))
+
+## 🧪 Testing
+- Added unit tests for pool acquisition timeout ([#3381](https://github.com/redis/go-redis/pull/3381))
+- Added unit tests for utility functions ([#3377](https://github.com/redis/go-redis/pull/3377))
+
+## 👥 Contributors
+
+We would like to thank all the contributors who made this release possible:
+
+[@ndyakov](https://github.com/ndyakov), [@ofekshenawa](https://github.com/ofekshenawa), [@LINKIWI](https://github.com/LINKIWI), [@iamamirsalehi](https://github.com/iamamirsalehi), [@fukua95](https://github.com/fukua95), [@lzakharov](https://github.com/lzakharov), [@DengY11](https://github.com/DengY11)
+
+## 📝 Changelog
+
+For a complete list of changes, see the [full changelog](https://github.com/redis/go-redis/compare/v9.8.0...v9.9.0).
+
+# 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)
+
+
+# Old Changelog
+## Unreleased
+
+### Changed
+
+* `go-redis` won't skip span creation if the parent spans is not recording. ([#2980](https://github.com/redis/go-redis/issues/2980))
+ Users can use the OpenTelemetry sampler to control the sampling behavior.
+ For instance, you can use the `ParentBased(NeverSample())` sampler from `go.opentelemetry.io/otel/sdk/trace` to keep
+ a similar behavior (drop orphan spans) of `go-redis` as before.
+
+## [9.0.5](https://github.com/redis/go-redis/compare/v9.0.4...v9.0.5) (2023-05-29)
+
+
+### Features
+
+* Add ACL LOG ([#2536](https://github.com/redis/go-redis/issues/2536)) ([31ba855](https://github.com/redis/go-redis/commit/31ba855ddebc38fbcc69a75d9d4fb769417cf602))
+* add field protocol to setupClusterQueryParams ([#2600](https://github.com/redis/go-redis/issues/2600)) ([840c25c](https://github.com/redis/go-redis/commit/840c25cb6f320501886a82a5e75f47b491e46fbe))
+* add protocol option ([#2598](https://github.com/redis/go-redis/issues/2598)) ([3917988](https://github.com/redis/go-redis/commit/391798880cfb915c4660f6c3ba63e0c1a459e2af))
+
+
+
+## [9.0.4](https://github.com/redis/go-redis/compare/v9.0.3...v9.0.4) (2023-05-01)
+
+
+### Bug Fixes
+
+* reader float parser ([#2513](https://github.com/redis/go-redis/issues/2513)) ([46f2450](https://github.com/redis/go-redis/commit/46f245075e6e3a8bd8471f9ca67ea95fd675e241))
+
+
+### Features
+
+* add client info command ([#2483](https://github.com/redis/go-redis/issues/2483)) ([b8c7317](https://github.com/redis/go-redis/commit/b8c7317cc6af444603731f7017c602347c0ba61e))
+* no longer verify HELLO error messages ([#2515](https://github.com/redis/go-redis/issues/2515)) ([7b4f217](https://github.com/redis/go-redis/commit/7b4f2179cb5dba3d3c6b0c6f10db52b837c912c8))
+* read the structure to increase the judgment of the omitempty op… ([#2529](https://github.com/redis/go-redis/issues/2529)) ([37c057b](https://github.com/redis/go-redis/commit/37c057b8e597c5e8a0e372337f6a8ad27f6030af))
+
+
+
+## [9.0.3](https://github.com/redis/go-redis/compare/v9.0.2...v9.0.3) (2023-04-02)
+
+### New Features
+
+- feat(scan): scan time.Time sets the default decoding (#2413)
+- Add support for CLUSTER LINKS command (#2504)
+- Add support for acl dryrun command (#2502)
+- Add support for COMMAND GETKEYS & COMMAND GETKEYSANDFLAGS (#2500)
+- Add support for LCS Command (#2480)
+- Add support for BZMPOP (#2456)
+- Adding support for ZMPOP command (#2408)
+- Add support for LMPOP (#2440)
+- feat: remove pool unused fields (#2438)
+- Expiretime and PExpireTime (#2426)
+- Implement `FUNCTION` group of commands (#2475)
+- feat(zadd): add ZAddLT and ZAddGT (#2429)
+- Add: Support for COMMAND LIST command (#2491)
+- Add support for BLMPOP (#2442)
+- feat: check pipeline.Do to prevent confusion with Exec (#2517)
+- Function stats, function kill, fcall and fcall_ro (#2486)
+- feat: Add support for CLUSTER SHARDS command (#2507)
+- feat(cmd): support for adding byte,bit parameters to the bitpos command (#2498)
+
+### Fixed
+
+- fix: eval api cmd.SetFirstKeyPos (#2501)
+- fix: limit the number of connections created (#2441)
+- fixed #2462 v9 continue support dragonfly, it's Hello command return "NOAUTH Authentication required" error (#2479)
+- Fix for internal/hscan/structmap.go:89:23: undefined: reflect.Pointer (#2458)
+- fix: group lag can be null (#2448)
+
+### Maintenance
+
+- Updating to the latest version of redis (#2508)
+- Allowing for running tests on a port other than the fixed 6380 (#2466)
+- redis 7.0.8 in tests (#2450)
+- docs: Update redisotel example for v9 (#2425)
+- chore: update go mod, Upgrade golang.org/x/net version to 0.7.0 (#2476)
+- chore: add Chinese translation (#2436)
+- chore(deps): bump github.com/bsm/gomega from 1.20.0 to 1.26.0 (#2421)
+- chore(deps): bump github.com/bsm/ginkgo/v2 from 2.5.0 to 2.7.0 (#2420)
+- chore(deps): bump actions/setup-go from 3 to 4 (#2495)
+- docs: add instructions for the HSet api (#2503)
+- docs: add reading lag field comment (#2451)
+- test: update go mod before testing(go mod tidy) (#2423)
+- docs: fix comment typo (#2505)
+- test: remove testify (#2463)
+- refactor: change ListElementCmd to KeyValuesCmd. (#2443)
+- fix(appendArg): appendArg case special type (#2489)
+
+## [9.0.2](https://github.com/redis/go-redis/compare/v9.0.1...v9.0.2) (2023-02-01)
+
+### Features
+
+* upgrade OpenTelemetry, use the new metrics API. ([#2410](https://github.com/redis/go-redis/issues/2410)) ([e29e42c](https://github.com/redis/go-redis/commit/e29e42cde2755ab910d04185025dc43ce6f59c65))
+
+## v9 2023-01-30
+
+### Breaking
+
+- Changed Pipelines to not be thread-safe any more.
+
+### 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.
+
+### Fixed
+
+- Improved and fixed pipeline retries.
+- As usually, added support for more commands and fixed some bugs.
diff --git a/vendor/github.com/redis/go-redis/v9/acl_commands.go b/vendor/github.com/redis/go-redis/v9/acl_commands.go
new file mode 100644
index 00000000000..0a8a195ceb2
--- /dev/null
+++ b/vendor/github.com/redis/go-redis/v9/acl_commands.go
@@ -0,0 +1,116 @@
+package redis
+
+import "context"
+
+type ACLCmdable interface {
+ ACLDryRun(ctx context.Context, username string, command ...interface{}) *StringCmd
+
+ ACLLog(ctx context.Context, count int64) *ACLLogCmd
+ ACLLogReset(ctx context.Context) *StatusCmd
+
+ ACLGenPass(ctx context.Context, bit int) *StringCmd
+
+ ACLSetUser(ctx context.Context, username string, rules ...string) *StatusCmd
+ ACLDelUser(ctx context.Context, username string) *IntCmd
+ ACLUsers(ctx context.Context) *StringSliceCmd
+ ACLWhoAmI(ctx context.Context) *StringCmd
+ ACLList(ctx context.Context) *StringSliceCmd
+
+ ACLCat(ctx context.Context) *StringSliceCmd
+ ACLCatArgs(ctx context.Context, options *ACLCatArgs) *StringSliceCmd
+}
+
+type ACLCatArgs struct {
+ Category string
+}
+
+func (c cmdable) ACLDryRun(ctx context.Context, username string, command ...interface{}) *StringCmd {
+ args := make([]interface{}, 0, 3+len(command))
+ args = append(args, "acl", "dryrun", username)
+ args = append(args, command...)
+ cmd := NewStringCmd(ctx, args...)
+ _ = c(ctx, cmd)
+ return cmd
+}
+
+func (c cmdable) ACLLog(ctx context.Context, count int64) *ACLLogCmd {
+ args := make([]interface{}, 0, 3)
+ args = append(args, "acl", "log")
+ if count > 0 {
+ args = append(args, count)
+ }
+ cmd := NewACLLogCmd(ctx, args...)
+ _ = c(ctx, cmd)
+ return cmd
+}
+
+func (c cmdable) ACLLogReset(ctx context.Context) *StatusCmd {
+ cmd := NewStatusCmd(ctx, "acl", "log", "reset")
+ _ = c(ctx, cmd)
+ return cmd
+}
+
+func (c cmdable) ACLDelUser(ctx context.Context, username string) *IntCmd {
+ cmd := NewIntCmd(ctx, "acl", "deluser", username)
+ _ = c(ctx, cmd)
+ return cmd
+}
+
+func (c cmdable) ACLSetUser(ctx context.Context, username string, rules ...string) *StatusCmd {
+ args := make([]interface{}, 3+len(rules))
+ args[0] = "acl"
+ args[1] = "setuser"
+ args[2] = username
+ for i, rule := range rules {
+ args[i+3] = rule
+ }
+ cmd := NewStatusCmd(ctx, args...)
+ _ = c(ctx, cmd)
+ return cmd
+}
+
+func (c cmdable) ACLGenPass(ctx context.Context, bit int) *StringCmd {
+ args := make([]interface{}, 0, 3)
+ args = append(args, "acl", "genpass")
+ if bit > 0 {
+ args = append(args, bit)
+ }
+ cmd := NewStringCmd(ctx, args...)
+ _ = c(ctx, cmd)
+ return cmd
+}
+
+func (c cmdable) ACLUsers(ctx context.Context) *StringSliceCmd {
+ cmd := NewStringSliceCmd(ctx, "acl", "users")
+ _ = c(ctx, cmd)
+ return cmd
+}
+
+func (c cmdable) ACLWhoAmI(ctx context.Context) *StringCmd {
+ cmd := NewStringCmd(ctx, "acl", "whoami")
+ _ = c(ctx, cmd)
+ return cmd
+}
+
+func (c cmdable) ACLList(ctx context.Context) *StringSliceCmd {
+ cmd := NewStringSliceCmd(ctx, "acl", "list")
+ _ = c(ctx, cmd)
+ return cmd
+}
+
+func (c cmdable) ACLCat(ctx context.Context) *StringSliceCmd {
+ cmd := NewStringSliceCmd(ctx, "acl", "cat")
+ _ = c(ctx, cmd)
+ return cmd
+}
+
+func (c cmdable) ACLCatArgs(ctx context.Context, options *ACLCatArgs) *StringSliceCmd {
+ // if there is a category passed, build new cmd, if there isn't - use the ACLCat method
+ if options != nil && options.Category != "" {
+ cmd := NewStringSliceCmd(ctx, "acl", "cat", options.Category)
+ _ = c(ctx, cmd)
+ return cmd
+ }
+
+ return c.ACLCat(ctx)
+}
diff --git a/vendor/github.com/redis/go-redis/v9/adapters.go b/vendor/github.com/redis/go-redis/v9/adapters.go
new file mode 100644
index 00000000000..952a4c2660c
--- /dev/null
+++ b/vendor/github.com/redis/go-redis/v9/adapters.go
@@ -0,0 +1,118 @@
+package redis
+
+import (
+ "context"
+ "errors"
+ "net"
+ "time"
+
+ "github.com/redis/go-redis/v9/internal/interfaces"
+ "github.com/redis/go-redis/v9/push"
+)
+
+// ErrInvalidCommand is returned when an invalid command is passed to ExecuteCommand.
+var ErrInvalidCommand = errors.New("invalid command type")
+
+// ErrInvalidPool is returned when the pool type is not supported.
+var ErrInvalidPool = errors.New("invalid pool type")
+
+// newClientAdapter creates a new client adapter for regular Redis clients.
+func newClientAdapter(client *baseClient) interfaces.ClientInterface {
+ return &clientAdapter{client: client}
+}
+
+// clientAdapter adapts a Redis client to implement interfaces.ClientInterface.
+type clientAdapter struct {
+ client *baseClient
+}
+
+// GetOptions returns the client options.
+func (ca *clientAdapter) GetOptions() interfaces.OptionsInterface {
+ return &optionsAdapter{options: ca.client.opt}
+}
+
+// GetPushProcessor returns the client's push notification processor.
+func (ca *clientAdapter) GetPushProcessor() interfaces.NotificationProcessor {
+ return &pushProcessorAdapter{processor: ca.client.pushProcessor}
+}
+
+// optionsAdapter adapts Redis options to implement interfaces.OptionsInterface.
+type optionsAdapter struct {
+ options *Options
+}
+
+// GetReadTimeout returns the read timeout.
+func (oa *optionsAdapter) GetReadTimeout() time.Duration {
+ return oa.options.ReadTimeout
+}
+
+// GetWriteTimeout returns the write timeout.
+func (oa *optionsAdapter) GetWriteTimeout() time.Duration {
+ return oa.options.WriteTimeout
+}
+
+// GetNetwork returns the network type.
+func (oa *optionsAdapter) GetNetwork() string {
+ return oa.options.Network
+}
+
+// GetAddr returns the connection address.
+func (oa *optionsAdapter) GetAddr() string {
+ return oa.options.Addr
+}
+
+// GetNodeAddress returns the address of the Redis node as reported by the server.
+// For cluster clients, this is the endpoint from CLUSTER SLOTS before any transformation.
+// For standalone clients, this defaults to Addr.
+func (oa *optionsAdapter) GetNodeAddress() string {
+ return oa.options.NodeAddress
+}
+
+// IsTLSEnabled returns true if TLS is enabled.
+func (oa *optionsAdapter) IsTLSEnabled() bool {
+ return oa.options.TLSConfig != nil
+}
+
+// GetProtocol returns the protocol version.
+func (oa *optionsAdapter) GetProtocol() int {
+ return oa.options.Protocol
+}
+
+// GetPoolSize returns the connection pool size.
+func (oa *optionsAdapter) GetPoolSize() int {
+ return oa.options.PoolSize
+}
+
+// NewDialer returns a new dialer function for the connection.
+func (oa *optionsAdapter) NewDialer() func(context.Context) (net.Conn, error) {
+ baseDialer := oa.options.NewDialer()
+ return func(ctx context.Context) (net.Conn, error) {
+ // Extract network and address from the options
+ network := oa.options.Network
+ addr := oa.options.Addr
+ return baseDialer(ctx, network, addr)
+ }
+}
+
+// pushProcessorAdapter adapts a push.NotificationProcessor to implement interfaces.NotificationProcessor.
+type pushProcessorAdapter struct {
+ processor push.NotificationProcessor
+}
+
+// RegisterHandler registers a handler for a specific push notification name.
+func (ppa *pushProcessorAdapter) RegisterHandler(pushNotificationName string, handler interface{}, protected bool) error {
+ if pushHandler, ok := handler.(push.NotificationHandler); ok {
+ return ppa.processor.RegisterHandler(pushNotificationName, pushHandler, protected)
+ }
+ return errors.New("handler must implement push.NotificationHandler")
+}
+
+// UnregisterHandler removes a handler for a specific push notification name.
+func (ppa *pushProcessorAdapter) UnregisterHandler(pushNotificationName string) error {
+ return ppa.processor.UnregisterHandler(pushNotificationName)
+}
+
+// GetHandler returns the handler for a specific push notification name.
+func (ppa *pushProcessorAdapter) GetHandler(pushNotificationName string) interface{} {
+ return ppa.processor.GetHandler(pushNotificationName)
+}
diff --git a/vendor/github.com/redis/go-redis/v9/auth/auth.go b/vendor/github.com/redis/go-redis/v9/auth/auth.go
new file mode 100644
index 00000000000..1f5c8022485
--- /dev/null
+++ b/vendor/github.com/redis/go-redis/v9/auth/auth.go
@@ -0,0 +1,61 @@
+// Package auth package provides authentication-related interfaces and types.
+// It also includes a basic implementation of credentials using username and password.
+package auth
+
+// StreamingCredentialsProvider is an interface that defines the methods for a streaming credentials provider.
+// It is used to provide credentials for authentication.
+// The CredentialsListener is used to receive updates when the credentials change.
+type StreamingCredentialsProvider interface {
+ // Subscribe subscribes to the credentials provider for updates.
+ // It returns the current credentials, a cancel function to unsubscribe from the provider,
+ // and an error if any.
+ // TODO(ndyakov): Should we add context to the Subscribe method?
+ Subscribe(listener CredentialsListener) (Credentials, UnsubscribeFunc, error)
+}
+
+// UnsubscribeFunc is a function that is used to cancel the subscription to the credentials provider.
+// It is used to unsubscribe from the provider when the credentials are no longer needed.
+type UnsubscribeFunc func() error
+
+// CredentialsListener is an interface that defines the methods for a credentials listener.
+// It is used to receive updates when the credentials change.
+// The OnNext method is called when the credentials change.
+// The OnError method is called when an error occurs while requesting the credentials.
+type CredentialsListener interface {
+ OnNext(credentials Credentials)
+ OnError(err error)
+}
+
+// Credentials is an interface that defines the methods for credentials.
+// It is used to provide the credentials for authentication.
+type Credentials interface {
+ // BasicAuth returns the username and password for basic authentication.
+ BasicAuth() (username string, password string)
+ // RawCredentials returns the raw credentials as a string.
+ // This can be used to extract the username and password from the raw credentials or
+ // additional information if present in the token.
+ RawCredentials() string
+}
+
+type basicAuth struct {
+ username string
+ password string
+}
+
+// RawCredentials returns the raw credentials as a string.
+func (b *basicAuth) RawCredentials() string {
+ return b.username + ":" + b.password
+}
+
+// BasicAuth returns the username and password for basic authentication.
+func (b *basicAuth) BasicAuth() (username string, password string) {
+ return b.username, b.password
+}
+
+// NewBasicCredentials creates a new Credentials object from the given username and password.
+func NewBasicCredentials(username, password string) Credentials {
+ return &basicAuth{
+ username: username,
+ password: password,
+ }
+}
diff --git a/vendor/github.com/redis/go-redis/v9/auth/reauth_credentials_listener.go b/vendor/github.com/redis/go-redis/v9/auth/reauth_credentials_listener.go
new file mode 100644
index 00000000000..40076a0b135
--- /dev/null
+++ b/vendor/github.com/redis/go-redis/v9/auth/reauth_credentials_listener.go
@@ -0,0 +1,47 @@
+package auth
+
+// ReAuthCredentialsListener is a struct that implements the CredentialsListener interface.
+// It is used to re-authenticate the credentials when they are updated.
+// It contains:
+// - reAuth: a function that takes the new credentials and returns an error if any.
+// - onErr: a function that takes an error and handles it.
+type ReAuthCredentialsListener struct {
+ reAuth func(credentials Credentials) error
+ onErr func(err error)
+}
+
+// OnNext is called when the credentials are updated.
+// It calls the reAuth function with the new credentials.
+// If the reAuth function returns an error, it calls the onErr function with the error.
+func (c *ReAuthCredentialsListener) OnNext(credentials Credentials) {
+ if c.reAuth == nil {
+ return
+ }
+
+ err := c.reAuth(credentials)
+ if err != nil {
+ c.OnError(err)
+ }
+}
+
+// OnError is called when an error occurs.
+// It can be called from both the credentials provider and the reAuth function.
+func (c *ReAuthCredentialsListener) OnError(err error) {
+ if c.onErr == nil {
+ return
+ }
+
+ c.onErr(err)
+}
+
+// NewReAuthCredentialsListener creates a new ReAuthCredentialsListener.
+// Implements the auth.CredentialsListener interface.
+func NewReAuthCredentialsListener(reAuth func(credentials Credentials) error, onErr func(err error)) *ReAuthCredentialsListener {
+ return &ReAuthCredentialsListener{
+ reAuth: reAuth,
+ onErr: onErr,
+ }
+}
+
+// Ensure ReAuthCredentialsListener implements the CredentialsListener interface.
+var _ CredentialsListener = (*ReAuthCredentialsListener)(nil)
diff --git a/vendor/github.com/redis/go-redis/v9/bitmap_commands.go b/vendor/github.com/redis/go-redis/v9/bitmap_commands.go
new file mode 100644
index 00000000000..86aa9b7efc6
--- /dev/null
+++ b/vendor/github.com/redis/go-redis/v9/bitmap_commands.go
@@ -0,0 +1,197 @@
+package redis
+
+import (
+ "context"
+ "errors"
+)
+
+type BitMapCmdable interface {
+ GetBit(ctx context.Context, key string, offset int64) *IntCmd
+ SetBit(ctx context.Context, key string, offset int64, value int) *IntCmd
+ BitCount(ctx context.Context, key string, bitCount *BitCount) *IntCmd
+ BitOpAnd(ctx context.Context, destKey string, keys ...string) *IntCmd
+ BitOpOr(ctx context.Context, destKey string, keys ...string) *IntCmd
+ BitOpXor(ctx context.Context, destKey string, keys ...string) *IntCmd
+ BitOpDiff(ctx context.Context, destKey string, keys ...string) *IntCmd
+ BitOpDiff1(ctx context.Context, destKey string, keys ...string) *IntCmd
+ BitOpAndOr(ctx context.Context, destKey string, keys ...string) *IntCmd
+ BitOpOne(ctx context.Context, destKey string, keys ...string) *IntCmd
+ BitOpNot(ctx context.Context, destKey string, key string) *IntCmd
+ BitPos(ctx context.Context, key string, bit int64, pos ...int64) *IntCmd
+ BitPosSpan(ctx context.Context, key string, bit int8, start, end int64, span string) *IntCmd
+ BitField(ctx context.Context, key string, values ...interface{}) *IntSliceCmd
+ BitFieldRO(ctx context.Context, key string, values ...interface{}) *IntSliceCmd
+}
+
+func (c cmdable) GetBit(ctx context.Context, key string, offset int64) *IntCmd {
+ cmd := NewIntCmd(ctx, "getbit", key, offset)
+ _ = c(ctx, cmd)
+ return cmd
+}
+
+func (c cmdable) SetBit(ctx context.Context, key string, offset int64, value int) *IntCmd {
+ cmd := NewIntCmd(
+ ctx,
+ "setbit",
+ key,
+ offset,
+ value,
+ )
+ _ = c(ctx, cmd)
+ return cmd
+}
+
+type BitCount struct {
+ Start, End int64
+ Unit string // BYTE(default) | BIT
+}
+
+const BitCountIndexByte string = "BYTE"
+const BitCountIndexBit string = "BIT"
+
+func (c cmdable) BitCount(ctx context.Context, key string, bitCount *BitCount) *IntCmd {
+ args := make([]any, 2, 5)
+ args[0] = "bitcount"
+ args[1] = key
+ if bitCount != nil {
+ args = append(args, bitCount.Start, bitCount.End)
+ if bitCount.Unit != "" {
+ if bitCount.Unit != BitCountIndexByte && bitCount.Unit != BitCountIndexBit {
+ cmd := NewIntCmd(ctx)
+ cmd.SetErr(errors.New("redis: invalid bitcount index"))
+ return cmd
+ }
+ args = append(args, bitCount.Unit)
+ }
+ }
+ cmd := NewIntCmd(ctx, args...)
+ _ = c(ctx, cmd)
+ return cmd
+}
+
+func (c cmdable) bitOp(ctx context.Context, op, destKey string, keys ...string) *IntCmd {
+ args := make([]interface{}, 3+len(keys))
+ args[0] = "bitop"
+ args[1] = op
+ args[2] = destKey
+ for i, key := range keys {
+ args[3+i] = key
+ }
+ cmd := NewIntCmd(ctx, args...)
+ _ = c(ctx, cmd)
+ return cmd
+}
+
+// BitOpAnd creates a new bitmap in which users are members of all given bitmaps
+func (c cmdable) BitOpAnd(ctx context.Context, destKey string, keys ...string) *IntCmd {
+ return c.bitOp(ctx, "and", destKey, keys...)
+}
+
+// BitOpOr creates a new bitmap in which users are member of at least one given bitmap
+func (c cmdable) BitOpOr(ctx context.Context, destKey string, keys ...string) *IntCmd {
+ return c.bitOp(ctx, "or", destKey, keys...)
+}
+
+// BitOpXor creates a new bitmap in which users are the result of XORing all given bitmaps
+func (c cmdable) BitOpXor(ctx context.Context, destKey string, keys ...string) *IntCmd {
+ return c.bitOp(ctx, "xor", destKey, keys...)
+}
+
+// BitOpNot creates a new bitmap in which users are not members of a given bitmap
+func (c cmdable) BitOpNot(ctx context.Context, destKey string, key string) *IntCmd {
+ return c.bitOp(ctx, "not", destKey, key)
+}
+
+// BitOpDiff creates a new bitmap in which users are members of bitmap X but not of any of bitmaps Y1, Y2, …
+// Introduced with Redis 8.2
+func (c cmdable) BitOpDiff(ctx context.Context, destKey string, keys ...string) *IntCmd {
+ return c.bitOp(ctx, "diff", destKey, keys...)
+}
+
+// BitOpDiff1 creates a new bitmap in which users are members of one or more of bitmaps Y1, Y2, … but not members of bitmap X
+// Introduced with Redis 8.2
+func (c cmdable) BitOpDiff1(ctx context.Context, destKey string, keys ...string) *IntCmd {
+ return c.bitOp(ctx, "diff1", destKey, keys...)
+}
+
+// BitOpAndOr creates a new bitmap in which users are members of bitmap X and also members of one or more of bitmaps Y1, Y2, …
+// Introduced with Redis 8.2
+func (c cmdable) BitOpAndOr(ctx context.Context, destKey string, keys ...string) *IntCmd {
+ return c.bitOp(ctx, "andor", destKey, keys...)
+}
+
+// BitOpOne creates a new bitmap in which users are members of exactly one of the given bitmaps
+// Introduced with Redis 8.2
+func (c cmdable) BitOpOne(ctx context.Context, destKey string, keys ...string) *IntCmd {
+ return c.bitOp(ctx, "one", destKey, keys...)
+}
+
+// BitPos is an API before Redis version 7.0, cmd: bitpos key bit start end
+// if you need the `byte | bit` parameter, please use `BitPosSpan`.
+func (c cmdable) BitPos(ctx context.Context, key string, bit int64, pos ...int64) *IntCmd {
+ args := make([]interface{}, 3+len(pos))
+ args[0] = "bitpos"
+ args[1] = key
+ args[2] = bit
+ switch len(pos) {
+ case 0:
+ case 1:
+ args[3] = pos[0]
+ case 2:
+ args[3] = pos[0]
+ args[4] = pos[1]
+ default:
+ cmd := NewIntCmd(ctx)
+ cmd.SetErr(errors.New("too many arguments"))
+ return cmd
+ }
+ cmd := NewIntCmd(ctx, args...)
+ _ = c(ctx, cmd)
+ return cmd
+}
+
+// BitPosSpan supports the `byte | bit` parameters in redis version 7.0,
+// the bitpos command defaults to using byte type for the `start-end` range,
+// which means it counts in bytes from start to end. you can set the value
+// of "span" to determine the type of `start-end`.
+// span = "bit", cmd: bitpos key bit start end bit
+// span = "byte", cmd: bitpos key bit start end byte
+func (c cmdable) BitPosSpan(ctx context.Context, key string, bit int8, start, end int64, span string) *IntCmd {
+ cmd := NewIntCmd(ctx, "bitpos", key, bit, start, end, span)
+ _ = c(ctx, cmd)
+ return cmd
+}
+
+// BitField accepts multiple values:
+// - BitField("set", "i1", "offset1", "value1","cmd2", "type2", "offset2", "value2")
+// - BitField([]string{"cmd1", "type1", "offset1", "value1","cmd2", "type2", "offset2", "value2"})
+// - BitField([]interface{}{"cmd1", "type1", "offset1", "value1","cmd2", "type2", "offset2", "value2"})
+func (c cmdable) BitField(ctx context.Context, key string, values ...interface{}) *IntSliceCmd {
+ args := make([]interface{}, 2, 2+len(values))
+ args[0] = "bitfield"
+ args[1] = key
+ args = appendArgs(args, values)
+ cmd := NewIntSliceCmd(ctx, args...)
+ _ = c(ctx, cmd)
+ return cmd
+}
+
+// BitFieldRO - Read-only variant of the BITFIELD command.
+// It is like the original BITFIELD but only accepts GET subcommand and can safely be used in read-only replicas.
+// - BitFieldRO(ctx, key, "", "", "","")
+func (c cmdable) BitFieldRO(ctx context.Context, key string, values ...interface{}) *IntSliceCmd {
+ args := make([]interface{}, 2, 2+len(values))
+ args[0] = "BITFIELD_RO"
+ args[1] = key
+ if len(values)%2 != 0 {
+ c := NewIntSliceCmd(ctx)
+ c.SetErr(errors.New("BitFieldRO: invalid number of arguments, must be even"))
+ return c
+ }
+ for i := 0; i < len(values); i += 2 {
+ args = append(args, "GET", values[i], values[i+1])
+ }
+ cmd := NewIntSliceCmd(ctx, args...)
+ _ = c(ctx, cmd)
+ return cmd
+}
diff --git a/vendor/github.com/redis/go-redis/v9/cluster_commands.go b/vendor/github.com/redis/go-redis/v9/cluster_commands.go
index b13f8e7e9df..a02683f2075 100644
--- a/vendor/github.com/redis/go-redis/v9/cluster_commands.go
+++ b/vendor/github.com/redis/go-redis/v9/cluster_commands.go
@@ -1,109 +1,205 @@
package redis
-import (
- "context"
- "sync"
- "sync/atomic"
-)
-
-func (c *ClusterClient) DBSize(ctx context.Context) *IntCmd {
- cmd := NewIntCmd(ctx, "dbsize")
- _ = 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()
- if err != nil {
- return err
- }
- atomic.AddInt64(&size, n)
- return nil
- })
- if err != nil {
- cmd.SetErr(err)
- } else {
- cmd.val = size
- }
- return nil
- })
- return cmd
-}
-
-func (c *ClusterClient) ScriptLoad(ctx context.Context, script string) *StringCmd {
- cmd := NewStringCmd(ctx, "script", "load", script)
- _ = 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 {
- return err
- }
-
- mu.Lock()
- if cmd.Val() == "" {
- cmd.val = val
- }
- mu.Unlock()
-
- return nil
- })
- if err != nil {
- cmd.SetErr(err)
- }
- return nil
- })
- return cmd
-}
-
-func (c *ClusterClient) ScriptFlush(ctx context.Context) *StatusCmd {
- cmd := NewStatusCmd(ctx, "script", "flush")
- _ = 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()
- })
- if err != nil {
- cmd.SetErr(err)
- }
- return nil
- })
- return cmd
-}
-
-func (c *ClusterClient) ScriptExists(ctx context.Context, hashes ...string) *BoolSliceCmd {
- args := make([]interface{}, 2+len(hashes))
- args[0] = "script"
- args[1] = "exists"
- for i, hash := range hashes {
- args[2+i] = hash
+import "context"
+
+type ClusterCmdable interface {
+ ClusterMyShardID(ctx context.Context) *StringCmd
+ ClusterMyID(ctx context.Context) *StringCmd
+ ClusterSlots(ctx context.Context) *ClusterSlotsCmd
+ ClusterShards(ctx context.Context) *ClusterShardsCmd
+ ClusterLinks(ctx context.Context) *ClusterLinksCmd
+ ClusterNodes(ctx context.Context) *StringCmd
+ ClusterMeet(ctx context.Context, host, port string) *StatusCmd
+ ClusterForget(ctx context.Context, nodeID string) *StatusCmd
+ ClusterReplicate(ctx context.Context, nodeID string) *StatusCmd
+ ClusterResetSoft(ctx context.Context) *StatusCmd
+ ClusterResetHard(ctx context.Context) *StatusCmd
+ ClusterInfo(ctx context.Context) *StringCmd
+ ClusterKeySlot(ctx context.Context, key string) *IntCmd
+ ClusterGetKeysInSlot(ctx context.Context, slot int, count int) *StringSliceCmd
+ ClusterCountFailureReports(ctx context.Context, nodeID string) *IntCmd
+ ClusterCountKeysInSlot(ctx context.Context, slot int) *IntCmd
+ ClusterDelSlots(ctx context.Context, slots ...int) *StatusCmd
+ ClusterDelSlotsRange(ctx context.Context, min, max int) *StatusCmd
+ ClusterSaveConfig(ctx context.Context) *StatusCmd
+ ClusterSlaves(ctx context.Context, nodeID string) *StringSliceCmd
+ ClusterFailover(ctx context.Context) *StatusCmd
+ ClusterAddSlots(ctx context.Context, slots ...int) *StatusCmd
+ ClusterAddSlotsRange(ctx context.Context, min, max int) *StatusCmd
+ ReadOnly(ctx context.Context) *StatusCmd
+ ReadWrite(ctx context.Context) *StatusCmd
+}
+
+func (c cmdable) ClusterMyShardID(ctx context.Context) *StringCmd {
+ cmd := NewStringCmd(ctx, "cluster", "myshardid")
+ _ = c(ctx, cmd)
+ return cmd
+}
+
+func (c cmdable) ClusterMyID(ctx context.Context) *StringCmd {
+ cmd := NewStringCmd(ctx, "cluster", "myid")
+ _ = c(ctx, cmd)
+ return cmd
+}
+
+// ClusterSlots returns the mapping of cluster slots to nodes.
+//
+// Deprecated: Use ClusterShards instead as of Redis 7.0.0.
+func (c cmdable) ClusterSlots(ctx context.Context) *ClusterSlotsCmd {
+ cmd := NewClusterSlotsCmd(ctx, "cluster", "slots")
+ _ = c(ctx, cmd)
+ return cmd
+}
+
+func (c cmdable) ClusterShards(ctx context.Context) *ClusterShardsCmd {
+ cmd := NewClusterShardsCmd(ctx, "cluster", "shards")
+ _ = c(ctx, cmd)
+ return cmd
+}
+
+func (c cmdable) ClusterLinks(ctx context.Context) *ClusterLinksCmd {
+ cmd := NewClusterLinksCmd(ctx, "cluster", "links")
+ _ = c(ctx, cmd)
+ return cmd
+}
+
+func (c cmdable) ClusterNodes(ctx context.Context) *StringCmd {
+ cmd := NewStringCmd(ctx, "cluster", "nodes")
+ _ = c(ctx, cmd)
+ return cmd
+}
+
+func (c cmdable) ClusterMeet(ctx context.Context, host, port string) *StatusCmd {
+ cmd := NewStatusCmd(ctx, "cluster", "meet", host, port)
+ _ = c(ctx, cmd)
+ return cmd
+}
+
+func (c cmdable) ClusterForget(ctx context.Context, nodeID string) *StatusCmd {
+ cmd := NewStatusCmd(ctx, "cluster", "forget", nodeID)
+ _ = c(ctx, cmd)
+ return cmd
+}
+
+func (c cmdable) ClusterReplicate(ctx context.Context, nodeID string) *StatusCmd {
+ cmd := NewStatusCmd(ctx, "cluster", "replicate", nodeID)
+ _ = c(ctx, cmd)
+ return cmd
+}
+
+func (c cmdable) ClusterResetSoft(ctx context.Context) *StatusCmd {
+ cmd := NewStatusCmd(ctx, "cluster", "reset", "soft")
+ _ = c(ctx, cmd)
+ return cmd
+}
+
+func (c cmdable) ClusterResetHard(ctx context.Context) *StatusCmd {
+ cmd := NewStatusCmd(ctx, "cluster", "reset", "hard")
+ _ = c(ctx, cmd)
+ return cmd
+}
+
+func (c cmdable) ClusterInfo(ctx context.Context) *StringCmd {
+ cmd := NewStringCmd(ctx, "cluster", "info")
+ _ = c(ctx, cmd)
+ return cmd
+}
+
+func (c cmdable) ClusterKeySlot(ctx context.Context, key string) *IntCmd {
+ cmd := NewIntCmd(ctx, "cluster", "keyslot", key)
+ _ = c(ctx, cmd)
+ return cmd
+}
+
+func (c cmdable) ClusterGetKeysInSlot(ctx context.Context, slot int, count int) *StringSliceCmd {
+ cmd := NewStringSliceCmd(ctx, "cluster", "getkeysinslot", slot, count)
+ _ = c(ctx, cmd)
+ return cmd
+}
+
+func (c cmdable) ClusterCountFailureReports(ctx context.Context, nodeID string) *IntCmd {
+ cmd := NewIntCmd(ctx, "cluster", "count-failure-reports", nodeID)
+ _ = c(ctx, cmd)
+ return cmd
+}
+
+func (c cmdable) ClusterCountKeysInSlot(ctx context.Context, slot int) *IntCmd {
+ cmd := NewIntCmd(ctx, "cluster", "countkeysinslot", slot)
+ _ = c(ctx, cmd)
+ return cmd
+}
+
+func (c cmdable) ClusterDelSlots(ctx context.Context, slots ...int) *StatusCmd {
+ args := make([]interface{}, 2+len(slots))
+ args[0] = "cluster"
+ args[1] = "delslots"
+ for i, slot := range slots {
+ args[2+i] = slot
+ }
+ cmd := NewStatusCmd(ctx, args...)
+ _ = c(ctx, cmd)
+ return cmd
+}
+
+func (c cmdable) ClusterDelSlotsRange(ctx context.Context, min, max int) *StatusCmd {
+ size := max - min + 1
+ slots := make([]int, size)
+ for i := 0; i < size; i++ {
+ slots[i] = min + i
}
- cmd := NewBoolSliceCmd(ctx, args...)
+ return c.ClusterDelSlots(ctx, slots...)
+}
+
+func (c cmdable) ClusterSaveConfig(ctx context.Context) *StatusCmd {
+ cmd := NewStatusCmd(ctx, "cluster", "saveconfig")
+ _ = c(ctx, cmd)
+ return cmd
+}
+
+// ClusterSlaves lists the replica nodes of a master node.
+//
+// Deprecated: Use ClusterReplicas instead as of Redis 5.0.0.
+func (c cmdable) ClusterSlaves(ctx context.Context, nodeID string) *StringSliceCmd {
+ cmd := NewStringSliceCmd(ctx, "cluster", "slaves", nodeID)
+ _ = c(ctx, cmd)
+ return cmd
+}
+
+func (c cmdable) ClusterFailover(ctx context.Context) *StatusCmd {
+ cmd := NewStatusCmd(ctx, "cluster", "failover")
+ _ = c(ctx, cmd)
+ return cmd
+}
+
+func (c cmdable) ClusterAddSlots(ctx context.Context, slots ...int) *StatusCmd {
+ args := make([]interface{}, 2+len(slots))
+ args[0] = "cluster"
+ args[1] = "addslots"
+ for i, num := range slots {
+ args[2+i] = num
+ }
+ cmd := NewStatusCmd(ctx, args...)
+ _ = c(ctx, cmd)
+ return cmd
+}
- result := make([]bool, len(hashes))
- for i := range result {
- result[i] = true
+func (c cmdable) ClusterAddSlotsRange(ctx context.Context, min, max int) *StatusCmd {
+ size := max - min + 1
+ slots := make([]int, size)
+ for i := 0; i < size; i++ {
+ slots[i] = min + i
}
+ return c.ClusterAddSlots(ctx, slots...)
+}
+
+func (c cmdable) ReadOnly(ctx context.Context) *StatusCmd {
+ cmd := NewStatusCmd(ctx, "readonly")
+ _ = c(ctx, cmd)
+ return cmd
+}
- _ = 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()
- if err != nil {
- return err
- }
-
- mu.Lock()
- for i, v := range val {
- result[i] = result[i] && v
- }
- mu.Unlock()
-
- return nil
- })
- if err != nil {
- cmd.SetErr(err)
- } else {
- cmd.val = result
- }
- return nil
- })
+func (c cmdable) ReadWrite(ctx context.Context) *StatusCmd {
+ cmd := NewStatusCmd(ctx, "readwrite")
+ _ = c(ctx, cmd)
return cmd
}
diff --git a/vendor/github.com/redis/go-redis/v9/command.go b/vendor/github.com/redis/go-redis/v9/command.go
index 1bd4d5db104..a2a2f05196e 100644
--- a/vendor/github.com/redis/go-redis/v9/command.go
+++ b/vendor/github.com/redis/go-redis/v9/command.go
@@ -1,33 +1,221 @@
package redis
import (
+ "bufio"
"context"
"fmt"
+ "maps"
"net"
+ "regexp"
"strconv"
"strings"
+ "sync"
"time"
"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/routing"
"github.com/redis/go-redis/v9/internal/util"
)
+// keylessCommands contains Redis commands that have empty key specifications (9th slot empty)
+// Only includes core Redis commands, excludes FT.*, ts.*, timeseries.*, search.* and subcommands
+var keylessCommands = map[string]struct{}{
+ "acl": {},
+ "asking": {},
+ "auth": {},
+ "bgrewriteaof": {},
+ "bgsave": {},
+ "client": {},
+ "cluster": {},
+ "config": {},
+ "debug": {},
+ "discard": {},
+ "echo": {},
+ "exec": {},
+ "failover": {},
+ "function": {},
+ "hello": {},
+ "hotkeys": {},
+ "latency": {},
+ "lolwut": {},
+ "module": {},
+ "monitor": {},
+ "multi": {},
+ "pfselftest": {},
+ "ping": {},
+ "psubscribe": {},
+ "psync": {},
+ "publish": {},
+ "pubsub": {},
+ "punsubscribe": {},
+ "quit": {},
+ "readonly": {},
+ "readwrite": {},
+ "replconf": {},
+ "replicaof": {},
+ "role": {},
+ "save": {},
+ "script": {},
+ "select": {},
+ "shutdown": {},
+ "slaveof": {},
+ "slowlog": {},
+ "subscribe": {},
+ "swapdb": {},
+ "sync": {},
+ "unsubscribe": {},
+ "unwatch": {},
+ "wait": {},
+}
+
+// CmdTyper interface for getting command type
+type CmdTyper interface {
+ GetCmdType() CmdType
+}
+
+// CmdTypeGetter interface for getting command type without circular imports
+type CmdTypeGetter interface {
+ GetCmdType() CmdType
+}
+
+type CmdType uint8
+
+const (
+ CmdTypeGeneric CmdType = iota
+ CmdTypeString
+ CmdTypeInt
+ CmdTypeBool
+ CmdTypeFloat
+ CmdTypeStringSlice
+ CmdTypeIntSlice
+ CmdTypeFloatSlice
+ CmdTypeBoolSlice
+ CmdTypeMapStringString
+ CmdTypeMapStringInt
+ CmdTypeMapStringInterface
+ CmdTypeMapStringInterfaceSlice
+ CmdTypeSlice
+ CmdTypeStatus
+ CmdTypeDuration
+ CmdTypeTime
+ CmdTypeKeyValueSlice
+ CmdTypeStringStructMap
+ CmdTypeXMessageSlice
+ CmdTypeXStreamSlice
+ CmdTypeXPending
+ CmdTypeXPendingExt
+ CmdTypeXAutoClaim
+ CmdTypeXAutoClaimJustID
+ CmdTypeXInfoConsumers
+ CmdTypeXInfoGroups
+ CmdTypeXInfoStream
+ CmdTypeXInfoStreamFull
+ CmdTypeZSlice
+ CmdTypeZWithKey
+ CmdTypeScan
+ CmdTypeClusterSlots
+ CmdTypeGeoLocation
+ CmdTypeGeoSearchLocation
+ CmdTypeGeoPos
+ CmdTypeCommandsInfo
+ CmdTypeSlowLog
+ CmdTypeMapStringStringSlice
+ CmdTypeMapMapStringInterface
+ CmdTypeKeyValues
+ CmdTypeZSliceWithKey
+ CmdTypeFunctionList
+ CmdTypeFunctionStats
+ CmdTypeLCS
+ CmdTypeKeyFlags
+ CmdTypeClusterLinks
+ CmdTypeClusterShards
+ CmdTypeRankWithScore
+ CmdTypeClientInfo
+ CmdTypeACLLog
+ CmdTypeInfo
+ CmdTypeMonitor
+ CmdTypeJSON
+ CmdTypeJSONSlice
+ CmdTypeIntPointerSlice
+ CmdTypeScanDump
+ CmdTypeBFInfo
+ CmdTypeCFInfo
+ CmdTypeCMSInfo
+ CmdTypeTopKInfo
+ CmdTypeTDigestInfo
+ CmdTypeFTSynDump
+ CmdTypeAggregate
+ CmdTypeFTInfo
+ CmdTypeFTSpellCheck
+ CmdTypeFTSearch
+ CmdTypeTSTimestampValue
+ CmdTypeTSTimestampValueSlice
+ CmdTypeHotKeys
+)
+
+type (
+ CmdTypeXAutoClaimValue struct {
+ messages []XMessage
+ start string
+ }
+
+ CmdTypeXAutoClaimJustIDValue struct {
+ ids []string
+ start string
+ }
+
+ CmdTypeScanValue struct {
+ keys []string
+ cursor uint64
+ }
+
+ CmdTypeKeyValuesValue struct {
+ key string
+ values []string
+ }
+
+ CmdTypeZSliceWithKeyValue struct {
+ key string
+ zSlice []Z
+ }
+)
+
type Cmder interface {
+ // command name.
+ // e.g. "set k v ex 10" -> "set", "cluster info" -> "cluster".
Name() string
+
+ // full command name.
+ // e.g. "set k v ex 10" -> "set", "cluster info" -> "cluster info".
FullName() string
+
+ // all args of the command.
+ // e.g. "set k v ex 10" -> "[set k v ex 10]".
Args() []interface{}
+
+ // format request and response string.
+ // e.g. "set k v ex 10" -> "set k v ex 10: OK", "get k" -> "get k: v".
String() string
+
+ // Clone creates a copy of the command.
+ Clone() Cmder
+
stringArg(int) string
firstKeyPos() int8
SetFirstKeyPos(int8)
+ stepCount() int8
+ SetStepCount(int8)
readTimeout() *time.Duration
readReply(rd *proto.Reader) error
-
+ readRawReply(rd *proto.Reader) error
SetErr(error)
Err() error
+
+ // GetCmdType returns the command type for fast value extraction
+ GetCmdType() CmdType
}
func setCmdsErr(cmds []Cmder, e error) {
@@ -60,12 +248,22 @@ func writeCmd(wr *proto.Writer, cmd Cmder) error {
return wr.WriteArgs(cmd.Args())
}
-func cmdFirstKeyPos(cmd Cmder, info *CommandInfo) int {
+// cmdFirstKeyPos returns the position of the first key in the command's arguments.
+// If the command does not have a key, it returns 0.
+// TODO: Use the data in CommandInfo to determine the first key position.
+func cmdFirstKeyPos(cmd Cmder) int {
if pos := cmd.firstKeyPos(); pos != 0 {
return int(pos)
}
- switch cmd.Name() {
+ name := cmd.Name()
+
+ // first check if the command is keyless
+ if _, ok := keylessCommands[name]; ok {
+ return 0
+ }
+
+ switch name {
case "eval", "evalsha", "eval_ro", "evalsha_ro":
if cmd.stringArg(2) != "0" {
return 3
@@ -80,10 +278,6 @@ func cmdFirstKeyPos(cmd Cmder, info *CommandInfo) int {
return 2
}
}
-
- if info != nil {
- return int(info.FirstKeyPos)
- }
return 1
}
@@ -111,12 +305,14 @@ func cmdString(cmd Cmder, val interface{}) string {
//------------------------------------------------------------------------------
type baseCmd struct {
- ctx context.Context
- args []interface{}
- err error
- keyPos int8
-
+ ctx context.Context
+ args []interface{}
+ err error
+ keyPos int8
+ _stepCount int8
+ rawVal interface{}
_readTimeout *time.Duration
+ cmdType CmdType
}
var _ Cmder = (*Cmd)(nil)
@@ -156,6 +352,8 @@ func (cmd *baseCmd) stringArg(pos int) string {
switch v := arg.(type) {
case string:
return v
+ case []byte:
+ return string(v)
default:
// TODO: consider using appendArg
return fmt.Sprint(v)
@@ -170,6 +368,14 @@ func (cmd *baseCmd) SetFirstKeyPos(keyPos int8) {
cmd.keyPos = keyPos
}
+func (cmd *baseCmd) stepCount() int8 {
+ return cmd._stepCount
+}
+
+func (cmd *baseCmd) SetStepCount(stepCount int8) {
+ cmd._stepCount = stepCount
+}
+
func (cmd *baseCmd) SetErr(e error) {
cmd.err = e
}
@@ -186,6 +392,38 @@ func (cmd *baseCmd) setReadTimeout(d time.Duration) {
cmd._readTimeout = &d
}
+func (cmd *baseCmd) readRawReply(rd *proto.Reader) (err error) {
+ cmd.rawVal, err = rd.ReadReply()
+ return err
+}
+
+func (cmd *baseCmd) GetCmdType() CmdType {
+ return cmd.cmdType
+}
+
+func (cmd *baseCmd) cloneBaseCmd() baseCmd {
+ var readTimeout *time.Duration
+ if cmd._readTimeout != nil {
+ timeout := *cmd._readTimeout
+ readTimeout = &timeout
+ }
+
+ // Create a copy of args slice
+ args := make([]interface{}, len(cmd.args))
+ copy(args, cmd.args)
+
+ return baseCmd{
+ ctx: cmd.ctx,
+ args: args,
+ err: cmd.err,
+ keyPos: cmd.keyPos,
+ _stepCount: cmd._stepCount,
+ rawVal: cmd.rawVal,
+ _readTimeout: readTimeout,
+ cmdType: cmd.cmdType,
+ }
+}
+
//------------------------------------------------------------------------------
type Cmd struct {
@@ -197,8 +435,9 @@ type Cmd struct {
func NewCmd(ctx context.Context, args ...interface{}) *Cmd {
return &Cmd{
baseCmd: baseCmd{
- ctx: ctx,
- args: args,
+ ctx: ctx,
+ args: args,
+ cmdType: CmdTypeGeneric,
},
}
}
@@ -471,6 +710,13 @@ func (cmd *Cmd) readReply(rd *proto.Reader) (err error) {
return err
}
+func (cmd *Cmd) Clone() Cmder {
+ return &Cmd{
+ baseCmd: cmd.cloneBaseCmd(),
+ val: cmd.val,
+ }
+}
+
//------------------------------------------------------------------------------
type SliceCmd struct {
@@ -484,8 +730,9 @@ var _ Cmder = (*SliceCmd)(nil)
func NewSliceCmd(ctx context.Context, args ...interface{}) *SliceCmd {
return &SliceCmd{
baseCmd: baseCmd{
- ctx: ctx,
- args: args,
+ ctx: ctx,
+ args: args,
+ cmdType: CmdTypeSlice,
},
}
}
@@ -531,6 +778,18 @@ func (cmd *SliceCmd) readReply(rd *proto.Reader) (err error) {
return err
}
+func (cmd *SliceCmd) Clone() Cmder {
+ var val []interface{}
+ if cmd.val != nil {
+ val = make([]interface{}, len(cmd.val))
+ copy(val, cmd.val)
+ }
+ return &SliceCmd{
+ baseCmd: cmd.cloneBaseCmd(),
+ val: val,
+ }
+}
+
//------------------------------------------------------------------------------
type StatusCmd struct {
@@ -544,8 +803,9 @@ var _ Cmder = (*StatusCmd)(nil)
func NewStatusCmd(ctx context.Context, args ...interface{}) *StatusCmd {
return &StatusCmd{
baseCmd: baseCmd{
- ctx: ctx,
- args: args,
+ ctx: ctx,
+ args: args,
+ cmdType: CmdTypeStatus,
},
}
}
@@ -562,6 +822,10 @@ func (cmd *StatusCmd) Result() (string, error) {
return cmd.val, cmd.err
}
+func (cmd *StatusCmd) Bytes() ([]byte, error) {
+ return util.StringToBytes(cmd.val), cmd.err
+}
+
func (cmd *StatusCmd) String() string {
return cmdString(cmd, cmd.val)
}
@@ -571,6 +835,13 @@ func (cmd *StatusCmd) readReply(rd *proto.Reader) (err error) {
return err
}
+func (cmd *StatusCmd) Clone() Cmder {
+ return &StatusCmd{
+ baseCmd: cmd.cloneBaseCmd(),
+ val: cmd.val,
+ }
+}
+
//------------------------------------------------------------------------------
type IntCmd struct {
@@ -584,8 +855,9 @@ var _ Cmder = (*IntCmd)(nil)
func NewIntCmd(ctx context.Context, args ...interface{}) *IntCmd {
return &IntCmd{
baseCmd: baseCmd{
- ctx: ctx,
- args: args,
+ ctx: ctx,
+ args: args,
+ cmdType: CmdTypeInt,
},
}
}
@@ -615,6 +887,82 @@ func (cmd *IntCmd) readReply(rd *proto.Reader) (err error) {
return err
}
+func (cmd *IntCmd) Clone() Cmder {
+ return &IntCmd{
+ baseCmd: cmd.cloneBaseCmd(),
+ val: cmd.val,
+ }
+}
+
+//------------------------------------------------------------------------------
+
+// DigestCmd is a command that returns a uint64 xxh3 hash digest.
+//
+// This command is specifically designed for the Redis DIGEST command,
+// which returns the xxh3 hash of a key's value as a hex string.
+// The hex string is automatically parsed to a uint64 value.
+//
+// The digest can be used for optimistic locking with SetIFDEQ, SetIFDNE,
+// and DelExArgs commands.
+//
+// For examples of client-side digest generation and usage patterns, see:
+// example/digest-optimistic-locking/
+//
+// Redis 8.4+. See https://redis.io/commands/digest/
+type DigestCmd struct {
+ baseCmd
+
+ val uint64
+}
+
+var _ Cmder = (*DigestCmd)(nil)
+
+func NewDigestCmd(ctx context.Context, args ...interface{}) *DigestCmd {
+ return &DigestCmd{
+ baseCmd: baseCmd{
+ ctx: ctx,
+ args: args,
+ },
+ }
+}
+
+func (cmd *DigestCmd) SetVal(val uint64) {
+ cmd.val = val
+}
+
+func (cmd *DigestCmd) Val() uint64 {
+ return cmd.val
+}
+
+func (cmd *DigestCmd) Result() (uint64, error) {
+ return cmd.val, cmd.err
+}
+
+func (cmd *DigestCmd) String() string {
+ return cmdString(cmd, cmd.val)
+}
+
+func (cmd *DigestCmd) Clone() Cmder {
+ return &DigestCmd{
+ baseCmd: cmd.cloneBaseCmd(),
+ val: cmd.val,
+ }
+}
+
+func (cmd *DigestCmd) readReply(rd *proto.Reader) (err error) {
+ // Redis DIGEST command returns a hex string (e.g., "a1b2c3d4e5f67890")
+ // We parse it as a uint64 xxh3 hash value
+ var hexStr string
+ hexStr, err = rd.ReadString()
+ if err != nil {
+ return err
+ }
+
+ // Parse hex string to uint64
+ cmd.val, err = strconv.ParseUint(hexStr, 16, 64)
+ return err
+}
+
//------------------------------------------------------------------------------
type IntSliceCmd struct {
@@ -628,8 +976,9 @@ var _ Cmder = (*IntSliceCmd)(nil)
func NewIntSliceCmd(ctx context.Context, args ...interface{}) *IntSliceCmd {
return &IntSliceCmd{
baseCmd: baseCmd{
- ctx: ctx,
- args: args,
+ ctx: ctx,
+ args: args,
+ cmdType: CmdTypeIntSlice,
},
}
}
@@ -664,6 +1013,18 @@ func (cmd *IntSliceCmd) readReply(rd *proto.Reader) error {
return nil
}
+func (cmd *IntSliceCmd) Clone() Cmder {
+ var val []int64
+ if cmd.val != nil {
+ val = make([]int64, len(cmd.val))
+ copy(val, cmd.val)
+ }
+ return &IntSliceCmd{
+ baseCmd: cmd.cloneBaseCmd(),
+ val: val,
+ }
+}
+
//------------------------------------------------------------------------------
type DurationCmd struct {
@@ -678,8 +1039,9 @@ var _ Cmder = (*DurationCmd)(nil)
func NewDurationCmd(ctx context.Context, precision time.Duration, args ...interface{}) *DurationCmd {
return &DurationCmd{
baseCmd: baseCmd{
- ctx: ctx,
- args: args,
+ ctx: ctx,
+ args: args,
+ cmdType: CmdTypeDuration,
},
precision: precision,
}
@@ -717,6 +1079,14 @@ func (cmd *DurationCmd) readReply(rd *proto.Reader) error {
return nil
}
+func (cmd *DurationCmd) Clone() Cmder {
+ return &DurationCmd{
+ baseCmd: cmd.cloneBaseCmd(),
+ val: cmd.val,
+ precision: cmd.precision,
+ }
+}
+
//------------------------------------------------------------------------------
type TimeCmd struct {
@@ -730,8 +1100,9 @@ var _ Cmder = (*TimeCmd)(nil)
func NewTimeCmd(ctx context.Context, args ...interface{}) *TimeCmd {
return &TimeCmd{
baseCmd: baseCmd{
- ctx: ctx,
- args: args,
+ ctx: ctx,
+ args: args,
+ cmdType: CmdTypeTime,
},
}
}
@@ -768,6 +1139,13 @@ func (cmd *TimeCmd) readReply(rd *proto.Reader) error {
return nil
}
+func (cmd *TimeCmd) Clone() Cmder {
+ return &TimeCmd{
+ baseCmd: cmd.cloneBaseCmd(),
+ val: cmd.val,
+ }
+}
+
//------------------------------------------------------------------------------
type BoolCmd struct {
@@ -781,8 +1159,9 @@ var _ Cmder = (*BoolCmd)(nil)
func NewBoolCmd(ctx context.Context, args ...interface{}) *BoolCmd {
return &BoolCmd{
baseCmd: baseCmd{
- ctx: ctx,
- args: args,
+ ctx: ctx,
+ args: args,
+ cmdType: CmdTypeBool,
},
}
}
@@ -815,6 +1194,13 @@ func (cmd *BoolCmd) readReply(rd *proto.Reader) (err error) {
return err
}
+func (cmd *BoolCmd) Clone() Cmder {
+ return &BoolCmd{
+ baseCmd: cmd.cloneBaseCmd(),
+ val: cmd.val,
+ }
+}
+
//------------------------------------------------------------------------------
type StringCmd struct {
@@ -828,8 +1214,9 @@ var _ Cmder = (*StringCmd)(nil)
func NewStringCmd(ctx context.Context, args ...interface{}) *StringCmd {
return &StringCmd{
baseCmd: baseCmd{
- ctx: ctx,
- args: args,
+ ctx: ctx,
+ args: args,
+ cmdType: CmdTypeString,
},
}
}
@@ -843,7 +1230,7 @@ func (cmd *StringCmd) Val() string {
}
func (cmd *StringCmd) Result() (string, error) {
- return cmd.Val(), cmd.err
+ return cmd.val, cmd.err
}
func (cmd *StringCmd) Bytes() ([]byte, error) {
@@ -861,28 +1248,28 @@ func (cmd *StringCmd) Int() (int, error) {
if cmd.err != nil {
return 0, cmd.err
}
- return strconv.Atoi(cmd.Val())
+ return strconv.Atoi(cmd.val)
}
func (cmd *StringCmd) Int64() (int64, error) {
if cmd.err != nil {
return 0, cmd.err
}
- return strconv.ParseInt(cmd.Val(), 10, 64)
+ return strconv.ParseInt(cmd.val, 10, 64)
}
func (cmd *StringCmd) Uint64() (uint64, error) {
if cmd.err != nil {
return 0, cmd.err
}
- return strconv.ParseUint(cmd.Val(), 10, 64)
+ return strconv.ParseUint(cmd.val, 10, 64)
}
func (cmd *StringCmd) Float32() (float32, error) {
if cmd.err != nil {
return 0, cmd.err
}
- f, err := strconv.ParseFloat(cmd.Val(), 32)
+ f, err := strconv.ParseFloat(cmd.val, 32)
if err != nil {
return 0, err
}
@@ -893,14 +1280,14 @@ func (cmd *StringCmd) Float64() (float64, error) {
if cmd.err != nil {
return 0, cmd.err
}
- return strconv.ParseFloat(cmd.Val(), 64)
+ return strconv.ParseFloat(cmd.val, 64)
}
func (cmd *StringCmd) Time() (time.Time, error) {
if cmd.err != nil {
return time.Time{}, cmd.err
}
- return time.Parse(time.RFC3339Nano, cmd.Val())
+ return time.Parse(time.RFC3339Nano, cmd.val)
}
func (cmd *StringCmd) Scan(val interface{}) error {
@@ -919,6 +1306,13 @@ func (cmd *StringCmd) readReply(rd *proto.Reader) (err error) {
return err
}
+func (cmd *StringCmd) Clone() Cmder {
+ return &StringCmd{
+ baseCmd: cmd.cloneBaseCmd(),
+ val: cmd.val,
+ }
+}
+
//------------------------------------------------------------------------------
type FloatCmd struct {
@@ -932,8 +1326,9 @@ var _ Cmder = (*FloatCmd)(nil)
func NewFloatCmd(ctx context.Context, args ...interface{}) *FloatCmd {
return &FloatCmd{
baseCmd: baseCmd{
- ctx: ctx,
- args: args,
+ ctx: ctx,
+ args: args,
+ cmdType: CmdTypeFloat,
},
}
}
@@ -947,7 +1342,7 @@ func (cmd *FloatCmd) Val() float64 {
}
func (cmd *FloatCmd) Result() (float64, error) {
- return cmd.Val(), cmd.Err()
+ return cmd.val, cmd.err
}
func (cmd *FloatCmd) String() string {
@@ -959,6 +1354,13 @@ func (cmd *FloatCmd) readReply(rd *proto.Reader) (err error) {
return err
}
+func (cmd *FloatCmd) Clone() Cmder {
+ return &FloatCmd{
+ baseCmd: cmd.cloneBaseCmd(),
+ val: cmd.val,
+ }
+}
+
//------------------------------------------------------------------------------
type FloatSliceCmd struct {
@@ -972,8 +1374,9 @@ var _ Cmder = (*FloatSliceCmd)(nil)
func NewFloatSliceCmd(ctx context.Context, args ...interface{}) *FloatSliceCmd {
return &FloatSliceCmd{
baseCmd: baseCmd{
- ctx: ctx,
- args: args,
+ ctx: ctx,
+ args: args,
+ cmdType: CmdTypeFloatSlice,
},
}
}
@@ -1014,6 +1417,18 @@ func (cmd *FloatSliceCmd) readReply(rd *proto.Reader) error {
return nil
}
+func (cmd *FloatSliceCmd) Clone() Cmder {
+ var val []float64
+ if cmd.val != nil {
+ val = make([]float64, len(cmd.val))
+ copy(val, cmd.val)
+ }
+ return &FloatSliceCmd{
+ baseCmd: cmd.cloneBaseCmd(),
+ val: val,
+ }
+}
+
//------------------------------------------------------------------------------
type StringSliceCmd struct {
@@ -1027,8 +1442,9 @@ var _ Cmder = (*StringSliceCmd)(nil)
func NewStringSliceCmd(ctx context.Context, args ...interface{}) *StringSliceCmd {
return &StringSliceCmd{
baseCmd: baseCmd{
- ctx: ctx,
- args: args,
+ ctx: ctx,
+ args: args,
+ cmdType: CmdTypeStringSlice,
},
}
}
@@ -1042,7 +1458,7 @@ func (cmd *StringSliceCmd) Val() []string {
}
func (cmd *StringSliceCmd) Result() ([]string, error) {
- return cmd.Val(), cmd.Err()
+ return cmd.val, cmd.err
}
func (cmd *StringSliceCmd) String() string {
@@ -1050,7 +1466,7 @@ func (cmd *StringSliceCmd) String() string {
}
func (cmd *StringSliceCmd) ScanSlice(container interface{}) error {
- return proto.ScanSlice(cmd.Val(), container)
+ return proto.ScanSlice(cmd.val, container)
}
func (cmd *StringSliceCmd) readReply(rd *proto.Reader) error {
@@ -1072,6 +1488,18 @@ func (cmd *StringSliceCmd) readReply(rd *proto.Reader) error {
return nil
}
+func (cmd *StringSliceCmd) Clone() Cmder {
+ var val []string
+ if cmd.val != nil {
+ val = make([]string, len(cmd.val))
+ copy(val, cmd.val)
+ }
+ return &StringSliceCmd{
+ baseCmd: cmd.cloneBaseCmd(),
+ val: val,
+ }
+}
+
//------------------------------------------------------------------------------
type KeyValue struct {
@@ -1090,8 +1518,9 @@ var _ Cmder = (*KeyValueSliceCmd)(nil)
func NewKeyValueSliceCmd(ctx context.Context, args ...interface{}) *KeyValueSliceCmd {
return &KeyValueSliceCmd{
baseCmd: baseCmd{
- ctx: ctx,
- args: args,
+ ctx: ctx,
+ args: args,
+ cmdType: CmdTypeKeyValueSlice,
},
}
}
@@ -1166,6 +1595,18 @@ func (cmd *KeyValueSliceCmd) readReply(rd *proto.Reader) error { // nolint:dupl
return nil
}
+func (cmd *KeyValueSliceCmd) Clone() Cmder {
+ var val []KeyValue
+ if cmd.val != nil {
+ val = make([]KeyValue, len(cmd.val))
+ copy(val, cmd.val)
+ }
+ return &KeyValueSliceCmd{
+ baseCmd: cmd.cloneBaseCmd(),
+ val: val,
+ }
+}
+
//------------------------------------------------------------------------------
type BoolSliceCmd struct {
@@ -1179,8 +1620,9 @@ var _ Cmder = (*BoolSliceCmd)(nil)
func NewBoolSliceCmd(ctx context.Context, args ...interface{}) *BoolSliceCmd {
return &BoolSliceCmd{
baseCmd: baseCmd{
- ctx: ctx,
- args: args,
+ ctx: ctx,
+ args: args,
+ cmdType: CmdTypeBoolSlice,
},
}
}
@@ -1215,6 +1657,18 @@ func (cmd *BoolSliceCmd) readReply(rd *proto.Reader) error {
return nil
}
+func (cmd *BoolSliceCmd) Clone() Cmder {
+ var val []bool
+ if cmd.val != nil {
+ val = make([]bool, len(cmd.val))
+ copy(val, cmd.val)
+ }
+ return &BoolSliceCmd{
+ baseCmd: cmd.cloneBaseCmd(),
+ val: val,
+ }
+}
+
//------------------------------------------------------------------------------
type MapStringStringCmd struct {
@@ -1228,8 +1682,9 @@ var _ Cmder = (*MapStringStringCmd)(nil)
func NewMapStringStringCmd(ctx context.Context, args ...interface{}) *MapStringStringCmd {
return &MapStringStringCmd{
baseCmd: baseCmd{
- ctx: ctx,
- args: args,
+ ctx: ctx,
+ args: args,
+ cmdType: CmdTypeMapStringString,
},
}
}
@@ -1294,6 +1749,20 @@ func (cmd *MapStringStringCmd) readReply(rd *proto.Reader) error {
return nil
}
+func (cmd *MapStringStringCmd) Clone() Cmder {
+ var val map[string]string
+ if cmd.val != nil {
+ val = make(map[string]string, len(cmd.val))
+ for k, v := range cmd.val {
+ val[k] = v
+ }
+ }
+ return &MapStringStringCmd{
+ baseCmd: cmd.cloneBaseCmd(),
+ val: val,
+ }
+}
+
//------------------------------------------------------------------------------
type MapStringIntCmd struct {
@@ -1307,8 +1776,9 @@ var _ Cmder = (*MapStringIntCmd)(nil)
func NewMapStringIntCmd(ctx context.Context, args ...interface{}) *MapStringIntCmd {
return &MapStringIntCmd{
baseCmd: baseCmd{
- ctx: ctx,
- args: args,
+ ctx: ctx,
+ args: args,
+ cmdType: CmdTypeMapStringInt,
},
}
}
@@ -1351,63 +1821,210 @@ func (cmd *MapStringIntCmd) readReply(rd *proto.Reader) error {
return nil
}
-//------------------------------------------------------------------------------
+func (cmd *MapStringIntCmd) Clone() Cmder {
+ var val map[string]int64
+ if cmd.val != nil {
+ val = make(map[string]int64, len(cmd.val))
+ for k, v := range cmd.val {
+ val[k] = v
+ }
+ }
+ return &MapStringIntCmd{
+ baseCmd: cmd.cloneBaseCmd(),
+ val: val,
+ }
+}
-type StringStructMapCmd struct {
+// ------------------------------------------------------------------------------
+type MapStringSliceInterfaceCmd struct {
baseCmd
-
- val map[string]struct{}
+ val map[string][]interface{}
}
-var _ Cmder = (*StringStructMapCmd)(nil)
-
-func NewStringStructMapCmd(ctx context.Context, args ...interface{}) *StringStructMapCmd {
- return &StringStructMapCmd{
+func NewMapStringSliceInterfaceCmd(ctx context.Context, args ...interface{}) *MapStringSliceInterfaceCmd {
+ return &MapStringSliceInterfaceCmd{
baseCmd: baseCmd{
- ctx: ctx,
- args: args,
+ ctx: ctx,
+ args: args,
+ cmdType: CmdTypeMapStringInterfaceSlice,
},
}
}
-func (cmd *StringStructMapCmd) SetVal(val map[string]struct{}) {
- cmd.val = val
+func (cmd *MapStringSliceInterfaceCmd) String() string {
+ return cmdString(cmd, cmd.val)
}
-func (cmd *StringStructMapCmd) Val() map[string]struct{} {
- return cmd.val
+func (cmd *MapStringSliceInterfaceCmd) SetVal(val map[string][]interface{}) {
+ cmd.val = val
}
-func (cmd *StringStructMapCmd) Result() (map[string]struct{}, error) {
+func (cmd *MapStringSliceInterfaceCmd) Result() (map[string][]interface{}, error) {
return cmd.val, cmd.err
}
-func (cmd *StringStructMapCmd) String() string {
- return cmdString(cmd, cmd.val)
+func (cmd *MapStringSliceInterfaceCmd) Val() map[string][]interface{} {
+ return cmd.val
}
-func (cmd *StringStructMapCmd) readReply(rd *proto.Reader) error {
- n, err := rd.ReadArrayLen()
+func (cmd *MapStringSliceInterfaceCmd) readReply(rd *proto.Reader) (err error) {
+ readType, err := rd.PeekReplyType()
if err != nil {
return err
}
- cmd.val = make(map[string]struct{}, n)
- for i := 0; i < n; i++ {
- key, err := rd.ReadString()
+ cmd.val = make(map[string][]interface{})
+
+ switch readType {
+ case proto.RespMap:
+ n, err := rd.ReadMapLen()
if err != nil {
return err
}
- cmd.val[key] = struct{}{}
+ for i := 0; i < n; i++ {
+ k, err := rd.ReadString()
+ if err != nil {
+ return err
+ }
+ nn, err := rd.ReadArrayLen()
+ if err != nil {
+ return err
+ }
+ cmd.val[k] = make([]interface{}, nn)
+ for j := 0; j < nn; j++ {
+ value, err := rd.ReadReply()
+ if err != nil {
+ return err
+ }
+ cmd.val[k][j] = value
+ }
+ }
+ case proto.RespArray:
+ // RESP2 response
+ n, err := rd.ReadArrayLen()
+ if err != nil {
+ return err
+ }
+
+ for i := 0; i < n; i++ {
+ // Each entry in this array is itself an array with key details
+ itemLen, err := rd.ReadArrayLen()
+ if err != nil {
+ return err
+ }
+
+ key, err := rd.ReadString()
+ if err != nil {
+ return err
+ }
+ cmd.val[key] = make([]interface{}, 0, itemLen-1)
+ for j := 1; j < itemLen; j++ {
+ // Read the inner array for timestamp-value pairs
+ data, err := rd.ReadReply()
+ if err != nil {
+ return err
+ }
+ cmd.val[key] = append(cmd.val[key], data)
+ }
+ }
}
+
return nil
}
-//------------------------------------------------------------------------------
-
-type XMessage struct {
+func (cmd *MapStringSliceInterfaceCmd) Clone() Cmder {
+ var val map[string][]interface{}
+ if cmd.val != nil {
+ val = make(map[string][]interface{}, len(cmd.val))
+ for k, v := range cmd.val {
+ if v != nil {
+ newSlice := make([]interface{}, len(v))
+ copy(newSlice, v)
+ val[k] = newSlice
+ }
+ }
+ }
+ return &MapStringSliceInterfaceCmd{
+ baseCmd: cmd.cloneBaseCmd(),
+ val: val,
+ }
+}
+
+//------------------------------------------------------------------------------
+
+type StringStructMapCmd struct {
+ baseCmd
+
+ val map[string]struct{}
+}
+
+var _ Cmder = (*StringStructMapCmd)(nil)
+
+func NewStringStructMapCmd(ctx context.Context, args ...interface{}) *StringStructMapCmd {
+ return &StringStructMapCmd{
+ baseCmd: baseCmd{
+ ctx: ctx,
+ args: args,
+ cmdType: CmdTypeStringStructMap,
+ },
+ }
+}
+
+func (cmd *StringStructMapCmd) SetVal(val map[string]struct{}) {
+ cmd.val = val
+}
+
+func (cmd *StringStructMapCmd) Val() map[string]struct{} {
+ return cmd.val
+}
+
+func (cmd *StringStructMapCmd) Result() (map[string]struct{}, error) {
+ return cmd.val, cmd.err
+}
+
+func (cmd *StringStructMapCmd) String() string {
+ return cmdString(cmd, cmd.val)
+}
+
+func (cmd *StringStructMapCmd) readReply(rd *proto.Reader) error {
+ n, err := rd.ReadArrayLen()
+ if err != nil {
+ return err
+ }
+
+ cmd.val = make(map[string]struct{}, n)
+ for i := 0; i < n; i++ {
+ key, err := rd.ReadString()
+ if err != nil {
+ return err
+ }
+ cmd.val[key] = struct{}{}
+ }
+ return nil
+}
+
+func (cmd *StringStructMapCmd) Clone() Cmder {
+ var val map[string]struct{}
+ if cmd.val != nil {
+ val = maps.Clone(cmd.val)
+ }
+ return &StringStructMapCmd{
+ baseCmd: cmd.cloneBaseCmd(),
+ val: val,
+ }
+}
+
+//------------------------------------------------------------------------------
+
+type XMessage struct {
ID string
Values map[string]interface{}
+ // MillisElapsedFromDelivery is the number of milliseconds since the entry was last delivered.
+ // Only populated when using XREADGROUP with CLAIM argument for claimed entries.
+ MillisElapsedFromDelivery int64
+ // DeliveredCount is the number of times the entry was delivered.
+ // Only populated when using XREADGROUP with CLAIM argument for claimed entries.
+ DeliveredCount int64
}
type XMessageSliceCmd struct {
@@ -1421,8 +2038,9 @@ var _ Cmder = (*XMessageSliceCmd)(nil)
func NewXMessageSliceCmd(ctx context.Context, args ...interface{}) *XMessageSliceCmd {
return &XMessageSliceCmd{
baseCmd: baseCmd{
- ctx: ctx,
- args: args,
+ ctx: ctx,
+ args: args,
+ cmdType: CmdTypeXMessageSlice,
},
}
}
@@ -1448,6 +2066,28 @@ func (cmd *XMessageSliceCmd) readReply(rd *proto.Reader) (err error) {
return err
}
+func (cmd *XMessageSliceCmd) Clone() Cmder {
+ var val []XMessage
+ if cmd.val != nil {
+ val = make([]XMessage, len(cmd.val))
+ for i, msg := range cmd.val {
+ val[i] = XMessage{
+ ID: msg.ID,
+ }
+ if msg.Values != nil {
+ val[i].Values = make(map[string]interface{}, len(msg.Values))
+ for k, v := range msg.Values {
+ val[i].Values[k] = v
+ }
+ }
+ }
+ }
+ return &XMessageSliceCmd{
+ baseCmd: cmd.cloneBaseCmd(),
+ val: val,
+ }
+}
+
func readXMessageSlice(rd *proto.Reader) ([]XMessage, error) {
n, err := rd.ReadArrayLen()
if err != nil {
@@ -1464,10 +2104,16 @@ func readXMessageSlice(rd *proto.Reader) ([]XMessage, error) {
}
func readXMessage(rd *proto.Reader) (XMessage, error) {
- if err := rd.ReadFixedArrayLen(2); err != nil {
+ // Read array length can be 2 or 4 (with CLAIM metadata)
+ n, err := rd.ReadArrayLen()
+ if err != nil {
return XMessage{}, err
}
+ if n != 2 && n != 4 {
+ return XMessage{}, fmt.Errorf("redis: got %d elements in the XMessage array, expected 2 or 4", n)
+ }
+
id, err := rd.ReadString()
if err != nil {
return XMessage{}, err
@@ -1480,10 +2126,24 @@ func readXMessage(rd *proto.Reader) (XMessage, error) {
}
}
- return XMessage{
+ msg := XMessage{
ID: id,
Values: v,
- }, nil
+ }
+
+ if n == 4 {
+ msg.MillisElapsedFromDelivery, err = rd.ReadInt()
+ if err != nil {
+ return XMessage{}, err
+ }
+
+ msg.DeliveredCount, err = rd.ReadInt()
+ if err != nil {
+ return XMessage{}, err
+ }
+ }
+
+ return msg, nil
}
func stringInterfaceMapParser(rd *proto.Reader) (map[string]interface{}, error) {
@@ -1527,8 +2187,9 @@ var _ Cmder = (*XStreamSliceCmd)(nil)
func NewXStreamSliceCmd(ctx context.Context, args ...interface{}) *XStreamSliceCmd {
return &XStreamSliceCmd{
baseCmd: baseCmd{
- ctx: ctx,
- args: args,
+ ctx: ctx,
+ args: args,
+ cmdType: CmdTypeXStreamSlice,
},
}
}
@@ -1581,6 +2242,36 @@ func (cmd *XStreamSliceCmd) readReply(rd *proto.Reader) error {
return nil
}
+func (cmd *XStreamSliceCmd) Clone() Cmder {
+ var val []XStream
+ if cmd.val != nil {
+ val = make([]XStream, len(cmd.val))
+ for i, stream := range cmd.val {
+ val[i] = XStream{
+ Stream: stream.Stream,
+ }
+ if stream.Messages != nil {
+ val[i].Messages = make([]XMessage, len(stream.Messages))
+ for j, msg := range stream.Messages {
+ val[i].Messages[j] = XMessage{
+ ID: msg.ID,
+ }
+ if msg.Values != nil {
+ val[i].Messages[j].Values = make(map[string]interface{}, len(msg.Values))
+ for k, v := range msg.Values {
+ val[i].Messages[j].Values[k] = v
+ }
+ }
+ }
+ }
+ }
+ }
+ return &XStreamSliceCmd{
+ baseCmd: cmd.cloneBaseCmd(),
+ val: val,
+ }
+}
+
//------------------------------------------------------------------------------
type XPending struct {
@@ -1600,8 +2291,9 @@ var _ Cmder = (*XPendingCmd)(nil)
func NewXPendingCmd(ctx context.Context, args ...interface{}) *XPendingCmd {
return &XPendingCmd{
baseCmd: baseCmd{
- ctx: ctx,
- args: args,
+ ctx: ctx,
+ args: args,
+ cmdType: CmdTypeXPending,
},
}
}
@@ -1664,6 +2356,27 @@ func (cmd *XPendingCmd) readReply(rd *proto.Reader) error {
return nil
}
+func (cmd *XPendingCmd) Clone() Cmder {
+ var val *XPending
+ if cmd.val != nil {
+ val = &XPending{
+ Count: cmd.val.Count,
+ Lower: cmd.val.Lower,
+ Higher: cmd.val.Higher,
+ }
+ if cmd.val.Consumers != nil {
+ val.Consumers = make(map[string]int64, len(cmd.val.Consumers))
+ for k, v := range cmd.val.Consumers {
+ val.Consumers[k] = v
+ }
+ }
+ }
+ return &XPendingCmd{
+ baseCmd: cmd.cloneBaseCmd(),
+ val: val,
+ }
+}
+
//------------------------------------------------------------------------------
type XPendingExt struct {
@@ -1683,8 +2396,9 @@ var _ Cmder = (*XPendingExtCmd)(nil)
func NewXPendingExtCmd(ctx context.Context, args ...interface{}) *XPendingExtCmd {
return &XPendingExtCmd{
baseCmd: baseCmd{
- ctx: ctx,
- args: args,
+ ctx: ctx,
+ args: args,
+ cmdType: CmdTypeXPendingExt,
},
}
}
@@ -1739,6 +2453,18 @@ func (cmd *XPendingExtCmd) readReply(rd *proto.Reader) error {
return nil
}
+func (cmd *XPendingExtCmd) Clone() Cmder {
+ var val []XPendingExt
+ if cmd.val != nil {
+ val = make([]XPendingExt, len(cmd.val))
+ copy(val, cmd.val)
+ }
+ return &XPendingExtCmd{
+ baseCmd: cmd.cloneBaseCmd(),
+ val: val,
+ }
+}
+
//------------------------------------------------------------------------------
type XAutoClaimCmd struct {
@@ -1753,8 +2479,9 @@ var _ Cmder = (*XAutoClaimCmd)(nil)
func NewXAutoClaimCmd(ctx context.Context, args ...interface{}) *XAutoClaimCmd {
return &XAutoClaimCmd{
baseCmd: baseCmd{
- ctx: ctx,
- args: args,
+ ctx: ctx,
+ args: args,
+ cmdType: CmdTypeXAutoClaim,
},
}
}
@@ -1809,6 +2536,29 @@ func (cmd *XAutoClaimCmd) readReply(rd *proto.Reader) error {
return nil
}
+func (cmd *XAutoClaimCmd) Clone() Cmder {
+ var val []XMessage
+ if cmd.val != nil {
+ val = make([]XMessage, len(cmd.val))
+ for i, msg := range cmd.val {
+ val[i] = XMessage{
+ ID: msg.ID,
+ }
+ if msg.Values != nil {
+ val[i].Values = make(map[string]interface{}, len(msg.Values))
+ for k, v := range msg.Values {
+ val[i].Values[k] = v
+ }
+ }
+ }
+ }
+ return &XAutoClaimCmd{
+ baseCmd: cmd.cloneBaseCmd(),
+ start: cmd.start,
+ val: val,
+ }
+}
+
//------------------------------------------------------------------------------
type XAutoClaimJustIDCmd struct {
@@ -1823,8 +2573,9 @@ var _ Cmder = (*XAutoClaimJustIDCmd)(nil)
func NewXAutoClaimJustIDCmd(ctx context.Context, args ...interface{}) *XAutoClaimJustIDCmd {
return &XAutoClaimJustIDCmd{
baseCmd: baseCmd{
- ctx: ctx,
- args: args,
+ ctx: ctx,
+ args: args,
+ cmdType: CmdTypeXAutoClaimJustID,
},
}
}
@@ -1887,6 +2638,19 @@ func (cmd *XAutoClaimJustIDCmd) readReply(rd *proto.Reader) error {
return nil
}
+func (cmd *XAutoClaimJustIDCmd) Clone() Cmder {
+ var val []string
+ if cmd.val != nil {
+ val = make([]string, len(cmd.val))
+ copy(val, cmd.val)
+ }
+ return &XAutoClaimJustIDCmd{
+ baseCmd: cmd.cloneBaseCmd(),
+ start: cmd.start,
+ val: val,
+ }
+}
+
//------------------------------------------------------------------------------
type XInfoConsumersCmd struct {
@@ -1906,8 +2670,9 @@ var _ Cmder = (*XInfoConsumersCmd)(nil)
func NewXInfoConsumersCmd(ctx context.Context, stream string, group string) *XInfoConsumersCmd {
return &XInfoConsumersCmd{
baseCmd: baseCmd{
- ctx: ctx,
- args: []interface{}{"xinfo", "consumers", stream, group},
+ ctx: ctx,
+ args: []interface{}{"xinfo", "consumers", stream, group},
+ cmdType: CmdTypeXInfoConsumers,
},
}
}
@@ -1973,6 +2738,18 @@ func (cmd *XInfoConsumersCmd) readReply(rd *proto.Reader) error {
return nil
}
+func (cmd *XInfoConsumersCmd) Clone() Cmder {
+ var val []XInfoConsumer
+ if cmd.val != nil {
+ val = make([]XInfoConsumer, len(cmd.val))
+ copy(val, cmd.val)
+ }
+ return &XInfoConsumersCmd{
+ baseCmd: cmd.cloneBaseCmd(),
+ val: val,
+ }
+}
+
//------------------------------------------------------------------------------
type XInfoGroupsCmd struct {
@@ -1986,7 +2763,9 @@ type XInfoGroup struct {
Pending int64
LastDeliveredID string
EntriesRead int64
- Lag int64
+ // Lag represents the number of pending messages in the stream not yet
+ // delivered to this consumer group. Returns -1 when the lag cannot be determined.
+ Lag int64
}
var _ Cmder = (*XInfoGroupsCmd)(nil)
@@ -1994,8 +2773,9 @@ var _ Cmder = (*XInfoGroupsCmd)(nil)
func NewXInfoGroupsCmd(ctx context.Context, stream string) *XInfoGroupsCmd {
return &XInfoGroupsCmd{
baseCmd: baseCmd{
- ctx: ctx,
- args: []interface{}{"xinfo", "groups", stream},
+ ctx: ctx,
+ args: []interface{}{"xinfo", "groups", stream},
+ cmdType: CmdTypeXInfoGroups,
},
}
}
@@ -2069,8 +2849,11 @@ func (cmd *XInfoGroupsCmd) readReply(rd *proto.Reader) error {
// lag: the number of entries in the stream that are still waiting to be delivered
// to the group's consumers, or a NULL(Nil) when that number can't be determined.
+ // In that case, we return -1.
if err != nil && err != Nil {
return err
+ } else if err == Nil {
+ group.Lag = -1
}
default:
return fmt.Errorf("redis: unexpected key %q in XINFO GROUPS reply", key)
@@ -2081,6 +2864,18 @@ func (cmd *XInfoGroupsCmd) readReply(rd *proto.Reader) error {
return nil
}
+func (cmd *XInfoGroupsCmd) Clone() Cmder {
+ var val []XInfoGroup
+ if cmd.val != nil {
+ val = make([]XInfoGroup, len(cmd.val))
+ copy(val, cmd.val)
+ }
+ return &XInfoGroupsCmd{
+ baseCmd: cmd.cloneBaseCmd(),
+ val: val,
+ }
+}
+
//------------------------------------------------------------------------------
type XInfoStreamCmd struct {
@@ -2099,6 +2894,13 @@ type XInfoStream struct {
FirstEntry XMessage
LastEntry XMessage
RecordedFirstEntryID string
+
+ IDMPDuration int64
+ IDMPMaxSize int64
+ PIDsTracked int64
+ IIDsTracked int64
+ IIDsAdded int64
+ IIDsDuplicates int64
}
var _ Cmder = (*XInfoStreamCmd)(nil)
@@ -2106,8 +2908,9 @@ var _ Cmder = (*XInfoStreamCmd)(nil)
func NewXInfoStreamCmd(ctx context.Context, stream string) *XInfoStreamCmd {
return &XInfoStreamCmd{
baseCmd: baseCmd{
- ctx: ctx,
- args: []interface{}{"xinfo", "stream", stream},
+ ctx: ctx,
+ args: []interface{}{"xinfo", "stream", stream},
+ cmdType: CmdTypeXInfoStream,
},
}
}
@@ -2191,6 +2994,36 @@ func (cmd *XInfoStreamCmd) readReply(rd *proto.Reader) error {
if err != nil {
return err
}
+ case "idmp-duration":
+ cmd.val.IDMPDuration, err = rd.ReadInt()
+ if err != nil {
+ return err
+ }
+ case "idmp-maxsize":
+ cmd.val.IDMPMaxSize, err = rd.ReadInt()
+ if err != nil {
+ return err
+ }
+ case "pids-tracked":
+ cmd.val.PIDsTracked, err = rd.ReadInt()
+ if err != nil {
+ return err
+ }
+ case "iids-tracked":
+ cmd.val.IIDsTracked, err = rd.ReadInt()
+ if err != nil {
+ return err
+ }
+ case "iids-added":
+ cmd.val.IIDsAdded, err = rd.ReadInt()
+ if err != nil {
+ return err
+ }
+ case "iids-duplicates":
+ cmd.val.IIDsDuplicates, err = rd.ReadInt()
+ if err != nil {
+ return err
+ }
default:
return fmt.Errorf("redis: unexpected key %q in XINFO STREAM reply", key)
}
@@ -2198,6 +3031,45 @@ func (cmd *XInfoStreamCmd) readReply(rd *proto.Reader) error {
return nil
}
+func (cmd *XInfoStreamCmd) Clone() Cmder {
+ var val *XInfoStream
+ if cmd.val != nil {
+ val = &XInfoStream{
+ Length: cmd.val.Length,
+ RadixTreeKeys: cmd.val.RadixTreeKeys,
+ RadixTreeNodes: cmd.val.RadixTreeNodes,
+ Groups: cmd.val.Groups,
+ LastGeneratedID: cmd.val.LastGeneratedID,
+ MaxDeletedEntryID: cmd.val.MaxDeletedEntryID,
+ EntriesAdded: cmd.val.EntriesAdded,
+ RecordedFirstEntryID: cmd.val.RecordedFirstEntryID,
+ }
+ // Clone XMessage fields
+ val.FirstEntry = XMessage{
+ ID: cmd.val.FirstEntry.ID,
+ }
+ if cmd.val.FirstEntry.Values != nil {
+ val.FirstEntry.Values = make(map[string]interface{}, len(cmd.val.FirstEntry.Values))
+ for k, v := range cmd.val.FirstEntry.Values {
+ val.FirstEntry.Values[k] = v
+ }
+ }
+ val.LastEntry = XMessage{
+ ID: cmd.val.LastEntry.ID,
+ }
+ if cmd.val.LastEntry.Values != nil {
+ val.LastEntry.Values = make(map[string]interface{}, len(cmd.val.LastEntry.Values))
+ for k, v := range cmd.val.LastEntry.Values {
+ val.LastEntry.Values[k] = v
+ }
+ }
+ }
+ return &XInfoStreamCmd{
+ baseCmd: cmd.cloneBaseCmd(),
+ val: val,
+ }
+}
+
//------------------------------------------------------------------------------
type XInfoStreamFullCmd struct {
@@ -2215,6 +3087,12 @@ type XInfoStreamFull struct {
Entries []XMessage
Groups []XInfoStreamGroup
RecordedFirstEntryID string
+ IDMPDuration int64
+ IDMPMaxSize int64
+ PIDsTracked int64
+ IIDsTracked int64
+ IIDsAdded int64
+ IIDsDuplicates int64
}
type XInfoStreamGroup struct {
@@ -2253,8 +3131,9 @@ var _ Cmder = (*XInfoStreamFullCmd)(nil)
func NewXInfoStreamFullCmd(ctx context.Context, args ...interface{}) *XInfoStreamFullCmd {
return &XInfoStreamFullCmd{
baseCmd: baseCmd{
- ctx: ctx,
- args: args,
+ ctx: ctx,
+ args: args,
+ cmdType: CmdTypeXInfoStreamFull,
},
}
}
@@ -2335,6 +3214,36 @@ func (cmd *XInfoStreamFullCmd) readReply(rd *proto.Reader) error {
if err != nil {
return err
}
+ case "idmp-duration":
+ cmd.val.IDMPDuration, err = rd.ReadInt()
+ if err != nil {
+ return err
+ }
+ case "idmp-maxsize":
+ cmd.val.IDMPMaxSize, err = rd.ReadInt()
+ if err != nil {
+ return err
+ }
+ case "pids-tracked":
+ cmd.val.PIDsTracked, err = rd.ReadInt()
+ if err != nil {
+ return err
+ }
+ case "iids-tracked":
+ cmd.val.IIDsTracked, err = rd.ReadInt()
+ if err != nil {
+ return err
+ }
+ case "iids-added":
+ cmd.val.IIDsAdded, err = rd.ReadInt()
+ if err != nil {
+ return err
+ }
+ case "iids-duplicates":
+ cmd.val.IIDsDuplicates, err = rd.ReadInt()
+ if err != nil {
+ return err
+ }
default:
return fmt.Errorf("redis: unexpected key %q in XINFO STREAM FULL reply", key)
}
@@ -2539,6 +3448,45 @@ func readXInfoStreamConsumers(rd *proto.Reader) ([]XInfoStreamConsumer, error) {
return consumers, nil
}
+func (cmd *XInfoStreamFullCmd) Clone() Cmder {
+ var val *XInfoStreamFull
+ if cmd.val != nil {
+ val = &XInfoStreamFull{
+ Length: cmd.val.Length,
+ RadixTreeKeys: cmd.val.RadixTreeKeys,
+ RadixTreeNodes: cmd.val.RadixTreeNodes,
+ LastGeneratedID: cmd.val.LastGeneratedID,
+ MaxDeletedEntryID: cmd.val.MaxDeletedEntryID,
+ EntriesAdded: cmd.val.EntriesAdded,
+ RecordedFirstEntryID: cmd.val.RecordedFirstEntryID,
+ }
+ // Clone Entries
+ if cmd.val.Entries != nil {
+ val.Entries = make([]XMessage, len(cmd.val.Entries))
+ for i, msg := range cmd.val.Entries {
+ val.Entries[i] = XMessage{
+ ID: msg.ID,
+ }
+ if msg.Values != nil {
+ val.Entries[i].Values = make(map[string]interface{}, len(msg.Values))
+ for k, v := range msg.Values {
+ val.Entries[i].Values[k] = v
+ }
+ }
+ }
+ }
+ // Clone Groups - simplified copy for now due to complexity
+ if cmd.val.Groups != nil {
+ val.Groups = make([]XInfoStreamGroup, len(cmd.val.Groups))
+ copy(val.Groups, cmd.val.Groups)
+ }
+ }
+ return &XInfoStreamFullCmd{
+ baseCmd: cmd.cloneBaseCmd(),
+ val: val,
+ }
+}
+
//------------------------------------------------------------------------------
type ZSliceCmd struct {
@@ -2552,8 +3500,9 @@ var _ Cmder = (*ZSliceCmd)(nil)
func NewZSliceCmd(ctx context.Context, args ...interface{}) *ZSliceCmd {
return &ZSliceCmd{
baseCmd: baseCmd{
- ctx: ctx,
- args: args,
+ ctx: ctx,
+ args: args,
+ cmdType: CmdTypeZSlice,
},
}
}
@@ -2617,6 +3566,18 @@ func (cmd *ZSliceCmd) readReply(rd *proto.Reader) error { // nolint:dupl
return nil
}
+func (cmd *ZSliceCmd) Clone() Cmder {
+ var val []Z
+ if cmd.val != nil {
+ val = make([]Z, len(cmd.val))
+ copy(val, cmd.val)
+ }
+ return &ZSliceCmd{
+ baseCmd: cmd.cloneBaseCmd(),
+ val: val,
+ }
+}
+
//------------------------------------------------------------------------------
type ZWithKeyCmd struct {
@@ -2630,8 +3591,9 @@ var _ Cmder = (*ZWithKeyCmd)(nil)
func NewZWithKeyCmd(ctx context.Context, args ...interface{}) *ZWithKeyCmd {
return &ZWithKeyCmd{
baseCmd: baseCmd{
- ctx: ctx,
- args: args,
+ ctx: ctx,
+ args: args,
+ cmdType: CmdTypeZWithKey,
},
}
}
@@ -2645,7 +3607,7 @@ func (cmd *ZWithKeyCmd) Val() *ZWithKey {
}
func (cmd *ZWithKeyCmd) Result() (*ZWithKey, error) {
- return cmd.Val(), cmd.Err()
+ return cmd.val, cmd.err
}
func (cmd *ZWithKeyCmd) String() string {
@@ -2671,6 +3633,23 @@ func (cmd *ZWithKeyCmd) readReply(rd *proto.Reader) (err error) {
return nil
}
+func (cmd *ZWithKeyCmd) Clone() Cmder {
+ var val *ZWithKey
+ if cmd.val != nil {
+ val = &ZWithKey{
+ Z: Z{
+ Score: cmd.val.Score,
+ Member: cmd.val.Member,
+ },
+ Key: cmd.val.Key,
+ }
+ }
+ return &ZWithKeyCmd{
+ baseCmd: cmd.cloneBaseCmd(),
+ val: val,
+ }
+}
+
//------------------------------------------------------------------------------
type ScanCmd struct {
@@ -2687,8 +3666,9 @@ var _ Cmder = (*ScanCmd)(nil)
func NewScanCmd(ctx context.Context, process cmdable, args ...interface{}) *ScanCmd {
return &ScanCmd{
baseCmd: baseCmd{
- ctx: ctx,
- args: args,
+ ctx: ctx,
+ args: args,
+ cmdType: CmdTypeScan,
},
process: process,
}
@@ -2736,6 +3716,20 @@ func (cmd *ScanCmd) readReply(rd *proto.Reader) error {
return nil
}
+func (cmd *ScanCmd) Clone() Cmder {
+ var page []string
+ if cmd.page != nil {
+ page = make([]string, len(cmd.page))
+ copy(page, cmd.page)
+ }
+ return &ScanCmd{
+ baseCmd: cmd.cloneBaseCmd(),
+ page: page,
+ cursor: cmd.cursor,
+ process: cmd.process,
+ }
+}
+
// Iterator creates a new ScanIterator.
func (cmd *ScanCmd) Iterator() *ScanIterator {
return &ScanIterator{
@@ -2768,8 +3762,9 @@ var _ Cmder = (*ClusterSlotsCmd)(nil)
func NewClusterSlotsCmd(ctx context.Context, args ...interface{}) *ClusterSlotsCmd {
return &ClusterSlotsCmd{
baseCmd: baseCmd{
- ctx: ctx,
- args: args,
+ ctx: ctx,
+ args: args,
+ cmdType: CmdTypeClusterSlots,
},
}
}
@@ -2783,7 +3778,7 @@ func (cmd *ClusterSlotsCmd) Val() []ClusterSlot {
}
func (cmd *ClusterSlotsCmd) Result() ([]ClusterSlot, error) {
- return cmd.Val(), cmd.Err()
+ return cmd.val, cmd.err
}
func (cmd *ClusterSlotsCmd) String() string {
@@ -2882,6 +3877,38 @@ func (cmd *ClusterSlotsCmd) readReply(rd *proto.Reader) error {
return nil
}
+func (cmd *ClusterSlotsCmd) Clone() Cmder {
+ var val []ClusterSlot
+ if cmd.val != nil {
+ val = make([]ClusterSlot, len(cmd.val))
+ for i, slot := range cmd.val {
+ val[i] = ClusterSlot{
+ Start: slot.Start,
+ End: slot.End,
+ }
+ if slot.Nodes != nil {
+ val[i].Nodes = make([]ClusterNode, len(slot.Nodes))
+ for j, node := range slot.Nodes {
+ val[i].Nodes[j] = ClusterNode{
+ ID: node.ID,
+ Addr: node.Addr,
+ }
+ if node.NetworkingMetadata != nil {
+ val[i].Nodes[j].NetworkingMetadata = make(map[string]string, len(node.NetworkingMetadata))
+ for k, v := range node.NetworkingMetadata {
+ val[i].Nodes[j].NetworkingMetadata[k] = v
+ }
+ }
+ }
+ }
+ }
+ }
+ return &ClusterSlotsCmd{
+ baseCmd: cmd.cloneBaseCmd(),
+ val: val,
+ }
+}
+
//------------------------------------------------------------------------------
// GeoLocation is used with GeoAdd to add geospatial location.
@@ -2921,8 +3948,9 @@ var _ Cmder = (*GeoLocationCmd)(nil)
func NewGeoLocationCmd(ctx context.Context, q *GeoRadiusQuery, args ...interface{}) *GeoLocationCmd {
return &GeoLocationCmd{
baseCmd: baseCmd{
- ctx: ctx,
- args: geoLocationArgs(q, args...),
+ ctx: ctx,
+ args: geoLocationArgs(q, args...),
+ cmdType: CmdTypeGeoLocation,
},
q: q,
}
@@ -3030,9 +4058,37 @@ func (cmd *GeoLocationCmd) readReply(rd *proto.Reader) error {
return nil
}
-//------------------------------------------------------------------------------
-
-// GeoSearchQuery is used for GEOSearch/GEOSearchStore command query.
+func (cmd *GeoLocationCmd) Clone() Cmder {
+ var q *GeoRadiusQuery
+ if cmd.q != nil {
+ q = &GeoRadiusQuery{
+ Radius: cmd.q.Radius,
+ Unit: cmd.q.Unit,
+ WithCoord: cmd.q.WithCoord,
+ WithDist: cmd.q.WithDist,
+ WithGeoHash: cmd.q.WithGeoHash,
+ Count: cmd.q.Count,
+ Sort: cmd.q.Sort,
+ Store: cmd.q.Store,
+ StoreDist: cmd.q.StoreDist,
+ withLen: cmd.q.withLen,
+ }
+ }
+ var locations []GeoLocation
+ if cmd.locations != nil {
+ locations = make([]GeoLocation, len(cmd.locations))
+ copy(locations, cmd.locations)
+ }
+ return &GeoLocationCmd{
+ baseCmd: cmd.cloneBaseCmd(),
+ q: q,
+ locations: locations,
+ }
+}
+
+//------------------------------------------------------------------------------
+
+// GeoSearchQuery is used for GEOSearch/GEOSearchStore command query.
type GeoSearchQuery struct {
Member string
@@ -3137,8 +4193,9 @@ func NewGeoSearchLocationCmd(
) *GeoSearchLocationCmd {
return &GeoSearchLocationCmd{
baseCmd: baseCmd{
- ctx: ctx,
- args: args,
+ ctx: ctx,
+ args: geoSearchLocationArgs(opt, args),
+ cmdType: CmdTypeGeoSearchLocation,
},
opt: opt,
}
@@ -3211,6 +4268,40 @@ func (cmd *GeoSearchLocationCmd) readReply(rd *proto.Reader) error {
return nil
}
+func (cmd *GeoSearchLocationCmd) Clone() Cmder {
+ var opt *GeoSearchLocationQuery
+ if cmd.opt != nil {
+ opt = &GeoSearchLocationQuery{
+ GeoSearchQuery: GeoSearchQuery{
+ Member: cmd.opt.Member,
+ Longitude: cmd.opt.Longitude,
+ Latitude: cmd.opt.Latitude,
+ Radius: cmd.opt.Radius,
+ RadiusUnit: cmd.opt.RadiusUnit,
+ BoxWidth: cmd.opt.BoxWidth,
+ BoxHeight: cmd.opt.BoxHeight,
+ BoxUnit: cmd.opt.BoxUnit,
+ Sort: cmd.opt.Sort,
+ Count: cmd.opt.Count,
+ CountAny: cmd.opt.CountAny,
+ },
+ WithCoord: cmd.opt.WithCoord,
+ WithDist: cmd.opt.WithDist,
+ WithHash: cmd.opt.WithHash,
+ }
+ }
+ var val []GeoLocation
+ if cmd.val != nil {
+ val = make([]GeoLocation, len(cmd.val))
+ copy(val, cmd.val)
+ }
+ return &GeoSearchLocationCmd{
+ baseCmd: cmd.cloneBaseCmd(),
+ opt: opt,
+ val: val,
+ }
+}
+
//------------------------------------------------------------------------------
type GeoPos struct {
@@ -3228,8 +4319,9 @@ var _ Cmder = (*GeoPosCmd)(nil)
func NewGeoPosCmd(ctx context.Context, args ...interface{}) *GeoPosCmd {
return &GeoPosCmd{
baseCmd: baseCmd{
- ctx: ctx,
- args: args,
+ ctx: ctx,
+ args: args,
+ cmdType: CmdTypeGeoPos,
},
}
}
@@ -3243,7 +4335,7 @@ func (cmd *GeoPosCmd) Val() []*GeoPos {
}
func (cmd *GeoPosCmd) Result() ([]*GeoPos, error) {
- return cmd.Val(), cmd.Err()
+ return cmd.val, cmd.err
}
func (cmd *GeoPosCmd) String() string {
@@ -3285,17 +4377,37 @@ func (cmd *GeoPosCmd) readReply(rd *proto.Reader) error {
return nil
}
+func (cmd *GeoPosCmd) Clone() Cmder {
+ var val []*GeoPos
+ if cmd.val != nil {
+ val = make([]*GeoPos, len(cmd.val))
+ for i, pos := range cmd.val {
+ if pos != nil {
+ val[i] = &GeoPos{
+ Longitude: pos.Longitude,
+ Latitude: pos.Latitude,
+ }
+ }
+ }
+ }
+ return &GeoPosCmd{
+ baseCmd: cmd.cloneBaseCmd(),
+ val: val,
+ }
+}
+
//------------------------------------------------------------------------------
type CommandInfo struct {
- Name string
- Arity int8
- Flags []string
- ACLFlags []string
- FirstKeyPos int8
- LastKeyPos int8
- StepCount int8
- ReadOnly bool
+ Name string
+ Arity int8
+ Flags []string
+ ACLFlags []string
+ FirstKeyPos int8
+ LastKeyPos int8
+ StepCount int8
+ ReadOnly bool
+ CommandPolicy *routing.CommandPolicy
}
type CommandsInfoCmd struct {
@@ -3309,8 +4421,9 @@ var _ Cmder = (*CommandsInfoCmd)(nil)
func NewCommandsInfoCmd(ctx context.Context, args ...interface{}) *CommandsInfoCmd {
return &CommandsInfoCmd{
baseCmd: baseCmd{
- ctx: ctx,
- args: args,
+ ctx: ctx,
+ args: args,
+ cmdType: CmdTypeCommandsInfo,
},
}
}
@@ -3324,7 +4437,7 @@ func (cmd *CommandsInfoCmd) Val() map[string]*CommandInfo {
}
func (cmd *CommandsInfoCmd) Result() (map[string]*CommandInfo, error) {
- return cmd.Val(), cmd.Err()
+ return cmd.val, cmd.err
}
func (cmd *CommandsInfoCmd) String() string {
@@ -3334,7 +4447,7 @@ func (cmd *CommandsInfoCmd) String() string {
func (cmd *CommandsInfoCmd) readReply(rd *proto.Reader) error {
const numArgRedis5 = 6
const numArgRedis6 = 7
- const numArgRedis7 = 10
+ const numArgRedis7 = 10 // Also matches redis 8
n, err := rd.ReadArrayLen()
if err != nil {
@@ -3422,9 +4535,33 @@ func (cmd *CommandsInfoCmd) readReply(rd *proto.Reader) error {
}
if nn >= numArgRedis7 {
- if err := rd.DiscardNext(); err != nil {
+ // The 8th argument is an array of tips.
+ tipsLen, err := rd.ReadArrayLen()
+ if err != nil {
return err
}
+
+ rawTips := make(map[string]string, tipsLen)
+ if cmdInfo.ReadOnly {
+ rawTips[routing.ReadOnlyCMD] = ""
+ }
+ for f := 0; f < tipsLen; f++ {
+ tip, err := rd.ReadString()
+ if err != nil {
+ return err
+ }
+
+ k, v, ok := strings.Cut(tip, ":")
+ if !ok {
+ // Handle tips that don't have a colon (like "nondeterministic_output")
+ rawTips[tip] = ""
+ } else {
+ // Handle normal key:value tips
+ rawTips[k] = v
+ }
+ }
+ cmdInfo.CommandPolicy = parseCommandPolicies(rawTips, cmdInfo.FirstKeyPos)
+
if err := rd.DiscardNext(); err != nil {
return err
}
@@ -3439,13 +4576,47 @@ func (cmd *CommandsInfoCmd) readReply(rd *proto.Reader) error {
return nil
}
+func (cmd *CommandsInfoCmd) Clone() Cmder {
+ var val map[string]*CommandInfo
+ if cmd.val != nil {
+ val = make(map[string]*CommandInfo, len(cmd.val))
+ for k, v := range cmd.val {
+ if v != nil {
+ newInfo := &CommandInfo{
+ Name: v.Name,
+ Arity: v.Arity,
+ FirstKeyPos: v.FirstKeyPos,
+ LastKeyPos: v.LastKeyPos,
+ StepCount: v.StepCount,
+ ReadOnly: v.ReadOnly,
+ CommandPolicy: v.CommandPolicy, // CommandPolicy can be shared as it's immutable
+ }
+ if v.Flags != nil {
+ newInfo.Flags = make([]string, len(v.Flags))
+ copy(newInfo.Flags, v.Flags)
+ }
+ if v.ACLFlags != nil {
+ newInfo.ACLFlags = make([]string, len(v.ACLFlags))
+ copy(newInfo.ACLFlags, v.ACLFlags)
+ }
+ val[k] = newInfo
+ }
+ }
+ }
+ return &CommandsInfoCmd{
+ baseCmd: cmd.cloneBaseCmd(),
+ val: val,
+ }
+}
+
//------------------------------------------------------------------------------
type cmdsInfoCache struct {
fn func(ctx context.Context) (map[string]*CommandInfo, error)
- once internal.Once
- cmds map[string]*CommandInfo
+ once internal.Once
+ refreshLock sync.Mutex
+ cmds map[string]*CommandInfo
}
func newCmdsInfoCache(fn func(ctx context.Context) (map[string]*CommandInfo, error)) *cmdsInfoCache {
@@ -3455,26 +4626,66 @@ func newCmdsInfoCache(fn func(ctx context.Context) (map[string]*CommandInfo, err
}
func (c *cmdsInfoCache) Get(ctx context.Context) (map[string]*CommandInfo, error) {
+ c.refreshLock.Lock()
+ defer c.refreshLock.Unlock()
+
err := c.once.Do(func() error {
cmds, err := c.fn(ctx)
if err != nil {
return err
}
+ lowerCmds := make(map[string]*CommandInfo, len(cmds))
+
// Extensions have cmd names in upper case. Convert them to lower case.
for k, v := range cmds {
- lower := internal.ToLower(k)
- if lower != k {
- cmds[lower] = v
- }
+ lowerCmds[internal.ToLower(k)] = v
}
- c.cmds = cmds
+ c.cmds = lowerCmds
return nil
})
return c.cmds, err
}
+func (c *cmdsInfoCache) Refresh() {
+ c.refreshLock.Lock()
+ defer c.refreshLock.Unlock()
+
+ c.once = internal.Once{}
+}
+
+// ------------------------------------------------------------------------------
+const requestPolicy = "request_policy"
+const responsePolicy = "response_policy"
+
+func parseCommandPolicies(commandInfoTips map[string]string, firstKeyPos int8) *routing.CommandPolicy {
+ req := routing.ReqDefault
+ resp := routing.RespDefaultKeyless
+ if firstKeyPos > 0 {
+ resp = routing.RespDefaultHashSlot
+ }
+
+ tips := make(map[string]string, len(commandInfoTips))
+ for k, v := range commandInfoTips {
+ if k == requestPolicy {
+ if p, err := routing.ParseRequestPolicy(v); err == nil {
+ req = p
+ }
+ continue
+ }
+ if k == responsePolicy {
+ if p, err := routing.ParseResponsePolicy(v); err == nil {
+ resp = p
+ }
+ continue
+ }
+ tips[k] = v
+ }
+
+ return &routing.CommandPolicy{Request: req, Response: resp, Tips: tips}
+}
+
//------------------------------------------------------------------------------
type SlowLog struct {
@@ -3499,8 +4710,9 @@ var _ Cmder = (*SlowLogCmd)(nil)
func NewSlowLogCmd(ctx context.Context, args ...interface{}) *SlowLogCmd {
return &SlowLogCmd{
baseCmd: baseCmd{
- ctx: ctx,
- args: args,
+ ctx: ctx,
+ args: args,
+ cmdType: CmdTypeSlowLog,
},
}
}
@@ -3514,7 +4726,7 @@ func (cmd *SlowLogCmd) Val() []SlowLog {
}
func (cmd *SlowLogCmd) Result() ([]SlowLog, error) {
- return cmd.Val(), cmd.Err()
+ return cmd.val, cmd.err
}
func (cmd *SlowLogCmd) String() string {
@@ -3585,6 +4797,356 @@ func (cmd *SlowLogCmd) readReply(rd *proto.Reader) error {
return nil
}
+func (cmd *SlowLogCmd) Clone() Cmder {
+ var val []SlowLog
+ if cmd.val != nil {
+ val = make([]SlowLog, len(cmd.val))
+ for i, log := range cmd.val {
+ val[i] = SlowLog{
+ ID: log.ID,
+ Time: log.Time,
+ Duration: log.Duration,
+ ClientAddr: log.ClientAddr,
+ ClientName: log.ClientName,
+ }
+ if log.Args != nil {
+ val[i].Args = make([]string, len(log.Args))
+ copy(val[i].Args, log.Args)
+ }
+ }
+ }
+ return &SlowLogCmd{
+ baseCmd: cmd.cloneBaseCmd(),
+ val: val,
+ }
+}
+
+//-----------------------------------------------------------------------
+
+type Latency struct {
+ Name string
+ Time time.Time
+ Latest time.Duration
+ Max time.Duration
+}
+
+type LatencyCmd struct {
+ baseCmd
+ val []Latency
+}
+
+var _ Cmder = (*LatencyCmd)(nil)
+
+func NewLatencyCmd(ctx context.Context, args ...interface{}) *LatencyCmd {
+ return &LatencyCmd{
+ baseCmd: baseCmd{
+ ctx: ctx,
+ args: args,
+ },
+ }
+}
+
+func (cmd *LatencyCmd) SetVal(val []Latency) {
+ cmd.val = val
+}
+
+func (cmd *LatencyCmd) Val() []Latency {
+ return cmd.val
+}
+
+func (cmd *LatencyCmd) Result() ([]Latency, error) {
+ return cmd.val, cmd.err
+}
+
+func (cmd *LatencyCmd) String() string {
+ return cmdString(cmd, cmd.val)
+}
+
+func (cmd *LatencyCmd) readReply(rd *proto.Reader) error {
+ n, err := rd.ReadArrayLen()
+ if err != nil {
+ return err
+ }
+ cmd.val = make([]Latency, n)
+ for i := 0; i < len(cmd.val); i++ {
+ nn, err := rd.ReadArrayLen()
+ if err != nil {
+ return err
+ }
+ if nn < 3 {
+ return fmt.Errorf("redis: got %d elements in latency get, expected at least 3", nn)
+ }
+ if cmd.val[i].Name, err = rd.ReadString(); err != nil {
+ return err
+ }
+ createdAt, err := rd.ReadInt()
+ if err != nil {
+ return err
+ }
+ cmd.val[i].Time = time.Unix(createdAt, 0)
+ latest, err := rd.ReadInt()
+ if err != nil {
+ return err
+ }
+ cmd.val[i].Latest = time.Duration(latest) * time.Millisecond
+ maximum, err := rd.ReadInt()
+ if err != nil {
+ return err
+ }
+ cmd.val[i].Max = time.Duration(maximum) * time.Millisecond
+ }
+ return nil
+}
+
+func (cmd *LatencyCmd) Clone() Cmder {
+ var val []Latency
+ if cmd.val != nil {
+ val = make([]Latency, len(cmd.val))
+ copy(val, cmd.val)
+ }
+ return &LatencyCmd{
+ baseCmd: cmd.cloneBaseCmd(),
+ val: val,
+ }
+}
+
+//-----------------------------------------------------------------------
+
+// HotKeysSlotRange represents a slot or slot range in the response.
+// Single element slice = individual slot, two element slice = slot range [start, end].
+type HotKeysSlotRange []int64
+
+// HotKeysKeyEntry represents a hot key entry with its metric value.
+type HotKeysKeyEntry struct {
+ Key string
+ Value interface{} // Can be int64 or string
+}
+
+// HotKeysResult represents the response data from HOTKEYS GET command.
+// Field names match the Redis response format.
+type HotKeysResult struct {
+ TrackingActive bool
+ SampleRatio uint8
+ SelectedSlots []HotKeysSlotRange
+ SampledCommandsSelectedSlots time.Duration // Present when sample-ratio > 1 and selected-slots is not empty
+ AllCommandsSelectedSlots time.Duration // Present when selected-slots is not empty
+ AllCommandsAllSlots time.Duration
+ NetBytesSampledCommandsSelectedSlots int64 // Present when sample-ratio > 1 and selected-slots is not empty
+ NetBytesAllCommandsSelectedSlots int64 // Present when selected-slots is not empty
+ NetBytesAllCommandsAllSlots int64
+ CollectionStartTime time.Time
+ CollectionDuration time.Duration
+ UsedCPUSys time.Duration
+ UsedCPUUser time.Duration
+ TotalNetBytes int64
+ ByCPUTime []HotKeysKeyEntry
+ ByNetBytes []HotKeysKeyEntry
+}
+
+type HotKeysCmd struct {
+ baseCmd
+
+ val *HotKeysResult
+}
+
+var _ Cmder = (*HotKeysCmd)(nil)
+
+func NewHotKeysCmd(ctx context.Context, args ...interface{}) *HotKeysCmd {
+ return &HotKeysCmd{
+ baseCmd: baseCmd{
+ ctx: ctx,
+ args: args,
+ cmdType: CmdTypeHotKeys,
+ },
+ }
+}
+
+func (cmd *HotKeysCmd) SetVal(val *HotKeysResult) {
+ cmd.val = val
+}
+
+func (cmd *HotKeysCmd) Val() *HotKeysResult {
+ return cmd.val
+}
+
+func (cmd *HotKeysCmd) Result() (*HotKeysResult, error) {
+ return cmd.val, cmd.err
+}
+
+func (cmd *HotKeysCmd) String() string {
+ return cmdString(cmd, cmd.val)
+}
+
+func (cmd *HotKeysCmd) readReply(rd *proto.Reader) error {
+ // HOTKEYS GET response is wrapped in an array for aggregation support
+ arrayLen, err := rd.ReadArrayLen()
+ if err != nil {
+ return err
+ }
+
+ if arrayLen == 0 {
+ // Empty array means no tracking was started or after reset
+ cmd.val = nil
+ return nil
+ }
+
+ // Read the first (and typically only) element which is a map
+ n, err := rd.ReadMapLen()
+ if err != nil {
+ return err
+ }
+
+ result := &HotKeysResult{}
+ data := make(map[string]interface{}, n)
+
+ for i := 0; i < n; i++ {
+ k, err := rd.ReadString()
+ if err != nil {
+ return err
+ }
+ v, err := rd.ReadReply()
+ if err != nil {
+ if err == Nil {
+ data[k] = Nil
+ continue
+ }
+ if err, ok := err.(proto.RedisError); ok {
+ data[k] = err
+ continue
+ }
+ return err
+ }
+ data[k] = v
+ }
+
+ if v, ok := data["tracking-active"].(int64); ok {
+ result.TrackingActive = v == 1
+ }
+ if v, ok := data["sample-ratio"].(int64); ok {
+ result.SampleRatio = uint8(v)
+ }
+ if v, ok := data["selected-slots"].([]interface{}); ok {
+ result.SelectedSlots = make([]HotKeysSlotRange, 0, len(v))
+ for _, slot := range v {
+ switch s := slot.(type) {
+ case int64:
+ // Single slot
+ result.SelectedSlots = append(result.SelectedSlots, HotKeysSlotRange{s})
+ case []interface{}:
+ // Slot range
+ slotRange := make(HotKeysSlotRange, 0, len(s))
+ for _, sr := range s {
+ if val, ok := sr.(int64); ok {
+ slotRange = append(slotRange, val)
+ }
+ }
+ result.SelectedSlots = append(result.SelectedSlots, slotRange)
+ }
+ }
+ }
+ if v, ok := data["sampled-commands-selected-slots-us"].(int64); ok {
+ result.SampledCommandsSelectedSlots = time.Duration(v) * time.Microsecond
+ }
+ if v, ok := data["all-commands-selected-slots-us"].(int64); ok {
+ result.AllCommandsSelectedSlots = time.Duration(v) * time.Microsecond
+ }
+ if v, ok := data["all-commands-all-slots-us"].(int64); ok {
+ result.AllCommandsAllSlots = time.Duration(v) * time.Microsecond
+ }
+ if v, ok := data["net-bytes-sampled-commands-selected-slots"].(int64); ok {
+ result.NetBytesSampledCommandsSelectedSlots = v
+ }
+ if v, ok := data["net-bytes-all-commands-selected-slots"].(int64); ok {
+ result.NetBytesAllCommandsSelectedSlots = v
+ }
+ if v, ok := data["net-bytes-all-commands-all-slots"].(int64); ok {
+ result.NetBytesAllCommandsAllSlots = v
+ }
+ if v, ok := data["collection-start-time-unix-ms"].(int64); ok {
+ result.CollectionStartTime = time.UnixMilli(v)
+ }
+ if v, ok := data["collection-duration-ms"].(int64); ok {
+ result.CollectionDuration = time.Duration(v) * time.Millisecond
+ }
+ if v, ok := data["used-cpu-sys-ms"].(int64); ok {
+ result.UsedCPUSys = time.Duration(v) * time.Millisecond
+ }
+ if v, ok := data["used-cpu-user-ms"].(int64); ok {
+ result.UsedCPUUser = time.Duration(v) * time.Millisecond
+ }
+ if v, ok := data["total-net-bytes"].(int64); ok {
+ result.TotalNetBytes = v
+ }
+
+ if v, ok := data["by-cpu-time-us"].([]interface{}); ok {
+ result.ByCPUTime = parseHotKeysKeyEntries(v)
+ }
+
+ if v, ok := data["by-net-bytes"].([]interface{}); ok {
+ result.ByNetBytes = parseHotKeysKeyEntries(v)
+ }
+
+ cmd.val = result
+ return nil
+}
+
+// parseHotKeysKeyEntries parses the key-value pairs from HOTKEYS GET response.
+func parseHotKeysKeyEntries(v []interface{}) []HotKeysKeyEntry {
+ entries := make([]HotKeysKeyEntry, 0, len(v)/2)
+ for i := 0; i < len(v); i += 2 {
+ if i+1 < len(v) {
+ key, keyOk := v[i].(string)
+ if keyOk {
+ entries = append(entries, HotKeysKeyEntry{
+ Key: key,
+ Value: v[i+1], // Can be int64 or string
+ })
+ }
+ }
+ }
+ return entries
+}
+
+func (cmd *HotKeysCmd) Clone() Cmder {
+ var val *HotKeysResult
+ if cmd.val != nil {
+ val = &HotKeysResult{
+ TrackingActive: cmd.val.TrackingActive,
+ SampleRatio: cmd.val.SampleRatio,
+ SampledCommandsSelectedSlots: cmd.val.SampledCommandsSelectedSlots,
+ AllCommandsSelectedSlots: cmd.val.AllCommandsSelectedSlots,
+ AllCommandsAllSlots: cmd.val.AllCommandsAllSlots,
+ NetBytesSampledCommandsSelectedSlots: cmd.val.NetBytesSampledCommandsSelectedSlots,
+ NetBytesAllCommandsSelectedSlots: cmd.val.NetBytesAllCommandsSelectedSlots,
+ NetBytesAllCommandsAllSlots: cmd.val.NetBytesAllCommandsAllSlots,
+ CollectionStartTime: cmd.val.CollectionStartTime,
+ CollectionDuration: cmd.val.CollectionDuration,
+ UsedCPUSys: cmd.val.UsedCPUSys,
+ UsedCPUUser: cmd.val.UsedCPUUser,
+ TotalNetBytes: cmd.val.TotalNetBytes,
+ }
+ if cmd.val.SelectedSlots != nil {
+ val.SelectedSlots = make([]HotKeysSlotRange, len(cmd.val.SelectedSlots))
+ for i, sr := range cmd.val.SelectedSlots {
+ val.SelectedSlots[i] = make(HotKeysSlotRange, len(sr))
+ copy(val.SelectedSlots[i], sr)
+ }
+ }
+ if cmd.val.ByCPUTime != nil {
+ val.ByCPUTime = make([]HotKeysKeyEntry, len(cmd.val.ByCPUTime))
+ copy(val.ByCPUTime, cmd.val.ByCPUTime)
+ }
+ if cmd.val.ByNetBytes != nil {
+ val.ByNetBytes = make([]HotKeysKeyEntry, len(cmd.val.ByNetBytes))
+ copy(val.ByNetBytes, cmd.val.ByNetBytes)
+ }
+ }
+ return &HotKeysCmd{
+ baseCmd: cmd.cloneBaseCmd(),
+ val: val,
+ }
+}
+
//-----------------------------------------------------------------------
type MapStringInterfaceCmd struct {
@@ -3598,8 +5160,9 @@ var _ Cmder = (*MapStringInterfaceCmd)(nil)
func NewMapStringInterfaceCmd(ctx context.Context, args ...interface{}) *MapStringInterfaceCmd {
return &MapStringInterfaceCmd{
baseCmd: baseCmd{
- ctx: ctx,
- args: args,
+ ctx: ctx,
+ args: args,
+ cmdType: CmdTypeMapStringInterface,
},
}
}
@@ -3613,7 +5176,7 @@ func (cmd *MapStringInterfaceCmd) Val() map[string]interface{} {
}
func (cmd *MapStringInterfaceCmd) Result() (map[string]interface{}, error) {
- return cmd.Val(), cmd.Err()
+ return cmd.val, cmd.err
}
func (cmd *MapStringInterfaceCmd) String() string {
@@ -3649,6 +5212,20 @@ func (cmd *MapStringInterfaceCmd) readReply(rd *proto.Reader) error {
return nil
}
+func (cmd *MapStringInterfaceCmd) Clone() Cmder {
+ var val map[string]interface{}
+ if cmd.val != nil {
+ val = make(map[string]interface{}, len(cmd.val))
+ for k, v := range cmd.val {
+ val[k] = v
+ }
+ }
+ return &MapStringInterfaceCmd{
+ baseCmd: cmd.cloneBaseCmd(),
+ val: val,
+ }
+}
+
//-----------------------------------------------------------------------
type MapStringStringSliceCmd struct {
@@ -3662,8 +5239,9 @@ var _ Cmder = (*MapStringStringSliceCmd)(nil)
func NewMapStringStringSliceCmd(ctx context.Context, args ...interface{}) *MapStringStringSliceCmd {
return &MapStringStringSliceCmd{
baseCmd: baseCmd{
- ctx: ctx,
- args: args,
+ ctx: ctx,
+ args: args,
+ cmdType: CmdTypeMapStringStringSlice,
},
}
}
@@ -3677,7 +5255,7 @@ func (cmd *MapStringStringSliceCmd) Val() []map[string]string {
}
func (cmd *MapStringStringSliceCmd) Result() ([]map[string]string, error) {
- return cmd.Val(), cmd.Err()
+ return cmd.val, cmd.err
}
func (cmd *MapStringStringSliceCmd) String() string {
@@ -3713,7 +5291,119 @@ func (cmd *MapStringStringSliceCmd) readReply(rd *proto.Reader) error {
return nil
}
-//-----------------------------------------------------------------------
+func (cmd *MapStringStringSliceCmd) Clone() Cmder {
+ var val []map[string]string
+ if cmd.val != nil {
+ val = make([]map[string]string, len(cmd.val))
+ for i, m := range cmd.val {
+ if m != nil {
+ val[i] = make(map[string]string, len(m))
+ for k, v := range m {
+ val[i][k] = v
+ }
+ }
+ }
+ }
+ return &MapStringStringSliceCmd{
+ baseCmd: cmd.cloneBaseCmd(),
+ val: val,
+ }
+}
+
+// -----------------------------------------------------------------------
+
+// MapMapStringInterfaceCmd represents a command that returns a map of strings to interface{}.
+type MapMapStringInterfaceCmd struct {
+ baseCmd
+ val map[string]interface{}
+}
+
+func NewMapMapStringInterfaceCmd(ctx context.Context, args ...interface{}) *MapMapStringInterfaceCmd {
+ return &MapMapStringInterfaceCmd{
+ baseCmd: baseCmd{
+ ctx: ctx,
+ args: args,
+ cmdType: CmdTypeMapMapStringInterface,
+ },
+ }
+}
+
+func (cmd *MapMapStringInterfaceCmd) String() string {
+ return cmdString(cmd, cmd.val)
+}
+
+func (cmd *MapMapStringInterfaceCmd) SetVal(val map[string]interface{}) {
+ cmd.val = val
+}
+
+func (cmd *MapMapStringInterfaceCmd) Result() (map[string]interface{}, error) {
+ return cmd.val, cmd.err
+}
+
+func (cmd *MapMapStringInterfaceCmd) Val() map[string]interface{} {
+ return cmd.val
+}
+
+// readReply will try to parse the reply from the proto.Reader for both resp2 and resp3
+func (cmd *MapMapStringInterfaceCmd) readReply(rd *proto.Reader) (err error) {
+ data, err := rd.ReadReply()
+ if err != nil {
+ return err
+ }
+ resultMap := map[string]interface{}{}
+
+ switch midResponse := data.(type) {
+ case map[interface{}]interface{}: // resp3 will return map
+ for k, v := range midResponse {
+ stringKey, ok := k.(string)
+ if !ok {
+ return fmt.Errorf("redis: invalid map key %#v", k)
+ }
+ resultMap[stringKey] = v
+ }
+ case []interface{}: // resp2 will return array of arrays
+ n := len(midResponse)
+ for i := 0; i < n; i++ {
+ finalArr, ok := midResponse[i].([]interface{}) // final array that we need to transform to map
+ if !ok {
+ return fmt.Errorf("redis: unexpected response %#v", data)
+ }
+ m := len(finalArr)
+ if m%2 != 0 { // since this should be map, keys should be even number
+ return fmt.Errorf("redis: unexpected response %#v", data)
+ }
+
+ for j := 0; j < m; j += 2 {
+ stringKey, ok := finalArr[j].(string) // the first one
+ if !ok {
+ return fmt.Errorf("redis: invalid map key %#v", finalArr[i])
+ }
+ resultMap[stringKey] = finalArr[j+1] // second one is value
+ }
+ }
+ default:
+ return fmt.Errorf("redis: unexpected response %#v", data)
+ }
+
+ cmd.val = resultMap
+ return nil
+}
+
+func (cmd *MapMapStringInterfaceCmd) Clone() Cmder {
+ var val map[string]interface{}
+ if cmd.val != nil {
+ val = make(map[string]interface{}, len(cmd.val))
+ for k, v := range cmd.val {
+ val[k] = v
+ }
+ }
+ return &MapMapStringInterfaceCmd{
+ baseCmd: cmd.cloneBaseCmd(),
+ val: val,
+ }
+}
+
+//-----------------------------------------------------------------------
type MapStringInterfaceSliceCmd struct {
baseCmd
@@ -3726,8 +5416,9 @@ var _ Cmder = (*MapStringInterfaceSliceCmd)(nil)
func NewMapStringInterfaceSliceCmd(ctx context.Context, args ...interface{}) *MapStringInterfaceSliceCmd {
return &MapStringInterfaceSliceCmd{
baseCmd: baseCmd{
- ctx: ctx,
- args: args,
+ ctx: ctx,
+ args: args,
+ cmdType: CmdTypeMapStringInterfaceSlice,
},
}
}
@@ -3741,7 +5432,7 @@ func (cmd *MapStringInterfaceSliceCmd) Val() []map[string]interface{} {
}
func (cmd *MapStringInterfaceSliceCmd) Result() ([]map[string]interface{}, error) {
- return cmd.Val(), cmd.Err()
+ return cmd.val, cmd.err
}
func (cmd *MapStringInterfaceSliceCmd) String() string {
@@ -3778,6 +5469,25 @@ func (cmd *MapStringInterfaceSliceCmd) readReply(rd *proto.Reader) error {
return nil
}
+func (cmd *MapStringInterfaceSliceCmd) Clone() Cmder {
+ var val []map[string]interface{}
+ if cmd.val != nil {
+ val = make([]map[string]interface{}, len(cmd.val))
+ for i, m := range cmd.val {
+ if m != nil {
+ val[i] = make(map[string]interface{}, len(m))
+ for k, v := range m {
+ val[i][k] = v
+ }
+ }
+ }
+ }
+ return &MapStringInterfaceSliceCmd{
+ baseCmd: cmd.cloneBaseCmd(),
+ val: val,
+ }
+}
+
//------------------------------------------------------------------------------
type KeyValuesCmd struct {
@@ -3792,8 +5502,9 @@ var _ Cmder = (*KeyValuesCmd)(nil)
func NewKeyValuesCmd(ctx context.Context, args ...interface{}) *KeyValuesCmd {
return &KeyValuesCmd{
baseCmd: baseCmd{
- ctx: ctx,
- args: args,
+ ctx: ctx,
+ args: args,
+ cmdType: CmdTypeKeyValues,
},
}
}
@@ -3840,6 +5551,19 @@ func (cmd *KeyValuesCmd) readReply(rd *proto.Reader) (err error) {
return nil
}
+func (cmd *KeyValuesCmd) Clone() Cmder {
+ var val []string
+ if cmd.val != nil {
+ val = make([]string, len(cmd.val))
+ copy(val, cmd.val)
+ }
+ return &KeyValuesCmd{
+ baseCmd: cmd.cloneBaseCmd(),
+ key: cmd.key,
+ val: val,
+ }
+}
+
//------------------------------------------------------------------------------
type ZSliceWithKeyCmd struct {
@@ -3854,8 +5578,9 @@ var _ Cmder = (*ZSliceWithKeyCmd)(nil)
func NewZSliceWithKeyCmd(ctx context.Context, args ...interface{}) *ZSliceWithKeyCmd {
return &ZSliceWithKeyCmd{
baseCmd: baseCmd{
- ctx: ctx,
- args: args,
+ ctx: ctx,
+ args: args,
+ cmdType: CmdTypeZSliceWithKey,
},
}
}
@@ -3923,6 +5648,19 @@ func (cmd *ZSliceWithKeyCmd) readReply(rd *proto.Reader) (err error) {
return nil
}
+func (cmd *ZSliceWithKeyCmd) Clone() Cmder {
+ var val []Z
+ if cmd.val != nil {
+ val = make([]Z, len(cmd.val))
+ copy(val, cmd.val)
+ }
+ return &ZSliceWithKeyCmd{
+ baseCmd: cmd.cloneBaseCmd(),
+ key: cmd.key,
+ val: val,
+ }
+}
+
type Function struct {
Name string
Description string
@@ -3947,8 +5685,9 @@ var _ Cmder = (*FunctionListCmd)(nil)
func NewFunctionListCmd(ctx context.Context, args ...interface{}) *FunctionListCmd {
return &FunctionListCmd{
baseCmd: baseCmd{
- ctx: ctx,
- args: args,
+ ctx: ctx,
+ args: args,
+ cmdType: CmdTypeFunctionList,
},
}
}
@@ -4075,6 +5814,37 @@ func (cmd *FunctionListCmd) readFunctions(rd *proto.Reader) ([]Function, error)
return functions, nil
}
+func (cmd *FunctionListCmd) Clone() Cmder {
+ var val []Library
+ if cmd.val != nil {
+ val = make([]Library, len(cmd.val))
+ for i, lib := range cmd.val {
+ val[i] = Library{
+ Name: lib.Name,
+ Engine: lib.Engine,
+ Code: lib.Code,
+ }
+ if lib.Functions != nil {
+ val[i].Functions = make([]Function, len(lib.Functions))
+ for j, fn := range lib.Functions {
+ val[i].Functions[j] = Function{
+ Name: fn.Name,
+ Description: fn.Description,
+ }
+ if fn.Flags != nil {
+ val[i].Functions[j].Flags = make([]string, len(fn.Flags))
+ copy(val[i].Functions[j].Flags, fn.Flags)
+ }
+ }
+ }
+ }
+ }
+ return &FunctionListCmd{
+ baseCmd: cmd.cloneBaseCmd(),
+ val: val,
+ }
+}
+
// FunctionStats contains information about the scripts currently executing on the server, and the available engines
// - Engines:
// Statistics about the engine like number of functions and number of libraries
@@ -4128,8 +5898,9 @@ var _ Cmder = (*FunctionStatsCmd)(nil)
func NewFunctionStatsCmd(ctx context.Context, args ...interface{}) *FunctionStatsCmd {
return &FunctionStatsCmd{
baseCmd: baseCmd{
- ctx: ctx,
- args: args,
+ ctx: ctx,
+ args: args,
+ cmdType: CmdTypeFunctionStats,
},
}
}
@@ -4265,7 +6036,6 @@ func (cmd *FunctionStatsCmd) readDuration(rd *proto.Reader) (time.Duration, erro
}
func (cmd *FunctionStatsCmd) readCommand(rd *proto.Reader) ([]string, error) {
-
n, err := rd.ReadArrayLen()
if err != nil {
return nil, err
@@ -4282,6 +6052,7 @@ func (cmd *FunctionStatsCmd) readCommand(rd *proto.Reader) ([]string, error) {
return command, nil
}
+
func (cmd *FunctionStatsCmd) readRunningScripts(rd *proto.Reader) ([]RunningScript, bool, error) {
n, err := rd.ReadArrayLen()
if err != nil {
@@ -4300,6 +6071,34 @@ func (cmd *FunctionStatsCmd) readRunningScripts(rd *proto.Reader) ([]RunningScri
return runningScripts, len(runningScripts) > 0, nil
}
+func (cmd *FunctionStatsCmd) Clone() Cmder {
+ val := FunctionStats{
+ isRunning: cmd.val.isRunning,
+ rs: cmd.val.rs, // RunningScript is a simple struct, can be copied directly
+ }
+ if cmd.val.Engines != nil {
+ val.Engines = make([]Engine, len(cmd.val.Engines))
+ copy(val.Engines, cmd.val.Engines)
+ }
+ if cmd.val.allrs != nil {
+ val.allrs = make([]RunningScript, len(cmd.val.allrs))
+ for i, rs := range cmd.val.allrs {
+ val.allrs[i] = RunningScript{
+ Name: rs.Name,
+ Duration: rs.Duration,
+ }
+ if rs.Command != nil {
+ val.allrs[i].Command = make([]string, len(rs.Command))
+ copy(val.allrs[i].Command, rs.Command)
+ }
+ }
+ }
+ return &FunctionStatsCmd{
+ baseCmd: cmd.cloneBaseCmd(),
+ val: val,
+ }
+}
+
//------------------------------------------------------------------------------
// LCSQuery is a parameter used for the LCS command
@@ -4363,8 +6162,9 @@ func NewLCSCmd(ctx context.Context, q *LCSQuery) *LCSCmd {
}
}
cmd.baseCmd = baseCmd{
- ctx: ctx,
- args: args,
+ ctx: ctx,
+ args: args,
+ cmdType: CmdTypeLCS,
}
return cmd
@@ -4476,6 +6276,25 @@ func (cmd *LCSCmd) readPosition(rd *proto.Reader) (pos LCSPosition, err error) {
return pos, nil
}
+func (cmd *LCSCmd) Clone() Cmder {
+ var val *LCSMatch
+ if cmd.val != nil {
+ val = &LCSMatch{
+ MatchString: cmd.val.MatchString,
+ Len: cmd.val.Len,
+ }
+ if cmd.val.Matches != nil {
+ val.Matches = make([]LCSMatchedPosition, len(cmd.val.Matches))
+ copy(val.Matches, cmd.val.Matches)
+ }
+ }
+ return &LCSCmd{
+ baseCmd: cmd.cloneBaseCmd(),
+ readType: cmd.readType,
+ val: val,
+ }
+}
+
// ------------------------------------------------------------------------
type KeyFlags struct {
@@ -4494,8 +6313,9 @@ var _ Cmder = (*KeyFlagsCmd)(nil)
func NewKeyFlagsCmd(ctx context.Context, args ...interface{}) *KeyFlagsCmd {
return &KeyFlagsCmd{
baseCmd: baseCmd{
- ctx: ctx,
- args: args,
+ ctx: ctx,
+ args: args,
+ cmdType: CmdTypeKeyFlags,
},
}
}
@@ -4554,6 +6374,26 @@ func (cmd *KeyFlagsCmd) readReply(rd *proto.Reader) error {
return nil
}
+func (cmd *KeyFlagsCmd) Clone() Cmder {
+ var val []KeyFlags
+ if cmd.val != nil {
+ val = make([]KeyFlags, len(cmd.val))
+ for i, kf := range cmd.val {
+ val[i] = KeyFlags{
+ Key: kf.Key,
+ }
+ if kf.Flags != nil {
+ val[i].Flags = make([]string, len(kf.Flags))
+ copy(val[i].Flags, kf.Flags)
+ }
+ }
+ }
+ return &KeyFlagsCmd{
+ baseCmd: cmd.cloneBaseCmd(),
+ val: val,
+ }
+}
+
// ---------------------------------------------------------------------------------------------------
type ClusterLink struct {
@@ -4576,8 +6416,9 @@ var _ Cmder = (*ClusterLinksCmd)(nil)
func NewClusterLinksCmd(ctx context.Context, args ...interface{}) *ClusterLinksCmd {
return &ClusterLinksCmd{
baseCmd: baseCmd{
- ctx: ctx,
- args: args,
+ ctx: ctx,
+ args: args,
+ cmdType: CmdTypeClusterLinks,
},
}
}
@@ -4591,7 +6432,7 @@ func (cmd *ClusterLinksCmd) Val() []ClusterLink {
}
func (cmd *ClusterLinksCmd) Result() ([]ClusterLink, error) {
- return cmd.Val(), cmd.Err()
+ return cmd.val, cmd.err
}
func (cmd *ClusterLinksCmd) String() string {
@@ -4643,6 +6484,18 @@ func (cmd *ClusterLinksCmd) readReply(rd *proto.Reader) error {
return nil
}
+func (cmd *ClusterLinksCmd) Clone() Cmder {
+ var val []ClusterLink
+ if cmd.val != nil {
+ val = make([]ClusterLink, len(cmd.val))
+ copy(val, cmd.val)
+ }
+ return &ClusterLinksCmd{
+ baseCmd: cmd.cloneBaseCmd(),
+ val: val,
+ }
+}
+
// ------------------------------------------------------------------------------------------------------------------
type SlotRange struct {
@@ -4678,8 +6531,9 @@ var _ Cmder = (*ClusterShardsCmd)(nil)
func NewClusterShardsCmd(ctx context.Context, args ...interface{}) *ClusterShardsCmd {
return &ClusterShardsCmd{
baseCmd: baseCmd{
- ctx: ctx,
- args: args,
+ ctx: ctx,
+ args: args,
+ cmdType: CmdTypeClusterShards,
},
}
}
@@ -4693,7 +6547,7 @@ func (cmd *ClusterShardsCmd) Val() []ClusterShard {
}
func (cmd *ClusterShardsCmd) Result() ([]ClusterShard, error) {
- return cmd.Val(), cmd.Err()
+ return cmd.val, cmd.err
}
func (cmd *ClusterShardsCmd) String() string {
@@ -4793,6 +6647,28 @@ func (cmd *ClusterShardsCmd) readReply(rd *proto.Reader) error {
return nil
}
+func (cmd *ClusterShardsCmd) Clone() Cmder {
+ var val []ClusterShard
+ if cmd.val != nil {
+ val = make([]ClusterShard, len(cmd.val))
+ for i, shard := range cmd.val {
+ val[i] = ClusterShard{}
+ if shard.Slots != nil {
+ val[i].Slots = make([]SlotRange, len(shard.Slots))
+ copy(val[i].Slots, shard.Slots)
+ }
+ if shard.Nodes != nil {
+ val[i].Nodes = make([]Node, len(shard.Nodes))
+ copy(val[i].Nodes, shard.Nodes)
+ }
+ }
+ }
+ return &ClusterShardsCmd{
+ baseCmd: cmd.cloneBaseCmd(),
+ val: val,
+ }
+}
+
// -----------------------------------------
type RankScore struct {
@@ -4811,8 +6687,9 @@ var _ Cmder = (*RankWithScoreCmd)(nil)
func NewRankWithScoreCmd(ctx context.Context, args ...interface{}) *RankWithScoreCmd {
return &RankWithScoreCmd{
baseCmd: baseCmd{
- ctx: ctx,
- args: args,
+ ctx: ctx,
+ args: args,
+ cmdType: CmdTypeRankWithScore,
},
}
}
@@ -4853,6 +6730,13 @@ func (cmd *RankWithScoreCmd) readReply(rd *proto.Reader) error {
return nil
}
+func (cmd *RankWithScoreCmd) Clone() Cmder {
+ return &RankWithScoreCmd{
+ baseCmd: cmd.cloneBaseCmd(),
+ val: cmd.val, // RankScore is a simple struct, can be copied directly
+ }
+}
+
// --------------------------------------------------------------------------------------------------
// ClientFlags is redis-server client flags, copy from redis/src/server.h (redis 7.0)
@@ -4927,6 +6811,7 @@ type ClientInfo struct {
PSub int // number of pattern matching subscriptions
SSub int // redis version 7.0.3, number of shard channel subscriptions
Multi int // number of commands in a MULTI/EXEC context
+ Watch int // redis version 7.4 RC1, number of keys this client is currently watching.
QueryBuf int // qbuf, query buffer length (0 means no query pending)
QueryBufFree int // qbuf-free, free space of the query buffer (0 means the buffer is full)
ArgvMem int // incomplete arguments for the next command (already extracted from query buffer)
@@ -4937,6 +6822,10 @@ type ClientInfo struct {
OutputListLength int // oll, output list length (replies are queued in this list when the buffer is full)
OutputMemory int // omem, output buffer memory usage
TotalMemory int // tot-mem, total memory consumed by this client in its various buffers
+ TotalNetIn int // tot-net-in, total network input
+ TotalNetOut int // tot-net-out, total network output
+ TotalCmds int // tot-cmds, total number of commands processed
+ IoThread int // io-thread id
Events string // file descriptor events (see below)
LastCmd string // cmd, last command played
User string // the authenticated username of the client
@@ -4957,8 +6846,9 @@ var _ Cmder = (*ClientInfoCmd)(nil)
func NewClientInfoCmd(ctx context.Context, args ...interface{}) *ClientInfoCmd {
return &ClientInfoCmd{
baseCmd: baseCmd{
- ctx: ctx,
- args: args,
+ ctx: ctx,
+ args: args,
+ cmdType: CmdTypeClientInfo,
},
}
}
@@ -5079,6 +6969,8 @@ func parseClientInfo(txt string) (info *ClientInfo, err error) {
info.SSub, err = strconv.Atoi(val)
case "multi":
info.Multi, err = strconv.Atoi(val)
+ case "watch":
+ info.Watch, err = strconv.Atoi(val)
case "qbuf":
info.QueryBuf, err = strconv.Atoi(val)
case "qbuf-free":
@@ -5099,6 +6991,12 @@ func parseClientInfo(txt string) (info *ClientInfo, err error) {
info.OutputMemory, err = strconv.Atoi(val)
case "tot-mem":
info.TotalMemory, err = strconv.Atoi(val)
+ case "tot-net-in":
+ info.TotalNetIn, err = strconv.Atoi(val)
+ case "tot-net-out":
+ info.TotalNetOut, err = strconv.Atoi(val)
+ case "tot-cmds":
+ info.TotalCmds, err = strconv.Atoi(val)
case "events":
info.Events = val
case "cmd":
@@ -5113,6 +7011,8 @@ func parseClientInfo(txt string) (info *ClientInfo, err error) {
info.LibName = val
case "lib-ver":
info.LibVer = val
+ case "io-thread":
+ info.IoThread, err = strconv.Atoi(val)
default:
return nil, fmt.Errorf("redis: unexpected client info key(%s)", key)
}
@@ -5125,6 +7025,50 @@ func parseClientInfo(txt string) (info *ClientInfo, err error) {
return info, nil
}
+func (cmd *ClientInfoCmd) Clone() Cmder {
+ var val *ClientInfo
+ if cmd.val != nil {
+ val = &ClientInfo{
+ ID: cmd.val.ID,
+ Addr: cmd.val.Addr,
+ LAddr: cmd.val.LAddr,
+ FD: cmd.val.FD,
+ Name: cmd.val.Name,
+ Age: cmd.val.Age,
+ Idle: cmd.val.Idle,
+ Flags: cmd.val.Flags,
+ DB: cmd.val.DB,
+ Sub: cmd.val.Sub,
+ PSub: cmd.val.PSub,
+ SSub: cmd.val.SSub,
+ Multi: cmd.val.Multi,
+ Watch: cmd.val.Watch,
+ QueryBuf: cmd.val.QueryBuf,
+ QueryBufFree: cmd.val.QueryBufFree,
+ ArgvMem: cmd.val.ArgvMem,
+ MultiMem: cmd.val.MultiMem,
+ BufferSize: cmd.val.BufferSize,
+ BufferPeak: cmd.val.BufferPeak,
+ OutputBufferLength: cmd.val.OutputBufferLength,
+ OutputListLength: cmd.val.OutputListLength,
+ OutputMemory: cmd.val.OutputMemory,
+ TotalMemory: cmd.val.TotalMemory,
+ IoThread: cmd.val.IoThread,
+ Events: cmd.val.Events,
+ LastCmd: cmd.val.LastCmd,
+ User: cmd.val.User,
+ Redir: cmd.val.Redir,
+ Resp: cmd.val.Resp,
+ LibName: cmd.val.LibName,
+ LibVer: cmd.val.LibVer,
+ }
+ }
+ return &ClientInfoCmd{
+ baseCmd: cmd.cloneBaseCmd(),
+ val: val,
+ }
+}
+
// -------------------------------------------
type ACLLogEntry struct {
@@ -5151,8 +7095,9 @@ var _ Cmder = (*ACLLogCmd)(nil)
func NewACLLogCmd(ctx context.Context, args ...interface{}) *ACLLogCmd {
return &ACLLogCmd{
baseCmd: baseCmd{
- ctx: ctx,
- args: args,
+ ctx: ctx,
+ args: args,
+ cmdType: CmdTypeACLLog,
},
}
}
@@ -5166,7 +7111,7 @@ func (cmd *ACLLogCmd) Val() []*ACLLogEntry {
}
func (cmd *ACLLogCmd) Result() ([]*ACLLogEntry, error) {
- return cmd.Val(), cmd.Err()
+ return cmd.val, cmd.err
}
func (cmd *ACLLogCmd) String() string {
@@ -5233,3 +7178,850 @@ func (cmd *ACLLogCmd) readReply(rd *proto.Reader) error {
return nil
}
+
+func (cmd *ACLLogCmd) Clone() Cmder {
+ var val []*ACLLogEntry
+ if cmd.val != nil {
+ val = make([]*ACLLogEntry, len(cmd.val))
+ for i, entry := range cmd.val {
+ if entry != nil {
+ val[i] = &ACLLogEntry{
+ Count: entry.Count,
+ Reason: entry.Reason,
+ Context: entry.Context,
+ Object: entry.Object,
+ Username: entry.Username,
+ AgeSeconds: entry.AgeSeconds,
+ EntryID: entry.EntryID,
+ TimestampCreated: entry.TimestampCreated,
+ TimestampLastUpdated: entry.TimestampLastUpdated,
+ }
+ // Clone ClientInfo if present
+ if entry.ClientInfo != nil {
+ val[i].ClientInfo = &ClientInfo{
+ ID: entry.ClientInfo.ID,
+ Addr: entry.ClientInfo.Addr,
+ LAddr: entry.ClientInfo.LAddr,
+ FD: entry.ClientInfo.FD,
+ Name: entry.ClientInfo.Name,
+ Age: entry.ClientInfo.Age,
+ Idle: entry.ClientInfo.Idle,
+ Flags: entry.ClientInfo.Flags,
+ DB: entry.ClientInfo.DB,
+ Sub: entry.ClientInfo.Sub,
+ PSub: entry.ClientInfo.PSub,
+ SSub: entry.ClientInfo.SSub,
+ Multi: entry.ClientInfo.Multi,
+ Watch: entry.ClientInfo.Watch,
+ QueryBuf: entry.ClientInfo.QueryBuf,
+ QueryBufFree: entry.ClientInfo.QueryBufFree,
+ ArgvMem: entry.ClientInfo.ArgvMem,
+ MultiMem: entry.ClientInfo.MultiMem,
+ BufferSize: entry.ClientInfo.BufferSize,
+ BufferPeak: entry.ClientInfo.BufferPeak,
+ OutputBufferLength: entry.ClientInfo.OutputBufferLength,
+ OutputListLength: entry.ClientInfo.OutputListLength,
+ OutputMemory: entry.ClientInfo.OutputMemory,
+ TotalMemory: entry.ClientInfo.TotalMemory,
+ IoThread: entry.ClientInfo.IoThread,
+ Events: entry.ClientInfo.Events,
+ LastCmd: entry.ClientInfo.LastCmd,
+ User: entry.ClientInfo.User,
+ Redir: entry.ClientInfo.Redir,
+ Resp: entry.ClientInfo.Resp,
+ LibName: entry.ClientInfo.LibName,
+ LibVer: entry.ClientInfo.LibVer,
+ }
+ }
+ }
+ }
+ }
+ return &ACLLogCmd{
+ baseCmd: cmd.cloneBaseCmd(),
+ val: val,
+ }
+}
+
+// LibraryInfo holds the library info.
+type LibraryInfo struct {
+ LibName *string
+ LibVer *string
+}
+
+// WithLibraryName returns a valid LibraryInfo with library name only.
+func WithLibraryName(libName string) LibraryInfo {
+ return LibraryInfo{LibName: &libName}
+}
+
+// WithLibraryVersion returns a valid LibraryInfo with library version only.
+func WithLibraryVersion(libVer string) LibraryInfo {
+ return LibraryInfo{LibVer: &libVer}
+}
+
+// -------------------------------------------
+
+type InfoCmd struct {
+ baseCmd
+ val map[string]map[string]string
+}
+
+var _ Cmder = (*InfoCmd)(nil)
+
+func NewInfoCmd(ctx context.Context, args ...interface{}) *InfoCmd {
+ return &InfoCmd{
+ baseCmd: baseCmd{
+ ctx: ctx,
+ args: args,
+ cmdType: CmdTypeInfo,
+ },
+ }
+}
+
+func (cmd *InfoCmd) SetVal(val map[string]map[string]string) {
+ cmd.val = val
+}
+
+func (cmd *InfoCmd) Val() map[string]map[string]string {
+ return cmd.val
+}
+
+func (cmd *InfoCmd) Result() (map[string]map[string]string, error) {
+ return cmd.val, cmd.err
+}
+
+func (cmd *InfoCmd) String() string {
+ return cmdString(cmd, cmd.val)
+}
+
+func (cmd *InfoCmd) readReply(rd *proto.Reader) error {
+ val, err := rd.ReadString()
+ if err != nil {
+ return err
+ }
+
+ section := ""
+ scanner := bufio.NewScanner(strings.NewReader(val))
+ for scanner.Scan() {
+ line := scanner.Text()
+ if strings.HasPrefix(line, "#") {
+ if cmd.val == nil {
+ cmd.val = make(map[string]map[string]string)
+ }
+ section = strings.TrimPrefix(line, "# ")
+ cmd.val[section] = make(map[string]string)
+ } else if line != "" {
+ if section == "Modules" {
+ moduleRe := regexp.MustCompile(`module:name=(.+?),(.+)$`)
+ kv := moduleRe.FindStringSubmatch(line)
+ if len(kv) == 3 {
+ cmd.val[section][kv[1]] = kv[2]
+ }
+ } else {
+ kv := strings.SplitN(line, ":", 2)
+ if len(kv) == 2 {
+ cmd.val[section][kv[0]] = kv[1]
+ }
+ }
+ }
+ }
+
+ return nil
+}
+
+func (cmd *InfoCmd) Item(section, key string) string {
+ if cmd.val == nil {
+ return ""
+ } else if cmd.val[section] == nil {
+ return ""
+ } else {
+ return cmd.val[section][key]
+ }
+}
+
+func (cmd *InfoCmd) Clone() Cmder {
+ var val map[string]map[string]string
+ if cmd.val != nil {
+ val = make(map[string]map[string]string, len(cmd.val))
+ for section, sectionMap := range cmd.val {
+ if sectionMap != nil {
+ val[section] = make(map[string]string, len(sectionMap))
+ for k, v := range sectionMap {
+ val[section][k] = v
+ }
+ }
+ }
+ }
+ return &InfoCmd{
+ baseCmd: cmd.cloneBaseCmd(),
+ val: val,
+ }
+}
+
+type MonitorStatus int
+
+const (
+ monitorStatusIdle MonitorStatus = iota
+ monitorStatusStart
+ monitorStatusStop
+)
+
+type MonitorCmd struct {
+ baseCmd
+ ch chan string
+ status MonitorStatus
+ mu sync.Mutex
+}
+
+func newMonitorCmd(ctx context.Context, ch chan string) *MonitorCmd {
+ return &MonitorCmd{
+ baseCmd: baseCmd{
+ ctx: ctx,
+ args: []interface{}{"monitor"},
+ cmdType: CmdTypeMonitor,
+ },
+ ch: ch,
+ status: monitorStatusIdle,
+ mu: sync.Mutex{},
+ }
+}
+
+func (cmd *MonitorCmd) String() string {
+ return cmdString(cmd, nil)
+}
+
+func (cmd *MonitorCmd) readReply(rd *proto.Reader) error {
+ ctx, cancel := context.WithCancel(cmd.ctx)
+ go func(ctx context.Context) {
+ for {
+ select {
+ case <-ctx.Done():
+ return
+ default:
+ err := cmd.readMonitor(rd, cancel)
+ if err != nil {
+ cmd.err = err
+ return
+ }
+ }
+ }
+ }(ctx)
+ return nil
+}
+
+func (cmd *MonitorCmd) readMonitor(rd *proto.Reader, cancel context.CancelFunc) error {
+ for {
+ cmd.mu.Lock()
+ st := cmd.status
+ pk, _ := rd.Peek(1)
+ cmd.mu.Unlock()
+ if len(pk) != 0 && st == monitorStatusStart {
+ cmd.mu.Lock()
+ line, err := rd.ReadString()
+ cmd.mu.Unlock()
+ if err != nil {
+ return err
+ }
+ cmd.ch <- line
+ }
+ if st == monitorStatusStop {
+ cancel()
+ break
+ }
+ }
+ return nil
+}
+
+func (cmd *MonitorCmd) Start() {
+ cmd.mu.Lock()
+ defer cmd.mu.Unlock()
+ cmd.status = monitorStatusStart
+}
+
+func (cmd *MonitorCmd) Stop() {
+ cmd.mu.Lock()
+ defer cmd.mu.Unlock()
+ cmd.status = monitorStatusStop
+}
+
+type VectorScoreSliceCmd struct {
+ baseCmd
+
+ val []VectorScore
+}
+
+var _ Cmder = (*VectorScoreSliceCmd)(nil)
+
+func NewVectorInfoSliceCmd(ctx context.Context, args ...any) *VectorScoreSliceCmd {
+ return &VectorScoreSliceCmd{
+ baseCmd: baseCmd{
+ ctx: ctx,
+ args: args,
+ },
+ }
+}
+
+func (cmd *VectorScoreSliceCmd) SetVal(val []VectorScore) {
+ cmd.val = val
+}
+
+func (cmd *VectorScoreSliceCmd) Val() []VectorScore {
+ return cmd.val
+}
+
+func (cmd *VectorScoreSliceCmd) Result() ([]VectorScore, error) {
+ return cmd.val, cmd.err
+}
+
+func (cmd *VectorScoreSliceCmd) String() string {
+ return cmdString(cmd, cmd.val)
+}
+
+func (cmd *VectorScoreSliceCmd) readReply(rd *proto.Reader) error {
+ n, err := rd.ReadMapLen()
+ if err != nil {
+ return err
+ }
+
+ cmd.val = make([]VectorScore, n)
+ for i := 0; i < n; i++ {
+ name, err := rd.ReadString()
+ if err != nil {
+ return err
+ }
+ cmd.val[i].Name = name
+
+ score, err := rd.ReadFloat()
+ if err != nil {
+ return err
+ }
+ cmd.val[i].Score = score
+ }
+
+ return nil
+}
+
+func (cmd *VectorScoreSliceCmd) Clone() Cmder {
+ return &VectorScoreSliceCmd{
+ baseCmd: cmd.cloneBaseCmd(),
+ val: cmd.val,
+ }
+}
+
+func (cmd *MonitorCmd) Clone() Cmder {
+ // MonitorCmd cannot be safely cloned due to channels and goroutines
+ // Return a new MonitorCmd with the same channel
+ return newMonitorCmd(cmd.ctx, cmd.ch)
+}
+
+// ExtractCommandValue extracts the value from a command result using the fast enum-based approach
+func ExtractCommandValue(cmd interface{}) (interface{}, error) {
+ // First try to get the command type using the interface
+ if cmdTypeGetter, ok := cmd.(CmdTypeGetter); ok {
+ cmdType := cmdTypeGetter.GetCmdType()
+
+ // Use fast type-based extraction
+ switch cmdType {
+ case CmdTypeGeneric:
+ if genericCmd, ok := cmd.(interface {
+ Val() interface{}
+ Err() error
+ }); ok {
+ return genericCmd.Val(), genericCmd.Err()
+ }
+ case CmdTypeString:
+ if stringCmd, ok := cmd.(interface {
+ Val() string
+ Err() error
+ }); ok {
+ return stringCmd.Val(), stringCmd.Err()
+ }
+ case CmdTypeInt:
+ if intCmd, ok := cmd.(interface {
+ Val() int64
+ Err() error
+ }); ok {
+ return intCmd.Val(), intCmd.Err()
+ }
+ case CmdTypeBool:
+ if boolCmd, ok := cmd.(interface {
+ Val() bool
+ Err() error
+ }); ok {
+ return boolCmd.Val(), boolCmd.Err()
+ }
+ case CmdTypeFloat:
+ if floatCmd, ok := cmd.(interface {
+ Val() float64
+ Err() error
+ }); ok {
+ return floatCmd.Val(), floatCmd.Err()
+ }
+ case CmdTypeStatus:
+ if statusCmd, ok := cmd.(interface {
+ Val() string
+ Err() error
+ }); ok {
+ return statusCmd.Val(), statusCmd.Err()
+ }
+ case CmdTypeDuration:
+ if durationCmd, ok := cmd.(interface {
+ Val() time.Duration
+ Err() error
+ }); ok {
+ return durationCmd.Val(), durationCmd.Err()
+ }
+ case CmdTypeTime:
+ if timeCmd, ok := cmd.(interface {
+ Val() time.Time
+ Err() error
+ }); ok {
+ return timeCmd.Val(), timeCmd.Err()
+ }
+ case CmdTypeStringStructMap:
+ if structMapCmd, ok := cmd.(interface {
+ Val() map[string]struct{}
+ Err() error
+ }); ok {
+ return structMapCmd.Val(), structMapCmd.Err()
+ }
+ case CmdTypeXMessageSlice:
+ if xMessageSliceCmd, ok := cmd.(interface {
+ Val() []XMessage
+ Err() error
+ }); ok {
+ return xMessageSliceCmd.Val(), xMessageSliceCmd.Err()
+ }
+ case CmdTypeXStreamSlice:
+ if xStreamSliceCmd, ok := cmd.(interface {
+ Val() []XStream
+ Err() error
+ }); ok {
+ return xStreamSliceCmd.Val(), xStreamSliceCmd.Err()
+ }
+ case CmdTypeXPending:
+ if xPendingCmd, ok := cmd.(interface {
+ Val() *XPending
+ Err() error
+ }); ok {
+ return xPendingCmd.Val(), xPendingCmd.Err()
+ }
+ case CmdTypeXPendingExt:
+ if xPendingExtCmd, ok := cmd.(interface {
+ Val() []XPendingExt
+ Err() error
+ }); ok {
+ return xPendingExtCmd.Val(), xPendingExtCmd.Err()
+ }
+ case CmdTypeXAutoClaim:
+ if xAutoClaimCmd, ok := cmd.(interface {
+ Val() ([]XMessage, string)
+ Err() error
+ }); ok {
+ messages, start := xAutoClaimCmd.Val()
+ return CmdTypeXAutoClaimValue{messages: messages, start: start}, xAutoClaimCmd.Err()
+ }
+ case CmdTypeXAutoClaimJustID:
+ if xAutoClaimJustIDCmd, ok := cmd.(interface {
+ Val() ([]string, string)
+ Err() error
+ }); ok {
+ ids, start := xAutoClaimJustIDCmd.Val()
+ return CmdTypeXAutoClaimJustIDValue{ids: ids, start: start}, xAutoClaimJustIDCmd.Err()
+ }
+ case CmdTypeXInfoConsumers:
+ if xInfoConsumersCmd, ok := cmd.(interface {
+ Val() []XInfoConsumer
+ Err() error
+ }); ok {
+ return xInfoConsumersCmd.Val(), xInfoConsumersCmd.Err()
+ }
+ case CmdTypeXInfoGroups:
+ if xInfoGroupsCmd, ok := cmd.(interface {
+ Val() []XInfoGroup
+ Err() error
+ }); ok {
+ return xInfoGroupsCmd.Val(), xInfoGroupsCmd.Err()
+ }
+ case CmdTypeXInfoStream:
+ if xInfoStreamCmd, ok := cmd.(interface {
+ Val() *XInfoStream
+ Err() error
+ }); ok {
+ return xInfoStreamCmd.Val(), xInfoStreamCmd.Err()
+ }
+ case CmdTypeXInfoStreamFull:
+ if xInfoStreamFullCmd, ok := cmd.(interface {
+ Val() *XInfoStreamFull
+ Err() error
+ }); ok {
+ return xInfoStreamFullCmd.Val(), xInfoStreamFullCmd.Err()
+ }
+ case CmdTypeZSlice:
+ if zSliceCmd, ok := cmd.(interface {
+ Val() []Z
+ Err() error
+ }); ok {
+ return zSliceCmd.Val(), zSliceCmd.Err()
+ }
+ case CmdTypeZWithKey:
+ if zWithKeyCmd, ok := cmd.(interface {
+ Val() *ZWithKey
+ Err() error
+ }); ok {
+ return zWithKeyCmd.Val(), zWithKeyCmd.Err()
+ }
+ case CmdTypeScan:
+ if scanCmd, ok := cmd.(interface {
+ Val() ([]string, uint64)
+ Err() error
+ }); ok {
+ keys, cursor := scanCmd.Val()
+ return CmdTypeScanValue{keys: keys, cursor: cursor}, scanCmd.Err()
+ }
+ case CmdTypeClusterSlots:
+ if clusterSlotsCmd, ok := cmd.(interface {
+ Val() []ClusterSlot
+ Err() error
+ }); ok {
+ return clusterSlotsCmd.Val(), clusterSlotsCmd.Err()
+ }
+ case CmdTypeGeoLocation:
+ if geoLocationCmd, ok := cmd.(interface {
+ Val() []GeoLocation
+ Err() error
+ }); ok {
+ return geoLocationCmd.Val(), geoLocationCmd.Err()
+ }
+ case CmdTypeGeoSearchLocation:
+ if geoSearchLocationCmd, ok := cmd.(interface {
+ Val() []GeoLocation
+ Err() error
+ }); ok {
+ return geoSearchLocationCmd.Val(), geoSearchLocationCmd.Err()
+ }
+ case CmdTypeGeoPos:
+ if geoPosCmd, ok := cmd.(interface {
+ Val() []*GeoPos
+ Err() error
+ }); ok {
+ return geoPosCmd.Val(), geoPosCmd.Err()
+ }
+ case CmdTypeCommandsInfo:
+ if commandsInfoCmd, ok := cmd.(interface {
+ Val() map[string]*CommandInfo
+ Err() error
+ }); ok {
+ return commandsInfoCmd.Val(), commandsInfoCmd.Err()
+ }
+ case CmdTypeSlowLog:
+ if slowLogCmd, ok := cmd.(interface {
+ Val() []SlowLog
+ Err() error
+ }); ok {
+ return slowLogCmd.Val(), slowLogCmd.Err()
+ }
+ case CmdTypeHotKeys:
+ if hotKeysCmd, ok := cmd.(interface {
+ Val() *HotKeysResult
+ Err() error
+ }); ok {
+ return hotKeysCmd.Val(), hotKeysCmd.Err()
+ }
+ case CmdTypeKeyValues:
+ if keyValuesCmd, ok := cmd.(interface {
+ Val() (string, []string)
+ Err() error
+ }); ok {
+ key, values := keyValuesCmd.Val()
+ return CmdTypeKeyValuesValue{key: key, values: values}, keyValuesCmd.Err()
+ }
+ case CmdTypeZSliceWithKey:
+ if zSliceWithKeyCmd, ok := cmd.(interface {
+ Val() (string, []Z)
+ Err() error
+ }); ok {
+ key, zSlice := zSliceWithKeyCmd.Val()
+ return CmdTypeZSliceWithKeyValue{key: key, zSlice: zSlice}, zSliceWithKeyCmd.Err()
+ }
+ case CmdTypeFunctionList:
+ if functionListCmd, ok := cmd.(interface {
+ Val() []Library
+ Err() error
+ }); ok {
+ return functionListCmd.Val(), functionListCmd.Err()
+ }
+ case CmdTypeFunctionStats:
+ if functionStatsCmd, ok := cmd.(interface {
+ Val() FunctionStats
+ Err() error
+ }); ok {
+ return functionStatsCmd.Val(), functionStatsCmd.Err()
+ }
+ case CmdTypeLCS:
+ if lcsCmd, ok := cmd.(interface {
+ Val() *LCSMatch
+ Err() error
+ }); ok {
+ return lcsCmd.Val(), lcsCmd.Err()
+ }
+ case CmdTypeKeyFlags:
+ if keyFlagsCmd, ok := cmd.(interface {
+ Val() []KeyFlags
+ Err() error
+ }); ok {
+ return keyFlagsCmd.Val(), keyFlagsCmd.Err()
+ }
+ case CmdTypeClusterLinks:
+ if clusterLinksCmd, ok := cmd.(interface {
+ Val() []ClusterLink
+ Err() error
+ }); ok {
+ return clusterLinksCmd.Val(), clusterLinksCmd.Err()
+ }
+ case CmdTypeClusterShards:
+ if clusterShardsCmd, ok := cmd.(interface {
+ Val() []ClusterShard
+ Err() error
+ }); ok {
+ return clusterShardsCmd.Val(), clusterShardsCmd.Err()
+ }
+ case CmdTypeRankWithScore:
+ if rankWithScoreCmd, ok := cmd.(interface {
+ Val() RankScore
+ Err() error
+ }); ok {
+ return rankWithScoreCmd.Val(), rankWithScoreCmd.Err()
+ }
+ case CmdTypeClientInfo:
+ if clientInfoCmd, ok := cmd.(interface {
+ Val() *ClientInfo
+ Err() error
+ }); ok {
+ return clientInfoCmd.Val(), clientInfoCmd.Err()
+ }
+ case CmdTypeACLLog:
+ if aclLogCmd, ok := cmd.(interface {
+ Val() []*ACLLogEntry
+ Err() error
+ }); ok {
+ return aclLogCmd.Val(), aclLogCmd.Err()
+ }
+ case CmdTypeInfo:
+ if infoCmd, ok := cmd.(interface {
+ Val() string
+ Err() error
+ }); ok {
+ return infoCmd.Val(), infoCmd.Err()
+ }
+ case CmdTypeMonitor:
+ if monitorCmd, ok := cmd.(interface {
+ Val() string
+ Err() error
+ }); ok {
+ return monitorCmd.Val(), monitorCmd.Err()
+ }
+ case CmdTypeJSON:
+ if jsonCmd, ok := cmd.(interface {
+ Val() string
+ Err() error
+ }); ok {
+ return jsonCmd.Val(), jsonCmd.Err()
+ }
+ case CmdTypeJSONSlice:
+ if jsonSliceCmd, ok := cmd.(interface {
+ Val() []interface{}
+ Err() error
+ }); ok {
+ return jsonSliceCmd.Val(), jsonSliceCmd.Err()
+ }
+ case CmdTypeIntPointerSlice:
+ if intPointerSliceCmd, ok := cmd.(interface {
+ Val() []*int64
+ Err() error
+ }); ok {
+ return intPointerSliceCmd.Val(), intPointerSliceCmd.Err()
+ }
+ case CmdTypeScanDump:
+ if scanDumpCmd, ok := cmd.(interface {
+ Val() ScanDump
+ Err() error
+ }); ok {
+ return scanDumpCmd.Val(), scanDumpCmd.Err()
+ }
+ case CmdTypeBFInfo:
+ if bfInfoCmd, ok := cmd.(interface {
+ Val() BFInfo
+ Err() error
+ }); ok {
+ return bfInfoCmd.Val(), bfInfoCmd.Err()
+ }
+ case CmdTypeCFInfo:
+ if cfInfoCmd, ok := cmd.(interface {
+ Val() CFInfo
+ Err() error
+ }); ok {
+ return cfInfoCmd.Val(), cfInfoCmd.Err()
+ }
+ case CmdTypeCMSInfo:
+ if cmsInfoCmd, ok := cmd.(interface {
+ Val() CMSInfo
+ Err() error
+ }); ok {
+ return cmsInfoCmd.Val(), cmsInfoCmd.Err()
+ }
+ case CmdTypeTopKInfo:
+ if topKInfoCmd, ok := cmd.(interface {
+ Val() TopKInfo
+ Err() error
+ }); ok {
+ return topKInfoCmd.Val(), topKInfoCmd.Err()
+ }
+ case CmdTypeTDigestInfo:
+ if tDigestInfoCmd, ok := cmd.(interface {
+ Val() TDigestInfo
+ Err() error
+ }); ok {
+ return tDigestInfoCmd.Val(), tDigestInfoCmd.Err()
+ }
+ case CmdTypeFTSearch:
+ if ftSearchCmd, ok := cmd.(interface {
+ Val() FTSearchResult
+ Err() error
+ }); ok {
+ return ftSearchCmd.Val(), ftSearchCmd.Err()
+ }
+ case CmdTypeFTInfo:
+ if ftInfoCmd, ok := cmd.(interface {
+ Val() FTInfoResult
+ Err() error
+ }); ok {
+ return ftInfoCmd.Val(), ftInfoCmd.Err()
+ }
+ case CmdTypeFTSpellCheck:
+ if ftSpellCheckCmd, ok := cmd.(interface {
+ Val() []SpellCheckResult
+ Err() error
+ }); ok {
+ return ftSpellCheckCmd.Val(), ftSpellCheckCmd.Err()
+ }
+ case CmdTypeFTSynDump:
+ if ftSynDumpCmd, ok := cmd.(interface {
+ Val() []FTSynDumpResult
+ Err() error
+ }); ok {
+ return ftSynDumpCmd.Val(), ftSynDumpCmd.Err()
+ }
+ case CmdTypeAggregate:
+ if aggregateCmd, ok := cmd.(interface {
+ Val() *FTAggregateResult
+ Err() error
+ }); ok {
+ return aggregateCmd.Val(), aggregateCmd.Err()
+ }
+ case CmdTypeTSTimestampValue:
+ if tsTimestampValueCmd, ok := cmd.(interface {
+ Val() TSTimestampValue
+ Err() error
+ }); ok {
+ return tsTimestampValueCmd.Val(), tsTimestampValueCmd.Err()
+ }
+ case CmdTypeTSTimestampValueSlice:
+ if tsTimestampValueSliceCmd, ok := cmd.(interface {
+ Val() []TSTimestampValue
+ Err() error
+ }); ok {
+ return tsTimestampValueSliceCmd.Val(), tsTimestampValueSliceCmd.Err()
+ }
+ case CmdTypeStringSlice:
+ if stringSliceCmd, ok := cmd.(interface {
+ Val() []string
+ Err() error
+ }); ok {
+ return stringSliceCmd.Val(), stringSliceCmd.Err()
+ }
+ case CmdTypeIntSlice:
+ if intSliceCmd, ok := cmd.(interface {
+ Val() []int64
+ Err() error
+ }); ok {
+ return intSliceCmd.Val(), intSliceCmd.Err()
+ }
+ case CmdTypeBoolSlice:
+ if boolSliceCmd, ok := cmd.(interface {
+ Val() []bool
+ Err() error
+ }); ok {
+ return boolSliceCmd.Val(), boolSliceCmd.Err()
+ }
+ case CmdTypeFloatSlice:
+ if floatSliceCmd, ok := cmd.(interface {
+ Val() []float64
+ Err() error
+ }); ok {
+ return floatSliceCmd.Val(), floatSliceCmd.Err()
+ }
+ case CmdTypeSlice:
+ if sliceCmd, ok := cmd.(interface {
+ Val() []interface{}
+ Err() error
+ }); ok {
+ return sliceCmd.Val(), sliceCmd.Err()
+ }
+ case CmdTypeKeyValueSlice:
+ if keyValueSliceCmd, ok := cmd.(interface {
+ Val() []KeyValue
+ Err() error
+ }); ok {
+ return keyValueSliceCmd.Val(), keyValueSliceCmd.Err()
+ }
+ case CmdTypeMapStringString:
+ if mapCmd, ok := cmd.(interface {
+ Val() map[string]string
+ Err() error
+ }); ok {
+ return mapCmd.Val(), mapCmd.Err()
+ }
+ case CmdTypeMapStringInt:
+ if mapCmd, ok := cmd.(interface {
+ Val() map[string]int64
+ Err() error
+ }); ok {
+ return mapCmd.Val(), mapCmd.Err()
+ }
+ case CmdTypeMapStringInterfaceSlice:
+ if mapCmd, ok := cmd.(interface {
+ Val() []map[string]interface{}
+ Err() error
+ }); ok {
+ return mapCmd.Val(), mapCmd.Err()
+ }
+ case CmdTypeMapStringInterface:
+ if mapCmd, ok := cmd.(interface {
+ Val() map[string]interface{}
+ Err() error
+ }); ok {
+ return mapCmd.Val(), mapCmd.Err()
+ }
+ case CmdTypeMapStringStringSlice:
+ if mapCmd, ok := cmd.(interface {
+ Val() []map[string]string
+ Err() error
+ }); ok {
+ return mapCmd.Val(), mapCmd.Err()
+ }
+ case CmdTypeMapMapStringInterface:
+ if mapCmd, ok := cmd.(interface {
+ Val() map[string]interface{}
+ Err() error
+ }); ok {
+ return mapCmd.Val(), mapCmd.Err()
+ }
+ default:
+ // For unknown command types, return nil
+ return nil, nil
+ }
+ }
+
+ // If we can't get the command type, return nil
+ return nil, nil
+}
diff --git a/vendor/github.com/redis/go-redis/v9/command_policy_resolver.go b/vendor/github.com/redis/go-redis/v9/command_policy_resolver.go
new file mode 100644
index 00000000000..da8c6d314c0
--- /dev/null
+++ b/vendor/github.com/redis/go-redis/v9/command_policy_resolver.go
@@ -0,0 +1,209 @@
+package redis
+
+import (
+ "context"
+ "strings"
+
+ "github.com/redis/go-redis/v9/internal/routing"
+)
+
+type (
+ module = string
+ commandName = string
+)
+
+var defaultPolicies = map[module]map[commandName]*routing.CommandPolicy{
+ "ft": {
+ "create": {
+ Request: routing.ReqDefault,
+ Response: routing.RespDefaultKeyless,
+ },
+ "search": {
+ Request: routing.ReqDefault,
+ Response: routing.RespDefaultKeyless,
+ Tips: map[string]string{
+ routing.ReadOnlyCMD: "",
+ },
+ },
+ "aggregate": {
+ Request: routing.ReqDefault,
+ Response: routing.RespDefaultKeyless,
+ Tips: map[string]string{
+ routing.ReadOnlyCMD: "",
+ },
+ },
+ "dictadd": {
+ Request: routing.ReqDefault,
+ Response: routing.RespDefaultKeyless,
+ },
+ "dictdump": {
+ Request: routing.ReqDefault,
+ Response: routing.RespDefaultKeyless,
+ Tips: map[string]string{
+ routing.ReadOnlyCMD: "",
+ },
+ },
+ "dictdel": {
+ Request: routing.ReqDefault,
+ Response: routing.RespDefaultKeyless,
+ },
+ "suglen": {
+ Request: routing.ReqDefault,
+ Response: routing.RespDefaultHashSlot,
+ Tips: map[string]string{
+ routing.ReadOnlyCMD: "",
+ },
+ },
+ "cursor": {
+ Request: routing.ReqSpecial,
+ Response: routing.RespDefaultKeyless,
+ Tips: map[string]string{
+ routing.ReadOnlyCMD: "",
+ },
+ },
+ "sugadd": {
+ Request: routing.ReqDefault,
+ Response: routing.RespDefaultHashSlot,
+ },
+ "sugget": {
+ Request: routing.ReqDefault,
+ Response: routing.RespDefaultHashSlot,
+ Tips: map[string]string{
+ routing.ReadOnlyCMD: "",
+ },
+ },
+ "sugdel": {
+ Request: routing.ReqDefault,
+ Response: routing.RespDefaultHashSlot,
+ },
+ "spellcheck": {
+ Request: routing.ReqDefault,
+ Response: routing.RespDefaultKeyless,
+ Tips: map[string]string{
+ routing.ReadOnlyCMD: "",
+ },
+ },
+ "explain": {
+ Request: routing.ReqDefault,
+ Response: routing.RespDefaultKeyless,
+ Tips: map[string]string{
+ routing.ReadOnlyCMD: "",
+ },
+ },
+ "explaincli": {
+ Request: routing.ReqDefault,
+ Response: routing.RespDefaultKeyless,
+ Tips: map[string]string{
+ routing.ReadOnlyCMD: "",
+ },
+ },
+ "aliasadd": {
+ Request: routing.ReqDefault,
+ Response: routing.RespDefaultKeyless,
+ },
+ "aliasupdate": {
+ Request: routing.ReqDefault,
+ Response: routing.RespDefaultKeyless,
+ },
+ "aliasdel": {
+ Request: routing.ReqDefault,
+ Response: routing.RespDefaultKeyless,
+ },
+ "info": {
+ Request: routing.ReqDefault,
+ Response: routing.RespDefaultKeyless,
+ Tips: map[string]string{
+ routing.ReadOnlyCMD: "",
+ },
+ },
+ "tagvals": {
+ Request: routing.ReqDefault,
+ Response: routing.RespDefaultKeyless,
+ Tips: map[string]string{
+ routing.ReadOnlyCMD: "",
+ },
+ },
+ "syndump": {
+ Request: routing.ReqDefault,
+ Response: routing.RespDefaultKeyless,
+ Tips: map[string]string{
+ routing.ReadOnlyCMD: "",
+ },
+ },
+ "synupdate": {
+ Request: routing.ReqDefault,
+ Response: routing.RespDefaultKeyless,
+ },
+ "profile": {
+ Request: routing.ReqDefault,
+ Response: routing.RespDefaultKeyless,
+ Tips: map[string]string{
+ routing.ReadOnlyCMD: "",
+ },
+ },
+ "alter": {
+ Request: routing.ReqDefault,
+ Response: routing.RespDefaultKeyless,
+ },
+ "dropindex": {
+ Request: routing.ReqDefault,
+ Response: routing.RespDefaultKeyless,
+ },
+ "drop": {
+ Request: routing.ReqDefault,
+ Response: routing.RespDefaultKeyless,
+ },
+ },
+}
+
+type CommandInfoResolveFunc func(ctx context.Context, cmd Cmder) *routing.CommandPolicy
+
+type commandInfoResolver struct {
+ resolveFunc CommandInfoResolveFunc
+ fallBackResolver *commandInfoResolver
+}
+
+func NewCommandInfoResolver(resolveFunc CommandInfoResolveFunc) *commandInfoResolver {
+ return &commandInfoResolver{
+ resolveFunc: resolveFunc,
+ }
+}
+
+func NewDefaultCommandPolicyResolver() *commandInfoResolver {
+ return NewCommandInfoResolver(func(ctx context.Context, cmd Cmder) *routing.CommandPolicy {
+ module := "core"
+ command := cmd.Name()
+ cmdParts := strings.Split(command, ".")
+ if len(cmdParts) == 2 {
+ module = cmdParts[0]
+ command = cmdParts[1]
+ }
+
+ if policy, ok := defaultPolicies[module][command]; ok {
+ return policy
+ }
+
+ return nil
+ })
+}
+
+func (r *commandInfoResolver) GetCommandPolicy(ctx context.Context, cmd Cmder) *routing.CommandPolicy {
+ if r.resolveFunc == nil {
+ return nil
+ }
+
+ policy := r.resolveFunc(ctx, cmd)
+ if policy != nil {
+ return policy
+ }
+
+ if r.fallBackResolver != nil {
+ return r.fallBackResolver.GetCommandPolicy(ctx, cmd)
+ }
+
+ return nil
+}
+
+func (r *commandInfoResolver) SetFallbackResolver(fallbackResolver *commandInfoResolver) {
+ r.fallBackResolver = fallbackResolver
+}
diff --git a/vendor/github.com/redis/go-redis/v9/commands.go b/vendor/github.com/redis/go-redis/v9/commands.go
index 07c8e2c8878..219fe464b4d 100644
--- a/vendor/github.com/redis/go-redis/v9/commands.go
+++ b/vendor/github.com/redis/go-redis/v9/commands.go
@@ -4,9 +4,11 @@ import (
"context"
"encoding"
"errors"
+ "fmt"
"io"
"net"
"reflect"
+ "runtime"
"strings"
"time"
@@ -53,6 +55,11 @@ func appendArgs(dst, src []interface{}) []interface{} {
return appendArg(dst, src[0])
}
+ if cap(dst) < len(dst)+len(src) {
+ newDst := make([]interface{}, len(dst), len(dst)+len(src))
+ copy(newDst, dst)
+ dst = newDst
+ }
dst = append(dst, src...)
return dst
}
@@ -79,6 +86,8 @@ func appendArg(dst []interface{}, arg interface{}) []interface{} {
return dst
case time.Time, time.Duration, encoding.BinaryMarshaler, net.IP:
return append(dst, arg)
+ case nil:
+ return dst
default:
// scan struct field
v := reflect.ValueOf(arg)
@@ -151,6 +160,12 @@ func isEmptyValue(v reflect.Value) bool {
return v.Float() == 0
case reflect.Interface, reflect.Pointer:
return v.IsNil()
+ case reflect.Struct:
+ if v.Type() == reflect.TypeOf(time.Time{}) {
+ return v.IsZero()
+ }
+ // Only supports the struct time.Time,
+ // subsequent iterations will follow the func Scan support decoder.
}
return false
}
@@ -170,234 +185,7 @@ type Cmdable interface {
Echo(ctx context.Context, message interface{}) *StringCmd
Ping(ctx context.Context) *StatusCmd
Quit(ctx context.Context) *StatusCmd
- Del(ctx context.Context, keys ...string) *IntCmd
Unlink(ctx context.Context, keys ...string) *IntCmd
- Dump(ctx context.Context, key string) *StringCmd
- Exists(ctx context.Context, keys ...string) *IntCmd
- Expire(ctx context.Context, key string, expiration time.Duration) *BoolCmd
- ExpireAt(ctx context.Context, key string, tm time.Time) *BoolCmd
- ExpireTime(ctx context.Context, key string) *DurationCmd
- ExpireNX(ctx context.Context, key string, expiration time.Duration) *BoolCmd
- ExpireXX(ctx context.Context, key string, expiration time.Duration) *BoolCmd
- ExpireGT(ctx context.Context, key string, expiration time.Duration) *BoolCmd
- ExpireLT(ctx context.Context, key string, expiration time.Duration) *BoolCmd
- Keys(ctx context.Context, pattern string) *StringSliceCmd
- Migrate(ctx context.Context, host, port, key string, db int, timeout time.Duration) *StatusCmd
- Move(ctx context.Context, key string, db int) *BoolCmd
- ObjectRefCount(ctx context.Context, key string) *IntCmd
- ObjectEncoding(ctx context.Context, key string) *StringCmd
- ObjectIdleTime(ctx context.Context, key string) *DurationCmd
- Persist(ctx context.Context, key string) *BoolCmd
- PExpire(ctx context.Context, key string, expiration time.Duration) *BoolCmd
- PExpireAt(ctx context.Context, key string, tm time.Time) *BoolCmd
- PExpireTime(ctx context.Context, key string) *DurationCmd
- PTTL(ctx context.Context, key string) *DurationCmd
- RandomKey(ctx context.Context) *StringCmd
- Rename(ctx context.Context, key, newkey string) *StatusCmd
- RenameNX(ctx context.Context, key, newkey string) *BoolCmd
- 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
- TTL(ctx context.Context, key string) *DurationCmd
- Type(ctx context.Context, key string) *StatusCmd
- Append(ctx context.Context, key, value string) *IntCmd
- Decr(ctx context.Context, key string) *IntCmd
- DecrBy(ctx context.Context, key string, decrement int64) *IntCmd
- Get(ctx context.Context, key string) *StringCmd
- GetRange(ctx context.Context, key string, start, end int64) *StringCmd
- GetSet(ctx context.Context, key string, value interface{}) *StringCmd
- GetEx(ctx context.Context, key string, expiration time.Duration) *StringCmd
- GetDel(ctx context.Context, key string) *StringCmd
- Incr(ctx context.Context, key string) *IntCmd
- IncrBy(ctx context.Context, key string, value int64) *IntCmd
- IncrByFloat(ctx context.Context, key string, value float64) *FloatCmd
- MGet(ctx context.Context, keys ...string) *SliceCmd
- MSet(ctx context.Context, values ...interface{}) *StatusCmd
- MSetNX(ctx context.Context, values ...interface{}) *BoolCmd
- Set(ctx context.Context, key string, value interface{}, expiration time.Duration) *StatusCmd
- SetArgs(ctx context.Context, key string, value interface{}, a SetArgs) *StatusCmd
- SetEx(ctx context.Context, key string, value interface{}, expiration time.Duration) *StatusCmd
- SetNX(ctx context.Context, key string, value interface{}, expiration time.Duration) *BoolCmd
- SetXX(ctx context.Context, key string, value interface{}, expiration time.Duration) *BoolCmd
- SetRange(ctx context.Context, key string, offset int64, value string) *IntCmd
- StrLen(ctx context.Context, key string) *IntCmd
- Copy(ctx context.Context, sourceKey string, destKey string, db int, replace bool) *IntCmd
-
- GetBit(ctx context.Context, key string, offset int64) *IntCmd
- SetBit(ctx context.Context, key string, offset int64, value int) *IntCmd
- BitCount(ctx context.Context, key string, bitCount *BitCount) *IntCmd
- BitOpAnd(ctx context.Context, destKey string, keys ...string) *IntCmd
- BitOpOr(ctx context.Context, destKey string, keys ...string) *IntCmd
- BitOpXor(ctx context.Context, destKey string, keys ...string) *IntCmd
- BitOpNot(ctx context.Context, destKey string, key string) *IntCmd
- BitPos(ctx context.Context, key string, bit int64, pos ...int64) *IntCmd
- BitPosSpan(ctx context.Context, key string, bit int8, start, end int64, span string) *IntCmd
- BitField(ctx context.Context, key string, args ...interface{}) *IntSliceCmd
-
- Scan(ctx context.Context, cursor uint64, match string, count int64) *ScanCmd
- ScanType(ctx context.Context, cursor uint64, match string, count int64, keyType string) *ScanCmd
- SScan(ctx context.Context, key string, cursor uint64, match string, count int64) *ScanCmd
- HScan(ctx context.Context, key string, cursor uint64, match string, count int64) *ScanCmd
- ZScan(ctx context.Context, key string, cursor uint64, match string, count int64) *ScanCmd
-
- HDel(ctx context.Context, key string, fields ...string) *IntCmd
- HExists(ctx context.Context, key, field string) *BoolCmd
- HGet(ctx context.Context, key, field string) *StringCmd
- HGetAll(ctx context.Context, key string) *MapStringStringCmd
- HIncrBy(ctx context.Context, key, field string, incr int64) *IntCmd
- HIncrByFloat(ctx context.Context, key, field string, incr float64) *FloatCmd
- HKeys(ctx context.Context, key string) *StringSliceCmd
- HLen(ctx context.Context, key string) *IntCmd
- HMGet(ctx context.Context, key string, fields ...string) *SliceCmd
- HSet(ctx context.Context, key string, values ...interface{}) *IntCmd
- HMSet(ctx context.Context, key string, values ...interface{}) *BoolCmd
- HSetNX(ctx context.Context, key, field string, value interface{}) *BoolCmd
- HVals(ctx context.Context, key string) *StringSliceCmd
- HRandField(ctx context.Context, key string, count int) *StringSliceCmd
- HRandFieldWithValues(ctx context.Context, key string, count int) *KeyValueSliceCmd
-
- BLPop(ctx context.Context, timeout time.Duration, keys ...string) *StringSliceCmd
- BLMPop(ctx context.Context, timeout time.Duration, direction string, count int64, keys ...string) *KeyValuesCmd
- BRPop(ctx context.Context, timeout time.Duration, keys ...string) *StringSliceCmd
- BRPopLPush(ctx context.Context, source, destination string, timeout time.Duration) *StringCmd
- LCS(ctx context.Context, q *LCSQuery) *LCSCmd
- LIndex(ctx context.Context, key string, index int64) *StringCmd
- LInsert(ctx context.Context, key, op string, pivot, value interface{}) *IntCmd
- LInsertBefore(ctx context.Context, key string, pivot, value interface{}) *IntCmd
- LInsertAfter(ctx context.Context, key string, pivot, value interface{}) *IntCmd
- LLen(ctx context.Context, key string) *IntCmd
- LMPop(ctx context.Context, direction string, count int64, keys ...string) *KeyValuesCmd
- LPop(ctx context.Context, key string) *StringCmd
- LPopCount(ctx context.Context, key string, count int) *StringSliceCmd
- LPos(ctx context.Context, key string, value string, args LPosArgs) *IntCmd
- LPosCount(ctx context.Context, key string, value string, count int64, args LPosArgs) *IntSliceCmd
- LPush(ctx context.Context, key string, values ...interface{}) *IntCmd
- LPushX(ctx context.Context, key string, values ...interface{}) *IntCmd
- LRange(ctx context.Context, key string, start, stop int64) *StringSliceCmd
- LRem(ctx context.Context, key string, count int64, value interface{}) *IntCmd
- LSet(ctx context.Context, key string, index int64, value interface{}) *StatusCmd
- LTrim(ctx context.Context, key string, start, stop int64) *StatusCmd
- RPop(ctx context.Context, key string) *StringCmd
- RPopCount(ctx context.Context, key string, count int) *StringSliceCmd
- RPopLPush(ctx context.Context, source, destination string) *StringCmd
- RPush(ctx context.Context, key string, values ...interface{}) *IntCmd
- RPushX(ctx context.Context, key string, values ...interface{}) *IntCmd
- LMove(ctx context.Context, source, destination, srcpos, destpos string) *StringCmd
- BLMove(ctx context.Context, source, destination, srcpos, destpos string, timeout time.Duration) *StringCmd
-
- SAdd(ctx context.Context, key string, members ...interface{}) *IntCmd
- SCard(ctx context.Context, key string) *IntCmd
- SDiff(ctx context.Context, keys ...string) *StringSliceCmd
- SDiffStore(ctx context.Context, destination string, keys ...string) *IntCmd
- SInter(ctx context.Context, keys ...string) *StringSliceCmd
- SInterCard(ctx context.Context, limit int64, keys ...string) *IntCmd
- SInterStore(ctx context.Context, destination string, keys ...string) *IntCmd
- SIsMember(ctx context.Context, key string, member interface{}) *BoolCmd
- SMIsMember(ctx context.Context, key string, members ...interface{}) *BoolSliceCmd
- SMembers(ctx context.Context, key string) *StringSliceCmd
- SMembersMap(ctx context.Context, key string) *StringStructMapCmd
- SMove(ctx context.Context, source, destination string, member interface{}) *BoolCmd
- SPop(ctx context.Context, key string) *StringCmd
- SPopN(ctx context.Context, key string, count int64) *StringSliceCmd
- SRandMember(ctx context.Context, key string) *StringCmd
- SRandMemberN(ctx context.Context, key string, count int64) *StringSliceCmd
- SRem(ctx context.Context, key string, members ...interface{}) *IntCmd
- SUnion(ctx context.Context, keys ...string) *StringSliceCmd
- SUnionStore(ctx context.Context, destination string, keys ...string) *IntCmd
-
- XAdd(ctx context.Context, a *XAddArgs) *StringCmd
- XDel(ctx context.Context, stream string, ids ...string) *IntCmd
- XLen(ctx context.Context, stream string) *IntCmd
- XRange(ctx context.Context, stream, start, stop string) *XMessageSliceCmd
- XRangeN(ctx context.Context, stream, start, stop string, count int64) *XMessageSliceCmd
- XRevRange(ctx context.Context, stream string, start, stop string) *XMessageSliceCmd
- XRevRangeN(ctx context.Context, stream string, start, stop string, count int64) *XMessageSliceCmd
- XRead(ctx context.Context, a *XReadArgs) *XStreamSliceCmd
- XReadStreams(ctx context.Context, streams ...string) *XStreamSliceCmd
- XGroupCreate(ctx context.Context, stream, group, start string) *StatusCmd
- XGroupCreateMkStream(ctx context.Context, stream, group, start string) *StatusCmd
- XGroupSetID(ctx context.Context, stream, group, start string) *StatusCmd
- XGroupDestroy(ctx context.Context, stream, group string) *IntCmd
- XGroupCreateConsumer(ctx context.Context, stream, group, consumer string) *IntCmd
- XGroupDelConsumer(ctx context.Context, stream, group, consumer string) *IntCmd
- XReadGroup(ctx context.Context, a *XReadGroupArgs) *XStreamSliceCmd
- XAck(ctx context.Context, stream, group string, ids ...string) *IntCmd
- XPending(ctx context.Context, stream, group string) *XPendingCmd
- XPendingExt(ctx context.Context, a *XPendingExtArgs) *XPendingExtCmd
- XClaim(ctx context.Context, a *XClaimArgs) *XMessageSliceCmd
- XClaimJustID(ctx context.Context, a *XClaimArgs) *StringSliceCmd
- XAutoClaim(ctx context.Context, a *XAutoClaimArgs) *XAutoClaimCmd
- XAutoClaimJustID(ctx context.Context, a *XAutoClaimArgs) *XAutoClaimJustIDCmd
- XTrimMaxLen(ctx context.Context, key string, maxLen int64) *IntCmd
- XTrimMaxLenApprox(ctx context.Context, key string, maxLen, limit int64) *IntCmd
- XTrimMinID(ctx context.Context, key string, minID string) *IntCmd
- XTrimMinIDApprox(ctx context.Context, key string, minID string, limit int64) *IntCmd
- XInfoGroups(ctx context.Context, key string) *XInfoGroupsCmd
- XInfoStream(ctx context.Context, key string) *XInfoStreamCmd
- XInfoStreamFull(ctx context.Context, key string, count int) *XInfoStreamFullCmd
- XInfoConsumers(ctx context.Context, key string, group string) *XInfoConsumersCmd
-
- BZPopMax(ctx context.Context, timeout time.Duration, keys ...string) *ZWithKeyCmd
- BZPopMin(ctx context.Context, timeout time.Duration, keys ...string) *ZWithKeyCmd
- BZMPop(ctx context.Context, timeout time.Duration, order string, count int64, keys ...string) *ZSliceWithKeyCmd
-
- ZAdd(ctx context.Context, key string, members ...Z) *IntCmd
- ZAddLT(ctx context.Context, key string, members ...Z) *IntCmd
- ZAddGT(ctx context.Context, key string, members ...Z) *IntCmd
- ZAddNX(ctx context.Context, key string, members ...Z) *IntCmd
- ZAddXX(ctx context.Context, key string, members ...Z) *IntCmd
- ZAddArgs(ctx context.Context, key string, args ZAddArgs) *IntCmd
- ZAddArgsIncr(ctx context.Context, key string, args ZAddArgs) *FloatCmd
- ZCard(ctx context.Context, key string) *IntCmd
- ZCount(ctx context.Context, key, min, max string) *IntCmd
- ZLexCount(ctx context.Context, key, min, max string) *IntCmd
- 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
- ZMPop(ctx context.Context, order string, count int64, keys ...string) *ZSliceWithKeyCmd
- ZMScore(ctx context.Context, key string, members ...string) *FloatSliceCmd
- ZPopMax(ctx context.Context, key string, count ...int64) *ZSliceCmd
- ZPopMin(ctx context.Context, key string, count ...int64) *ZSliceCmd
- ZRange(ctx context.Context, key string, start, stop int64) *StringSliceCmd
- ZRangeWithScores(ctx context.Context, key string, start, stop int64) *ZSliceCmd
- ZRangeByScore(ctx context.Context, key string, opt *ZRangeBy) *StringSliceCmd
- ZRangeByLex(ctx context.Context, key string, opt *ZRangeBy) *StringSliceCmd
- ZRangeByScoreWithScores(ctx context.Context, key string, opt *ZRangeBy) *ZSliceCmd
- ZRangeArgs(ctx context.Context, z ZRangeArgs) *StringSliceCmd
- ZRangeArgsWithScores(ctx context.Context, z ZRangeArgs) *ZSliceCmd
- ZRangeStore(ctx context.Context, dst string, z ZRangeArgs) *IntCmd
- ZRank(ctx context.Context, key, member string) *IntCmd
- ZRankWithScore(ctx context.Context, key, member string) *RankWithScoreCmd
- ZRem(ctx context.Context, key string, members ...interface{}) *IntCmd
- ZRemRangeByRank(ctx context.Context, key string, start, stop int64) *IntCmd
- ZRemRangeByScore(ctx context.Context, key, min, max string) *IntCmd
- ZRemRangeByLex(ctx context.Context, key, min, max string) *IntCmd
- ZRevRange(ctx context.Context, key string, start, stop int64) *StringSliceCmd
- ZRevRangeWithScores(ctx context.Context, key string, start, stop int64) *ZSliceCmd
- ZRevRangeByScore(ctx context.Context, key string, opt *ZRangeBy) *StringSliceCmd
- ZRevRangeByLex(ctx context.Context, key string, opt *ZRangeBy) *StringSliceCmd
- ZRevRangeByScoreWithScores(ctx context.Context, key string, opt *ZRangeBy) *ZSliceCmd
- ZRevRank(ctx context.Context, key, member string) *IntCmd
- ZRevRankWithScore(ctx context.Context, key, member string) *RankWithScoreCmd
- ZScore(ctx context.Context, key, member string) *FloatCmd
- ZUnionStore(ctx context.Context, dest string, store *ZStore) *IntCmd
- ZRandMember(ctx context.Context, key string, count int) *StringSliceCmd
- ZRandMemberWithScores(ctx context.Context, key string, count int) *ZSliceCmd
- ZUnion(ctx context.Context, store ZStore) *StringSliceCmd
- ZUnionWithScores(ctx context.Context, store ZStore) *ZSliceCmd
- ZDiff(ctx context.Context, keys ...string) *StringSliceCmd
- ZDiffWithScores(ctx context.Context, keys ...string) *ZSliceCmd
- ZDiffStore(ctx context.Context, destination string, keys ...string) *IntCmd
-
- PFAdd(ctx context.Context, key string, els ...interface{}) *IntCmd
- PFCount(ctx context.Context, keys ...string) *IntCmd
- PFMerge(ctx context.Context, dest string, keys ...string) *StatusCmd
BgRewriteAOF(ctx context.Context) *StatusCmd
BgSave(ctx context.Context) *StatusCmd
@@ -410,6 +198,7 @@ type Cmdable interface {
ClientID(ctx context.Context) *IntCmd
ClientUnblock(ctx context.Context, id int64) *IntCmd
ClientUnblockWithError(ctx context.Context, id int64) *IntCmd
+ ClientMaintNotifications(ctx context.Context, enabled bool, endpointType string) *StatusCmd
ConfigGet(ctx context.Context, parameter string) *MapStringStringCmd
ConfigResetStat(ctx context.Context) *StatusCmd
ConfigSet(ctx context.Context, parameter, value string) *StatusCmd
@@ -427,86 +216,35 @@ type Cmdable interface {
ShutdownNoSave(ctx context.Context) *StatusCmd
SlaveOf(ctx context.Context, host, port string) *StatusCmd
SlowLogGet(ctx context.Context, num int64) *SlowLogCmd
+ SlowLogLen(ctx context.Context) *IntCmd
+ SlowLogReset(ctx context.Context) *StatusCmd
Time(ctx context.Context) *TimeCmd
DebugObject(ctx context.Context, key string) *StringCmd
- ReadOnly(ctx context.Context) *StatusCmd
- ReadWrite(ctx context.Context) *StatusCmd
MemoryUsage(ctx context.Context, key string, samples ...int) *IntCmd
-
- Eval(ctx context.Context, script string, keys []string, args ...interface{}) *Cmd
- EvalSha(ctx context.Context, sha1 string, keys []string, args ...interface{}) *Cmd
- EvalRO(ctx context.Context, script string, keys []string, args ...interface{}) *Cmd
- EvalShaRO(ctx context.Context, sha1 string, keys []string, args ...interface{}) *Cmd
- ScriptExists(ctx context.Context, hashes ...string) *BoolSliceCmd
- ScriptFlush(ctx context.Context) *StatusCmd
- ScriptKill(ctx context.Context) *StatusCmd
- ScriptLoad(ctx context.Context, script string) *StringCmd
-
- FunctionLoad(ctx context.Context, code string) *StringCmd
- FunctionLoadReplace(ctx context.Context, code string) *StringCmd
- FunctionDelete(ctx context.Context, libName string) *StringCmd
- FunctionFlush(ctx context.Context) *StringCmd
- FunctionKill(ctx context.Context) *StringCmd
- FunctionFlushAsync(ctx context.Context) *StringCmd
- FunctionList(ctx context.Context, q FunctionListQuery) *FunctionListCmd
- FunctionDump(ctx context.Context) *StringCmd
- FunctionRestore(ctx context.Context, libDump string) *StringCmd
- FunctionStats(ctx context.Context) *FunctionStatsCmd
- FCall(ctx context.Context, function string, keys []string, args ...interface{}) *Cmd
- FCallRo(ctx context.Context, function string, keys []string, args ...interface{}) *Cmd
- FCallRO(ctx context.Context, function string, keys []string, args ...interface{}) *Cmd
-
- Publish(ctx context.Context, channel string, message interface{}) *IntCmd
- SPublish(ctx context.Context, channel string, message interface{}) *IntCmd
- PubSubChannels(ctx context.Context, pattern string) *StringSliceCmd
- PubSubNumSub(ctx context.Context, channels ...string) *MapStringIntCmd
- PubSubNumPat(ctx context.Context) *IntCmd
- PubSubShardChannels(ctx context.Context, pattern string) *StringSliceCmd
- PubSubShardNumSub(ctx context.Context, channels ...string) *MapStringIntCmd
-
- ClusterMyShardID(ctx context.Context) *StringCmd
- ClusterSlots(ctx context.Context) *ClusterSlotsCmd
- ClusterShards(ctx context.Context) *ClusterShardsCmd
- ClusterLinks(ctx context.Context) *ClusterLinksCmd
- ClusterNodes(ctx context.Context) *StringCmd
- ClusterMeet(ctx context.Context, host, port string) *StatusCmd
- ClusterForget(ctx context.Context, nodeID string) *StatusCmd
- ClusterReplicate(ctx context.Context, nodeID string) *StatusCmd
- ClusterResetSoft(ctx context.Context) *StatusCmd
- ClusterResetHard(ctx context.Context) *StatusCmd
- ClusterInfo(ctx context.Context) *StringCmd
- ClusterKeySlot(ctx context.Context, key string) *IntCmd
- ClusterGetKeysInSlot(ctx context.Context, slot int, count int) *StringSliceCmd
- ClusterCountFailureReports(ctx context.Context, nodeID string) *IntCmd
- ClusterCountKeysInSlot(ctx context.Context, slot int) *IntCmd
- ClusterDelSlots(ctx context.Context, slots ...int) *StatusCmd
- ClusterDelSlotsRange(ctx context.Context, min, max int) *StatusCmd
- ClusterSaveConfig(ctx context.Context) *StatusCmd
- ClusterSlaves(ctx context.Context, nodeID string) *StringSliceCmd
- ClusterFailover(ctx context.Context) *StatusCmd
- ClusterAddSlots(ctx context.Context, slots ...int) *StatusCmd
- ClusterAddSlotsRange(ctx context.Context, min, max int) *StatusCmd
-
- GeoAdd(ctx context.Context, key string, geoLocation ...*GeoLocation) *IntCmd
- GeoPos(ctx context.Context, key string, members ...string) *GeoPosCmd
- GeoRadius(ctx context.Context, key string, longitude, latitude float64, query *GeoRadiusQuery) *GeoLocationCmd
- GeoRadiusStore(ctx context.Context, key string, longitude, latitude float64, query *GeoRadiusQuery) *IntCmd
- GeoRadiusByMember(ctx context.Context, key, member string, query *GeoRadiusQuery) *GeoLocationCmd
- GeoRadiusByMemberStore(ctx context.Context, key, member string, query *GeoRadiusQuery) *IntCmd
- GeoSearch(ctx context.Context, key string, q *GeoSearchQuery) *StringSliceCmd
- GeoSearchLocation(ctx context.Context, key string, q *GeoSearchLocationQuery) *GeoSearchLocationCmd
- GeoSearchStore(ctx context.Context, key, store string, q *GeoSearchStoreQuery) *IntCmd
- GeoDist(ctx context.Context, key string, member1, member2, unit string) *FloatCmd
- GeoHash(ctx context.Context, key string, members ...string) *StringSliceCmd
-
- ACLDryRun(ctx context.Context, username string, command ...interface{}) *StringCmd
- ACLLog(ctx context.Context, count int64) *ACLLogCmd
- ACLLogReset(ctx context.Context) *StatusCmd
+ Latency(ctx context.Context) *LatencyCmd
+ LatencyReset(ctx context.Context, events ...interface{}) *StatusCmd
ModuleLoadex(ctx context.Context, conf *ModuleLoadexConfig) *StringCmd
- gearsCmdable
- probabilisticCmdable
+ ACLCmdable
+ BitMapCmdable
+ ClusterCmdable
+ GenericCmdable
+ GeoCmdable
+ HashCmdable
+ HyperLogLogCmdable
+ ListCmdable
+ ProbabilisticCmdable
+ PubSubCmdable
+ ScriptingFunctionsCmdable
+ SearchCmdable
+ SetCmdable
+ SortedSetCmdable
+ StringCmdable
+ StreamCmdable
+ TimeseriesCmdable
+ JSONCmdable
+ VectorSetCmdable
}
type StatefulCmdable interface {
@@ -516,6 +254,7 @@ type StatefulCmdable interface {
Select(ctx context.Context, index int) *StatusCmd
SwapDB(ctx context.Context, index1, index2 int) *StatusCmd
ClientSetName(ctx context.Context, name string) *BoolCmd
+ ClientSetInfo(ctx context.Context, info LibraryInfo) *StatusCmd
Hello(ctx context.Context, ver int, username, password, clientName string) *MapStringInterfaceCmd
}
@@ -524,6 +263,7 @@ var (
_ Cmdable = (*Tx)(nil)
_ Cmdable = (*Ring)(nil)
_ Cmdable = (*ClusterClient)(nil)
+ _ Cmdable = (*Pipeline)(nil)
)
type cmdable func(ctx context.Context, cmd Cmder) error
@@ -554,6 +294,13 @@ func (c cmdable) Wait(ctx context.Context, numSlaves int, timeout time.Duration)
return cmd
}
+func (c cmdable) WaitAOF(ctx context.Context, numLocal, numSlaves int, timeout time.Duration) *IntCmd {
+ cmd := NewIntCmd(ctx, "waitAOF", numLocal, numSlaves, int(timeout/time.Millisecond))
+ cmd.setReadTimeout(timeout)
+ _ = c(ctx, cmd)
+ return cmd
+}
+
func (c statefulCmdable) Select(ctx context.Context, index int) *StatusCmd {
cmd := NewStatusCmd(ctx, "select", index)
_ = c(ctx, cmd)
@@ -573,9 +320,40 @@ func (c statefulCmdable) ClientSetName(ctx context.Context, name string) *BoolCm
return cmd
}
-// Hello Set the resp protocol used.
+// ClientSetInfo sends a CLIENT SETINFO command with the provided info.
+func (c statefulCmdable) ClientSetInfo(ctx context.Context, info LibraryInfo) *StatusCmd {
+ err := info.Validate()
+ if err != nil {
+ panic(err.Error())
+ }
+
+ var cmd *StatusCmd
+ if info.LibName != nil {
+ libName := fmt.Sprintf("go-redis(%s,%s)", *info.LibName, internal.ReplaceSpaces(runtime.Version()))
+ cmd = NewStatusCmd(ctx, "client", "setinfo", "LIB-NAME", libName)
+ } else {
+ cmd = NewStatusCmd(ctx, "client", "setinfo", "LIB-VER", *info.LibVer)
+ }
+
+ _ = c(ctx, cmd)
+ return cmd
+}
+
+// Validate checks if only one field in the struct is non-nil.
+func (info LibraryInfo) Validate() error {
+ if info.LibName != nil && info.LibVer != nil {
+ return errors.New("both LibName and LibVer cannot be set at the same time")
+ }
+ if info.LibName == nil && info.LibVer == nil {
+ return errors.New("at least one of LibName and LibVer should be set")
+ }
+ return nil
+}
+
+// Hello sets the resp protocol used.
func (c statefulCmdable) Hello(ctx context.Context,
- ver int, username, password, clientName string) *MapStringInterfaceCmd {
+ ver int, username, password, clientName string,
+) *MapStringInterfaceCmd {
args := make([]interface{}, 0, 7)
args = append(args, "hello", ver)
if password != "" {
@@ -664,3307 +442,356 @@ func (c cmdable) Ping(ctx context.Context) *StatusCmd {
return cmd
}
-func (c cmdable) Quit(_ context.Context) *StatusCmd {
- panic("not implemented")
-}
-
-func (c cmdable) Del(ctx context.Context, keys ...string) *IntCmd {
- args := make([]interface{}, 1+len(keys))
- args[0] = "del"
- for i, key := range keys {
- args[1+i] = key
- }
- cmd := NewIntCmd(ctx, args...)
+func (c cmdable) Do(ctx context.Context, args ...interface{}) *Cmd {
+ cmd := NewCmd(ctx, args...)
_ = c(ctx, cmd)
return cmd
}
-func (c cmdable) Unlink(ctx context.Context, keys ...string) *IntCmd {
- args := make([]interface{}, 1+len(keys))
- args[0] = "unlink"
- for i, key := range keys {
- args[1+i] = key
- }
- cmd := NewIntCmd(ctx, args...)
- _ = c(ctx, cmd)
- return cmd
+// Quit closes the connection.
+//
+// Deprecated: Just close the connection instead as of Redis 7.2.0.
+func (c cmdable) Quit(_ context.Context) *StatusCmd {
+ panic("not implemented")
}
-func (c cmdable) Dump(ctx context.Context, key string) *StringCmd {
- cmd := NewStringCmd(ctx, "dump", key)
- _ = c(ctx, cmd)
- return cmd
-}
+//------------------------------------------------------------------------------
-func (c cmdable) Exists(ctx context.Context, keys ...string) *IntCmd {
- args := make([]interface{}, 1+len(keys))
- args[0] = "exists"
- for i, key := range keys {
- args[1+i] = key
- }
- cmd := NewIntCmd(ctx, args...)
+func (c cmdable) BgRewriteAOF(ctx context.Context) *StatusCmd {
+ cmd := NewStatusCmd(ctx, "bgrewriteaof")
_ = c(ctx, cmd)
return cmd
}
-func (c cmdable) Expire(ctx context.Context, key string, expiration time.Duration) *BoolCmd {
- return c.expire(ctx, key, expiration, "")
-}
-
-func (c cmdable) ExpireNX(ctx context.Context, key string, expiration time.Duration) *BoolCmd {
- return c.expire(ctx, key, expiration, "NX")
-}
-
-func (c cmdable) ExpireXX(ctx context.Context, key string, expiration time.Duration) *BoolCmd {
- return c.expire(ctx, key, expiration, "XX")
-}
-
-func (c cmdable) ExpireGT(ctx context.Context, key string, expiration time.Duration) *BoolCmd {
- return c.expire(ctx, key, expiration, "GT")
-}
-
-func (c cmdable) ExpireLT(ctx context.Context, key string, expiration time.Duration) *BoolCmd {
- return c.expire(ctx, key, expiration, "LT")
-}
-
-func (c cmdable) expire(
- ctx context.Context, key string, expiration time.Duration, mode string,
-) *BoolCmd {
- args := make([]interface{}, 3, 4)
- args[0] = "expire"
- args[1] = key
- args[2] = formatSec(ctx, expiration)
- if mode != "" {
- args = append(args, mode)
- }
-
- cmd := NewBoolCmd(ctx, args...)
+func (c cmdable) BgSave(ctx context.Context) *StatusCmd {
+ cmd := NewStatusCmd(ctx, "bgsave")
_ = c(ctx, cmd)
return cmd
}
-func (c cmdable) ExpireAt(ctx context.Context, key string, tm time.Time) *BoolCmd {
- cmd := NewBoolCmd(ctx, "expireat", key, tm.Unix())
+func (c cmdable) ClientKill(ctx context.Context, ipPort string) *StatusCmd {
+ cmd := NewStatusCmd(ctx, "client", "kill", ipPort)
_ = c(ctx, cmd)
return cmd
}
-func (c cmdable) ExpireTime(ctx context.Context, key string) *DurationCmd {
- cmd := NewDurationCmd(ctx, time.Second, "expiretime", key)
+// ClientKillByFilter is new style syntax, while the ClientKill is old
+//
+// CLIENT KILL