vendor: move vendored sources in-tree
This should make it easier to see changes instead of just a blob
This commit is contained in:
11
vendor/github.com/redis/go-redis/v9/.gitignore
generated
vendored
Normal file
11
vendor/github.com/redis/go-redis/v9/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
*.rdb
|
||||
testdata/*
|
||||
.idea/
|
||||
.DS_Store
|
||||
*.tar.gz
|
||||
*.dic
|
||||
redis8tests.sh
|
||||
coverage.txt
|
||||
**/coverage.txt
|
||||
.vscode
|
||||
tmp/*
|
||||
34
vendor/github.com/redis/go-redis/v9/.golangci.yml
generated
vendored
Normal file
34
vendor/github.com/redis/go-redis/v9/.golangci.yml
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
version: "2"
|
||||
run:
|
||||
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:
|
||||
exclusions:
|
||||
generated: lax
|
||||
paths:
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
4
vendor/github.com/redis/go-redis/v9/.prettierrc.yml
generated
vendored
Normal file
4
vendor/github.com/redis/go-redis/v9/.prettierrc.yml
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
semi: false
|
||||
singleQuote: true
|
||||
proseWrap: always
|
||||
printWidth: 100
|
||||
133
vendor/github.com/redis/go-redis/v9/CHANGELOG.md
generated
vendored
Normal file
133
vendor/github.com/redis/go-redis/v9/CHANGELOG.md
generated
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
## 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.
|
||||
118
vendor/github.com/redis/go-redis/v9/CONTRIBUTING.md
generated
vendored
Normal file
118
vendor/github.com/redis/go-redis/v9/CONTRIBUTING.md
generated
vendored
Normal file
@@ -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:
|
||||
|
||||
- <http://makeapullrequest.com/>
|
||||
- <http://www.firsttimersonly.com/>
|
||||
|
||||
## 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:rs-7.4.0-v2`.
|
||||
> 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 (<oss@redis.com>)](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
|
||||
25
vendor/github.com/redis/go-redis/v9/LICENSE
generated
vendored
Normal file
25
vendor/github.com/redis/go-redis/v9/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
Copyright (c) 2013 The github.com/redis/go-redis Authors.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
67
vendor/github.com/redis/go-redis/v9/Makefile
generated
vendored
Normal file
67
vendor/github.com/redis/go-redis/v9/Makefile
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
GO_MOD_DIRS := $(shell find . -type f -name 'go.mod' -exec dirname {} \; | sort)
|
||||
|
||||
docker.start:
|
||||
docker compose --profile all up -d --quiet-pull
|
||||
|
||||
docker.stop:
|
||||
docker compose --profile all 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}" && \
|
||||
go mod tidy -compat=1.18 && \
|
||||
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
|
||||
|
||||
test.ci.skip-vectorsets:
|
||||
set -e; for dir in $(GO_MOD_DIRS); do \
|
||||
echo "go test in $${dir} (skipping vector sets)"; \
|
||||
(cd "$${dir}" && \
|
||||
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:
|
||||
go test ./... -test.run=NONE -test.bench=. -test.benchmem -skip Example
|
||||
|
||||
.PHONY: all test test.ci test.ci.skip-vectorsets bench fmt
|
||||
|
||||
build:
|
||||
go build .
|
||||
|
||||
fmt:
|
||||
gofumpt -w ./
|
||||
goimports -w -local github.com/redis/go-redis ./
|
||||
|
||||
go_mod_tidy:
|
||||
set -e; for dir in $(GO_MOD_DIRS); do \
|
||||
echo "go mod tidy in $${dir}"; \
|
||||
(cd "$${dir}" && \
|
||||
go get -u ./... && \
|
||||
go mod tidy -compat=1.18); \
|
||||
done
|
||||
458
vendor/github.com/redis/go-redis/v9/README.md
generated
vendored
Normal file
458
vendor/github.com/redis/go-redis/v9/README.md
generated
vendored
Normal file
@@ -0,0 +1,458 @@
|
||||
# Redis client for Go
|
||||
|
||||
[](https://github.com/redis/go-redis/actions)
|
||||
[](https://pkg.go.dev/github.com/redis/go-redis/v9?tab=doc)
|
||||
[](https://redis.uptrace.dev/)
|
||||
[](https://goreportcard.com/report/github.com/redis/go-redis/v9)
|
||||
[](https://codecov.io/github/redis/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)
|
||||
|
||||
> 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 7.2](https://raw.githubusercontent.com/redis/redis/7.2/00-RELEASENOTES) - using Redis Stack 7.2 for modules support
|
||||
- [Redis 7.4](https://raw.githubusercontent.com/redis/redis/7.4/00-RELEASENOTES) - using Redis Stack 7.4 for modules support
|
||||
- [Redis 8.0](https://raw.githubusercontent.com/redis/redis/8.0/00-RELEASENOTES) - using Redis CE 8.0 where modules are included
|
||||
|
||||
Although the `go.mod` states it requires at minimum `go 1.18`, our CI is configured to run the tests against all three
|
||||
versions of Redis and latest two versions of Go ([1.23](https://go.dev/doc/devel/release#go1.23.0),
|
||||
[1.24](https://go.dev/doc/devel/release#go1.24.0)). 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.
|
||||
Please do refer to the documentation and the tests if you experience any issues. We do plan to update the go version
|
||||
in the `go.mod` to `go 1.24` in one of the next releases.
|
||||
|
||||
## 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/)
|
||||
|
||||
## Documentation
|
||||
|
||||
- [English](https://redis.uptrace.dev)
|
||||
- [简体中文](https://redis.uptrace.dev/zh/)
|
||||
|
||||
## Resources
|
||||
|
||||
- [Discussions](https://github.com/redis/go-redis/discussions)
|
||||
- [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)
|
||||
|
||||
## Ecosystem
|
||||
|
||||
- [Redis Mock](https://github.com/go-redis/redismock)
|
||||
- [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 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/)
|
||||
|
||||
## Installation
|
||||
|
||||
go-redis supports 2 last Go versions and requires a Go version with
|
||||
[modules](https://github.com/golang/go/wiki/Modules) support. So make sure to initialize a Go
|
||||
module:
|
||||
|
||||
```shell
|
||||
go mod init github.com/my/repo
|
||||
```
|
||||
|
||||
Then install go-redis/**v9**:
|
||||
|
||||
```shell
|
||||
go get github.com/redis/go-redis/v9
|
||||
```
|
||||
|
||||
## Quickstart
|
||||
|
||||
```go
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
var ctx = context.Background()
|
||||
|
||||
func ExampleClient() {
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password set
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
err := rdb.Set(ctx, "key", "value", 0).Err()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
val, err := rdb.Get(ctx, "key").Result()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println("key", val)
|
||||
|
||||
val2, err := rdb.Get(ctx, "key2").Result()
|
||||
if err == redis.Nil {
|
||||
fmt.Println("key2 does not exist")
|
||||
} else if err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
fmt.Println("key2", val2)
|
||||
}
|
||||
// Output: key value
|
||||
// key2 does not exist
|
||||
}
|
||||
```
|
||||
|
||||
### 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
|
||||
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
|
||||
}
|
||||
```
|
||||
|
||||
Example usage:
|
||||
```go
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
StreamingCredentialsProvider: &MyCredentialsProvider{},
|
||||
})
|
||||
```
|
||||
|
||||
**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 (
|
||||
"github.com/redis/go-redis/v9"
|
||||
"github.com/redis/go-redis-entraid"
|
||||
)
|
||||
|
||||
// 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,
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
#### 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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### 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/).
|
||||
|
||||
## 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:
|
||||
|
||||
```go
|
||||
// SET key value EX 10 NX
|
||||
set, err := rdb.SetNX(ctx, "key", "value", 10*time.Second).Result()
|
||||
|
||||
// SET key value keepttl NX
|
||||
set, err := rdb.SetNX(ctx, "key", "value", redis.KeepTTL).Result()
|
||||
|
||||
// SORT list LIMIT 0 2 ASC
|
||||
vals, err := rdb.Sort(ctx, "list", &redis.Sort{Offset: 0, Count: 2, Order: "ASC"}).Result()
|
||||
|
||||
// ZRANGEBYSCORE zset -inf +inf WITHSCORES LIMIT 0 2
|
||||
vals, err := rdb.ZRangeByScoreWithScores(ctx, "zset", &redis.ZRangeBy{
|
||||
Min: "-inf",
|
||||
Max: "+inf",
|
||||
Offset: 0,
|
||||
Count: 2,
|
||||
}).Result()
|
||||
|
||||
// ZINTERSTORE out 2 zset1 zset2 WEIGHTS 2 3 AGGREGATE SUM
|
||||
vals, err := rdb.ZInterStore(ctx, "out", &redis.ZStore{
|
||||
Keys: []string{"zset1", "zset2"},
|
||||
Weights: []int64{2, 3}
|
||||
}).Result()
|
||||
|
||||
// EVAL "return {KEYS[1],ARGV[1]}" 1 "key" "hello"
|
||||
vals, err := rdb.Eval(ctx, "return {KEYS[1],ARGV[1]}", []string{"key"}, "hello").Result()
|
||||
|
||||
// custom command
|
||||
res, err := rdb.Do(ctx, "set", "key", "value").Result()
|
||||
```
|
||||
|
||||
## Run the test
|
||||
|
||||
go-redis will start a redis-server and run the test cases.
|
||||
|
||||
The paths of redis-server bin file and redis config file are defined in `main_test.go`:
|
||||
|
||||
```go
|
||||
var (
|
||||
redisServerBin, _ = filepath.Abs(filepath.Join("testdata", "redis", "src", "redis-server"))
|
||||
redisServerConf, _ = filepath.Abs(filepath.Join("testdata", "redis", "redis.conf"))
|
||||
)
|
||||
```
|
||||
|
||||
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/`:
|
||||
|
||||
```shell
|
||||
ln -s /usr/bin/redis-server ./go-redis/testdata/redis/src
|
||||
cp ./go-redis/testdata/redis.conf ./go-redis/testdata/redis/
|
||||
```
|
||||
|
||||
Lastly, run:
|
||||
|
||||
```shell
|
||||
go test
|
||||
```
|
||||
|
||||
Another option is to run your specific tests with an already running redis. The example below, tests
|
||||
against a redis running on port 9999.:
|
||||
|
||||
```shell
|
||||
REDIS_PORT=9999 go test <your options>
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
- [Golang ORM](https://bun.uptrace.dev) for PostgreSQL, MySQL, MSSQL, and SQLite
|
||||
- [Golang PostgreSQL](https://bun.uptrace.dev/postgres/)
|
||||
- [Golang HTTP router](https://bunrouter.uptrace.dev/)
|
||||
- [Golang ClickHouse ORM](https://github.com/uptrace/go-clickhouse)
|
||||
|
||||
## Contributors
|
||||
|
||||
> The go-redis project was originally initiated by :star: [**uptrace/uptrace**](https://github.com/uptrace/uptrace).
|
||||
> Uptrace is an open-source APM tool that supports distributed tracing, metrics, and logs. You can
|
||||
> use it to monitor applications and set up automatic alerts to receive notifications via email,
|
||||
> Slack, Telegram, and others.
|
||||
>
|
||||
> See [OpenTelemetry](https://github.com/redis/go-redis/tree/master/example/otel) example which
|
||||
> demonstrates how you can use Uptrace to monitor go-redis.
|
||||
|
||||
Thanks to all the people who already contributed!
|
||||
|
||||
<a href="https://github.com/redis/go-redis/graphs/contributors">
|
||||
<img src="https://contributors-img.web.app/image?repo=redis/go-redis" />
|
||||
</a>
|
||||
201
vendor/github.com/redis/go-redis/v9/RELEASE-NOTES.md
generated
vendored
Normal file
201
vendor/github.com/redis/go-redis/v9/RELEASE-NOTES.md
generated
vendored
Normal file
@@ -0,0 +1,201 @@
|
||||
# Release Notes
|
||||
|
||||
# 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)
|
||||
15
vendor/github.com/redis/go-redis/v9/RELEASING.md
generated
vendored
Normal file
15
vendor/github.com/redis/go-redis/v9/RELEASING.md
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
# Releasing
|
||||
|
||||
1. Run `release.sh` script which updates versions in go.mod files and pushes a new branch to GitHub:
|
||||
|
||||
```shell
|
||||
TAG=v1.0.0 ./scripts/release.sh
|
||||
```
|
||||
|
||||
2. Open a pull request and wait for the build to finish.
|
||||
|
||||
3. Merge the pull request and run `tag.sh` to create tags for packages:
|
||||
|
||||
```shell
|
||||
TAG=v1.0.0 ./scripts/tag.sh
|
||||
```
|
||||
89
vendor/github.com/redis/go-redis/v9/acl_commands.go
generated
vendored
Normal file
89
vendor/github.com/redis/go-redis/v9/acl_commands.go
generated
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
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
|
||||
|
||||
ACLSetUser(ctx context.Context, username string, rules ...string) *StatusCmd
|
||||
ACLDelUser(ctx context.Context, username string) *IntCmd
|
||||
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) 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)
|
||||
}
|
||||
61
vendor/github.com/redis/go-redis/v9/auth/auth.go
generated
vendored
Normal file
61
vendor/github.com/redis/go-redis/v9/auth/auth.go
generated
vendored
Normal file
@@ -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,
|
||||
}
|
||||
}
|
||||
47
vendor/github.com/redis/go-redis/v9/auth/reauth_credentials_listener.go
generated
vendored
Normal file
47
vendor/github.com/redis/go-redis/v9/auth/reauth_credentials_listener.go
generated
vendored
Normal file
@@ -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)
|
||||
161
vendor/github.com/redis/go-redis/v9/bitmap_commands.go
generated
vendored
Normal file
161
vendor/github.com/redis/go-redis/v9/bitmap_commands.go
generated
vendored
Normal file
@@ -0,0 +1,161 @@
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
func (c cmdable) BitOpAnd(ctx context.Context, destKey string, keys ...string) *IntCmd {
|
||||
return c.bitOp(ctx, "and", destKey, keys...)
|
||||
}
|
||||
|
||||
func (c cmdable) BitOpOr(ctx context.Context, destKey string, keys ...string) *IntCmd {
|
||||
return c.bitOp(ctx, "or", destKey, keys...)
|
||||
}
|
||||
|
||||
func (c cmdable) BitOpXor(ctx context.Context, destKey string, keys ...string) *IntCmd {
|
||||
return c.bitOp(ctx, "xor", destKey, keys...)
|
||||
}
|
||||
|
||||
func (c cmdable) BitOpNot(ctx context.Context, destKey string, key string) *IntCmd {
|
||||
return c.bitOp(ctx, "not", destKey, key)
|
||||
}
|
||||
|
||||
// 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:
|
||||
panic("too many arguments")
|
||||
}
|
||||
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, "<Encoding0>", "<Offset0>", "<Encoding1>","<Offset1>")
|
||||
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 {
|
||||
panic("BitFieldRO: invalid number of arguments, must be even")
|
||||
}
|
||||
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
|
||||
}
|
||||
199
vendor/github.com/redis/go-redis/v9/cluster_commands.go
generated
vendored
Normal file
199
vendor/github.com/redis/go-redis/v9/cluster_commands.go
generated
vendored
Normal file
@@ -0,0 +1,199 @@
|
||||
package redis
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
return c.ClusterDelSlots(ctx, slots...)
|
||||
}
|
||||
|
||||
func (c cmdable) ClusterSaveConfig(ctx context.Context) *StatusCmd {
|
||||
cmd := NewStatusCmd(ctx, "cluster", "saveconfig")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (c cmdable) ReadWrite(ctx context.Context) *StatusCmd {
|
||||
cmd := NewStatusCmd(ctx, "readwrite")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
5736
vendor/github.com/redis/go-redis/v9/command.go
generated
vendored
Normal file
5736
vendor/github.com/redis/go-redis/v9/command.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
733
vendor/github.com/redis/go-redis/v9/commands.go
generated
vendored
Normal file
733
vendor/github.com/redis/go-redis/v9/commands.go
generated
vendored
Normal file
@@ -0,0 +1,733 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/redis/go-redis/v9/internal"
|
||||
)
|
||||
|
||||
// KeepTTL is a Redis KEEPTTL option to keep existing TTL, it requires your redis-server version >= 6.0,
|
||||
// otherwise you will receive an error: (error) ERR syntax error.
|
||||
// For example:
|
||||
//
|
||||
// rdb.Set(ctx, key, value, redis.KeepTTL)
|
||||
const KeepTTL = -1
|
||||
|
||||
func usePrecise(dur time.Duration) bool {
|
||||
return dur < time.Second || dur%time.Second != 0
|
||||
}
|
||||
|
||||
func formatMs(ctx context.Context, dur time.Duration) int64 {
|
||||
if dur > 0 && dur < time.Millisecond {
|
||||
internal.Logger.Printf(
|
||||
ctx,
|
||||
"specified duration is %s, but minimal supported value is %s - truncating to 1ms",
|
||||
dur, time.Millisecond,
|
||||
)
|
||||
return 1
|
||||
}
|
||||
return int64(dur / time.Millisecond)
|
||||
}
|
||||
|
||||
func formatSec(ctx context.Context, dur time.Duration) int64 {
|
||||
if dur > 0 && dur < time.Second {
|
||||
internal.Logger.Printf(
|
||||
ctx,
|
||||
"specified duration is %s, but minimal supported value is %s - truncating to 1s",
|
||||
dur, time.Second,
|
||||
)
|
||||
return 1
|
||||
}
|
||||
return int64(dur / time.Second)
|
||||
}
|
||||
|
||||
func appendArgs(dst, src []interface{}) []interface{} {
|
||||
if len(src) == 1 {
|
||||
return appendArg(dst, src[0])
|
||||
}
|
||||
|
||||
dst = append(dst, src...)
|
||||
return dst
|
||||
}
|
||||
|
||||
func appendArg(dst []interface{}, arg interface{}) []interface{} {
|
||||
switch arg := arg.(type) {
|
||||
case []string:
|
||||
for _, s := range arg {
|
||||
dst = append(dst, s)
|
||||
}
|
||||
return dst
|
||||
case []interface{}:
|
||||
dst = append(dst, arg...)
|
||||
return dst
|
||||
case map[string]interface{}:
|
||||
for k, v := range arg {
|
||||
dst = append(dst, k, v)
|
||||
}
|
||||
return dst
|
||||
case map[string]string:
|
||||
for k, v := range arg {
|
||||
dst = append(dst, k, v)
|
||||
}
|
||||
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)
|
||||
if v.Type().Kind() == reflect.Ptr {
|
||||
if v.IsNil() {
|
||||
// error: arg is not a valid object
|
||||
return dst
|
||||
}
|
||||
v = v.Elem()
|
||||
}
|
||||
|
||||
if v.Type().Kind() == reflect.Struct {
|
||||
return appendStructField(dst, v)
|
||||
}
|
||||
|
||||
return append(dst, arg)
|
||||
}
|
||||
}
|
||||
|
||||
// appendStructField appends the field and value held by the structure v to dst, and returns the appended dst.
|
||||
func appendStructField(dst []interface{}, v reflect.Value) []interface{} {
|
||||
typ := v.Type()
|
||||
for i := 0; i < typ.NumField(); i++ {
|
||||
tag := typ.Field(i).Tag.Get("redis")
|
||||
if tag == "" || tag == "-" {
|
||||
continue
|
||||
}
|
||||
name, opt, _ := strings.Cut(tag, ",")
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
field := v.Field(i)
|
||||
|
||||
// miss field
|
||||
if omitEmpty(opt) && isEmptyValue(field) {
|
||||
continue
|
||||
}
|
||||
|
||||
if field.CanInterface() {
|
||||
dst = append(dst, name, field.Interface())
|
||||
}
|
||||
}
|
||||
|
||||
return dst
|
||||
}
|
||||
|
||||
func omitEmpty(opt string) bool {
|
||||
for opt != "" {
|
||||
var name string
|
||||
name, opt, _ = strings.Cut(opt, ",")
|
||||
if name == "omitempty" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isEmptyValue(v reflect.Value) bool {
|
||||
switch v.Kind() {
|
||||
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
|
||||
return v.Len() == 0
|
||||
case reflect.Bool:
|
||||
return !v.Bool()
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return v.Int() == 0
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return v.Uint() == 0
|
||||
case reflect.Float32, reflect.Float64:
|
||||
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
|
||||
}
|
||||
|
||||
type Cmdable interface {
|
||||
Pipeline() Pipeliner
|
||||
Pipelined(ctx context.Context, fn func(Pipeliner) error) ([]Cmder, error)
|
||||
|
||||
TxPipelined(ctx context.Context, fn func(Pipeliner) error) ([]Cmder, error)
|
||||
TxPipeline() Pipeliner
|
||||
|
||||
Command(ctx context.Context) *CommandsInfoCmd
|
||||
CommandList(ctx context.Context, filter *FilterBy) *StringSliceCmd
|
||||
CommandGetKeys(ctx context.Context, commands ...interface{}) *StringSliceCmd
|
||||
CommandGetKeysAndFlags(ctx context.Context, commands ...interface{}) *KeyFlagsCmd
|
||||
ClientGetName(ctx context.Context) *StringCmd
|
||||
Echo(ctx context.Context, message interface{}) *StringCmd
|
||||
Ping(ctx context.Context) *StatusCmd
|
||||
Quit(ctx context.Context) *StatusCmd
|
||||
Unlink(ctx context.Context, keys ...string) *IntCmd
|
||||
|
||||
BgRewriteAOF(ctx context.Context) *StatusCmd
|
||||
BgSave(ctx context.Context) *StatusCmd
|
||||
ClientKill(ctx context.Context, ipPort string) *StatusCmd
|
||||
ClientKillByFilter(ctx context.Context, keys ...string) *IntCmd
|
||||
ClientList(ctx context.Context) *StringCmd
|
||||
ClientInfo(ctx context.Context) *ClientInfoCmd
|
||||
ClientPause(ctx context.Context, dur time.Duration) *BoolCmd
|
||||
ClientUnpause(ctx context.Context) *BoolCmd
|
||||
ClientID(ctx context.Context) *IntCmd
|
||||
ClientUnblock(ctx context.Context, id int64) *IntCmd
|
||||
ClientUnblockWithError(ctx context.Context, id int64) *IntCmd
|
||||
ConfigGet(ctx context.Context, parameter string) *MapStringStringCmd
|
||||
ConfigResetStat(ctx context.Context) *StatusCmd
|
||||
ConfigSet(ctx context.Context, parameter, value string) *StatusCmd
|
||||
ConfigRewrite(ctx context.Context) *StatusCmd
|
||||
DBSize(ctx context.Context) *IntCmd
|
||||
FlushAll(ctx context.Context) *StatusCmd
|
||||
FlushAllAsync(ctx context.Context) *StatusCmd
|
||||
FlushDB(ctx context.Context) *StatusCmd
|
||||
FlushDBAsync(ctx context.Context) *StatusCmd
|
||||
Info(ctx context.Context, section ...string) *StringCmd
|
||||
LastSave(ctx context.Context) *IntCmd
|
||||
Save(ctx context.Context) *StatusCmd
|
||||
Shutdown(ctx context.Context) *StatusCmd
|
||||
ShutdownSave(ctx context.Context) *StatusCmd
|
||||
ShutdownNoSave(ctx context.Context) *StatusCmd
|
||||
SlaveOf(ctx context.Context, host, port string) *StatusCmd
|
||||
SlowLogGet(ctx context.Context, num int64) *SlowLogCmd
|
||||
Time(ctx context.Context) *TimeCmd
|
||||
DebugObject(ctx context.Context, key string) *StringCmd
|
||||
MemoryUsage(ctx context.Context, key string, samples ...int) *IntCmd
|
||||
|
||||
ModuleLoadex(ctx context.Context, conf *ModuleLoadexConfig) *StringCmd
|
||||
|
||||
ACLCmdable
|
||||
BitMapCmdable
|
||||
ClusterCmdable
|
||||
GenericCmdable
|
||||
GeoCmdable
|
||||
HashCmdable
|
||||
HyperLogLogCmdable
|
||||
ListCmdable
|
||||
ProbabilisticCmdable
|
||||
PubSubCmdable
|
||||
ScriptingFunctionsCmdable
|
||||
SearchCmdable
|
||||
SetCmdable
|
||||
SortedSetCmdable
|
||||
StringCmdable
|
||||
StreamCmdable
|
||||
TimeseriesCmdable
|
||||
JSONCmdable
|
||||
VectorSetCmdable
|
||||
}
|
||||
|
||||
type StatefulCmdable interface {
|
||||
Cmdable
|
||||
Auth(ctx context.Context, password string) *StatusCmd
|
||||
AuthACL(ctx context.Context, username, password string) *StatusCmd
|
||||
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
|
||||
}
|
||||
|
||||
var (
|
||||
_ Cmdable = (*Client)(nil)
|
||||
_ Cmdable = (*Tx)(nil)
|
||||
_ Cmdable = (*Ring)(nil)
|
||||
_ Cmdable = (*ClusterClient)(nil)
|
||||
)
|
||||
|
||||
type cmdable func(ctx context.Context, cmd Cmder) error
|
||||
|
||||
type statefulCmdable func(ctx context.Context, cmd Cmder) error
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
func (c statefulCmdable) Auth(ctx context.Context, password string) *StatusCmd {
|
||||
cmd := NewStatusCmd(ctx, "auth", password)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// AuthACL Perform an AUTH command, using the given user and pass.
|
||||
// Should be used to authenticate the current connection with one of the connections defined in the ACL list
|
||||
// when connecting to a Redis 6.0 instance, or greater, that is using the Redis ACL system.
|
||||
func (c statefulCmdable) AuthACL(ctx context.Context, username, password string) *StatusCmd {
|
||||
cmd := NewStatusCmd(ctx, "auth", username, password)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) Wait(ctx context.Context, numSlaves int, timeout time.Duration) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "wait", numSlaves, int(timeout/time.Millisecond))
|
||||
cmd.setReadTimeout(timeout)
|
||||
_ = c(ctx, cmd)
|
||||
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)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c statefulCmdable) SwapDB(ctx context.Context, index1, index2 int) *StatusCmd {
|
||||
cmd := NewStatusCmd(ctx, "swapdb", index1, index2)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// ClientSetName assigns a name to the connection.
|
||||
func (c statefulCmdable) ClientSetName(ctx context.Context, name string) *BoolCmd {
|
||||
cmd := NewBoolCmd(ctx, "client", "setname", name)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// 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 {
|
||||
args := make([]interface{}, 0, 7)
|
||||
args = append(args, "hello", ver)
|
||||
if password != "" {
|
||||
if username != "" {
|
||||
args = append(args, "auth", username, password)
|
||||
} else {
|
||||
args = append(args, "auth", "default", password)
|
||||
}
|
||||
}
|
||||
if clientName != "" {
|
||||
args = append(args, "setname", clientName)
|
||||
}
|
||||
cmd := NewMapStringInterfaceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
func (c cmdable) Command(ctx context.Context) *CommandsInfoCmd {
|
||||
cmd := NewCommandsInfoCmd(ctx, "command")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// FilterBy is used for the `CommandList` command parameter.
|
||||
type FilterBy struct {
|
||||
Module string
|
||||
ACLCat string
|
||||
Pattern string
|
||||
}
|
||||
|
||||
func (c cmdable) CommandList(ctx context.Context, filter *FilterBy) *StringSliceCmd {
|
||||
args := make([]interface{}, 0, 5)
|
||||
args = append(args, "command", "list")
|
||||
if filter != nil {
|
||||
if filter.Module != "" {
|
||||
args = append(args, "filterby", "module", filter.Module)
|
||||
} else if filter.ACLCat != "" {
|
||||
args = append(args, "filterby", "aclcat", filter.ACLCat)
|
||||
} else if filter.Pattern != "" {
|
||||
args = append(args, "filterby", "pattern", filter.Pattern)
|
||||
}
|
||||
}
|
||||
cmd := NewStringSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) CommandGetKeys(ctx context.Context, commands ...interface{}) *StringSliceCmd {
|
||||
args := make([]interface{}, 2+len(commands))
|
||||
args[0] = "command"
|
||||
args[1] = "getkeys"
|
||||
copy(args[2:], commands)
|
||||
cmd := NewStringSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) CommandGetKeysAndFlags(ctx context.Context, commands ...interface{}) *KeyFlagsCmd {
|
||||
args := make([]interface{}, 2+len(commands))
|
||||
args[0] = "command"
|
||||
args[1] = "getkeysandflags"
|
||||
copy(args[2:], commands)
|
||||
cmd := NewKeyFlagsCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// ClientGetName returns the name of the connection.
|
||||
func (c cmdable) ClientGetName(ctx context.Context) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "client", "getname")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) Echo(ctx context.Context, message interface{}) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "echo", message)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) Ping(ctx context.Context) *StatusCmd {
|
||||
cmd := NewStatusCmd(ctx, "ping")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) Do(ctx context.Context, args ...interface{}) *Cmd {
|
||||
cmd := NewCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) Quit(_ context.Context) *StatusCmd {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
func (c cmdable) BgRewriteAOF(ctx context.Context) *StatusCmd {
|
||||
cmd := NewStatusCmd(ctx, "bgrewriteaof")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) BgSave(ctx context.Context) *StatusCmd {
|
||||
cmd := NewStatusCmd(ctx, "bgsave")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ClientKill(ctx context.Context, ipPort string) *StatusCmd {
|
||||
cmd := NewStatusCmd(ctx, "client", "kill", ipPort)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// ClientKillByFilter is new style syntax, while the ClientKill is old
|
||||
//
|
||||
// CLIENT KILL <option> [value] ... <option> [value]
|
||||
func (c cmdable) ClientKillByFilter(ctx context.Context, keys ...string) *IntCmd {
|
||||
args := make([]interface{}, 2+len(keys))
|
||||
args[0] = "client"
|
||||
args[1] = "kill"
|
||||
for i, key := range keys {
|
||||
args[2+i] = key
|
||||
}
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ClientList(ctx context.Context) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "client", "list")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ClientPause(ctx context.Context, dur time.Duration) *BoolCmd {
|
||||
cmd := NewBoolCmd(ctx, "client", "pause", formatMs(ctx, dur))
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ClientUnpause(ctx context.Context) *BoolCmd {
|
||||
cmd := NewBoolCmd(ctx, "client", "unpause")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ClientID(ctx context.Context) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "client", "id")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ClientUnblock(ctx context.Context, id int64) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "client", "unblock", id)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ClientUnblockWithError(ctx context.Context, id int64) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "client", "unblock", id, "error")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ClientInfo(ctx context.Context) *ClientInfoCmd {
|
||||
cmd := NewClientInfoCmd(ctx, "client", "info")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
func (c cmdable) ConfigGet(ctx context.Context, parameter string) *MapStringStringCmd {
|
||||
cmd := NewMapStringStringCmd(ctx, "config", "get", parameter)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ConfigResetStat(ctx context.Context) *StatusCmd {
|
||||
cmd := NewStatusCmd(ctx, "config", "resetstat")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ConfigSet(ctx context.Context, parameter, value string) *StatusCmd {
|
||||
cmd := NewStatusCmd(ctx, "config", "set", parameter, value)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ConfigRewrite(ctx context.Context) *StatusCmd {
|
||||
cmd := NewStatusCmd(ctx, "config", "rewrite")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) DBSize(ctx context.Context) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "dbsize")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) FlushAll(ctx context.Context) *StatusCmd {
|
||||
cmd := NewStatusCmd(ctx, "flushall")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) FlushAllAsync(ctx context.Context) *StatusCmd {
|
||||
cmd := NewStatusCmd(ctx, "flushall", "async")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) FlushDB(ctx context.Context) *StatusCmd {
|
||||
cmd := NewStatusCmd(ctx, "flushdb")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) FlushDBAsync(ctx context.Context) *StatusCmd {
|
||||
cmd := NewStatusCmd(ctx, "flushdb", "async")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) Info(ctx context.Context, sections ...string) *StringCmd {
|
||||
args := make([]interface{}, 1+len(sections))
|
||||
args[0] = "info"
|
||||
for i, section := range sections {
|
||||
args[i+1] = section
|
||||
}
|
||||
cmd := NewStringCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) InfoMap(ctx context.Context, sections ...string) *InfoCmd {
|
||||
args := make([]interface{}, 1+len(sections))
|
||||
args[0] = "info"
|
||||
for i, section := range sections {
|
||||
args[i+1] = section
|
||||
}
|
||||
cmd := NewInfoCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) LastSave(ctx context.Context) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "lastsave")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) Save(ctx context.Context) *StatusCmd {
|
||||
cmd := NewStatusCmd(ctx, "save")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) shutdown(ctx context.Context, modifier string) *StatusCmd {
|
||||
var args []interface{}
|
||||
if modifier == "" {
|
||||
args = []interface{}{"shutdown"}
|
||||
} else {
|
||||
args = []interface{}{"shutdown", modifier}
|
||||
}
|
||||
cmd := NewStatusCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
if err := cmd.Err(); err != nil {
|
||||
if err == io.EOF {
|
||||
// Server quit as expected.
|
||||
cmd.err = nil
|
||||
}
|
||||
} else {
|
||||
// Server did not quit. String reply contains the reason.
|
||||
cmd.err = errors.New(cmd.val)
|
||||
cmd.val = ""
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) Shutdown(ctx context.Context) *StatusCmd {
|
||||
return c.shutdown(ctx, "")
|
||||
}
|
||||
|
||||
func (c cmdable) ShutdownSave(ctx context.Context) *StatusCmd {
|
||||
return c.shutdown(ctx, "save")
|
||||
}
|
||||
|
||||
func (c cmdable) ShutdownNoSave(ctx context.Context) *StatusCmd {
|
||||
return c.shutdown(ctx, "nosave")
|
||||
}
|
||||
|
||||
func (c cmdable) SlaveOf(ctx context.Context, host, port string) *StatusCmd {
|
||||
cmd := NewStatusCmd(ctx, "slaveof", host, port)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) SlowLogGet(ctx context.Context, num int64) *SlowLogCmd {
|
||||
cmd := NewSlowLogCmd(context.Background(), "slowlog", "get", num)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) Sync(_ context.Context) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (c cmdable) Time(ctx context.Context) *TimeCmd {
|
||||
cmd := NewTimeCmd(ctx, "time")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) DebugObject(ctx context.Context, key string) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "debug", "object", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) MemoryUsage(ctx context.Context, key string, samples ...int) *IntCmd {
|
||||
args := []interface{}{"memory", "usage", key}
|
||||
if len(samples) > 0 {
|
||||
if len(samples) != 1 {
|
||||
panic("MemoryUsage expects single sample count")
|
||||
}
|
||||
args = append(args, "SAMPLES", samples[0])
|
||||
}
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
cmd.SetFirstKeyPos(2)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// ModuleLoadexConfig struct is used to specify the arguments for the MODULE LOADEX command of redis.
|
||||
// `MODULE LOADEX path [CONFIG name value [CONFIG name value ...]] [ARGS args [args ...]]`
|
||||
type ModuleLoadexConfig struct {
|
||||
Path string
|
||||
Conf map[string]interface{}
|
||||
Args []interface{}
|
||||
}
|
||||
|
||||
func (c *ModuleLoadexConfig) toArgs() []interface{} {
|
||||
args := make([]interface{}, 3, 3+len(c.Conf)*3+len(c.Args)*2)
|
||||
args[0] = "MODULE"
|
||||
args[1] = "LOADEX"
|
||||
args[2] = c.Path
|
||||
for k, v := range c.Conf {
|
||||
args = append(args, "CONFIG", k, v)
|
||||
}
|
||||
for _, arg := range c.Args {
|
||||
args = append(args, "ARGS", arg)
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
// ModuleLoadex Redis `MODULE LOADEX path [CONFIG name value [CONFIG name value ...]] [ARGS args [args ...]]` command.
|
||||
func (c cmdable) ModuleLoadex(ctx context.Context, conf *ModuleLoadexConfig) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, conf.toArgs()...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
/*
|
||||
Monitor - represents a Redis MONITOR command, allowing the user to capture
|
||||
and process all commands sent to a Redis server. This mimics the behavior of
|
||||
MONITOR in the redis-cli.
|
||||
|
||||
Notes:
|
||||
- Using MONITOR blocks the connection to the server for itself. It needs a dedicated connection
|
||||
- The user should create a channel of type string
|
||||
- This runs concurrently in the background. Trigger via the Start and Stop functions
|
||||
See further: Redis MONITOR command: https://redis.io/commands/monitor
|
||||
*/
|
||||
func (c cmdable) Monitor(ctx context.Context, ch chan string) *MonitorCmd {
|
||||
cmd := newMonitorCmd(ctx, ch)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
4
vendor/github.com/redis/go-redis/v9/doc.go
generated
vendored
Normal file
4
vendor/github.com/redis/go-redis/v9/doc.go
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
/*
|
||||
Package redis implements a Redis client.
|
||||
*/
|
||||
package redis
|
||||
106
vendor/github.com/redis/go-redis/v9/docker-compose.yml
generated
vendored
Normal file
106
vendor/github.com/redis/go-redis/v9/docker-compose.yml
generated
vendored
Normal file
@@ -0,0 +1,106 @@
|
||||
---
|
||||
|
||||
services:
|
||||
redis:
|
||||
image: ${CLIENT_LIBS_TEST_IMAGE:-redislabs/client-libs-test:rs-7.4.0-v2}
|
||||
platform: linux/amd64
|
||||
container_name: redis-standalone
|
||||
environment:
|
||||
- TLS_ENABLED=yes
|
||||
- REDIS_CLUSTER=no
|
||||
- PORT=6379
|
||||
- TLS_PORT=6666
|
||||
command: ${REDIS_EXTRA_ARGS:---enable-debug-command yes --enable-module-command yes --tls-auth-clients optional --save ""}
|
||||
ports:
|
||||
- 6379:6379
|
||||
- 6666:6666 # TLS port
|
||||
volumes:
|
||||
- "./dockers/standalone:/redis/work"
|
||||
profiles:
|
||||
- standalone
|
||||
- sentinel
|
||||
- all-stack
|
||||
- all
|
||||
|
||||
osscluster:
|
||||
image: ${CLIENT_LIBS_TEST_IMAGE:-redislabs/client-libs-test:rs-7.4.0-v2}
|
||||
platform: linux/amd64
|
||||
container_name: redis-osscluster
|
||||
environment:
|
||||
- NODES=6
|
||||
- PORT=16600
|
||||
command: "--cluster-enabled yes"
|
||||
ports:
|
||||
- "16600-16605:16600-16605"
|
||||
volumes:
|
||||
- "./dockers/osscluster:/redis/work"
|
||||
profiles:
|
||||
- cluster
|
||||
- all-stack
|
||||
- all
|
||||
|
||||
sentinel-cluster:
|
||||
image: ${CLIENT_LIBS_TEST_IMAGE:-redislabs/client-libs-test:rs-7.4.0-v2}
|
||||
platform: linux/amd64
|
||||
container_name: redis-sentinel-cluster
|
||||
network_mode: "host"
|
||||
environment:
|
||||
- NODES=3
|
||||
- TLS_ENABLED=yes
|
||||
- REDIS_CLUSTER=no
|
||||
- PORT=9121
|
||||
command: ${REDIS_EXTRA_ARGS:---enable-debug-command yes --enable-module-command yes --tls-auth-clients optional --save ""}
|
||||
#ports:
|
||||
# - "9121-9123:9121-9123"
|
||||
volumes:
|
||||
- "./dockers/sentinel-cluster:/redis/work"
|
||||
profiles:
|
||||
- sentinel
|
||||
- all-stack
|
||||
- all
|
||||
|
||||
sentinel:
|
||||
image: ${CLIENT_LIBS_TEST_IMAGE:-redislabs/client-libs-test:rs-7.4.0-v2}
|
||||
platform: linux/amd64
|
||||
container_name: redis-sentinel
|
||||
depends_on:
|
||||
- sentinel-cluster
|
||||
environment:
|
||||
- NODES=3
|
||||
- REDIS_CLUSTER=no
|
||||
- PORT=26379
|
||||
command: ${REDIS_EXTRA_ARGS:---sentinel}
|
||||
network_mode: "host"
|
||||
#ports:
|
||||
# - 26379:26379
|
||||
# - 26380:26380
|
||||
# - 26381:26381
|
||||
volumes:
|
||||
- "./dockers/sentinel.conf:/redis/config-default/redis.conf"
|
||||
- "./dockers/sentinel:/redis/work"
|
||||
profiles:
|
||||
- sentinel
|
||||
- all-stack
|
||||
- all
|
||||
|
||||
ring-cluster:
|
||||
image: ${CLIENT_LIBS_TEST_IMAGE:-redislabs/client-libs-test:rs-7.4.0-v2}
|
||||
platform: linux/amd64
|
||||
container_name: redis-ring-cluster
|
||||
environment:
|
||||
- NODES=3
|
||||
- TLS_ENABLED=yes
|
||||
- REDIS_CLUSTER=no
|
||||
- PORT=6390
|
||||
command: ${REDIS_EXTRA_ARGS:---enable-debug-command yes --enable-module-command yes --tls-auth-clients optional --save ""}
|
||||
ports:
|
||||
- 6390:6390
|
||||
- 6391:6391
|
||||
- 6392:6392
|
||||
volumes:
|
||||
- "./dockers/ring:/redis/work"
|
||||
profiles:
|
||||
- ring
|
||||
- cluster
|
||||
- all-stack
|
||||
- all
|
||||
187
vendor/github.com/redis/go-redis/v9/error.go
generated
vendored
Normal file
187
vendor/github.com/redis/go-redis/v9/error.go
generated
vendored
Normal file
@@ -0,0 +1,187 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/redis/go-redis/v9/internal"
|
||||
"github.com/redis/go-redis/v9/internal/pool"
|
||||
"github.com/redis/go-redis/v9/internal/proto"
|
||||
)
|
||||
|
||||
// ErrClosed performs any operation on the closed client will return this error.
|
||||
var ErrClosed = pool.ErrClosed
|
||||
|
||||
// ErrPoolExhausted is returned from a pool connection method
|
||||
// when the maximum number of database connections in the pool has been reached.
|
||||
var ErrPoolExhausted = pool.ErrPoolExhausted
|
||||
|
||||
// ErrPoolTimeout timed out waiting to get a connection from the connection pool.
|
||||
var ErrPoolTimeout = pool.ErrPoolTimeout
|
||||
|
||||
// ErrCrossSlot is returned when keys are used in the same Redis command and
|
||||
// the keys are not in the same hash slot. This error is returned by Redis
|
||||
// Cluster and will be returned by the client when TxPipeline or TxPipelined
|
||||
// is used on a ClusterClient with keys in different slots.
|
||||
var ErrCrossSlot = proto.RedisError("CROSSSLOT Keys in request don't hash to the same slot")
|
||||
|
||||
// HasErrorPrefix checks if the err is a Redis error and the message contains a prefix.
|
||||
func HasErrorPrefix(err error, prefix string) bool {
|
||||
var rErr Error
|
||||
if !errors.As(err, &rErr) {
|
||||
return false
|
||||
}
|
||||
msg := rErr.Error()
|
||||
msg = strings.TrimPrefix(msg, "ERR ") // KVRocks adds such prefix
|
||||
return strings.HasPrefix(msg, prefix)
|
||||
}
|
||||
|
||||
type Error interface {
|
||||
error
|
||||
|
||||
// RedisError is a no-op function but
|
||||
// serves to distinguish types that are Redis
|
||||
// errors from ordinary errors: a type is a
|
||||
// Redis error if it has a RedisError method.
|
||||
RedisError()
|
||||
}
|
||||
|
||||
var _ Error = proto.RedisError("")
|
||||
|
||||
func isContextError(err error) bool {
|
||||
switch err {
|
||||
case context.Canceled, context.DeadlineExceeded:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func shouldRetry(err error, retryTimeout bool) bool {
|
||||
switch err {
|
||||
case io.EOF, io.ErrUnexpectedEOF:
|
||||
return true
|
||||
case nil, context.Canceled, context.DeadlineExceeded:
|
||||
return false
|
||||
case pool.ErrPoolTimeout:
|
||||
// connection pool timeout, increase retries. #3289
|
||||
return true
|
||||
}
|
||||
|
||||
if v, ok := err.(timeoutError); ok {
|
||||
if v.Timeout() {
|
||||
return retryTimeout
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
s := err.Error()
|
||||
if s == "ERR max number of clients reached" {
|
||||
return true
|
||||
}
|
||||
if strings.HasPrefix(s, "LOADING ") {
|
||||
return true
|
||||
}
|
||||
if strings.HasPrefix(s, "READONLY ") {
|
||||
return true
|
||||
}
|
||||
if strings.HasPrefix(s, "MASTERDOWN ") {
|
||||
return true
|
||||
}
|
||||
if strings.HasPrefix(s, "CLUSTERDOWN ") {
|
||||
return true
|
||||
}
|
||||
if strings.HasPrefix(s, "TRYAGAIN ") {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func isRedisError(err error) bool {
|
||||
_, ok := err.(proto.RedisError)
|
||||
return ok
|
||||
}
|
||||
|
||||
func isBadConn(err error, allowTimeout bool, addr string) bool {
|
||||
switch err {
|
||||
case nil:
|
||||
return false
|
||||
case context.Canceled, context.DeadlineExceeded:
|
||||
return true
|
||||
}
|
||||
|
||||
if isRedisError(err) {
|
||||
switch {
|
||||
case isReadOnlyError(err):
|
||||
// Close connections in read only state in case domain addr is used
|
||||
// and domain resolves to a different Redis Server. See #790.
|
||||
return true
|
||||
case isMovedSameConnAddr(err, addr):
|
||||
// Close connections when we are asked to move to the same addr
|
||||
// of the connection. Force a DNS resolution when all connections
|
||||
// of the pool are recycled
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if allowTimeout {
|
||||
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func isMovedError(err error) (moved bool, ask bool, addr string) {
|
||||
if !isRedisError(err) {
|
||||
return
|
||||
}
|
||||
|
||||
s := err.Error()
|
||||
switch {
|
||||
case strings.HasPrefix(s, "MOVED "):
|
||||
moved = true
|
||||
case strings.HasPrefix(s, "ASK "):
|
||||
ask = true
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
ind := strings.LastIndex(s, " ")
|
||||
if ind == -1 {
|
||||
return false, false, ""
|
||||
}
|
||||
|
||||
addr = s[ind+1:]
|
||||
addr = internal.GetAddr(addr)
|
||||
return
|
||||
}
|
||||
|
||||
func isLoadingError(err error) bool {
|
||||
return strings.HasPrefix(err.Error(), "LOADING ")
|
||||
}
|
||||
|
||||
func isReadOnlyError(err error) bool {
|
||||
return strings.HasPrefix(err.Error(), "READONLY ")
|
||||
}
|
||||
|
||||
func isMovedSameConnAddr(err error, addr string) bool {
|
||||
redisError := err.Error()
|
||||
if !strings.HasPrefix(redisError, "MOVED ") {
|
||||
return false
|
||||
}
|
||||
return strings.HasSuffix(redisError, " "+addr)
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
type timeoutError interface {
|
||||
Timeout() bool
|
||||
}
|
||||
392
vendor/github.com/redis/go-redis/v9/generic_commands.go
generated
vendored
Normal file
392
vendor/github.com/redis/go-redis/v9/generic_commands.go
generated
vendored
Normal file
@@ -0,0 +1,392 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/redis/go-redis/v9/internal/hashtag"
|
||||
)
|
||||
|
||||
type GenericCmdable interface {
|
||||
Del(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
|
||||
ObjectFreq(ctx context.Context, key string) *IntCmd
|
||||
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
|
||||
Copy(ctx context.Context, sourceKey string, destKey string, db int, replace bool) *IntCmd
|
||||
|
||||
Scan(ctx context.Context, cursor uint64, match string, count int64) *ScanCmd
|
||||
ScanType(ctx context.Context, cursor uint64, match string, count int64, keyType string) *ScanCmd
|
||||
}
|
||||
|
||||
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...)
|
||||
_ = 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
|
||||
}
|
||||
|
||||
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...)
|
||||
_ = 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...)
|
||||
_ = 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())
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ExpireTime(ctx context.Context, key string) *DurationCmd {
|
||||
cmd := NewDurationCmd(ctx, time.Second, "expiretime", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) Keys(ctx context.Context, pattern string) *StringSliceCmd {
|
||||
cmd := NewStringSliceCmd(ctx, "keys", pattern)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) Migrate(ctx context.Context, host, port, key string, db int, timeout time.Duration) *StatusCmd {
|
||||
cmd := NewStatusCmd(
|
||||
ctx,
|
||||
"migrate",
|
||||
host,
|
||||
port,
|
||||
key,
|
||||
db,
|
||||
formatMs(ctx, timeout),
|
||||
)
|
||||
cmd.setReadTimeout(timeout)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) Move(ctx context.Context, key string, db int) *BoolCmd {
|
||||
cmd := NewBoolCmd(ctx, "move", key, db)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ObjectFreq(ctx context.Context, key string) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "object", "freq", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ObjectRefCount(ctx context.Context, key string) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "object", "refcount", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ObjectEncoding(ctx context.Context, key string) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "object", "encoding", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ObjectIdleTime(ctx context.Context, key string) *DurationCmd {
|
||||
cmd := NewDurationCmd(ctx, time.Second, "object", "idletime", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) Persist(ctx context.Context, key string) *BoolCmd {
|
||||
cmd := NewBoolCmd(ctx, "persist", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) PExpire(ctx context.Context, key string, expiration time.Duration) *BoolCmd {
|
||||
cmd := NewBoolCmd(ctx, "pexpire", key, formatMs(ctx, expiration))
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) PExpireAt(ctx context.Context, key string, tm time.Time) *BoolCmd {
|
||||
cmd := NewBoolCmd(
|
||||
ctx,
|
||||
"pexpireat",
|
||||
key,
|
||||
tm.UnixNano()/int64(time.Millisecond),
|
||||
)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) PExpireTime(ctx context.Context, key string) *DurationCmd {
|
||||
cmd := NewDurationCmd(ctx, time.Millisecond, "pexpiretime", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) PTTL(ctx context.Context, key string) *DurationCmd {
|
||||
cmd := NewDurationCmd(ctx, time.Millisecond, "pttl", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) RandomKey(ctx context.Context) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "randomkey")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) Rename(ctx context.Context, key, newkey string) *StatusCmd {
|
||||
cmd := NewStatusCmd(ctx, "rename", key, newkey)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) RenameNX(ctx context.Context, key, newkey string) *BoolCmd {
|
||||
cmd := NewBoolCmd(ctx, "renamenx", key, newkey)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) Restore(ctx context.Context, key string, ttl time.Duration, value string) *StatusCmd {
|
||||
cmd := NewStatusCmd(
|
||||
ctx,
|
||||
"restore",
|
||||
key,
|
||||
formatMs(ctx, ttl),
|
||||
value,
|
||||
)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) RestoreReplace(ctx context.Context, key string, ttl time.Duration, value string) *StatusCmd {
|
||||
cmd := NewStatusCmd(
|
||||
ctx,
|
||||
"restore",
|
||||
key,
|
||||
formatMs(ctx, ttl),
|
||||
value,
|
||||
"replace",
|
||||
)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
type Sort struct {
|
||||
By string
|
||||
Offset, Count int64
|
||||
Get []string
|
||||
Order string
|
||||
Alpha bool
|
||||
}
|
||||
|
||||
func (sort *Sort) args(command, key string) []interface{} {
|
||||
args := []interface{}{command, key}
|
||||
|
||||
if sort.By != "" {
|
||||
args = append(args, "by", sort.By)
|
||||
}
|
||||
if sort.Offset != 0 || sort.Count != 0 {
|
||||
args = append(args, "limit", sort.Offset, sort.Count)
|
||||
}
|
||||
for _, get := range sort.Get {
|
||||
args = append(args, "get", get)
|
||||
}
|
||||
if sort.Order != "" {
|
||||
args = append(args, sort.Order)
|
||||
}
|
||||
if sort.Alpha {
|
||||
args = append(args, "alpha")
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
func (c cmdable) SortRO(ctx context.Context, key string, sort *Sort) *StringSliceCmd {
|
||||
cmd := NewStringSliceCmd(ctx, sort.args("sort_ro", key)...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) Sort(ctx context.Context, key string, sort *Sort) *StringSliceCmd {
|
||||
cmd := NewStringSliceCmd(ctx, sort.args("sort", key)...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) SortStore(ctx context.Context, key, store string, sort *Sort) *IntCmd {
|
||||
args := sort.args("sort", key)
|
||||
if store != "" {
|
||||
args = append(args, "store", store)
|
||||
}
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) SortInterfaces(ctx context.Context, key string, sort *Sort) *SliceCmd {
|
||||
cmd := NewSliceCmd(ctx, sort.args("sort", key)...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) Touch(ctx context.Context, keys ...string) *IntCmd {
|
||||
args := make([]interface{}, len(keys)+1)
|
||||
args[0] = "touch"
|
||||
for i, key := range keys {
|
||||
args[i+1] = key
|
||||
}
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) TTL(ctx context.Context, key string) *DurationCmd {
|
||||
cmd := NewDurationCmd(ctx, time.Second, "ttl", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) Type(ctx context.Context, key string) *StatusCmd {
|
||||
cmd := NewStatusCmd(ctx, "type", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) Copy(ctx context.Context, sourceKey, destKey string, db int, replace bool) *IntCmd {
|
||||
args := []interface{}{"copy", sourceKey, destKey, "DB", db}
|
||||
if replace {
|
||||
args = append(args, "REPLACE")
|
||||
}
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
func (c cmdable) Scan(ctx context.Context, cursor uint64, match string, count int64) *ScanCmd {
|
||||
args := []interface{}{"scan", cursor}
|
||||
if match != "" {
|
||||
args = append(args, "match", match)
|
||||
}
|
||||
if count > 0 {
|
||||
args = append(args, "count", count)
|
||||
}
|
||||
cmd := NewScanCmd(ctx, c, args...)
|
||||
if hashtag.Present(match) {
|
||||
cmd.SetFirstKeyPos(3)
|
||||
}
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ScanType(ctx context.Context, cursor uint64, match string, count int64, keyType string) *ScanCmd {
|
||||
args := []interface{}{"scan", cursor}
|
||||
if match != "" {
|
||||
args = append(args, "match", match)
|
||||
}
|
||||
if count > 0 {
|
||||
args = append(args, "count", count)
|
||||
}
|
||||
if keyType != "" {
|
||||
args = append(args, "type", keyType)
|
||||
}
|
||||
cmd := NewScanCmd(ctx, c, args...)
|
||||
if hashtag.Present(match) {
|
||||
cmd.SetFirstKeyPos(3)
|
||||
}
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
155
vendor/github.com/redis/go-redis/v9/geo_commands.go
generated
vendored
Normal file
155
vendor/github.com/redis/go-redis/v9/geo_commands.go
generated
vendored
Normal file
@@ -0,0 +1,155 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
)
|
||||
|
||||
type GeoCmdable interface {
|
||||
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
|
||||
}
|
||||
|
||||
func (c cmdable) GeoAdd(ctx context.Context, key string, geoLocation ...*GeoLocation) *IntCmd {
|
||||
args := make([]interface{}, 2+3*len(geoLocation))
|
||||
args[0] = "geoadd"
|
||||
args[1] = key
|
||||
for i, eachLoc := range geoLocation {
|
||||
args[2+3*i] = eachLoc.Longitude
|
||||
args[2+3*i+1] = eachLoc.Latitude
|
||||
args[2+3*i+2] = eachLoc.Name
|
||||
}
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// GeoRadius is a read-only GEORADIUS_RO command.
|
||||
func (c cmdable) GeoRadius(
|
||||
ctx context.Context, key string, longitude, latitude float64, query *GeoRadiusQuery,
|
||||
) *GeoLocationCmd {
|
||||
cmd := NewGeoLocationCmd(ctx, query, "georadius_ro", key, longitude, latitude)
|
||||
if query.Store != "" || query.StoreDist != "" {
|
||||
cmd.SetErr(errors.New("GeoRadius does not support Store or StoreDist"))
|
||||
return cmd
|
||||
}
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// GeoRadiusStore is a writing GEORADIUS command.
|
||||
func (c cmdable) GeoRadiusStore(
|
||||
ctx context.Context, key string, longitude, latitude float64, query *GeoRadiusQuery,
|
||||
) *IntCmd {
|
||||
args := geoLocationArgs(query, "georadius", key, longitude, latitude)
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
if query.Store == "" && query.StoreDist == "" {
|
||||
cmd.SetErr(errors.New("GeoRadiusStore requires Store or StoreDist"))
|
||||
return cmd
|
||||
}
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// GeoRadiusByMember is a read-only GEORADIUSBYMEMBER_RO command.
|
||||
func (c cmdable) GeoRadiusByMember(
|
||||
ctx context.Context, key, member string, query *GeoRadiusQuery,
|
||||
) *GeoLocationCmd {
|
||||
cmd := NewGeoLocationCmd(ctx, query, "georadiusbymember_ro", key, member)
|
||||
if query.Store != "" || query.StoreDist != "" {
|
||||
cmd.SetErr(errors.New("GeoRadiusByMember does not support Store or StoreDist"))
|
||||
return cmd
|
||||
}
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// GeoRadiusByMemberStore is a writing GEORADIUSBYMEMBER command.
|
||||
func (c cmdable) GeoRadiusByMemberStore(
|
||||
ctx context.Context, key, member string, query *GeoRadiusQuery,
|
||||
) *IntCmd {
|
||||
args := geoLocationArgs(query, "georadiusbymember", key, member)
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
if query.Store == "" && query.StoreDist == "" {
|
||||
cmd.SetErr(errors.New("GeoRadiusByMemberStore requires Store or StoreDist"))
|
||||
return cmd
|
||||
}
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) GeoSearch(ctx context.Context, key string, q *GeoSearchQuery) *StringSliceCmd {
|
||||
args := make([]interface{}, 0, 13)
|
||||
args = append(args, "geosearch", key)
|
||||
args = geoSearchArgs(q, args)
|
||||
cmd := NewStringSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) GeoSearchLocation(
|
||||
ctx context.Context, key string, q *GeoSearchLocationQuery,
|
||||
) *GeoSearchLocationCmd {
|
||||
args := make([]interface{}, 0, 16)
|
||||
args = append(args, "geosearch", key)
|
||||
args = geoSearchLocationArgs(q, args)
|
||||
cmd := NewGeoSearchLocationCmd(ctx, q, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) GeoSearchStore(ctx context.Context, key, store string, q *GeoSearchStoreQuery) *IntCmd {
|
||||
args := make([]interface{}, 0, 15)
|
||||
args = append(args, "geosearchstore", store, key)
|
||||
args = geoSearchArgs(&q.GeoSearchQuery, args)
|
||||
if q.StoreDist {
|
||||
args = append(args, "storedist")
|
||||
}
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) GeoDist(
|
||||
ctx context.Context, key string, member1, member2, unit string,
|
||||
) *FloatCmd {
|
||||
if unit == "" {
|
||||
unit = "km"
|
||||
}
|
||||
cmd := NewFloatCmd(ctx, "geodist", key, member1, member2, unit)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) GeoHash(ctx context.Context, key string, members ...string) *StringSliceCmd {
|
||||
args := make([]interface{}, 2+len(members))
|
||||
args[0] = "geohash"
|
||||
args[1] = key
|
||||
for i, member := range members {
|
||||
args[2+i] = member
|
||||
}
|
||||
cmd := NewStringSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) GeoPos(ctx context.Context, key string, members ...string) *GeoPosCmd {
|
||||
args := make([]interface{}, 2+len(members))
|
||||
args[0] = "geopos"
|
||||
args[1] = key
|
||||
for i, member := range members {
|
||||
args[2+i] = member
|
||||
}
|
||||
cmd := NewGeoPosCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
619
vendor/github.com/redis/go-redis/v9/hash_commands.go
generated
vendored
Normal file
619
vendor/github.com/redis/go-redis/v9/hash_commands.go
generated
vendored
Normal file
@@ -0,0 +1,619 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/redis/go-redis/v9/internal/hashtag"
|
||||
)
|
||||
|
||||
type HashCmdable interface {
|
||||
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
|
||||
HGetDel(ctx context.Context, key string, fields ...string) *StringSliceCmd
|
||||
HGetEX(ctx context.Context, key string, fields ...string) *StringSliceCmd
|
||||
HGetEXWithArgs(ctx context.Context, key string, options *HGetEXOptions, fields ...string) *StringSliceCmd
|
||||
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
|
||||
HSetEX(ctx context.Context, key string, fieldsAndValues ...string) *IntCmd
|
||||
HSetEXWithArgs(ctx context.Context, key string, options *HSetEXOptions, fieldsAndValues ...string) *IntCmd
|
||||
HSetNX(ctx context.Context, key, field string, value interface{}) *BoolCmd
|
||||
HScan(ctx context.Context, key string, cursor uint64, match string, count int64) *ScanCmd
|
||||
HScanNoValues(ctx context.Context, key string, cursor uint64, match string, count int64) *ScanCmd
|
||||
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
|
||||
HStrLen(ctx context.Context, key, field string) *IntCmd
|
||||
HExpire(ctx context.Context, key string, expiration time.Duration, fields ...string) *IntSliceCmd
|
||||
HExpireWithArgs(ctx context.Context, key string, expiration time.Duration, expirationArgs HExpireArgs, fields ...string) *IntSliceCmd
|
||||
HPExpire(ctx context.Context, key string, expiration time.Duration, fields ...string) *IntSliceCmd
|
||||
HPExpireWithArgs(ctx context.Context, key string, expiration time.Duration, expirationArgs HExpireArgs, fields ...string) *IntSliceCmd
|
||||
HExpireAt(ctx context.Context, key string, tm time.Time, fields ...string) *IntSliceCmd
|
||||
HExpireAtWithArgs(ctx context.Context, key string, tm time.Time, expirationArgs HExpireArgs, fields ...string) *IntSliceCmd
|
||||
HPExpireAt(ctx context.Context, key string, tm time.Time, fields ...string) *IntSliceCmd
|
||||
HPExpireAtWithArgs(ctx context.Context, key string, tm time.Time, expirationArgs HExpireArgs, fields ...string) *IntSliceCmd
|
||||
HPersist(ctx context.Context, key string, fields ...string) *IntSliceCmd
|
||||
HExpireTime(ctx context.Context, key string, fields ...string) *IntSliceCmd
|
||||
HPExpireTime(ctx context.Context, key string, fields ...string) *IntSliceCmd
|
||||
HTTL(ctx context.Context, key string, fields ...string) *IntSliceCmd
|
||||
HPTTL(ctx context.Context, key string, fields ...string) *IntSliceCmd
|
||||
}
|
||||
|
||||
func (c cmdable) HDel(ctx context.Context, key string, fields ...string) *IntCmd {
|
||||
args := make([]interface{}, 2+len(fields))
|
||||
args[0] = "hdel"
|
||||
args[1] = key
|
||||
for i, field := range fields {
|
||||
args[2+i] = field
|
||||
}
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) HExists(ctx context.Context, key, field string) *BoolCmd {
|
||||
cmd := NewBoolCmd(ctx, "hexists", key, field)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) HGet(ctx context.Context, key, field string) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "hget", key, field)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) HGetAll(ctx context.Context, key string) *MapStringStringCmd {
|
||||
cmd := NewMapStringStringCmd(ctx, "hgetall", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) HIncrBy(ctx context.Context, key, field string, incr int64) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "hincrby", key, field, incr)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) HIncrByFloat(ctx context.Context, key, field string, incr float64) *FloatCmd {
|
||||
cmd := NewFloatCmd(ctx, "hincrbyfloat", key, field, incr)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) HKeys(ctx context.Context, key string) *StringSliceCmd {
|
||||
cmd := NewStringSliceCmd(ctx, "hkeys", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) HLen(ctx context.Context, key string) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "hlen", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// HMGet returns the values for the specified fields in the hash stored at key.
|
||||
// It returns an interface{} to distinguish between empty string and nil value.
|
||||
func (c cmdable) HMGet(ctx context.Context, key string, fields ...string) *SliceCmd {
|
||||
args := make([]interface{}, 2+len(fields))
|
||||
args[0] = "hmget"
|
||||
args[1] = key
|
||||
for i, field := range fields {
|
||||
args[2+i] = field
|
||||
}
|
||||
cmd := NewSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// HSet accepts values in following formats:
|
||||
//
|
||||
// - HSet("myhash", "key1", "value1", "key2", "value2")
|
||||
//
|
||||
// - HSet("myhash", []string{"key1", "value1", "key2", "value2"})
|
||||
//
|
||||
// - HSet("myhash", map[string]interface{}{"key1": "value1", "key2": "value2"})
|
||||
//
|
||||
// Playing struct With "redis" tag.
|
||||
// type MyHash struct { Key1 string `redis:"key1"`; Key2 int `redis:"key2"` }
|
||||
//
|
||||
// - HSet("myhash", MyHash{"value1", "value2"}) Warn: redis-server >= 4.0
|
||||
//
|
||||
// For struct, can be a structure pointer type, we only parse the field whose tag is redis.
|
||||
// if you don't want the field to be read, you can use the `redis:"-"` flag to ignore it,
|
||||
// or you don't need to set the redis tag.
|
||||
// For the type of structure field, we only support simple data types:
|
||||
// string, int/uint(8,16,32,64), float(32,64), time.Time(to RFC3339Nano), time.Duration(to Nanoseconds ),
|
||||
// if you are other more complex or custom data types, please implement the encoding.BinaryMarshaler interface.
|
||||
//
|
||||
// Note that in older versions of Redis server(redis-server < 4.0), HSet only supports a single key-value pair.
|
||||
// redis-docs: https://redis.io/commands/hset (Starting with Redis version 4.0.0: Accepts multiple field and value arguments.)
|
||||
// If you are using a Struct type and the number of fields is greater than one,
|
||||
// you will receive an error similar to "ERR wrong number of arguments", you can use HMSet as a substitute.
|
||||
func (c cmdable) HSet(ctx context.Context, key string, values ...interface{}) *IntCmd {
|
||||
args := make([]interface{}, 2, 2+len(values))
|
||||
args[0] = "hset"
|
||||
args[1] = key
|
||||
args = appendArgs(args, values)
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// HMSet is a deprecated version of HSet left for compatibility with Redis 3.
|
||||
func (c cmdable) HMSet(ctx context.Context, key string, values ...interface{}) *BoolCmd {
|
||||
args := make([]interface{}, 2, 2+len(values))
|
||||
args[0] = "hmset"
|
||||
args[1] = key
|
||||
args = appendArgs(args, values)
|
||||
cmd := NewBoolCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) HSetNX(ctx context.Context, key, field string, value interface{}) *BoolCmd {
|
||||
cmd := NewBoolCmd(ctx, "hsetnx", key, field, value)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) HVals(ctx context.Context, key string) *StringSliceCmd {
|
||||
cmd := NewStringSliceCmd(ctx, "hvals", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// HRandField redis-server version >= 6.2.0.
|
||||
func (c cmdable) HRandField(ctx context.Context, key string, count int) *StringSliceCmd {
|
||||
cmd := NewStringSliceCmd(ctx, "hrandfield", key, count)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// HRandFieldWithValues redis-server version >= 6.2.0.
|
||||
func (c cmdable) HRandFieldWithValues(ctx context.Context, key string, count int) *KeyValueSliceCmd {
|
||||
cmd := NewKeyValueSliceCmd(ctx, "hrandfield", key, count, "withvalues")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) HScan(ctx context.Context, key string, cursor uint64, match string, count int64) *ScanCmd {
|
||||
args := []interface{}{"hscan", key, cursor}
|
||||
if match != "" {
|
||||
args = append(args, "match", match)
|
||||
}
|
||||
if count > 0 {
|
||||
args = append(args, "count", count)
|
||||
}
|
||||
cmd := NewScanCmd(ctx, c, args...)
|
||||
if hashtag.Present(match) {
|
||||
cmd.SetFirstKeyPos(4)
|
||||
}
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) HStrLen(ctx context.Context, key, field string) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "hstrlen", key, field)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
func (c cmdable) HScanNoValues(ctx context.Context, key string, cursor uint64, match string, count int64) *ScanCmd {
|
||||
args := []interface{}{"hscan", key, cursor}
|
||||
if match != "" {
|
||||
args = append(args, "match", match)
|
||||
}
|
||||
if count > 0 {
|
||||
args = append(args, "count", count)
|
||||
}
|
||||
args = append(args, "novalues")
|
||||
cmd := NewScanCmd(ctx, c, args...)
|
||||
if hashtag.Present(match) {
|
||||
cmd.SetFirstKeyPos(4)
|
||||
}
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
type HExpireArgs struct {
|
||||
NX bool
|
||||
XX bool
|
||||
GT bool
|
||||
LT bool
|
||||
}
|
||||
|
||||
// HExpire - Sets the expiration time for specified fields in a hash in seconds.
|
||||
// The command constructs an argument list starting with "HEXPIRE", followed by the key, duration, any conditional flags, and the specified fields.
|
||||
// Available since Redis 7.4 CE.
|
||||
// For more information refer to [HEXPIRE Documentation].
|
||||
//
|
||||
// [HEXPIRE Documentation]: https://redis.io/commands/hexpire/
|
||||
func (c cmdable) HExpire(ctx context.Context, key string, expiration time.Duration, fields ...string) *IntSliceCmd {
|
||||
args := []interface{}{"HEXPIRE", key, formatSec(ctx, expiration), "FIELDS", len(fields)}
|
||||
|
||||
for _, field := range fields {
|
||||
args = append(args, field)
|
||||
}
|
||||
cmd := NewIntSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// HExpireWithArgs - Sets the expiration time for specified fields in a hash in seconds.
|
||||
// It requires a key, an expiration duration, a struct with boolean flags for conditional expiration settings (NX, XX, GT, LT), and a list of fields.
|
||||
// The command constructs an argument list starting with "HEXPIRE", followed by the key, duration, any conditional flags, and the specified fields.
|
||||
// Available since Redis 7.4 CE.
|
||||
// For more information refer to [HEXPIRE Documentation].
|
||||
//
|
||||
// [HEXPIRE Documentation]: https://redis.io/commands/hexpire/
|
||||
func (c cmdable) HExpireWithArgs(ctx context.Context, key string, expiration time.Duration, expirationArgs HExpireArgs, fields ...string) *IntSliceCmd {
|
||||
args := []interface{}{"HEXPIRE", key, formatSec(ctx, expiration)}
|
||||
|
||||
// only if one argument is true, we can add it to the args
|
||||
// if more than one argument is true, it will cause an error
|
||||
if expirationArgs.NX {
|
||||
args = append(args, "NX")
|
||||
} else if expirationArgs.XX {
|
||||
args = append(args, "XX")
|
||||
} else if expirationArgs.GT {
|
||||
args = append(args, "GT")
|
||||
} else if expirationArgs.LT {
|
||||
args = append(args, "LT")
|
||||
}
|
||||
|
||||
args = append(args, "FIELDS", len(fields))
|
||||
|
||||
for _, field := range fields {
|
||||
args = append(args, field)
|
||||
}
|
||||
cmd := NewIntSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// HPExpire - Sets the expiration time for specified fields in a hash in milliseconds.
|
||||
// Similar to HExpire, it accepts a key, an expiration duration in milliseconds, a struct with expiration condition flags, and a list of fields.
|
||||
// The command modifies the standard time.Duration to milliseconds for the Redis command.
|
||||
// Available since Redis 7.4 CE.
|
||||
// For more information refer to [HPEXPIRE Documentation].
|
||||
//
|
||||
// [HPEXPIRE Documentation]: https://redis.io/commands/hpexpire/
|
||||
func (c cmdable) HPExpire(ctx context.Context, key string, expiration time.Duration, fields ...string) *IntSliceCmd {
|
||||
args := []interface{}{"HPEXPIRE", key, formatMs(ctx, expiration), "FIELDS", len(fields)}
|
||||
|
||||
for _, field := range fields {
|
||||
args = append(args, field)
|
||||
}
|
||||
cmd := NewIntSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// HPExpireWithArgs - Sets the expiration time for specified fields in a hash in milliseconds.
|
||||
// It requires a key, an expiration duration, a struct with boolean flags for conditional expiration settings (NX, XX, GT, LT), and a list of fields.
|
||||
// The command constructs an argument list starting with "HPEXPIRE", followed by the key, duration, any conditional flags, and the specified fields.
|
||||
// Available since Redis 7.4 CE.
|
||||
// For more information refer to [HPEXPIRE Documentation].
|
||||
//
|
||||
// [HPEXPIRE Documentation]: https://redis.io/commands/hpexpire/
|
||||
func (c cmdable) HPExpireWithArgs(ctx context.Context, key string, expiration time.Duration, expirationArgs HExpireArgs, fields ...string) *IntSliceCmd {
|
||||
args := []interface{}{"HPEXPIRE", key, formatMs(ctx, expiration)}
|
||||
|
||||
// only if one argument is true, we can add it to the args
|
||||
// if more than one argument is true, it will cause an error
|
||||
if expirationArgs.NX {
|
||||
args = append(args, "NX")
|
||||
} else if expirationArgs.XX {
|
||||
args = append(args, "XX")
|
||||
} else if expirationArgs.GT {
|
||||
args = append(args, "GT")
|
||||
} else if expirationArgs.LT {
|
||||
args = append(args, "LT")
|
||||
}
|
||||
|
||||
args = append(args, "FIELDS", len(fields))
|
||||
|
||||
for _, field := range fields {
|
||||
args = append(args, field)
|
||||
}
|
||||
cmd := NewIntSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// HExpireAt - Sets the expiration time for specified fields in a hash to a UNIX timestamp in seconds.
|
||||
// Takes a key, a UNIX timestamp, a struct of conditional flags, and a list of fields.
|
||||
// The command sets absolute expiration times based on the UNIX timestamp provided.
|
||||
// Available since Redis 7.4 CE.
|
||||
// For more information refer to [HExpireAt Documentation].
|
||||
//
|
||||
// [HExpireAt Documentation]: https://redis.io/commands/hexpireat/
|
||||
func (c cmdable) HExpireAt(ctx context.Context, key string, tm time.Time, fields ...string) *IntSliceCmd {
|
||||
|
||||
args := []interface{}{"HEXPIREAT", key, tm.Unix(), "FIELDS", len(fields)}
|
||||
|
||||
for _, field := range fields {
|
||||
args = append(args, field)
|
||||
}
|
||||
cmd := NewIntSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) HExpireAtWithArgs(ctx context.Context, key string, tm time.Time, expirationArgs HExpireArgs, fields ...string) *IntSliceCmd {
|
||||
args := []interface{}{"HEXPIREAT", key, tm.Unix()}
|
||||
|
||||
// only if one argument is true, we can add it to the args
|
||||
// if more than one argument is true, it will cause an error
|
||||
if expirationArgs.NX {
|
||||
args = append(args, "NX")
|
||||
} else if expirationArgs.XX {
|
||||
args = append(args, "XX")
|
||||
} else if expirationArgs.GT {
|
||||
args = append(args, "GT")
|
||||
} else if expirationArgs.LT {
|
||||
args = append(args, "LT")
|
||||
}
|
||||
|
||||
args = append(args, "FIELDS", len(fields))
|
||||
|
||||
for _, field := range fields {
|
||||
args = append(args, field)
|
||||
}
|
||||
cmd := NewIntSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// HPExpireAt - Sets the expiration time for specified fields in a hash to a UNIX timestamp in milliseconds.
|
||||
// Similar to HExpireAt but for timestamps in milliseconds. It accepts the same parameters and adjusts the UNIX time to milliseconds.
|
||||
// Available since Redis 7.4 CE.
|
||||
// For more information refer to [HExpireAt Documentation].
|
||||
//
|
||||
// [HExpireAt Documentation]: https://redis.io/commands/hexpireat/
|
||||
func (c cmdable) HPExpireAt(ctx context.Context, key string, tm time.Time, fields ...string) *IntSliceCmd {
|
||||
args := []interface{}{"HPEXPIREAT", key, tm.UnixNano() / int64(time.Millisecond), "FIELDS", len(fields)}
|
||||
|
||||
for _, field := range fields {
|
||||
args = append(args, field)
|
||||
}
|
||||
cmd := NewIntSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) HPExpireAtWithArgs(ctx context.Context, key string, tm time.Time, expirationArgs HExpireArgs, fields ...string) *IntSliceCmd {
|
||||
args := []interface{}{"HPEXPIREAT", key, tm.UnixNano() / int64(time.Millisecond)}
|
||||
|
||||
// only if one argument is true, we can add it to the args
|
||||
// if more than one argument is true, it will cause an error
|
||||
if expirationArgs.NX {
|
||||
args = append(args, "NX")
|
||||
} else if expirationArgs.XX {
|
||||
args = append(args, "XX")
|
||||
} else if expirationArgs.GT {
|
||||
args = append(args, "GT")
|
||||
} else if expirationArgs.LT {
|
||||
args = append(args, "LT")
|
||||
}
|
||||
|
||||
args = append(args, "FIELDS", len(fields))
|
||||
|
||||
for _, field := range fields {
|
||||
args = append(args, field)
|
||||
}
|
||||
cmd := NewIntSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// HPersist - Removes the expiration time from specified fields in a hash.
|
||||
// Accepts a key and the fields themselves.
|
||||
// This command ensures that each field specified will have its expiration removed if present.
|
||||
// Available since Redis 7.4 CE.
|
||||
// For more information refer to [HPersist Documentation].
|
||||
//
|
||||
// [HPersist Documentation]: https://redis.io/commands/hpersist/
|
||||
func (c cmdable) HPersist(ctx context.Context, key string, fields ...string) *IntSliceCmd {
|
||||
args := []interface{}{"HPERSIST", key, "FIELDS", len(fields)}
|
||||
|
||||
for _, field := range fields {
|
||||
args = append(args, field)
|
||||
}
|
||||
cmd := NewIntSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// HExpireTime - Retrieves the expiration time for specified fields in a hash as a UNIX timestamp in seconds.
|
||||
// Requires a key and the fields themselves to fetch their expiration timestamps.
|
||||
// This command returns the expiration times for each field or error/status codes for each field as specified.
|
||||
// Available since Redis 7.4 CE.
|
||||
// For more information refer to [HExpireTime Documentation].
|
||||
//
|
||||
// [HExpireTime Documentation]: https://redis.io/commands/hexpiretime/
|
||||
// For more information - https://redis.io/commands/hexpiretime/
|
||||
func (c cmdable) HExpireTime(ctx context.Context, key string, fields ...string) *IntSliceCmd {
|
||||
args := []interface{}{"HEXPIRETIME", key, "FIELDS", len(fields)}
|
||||
|
||||
for _, field := range fields {
|
||||
args = append(args, field)
|
||||
}
|
||||
cmd := NewIntSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// HPExpireTime - Retrieves the expiration time for specified fields in a hash as a UNIX timestamp in milliseconds.
|
||||
// Similar to HExpireTime, adjusted for timestamps in milliseconds. It requires the same parameters.
|
||||
// Provides the expiration timestamp for each field in milliseconds.
|
||||
// Available since Redis 7.4 CE.
|
||||
// For more information refer to [HExpireTime Documentation].
|
||||
//
|
||||
// [HExpireTime Documentation]: https://redis.io/commands/hexpiretime/
|
||||
// For more information - https://redis.io/commands/hexpiretime/
|
||||
func (c cmdable) HPExpireTime(ctx context.Context, key string, fields ...string) *IntSliceCmd {
|
||||
args := []interface{}{"HPEXPIRETIME", key, "FIELDS", len(fields)}
|
||||
|
||||
for _, field := range fields {
|
||||
args = append(args, field)
|
||||
}
|
||||
cmd := NewIntSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// HTTL - Retrieves the remaining time to live for specified fields in a hash in seconds.
|
||||
// Requires a key and the fields themselves. It returns the TTL for each specified field.
|
||||
// This command fetches the TTL in seconds for each field or returns error/status codes as appropriate.
|
||||
// Available since Redis 7.4 CE.
|
||||
// For more information refer to [HTTL Documentation].
|
||||
//
|
||||
// [HTTL Documentation]: https://redis.io/commands/httl/
|
||||
func (c cmdable) HTTL(ctx context.Context, key string, fields ...string) *IntSliceCmd {
|
||||
args := []interface{}{"HTTL", key, "FIELDS", len(fields)}
|
||||
|
||||
for _, field := range fields {
|
||||
args = append(args, field)
|
||||
}
|
||||
cmd := NewIntSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// HPTTL - Retrieves the remaining time to live for specified fields in a hash in milliseconds.
|
||||
// Similar to HTTL, but returns the TTL in milliseconds. It requires a key and the specified fields.
|
||||
// This command provides the TTL in milliseconds for each field or returns error/status codes as needed.
|
||||
// Available since Redis 7.4 CE.
|
||||
// For more information refer to [HPTTL Documentation].
|
||||
//
|
||||
// [HPTTL Documentation]: https://redis.io/commands/hpttl/
|
||||
// For more information - https://redis.io/commands/hpttl/
|
||||
func (c cmdable) HPTTL(ctx context.Context, key string, fields ...string) *IntSliceCmd {
|
||||
args := []interface{}{"HPTTL", key, "FIELDS", len(fields)}
|
||||
|
||||
for _, field := range fields {
|
||||
args = append(args, field)
|
||||
}
|
||||
cmd := NewIntSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) HGetDel(ctx context.Context, key string, fields ...string) *StringSliceCmd {
|
||||
args := []interface{}{"HGETDEL", key, "FIELDS", len(fields)}
|
||||
for _, field := range fields {
|
||||
args = append(args, field)
|
||||
}
|
||||
cmd := NewStringSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) HGetEX(ctx context.Context, key string, fields ...string) *StringSliceCmd {
|
||||
args := []interface{}{"HGETEX", key, "FIELDS", len(fields)}
|
||||
for _, field := range fields {
|
||||
args = append(args, field)
|
||||
}
|
||||
cmd := NewStringSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// HGetEXExpirationType represents an expiration option for the HGETEX command.
|
||||
type HGetEXExpirationType string
|
||||
|
||||
const (
|
||||
HGetEXExpirationEX HGetEXExpirationType = "EX"
|
||||
HGetEXExpirationPX HGetEXExpirationType = "PX"
|
||||
HGetEXExpirationEXAT HGetEXExpirationType = "EXAT"
|
||||
HGetEXExpirationPXAT HGetEXExpirationType = "PXAT"
|
||||
HGetEXExpirationPERSIST HGetEXExpirationType = "PERSIST"
|
||||
)
|
||||
|
||||
type HGetEXOptions struct {
|
||||
ExpirationType HGetEXExpirationType
|
||||
ExpirationVal int64
|
||||
}
|
||||
|
||||
func (c cmdable) HGetEXWithArgs(ctx context.Context, key string, options *HGetEXOptions, fields ...string) *StringSliceCmd {
|
||||
args := []interface{}{"HGETEX", key}
|
||||
if options.ExpirationType != "" {
|
||||
args = append(args, string(options.ExpirationType))
|
||||
if options.ExpirationType != HGetEXExpirationPERSIST {
|
||||
args = append(args, options.ExpirationVal)
|
||||
}
|
||||
}
|
||||
|
||||
args = append(args, "FIELDS", len(fields))
|
||||
for _, field := range fields {
|
||||
args = append(args, field)
|
||||
}
|
||||
|
||||
cmd := NewStringSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
type HSetEXCondition string
|
||||
|
||||
const (
|
||||
HSetEXFNX HSetEXCondition = "FNX" // Only set the fields if none of them already exist.
|
||||
HSetEXFXX HSetEXCondition = "FXX" // Only set the fields if all already exist.
|
||||
)
|
||||
|
||||
type HSetEXExpirationType string
|
||||
|
||||
const (
|
||||
HSetEXExpirationEX HSetEXExpirationType = "EX"
|
||||
HSetEXExpirationPX HSetEXExpirationType = "PX"
|
||||
HSetEXExpirationEXAT HSetEXExpirationType = "EXAT"
|
||||
HSetEXExpirationPXAT HSetEXExpirationType = "PXAT"
|
||||
HSetEXExpirationKEEPTTL HSetEXExpirationType = "KEEPTTL"
|
||||
)
|
||||
|
||||
type HSetEXOptions struct {
|
||||
Condition HSetEXCondition
|
||||
ExpirationType HSetEXExpirationType
|
||||
ExpirationVal int64
|
||||
}
|
||||
|
||||
func (c cmdable) HSetEX(ctx context.Context, key string, fieldsAndValues ...string) *IntCmd {
|
||||
args := []interface{}{"HSETEX", key, "FIELDS", len(fieldsAndValues) / 2}
|
||||
for _, field := range fieldsAndValues {
|
||||
args = append(args, field)
|
||||
}
|
||||
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) HSetEXWithArgs(ctx context.Context, key string, options *HSetEXOptions, fieldsAndValues ...string) *IntCmd {
|
||||
args := []interface{}{"HSETEX", key}
|
||||
if options.Condition != "" {
|
||||
args = append(args, string(options.Condition))
|
||||
}
|
||||
if options.ExpirationType != "" {
|
||||
args = append(args, string(options.ExpirationType))
|
||||
if options.ExpirationType != HSetEXExpirationKEEPTTL {
|
||||
args = append(args, options.ExpirationVal)
|
||||
}
|
||||
}
|
||||
args = append(args, "FIELDS", len(fieldsAndValues)/2)
|
||||
for _, field := range fieldsAndValues {
|
||||
args = append(args, field)
|
||||
}
|
||||
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
42
vendor/github.com/redis/go-redis/v9/hyperloglog_commands.go
generated
vendored
Normal file
42
vendor/github.com/redis/go-redis/v9/hyperloglog_commands.go
generated
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
package redis
|
||||
|
||||
import "context"
|
||||
|
||||
type HyperLogLogCmdable interface {
|
||||
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
|
||||
}
|
||||
|
||||
func (c cmdable) PFAdd(ctx context.Context, key string, els ...interface{}) *IntCmd {
|
||||
args := make([]interface{}, 2, 2+len(els))
|
||||
args[0] = "pfadd"
|
||||
args[1] = key
|
||||
args = appendArgs(args, els)
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) PFCount(ctx context.Context, keys ...string) *IntCmd {
|
||||
args := make([]interface{}, 1+len(keys))
|
||||
args[0] = "pfcount"
|
||||
for i, key := range keys {
|
||||
args[1+i] = key
|
||||
}
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) PFMerge(ctx context.Context, dest string, keys ...string) *StatusCmd {
|
||||
args := make([]interface{}, 2+len(keys))
|
||||
args[0] = "pfmerge"
|
||||
args[1] = dest
|
||||
for i, key := range keys {
|
||||
args[2+i] = key
|
||||
}
|
||||
cmd := NewStatusCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
58
vendor/github.com/redis/go-redis/v9/internal/arg.go
generated
vendored
Normal file
58
vendor/github.com/redis/go-redis/v9/internal/arg.go
generated
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/redis/go-redis/v9/internal/util"
|
||||
)
|
||||
|
||||
func AppendArg(b []byte, v interface{}) []byte {
|
||||
switch v := v.(type) {
|
||||
case nil:
|
||||
return append(b, "<nil>"...)
|
||||
case string:
|
||||
return appendUTF8String(b, util.StringToBytes(v))
|
||||
case []byte:
|
||||
return appendUTF8String(b, v)
|
||||
case int:
|
||||
return strconv.AppendInt(b, int64(v), 10)
|
||||
case int8:
|
||||
return strconv.AppendInt(b, int64(v), 10)
|
||||
case int16:
|
||||
return strconv.AppendInt(b, int64(v), 10)
|
||||
case int32:
|
||||
return strconv.AppendInt(b, int64(v), 10)
|
||||
case int64:
|
||||
return strconv.AppendInt(b, v, 10)
|
||||
case uint:
|
||||
return strconv.AppendUint(b, uint64(v), 10)
|
||||
case uint8:
|
||||
return strconv.AppendUint(b, uint64(v), 10)
|
||||
case uint16:
|
||||
return strconv.AppendUint(b, uint64(v), 10)
|
||||
case uint32:
|
||||
return strconv.AppendUint(b, uint64(v), 10)
|
||||
case uint64:
|
||||
return strconv.AppendUint(b, v, 10)
|
||||
case float32:
|
||||
return strconv.AppendFloat(b, float64(v), 'f', -1, 64)
|
||||
case float64:
|
||||
return strconv.AppendFloat(b, v, 'f', -1, 64)
|
||||
case bool:
|
||||
if v {
|
||||
return append(b, "true"...)
|
||||
}
|
||||
return append(b, "false"...)
|
||||
case time.Time:
|
||||
return v.AppendFormat(b, time.RFC3339Nano)
|
||||
default:
|
||||
return append(b, fmt.Sprint(v)...)
|
||||
}
|
||||
}
|
||||
|
||||
func appendUTF8String(dst []byte, src []byte) []byte {
|
||||
dst = append(dst, src...)
|
||||
return dst
|
||||
}
|
||||
90
vendor/github.com/redis/go-redis/v9/internal/hashtag/hashtag.go
generated
vendored
Normal file
90
vendor/github.com/redis/go-redis/v9/internal/hashtag/hashtag.go
generated
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
package hashtag
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/redis/go-redis/v9/internal/rand"
|
||||
)
|
||||
|
||||
const slotNumber = 16384
|
||||
|
||||
// CRC16 implementation according to CCITT standards.
|
||||
// Copyright 2001-2010 Georges Menie (www.menie.org)
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// http://redis.io/topics/cluster-spec#appendix-a-crc16-reference-implementation-in-ansi-c
|
||||
var crc16tab = [256]uint16{
|
||||
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
|
||||
0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,
|
||||
0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,
|
||||
0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de,
|
||||
0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485,
|
||||
0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,
|
||||
0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4,
|
||||
0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc,
|
||||
0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823,
|
||||
0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b,
|
||||
0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12,
|
||||
0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,
|
||||
0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41,
|
||||
0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49,
|
||||
0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,
|
||||
0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78,
|
||||
0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f,
|
||||
0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,
|
||||
0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e,
|
||||
0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256,
|
||||
0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,
|
||||
0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
|
||||
0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c,
|
||||
0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,
|
||||
0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab,
|
||||
0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3,
|
||||
0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,
|
||||
0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92,
|
||||
0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9,
|
||||
0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,
|
||||
0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8,
|
||||
0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0,
|
||||
}
|
||||
|
||||
func Key(key string) string {
|
||||
if s := strings.IndexByte(key, '{'); s > -1 {
|
||||
if e := strings.IndexByte(key[s+1:], '}'); e > 0 {
|
||||
return key[s+1 : s+e+1]
|
||||
}
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
func Present(key string) bool {
|
||||
if key == "" {
|
||||
return false
|
||||
}
|
||||
if s := strings.IndexByte(key, '{'); s > -1 {
|
||||
if e := strings.IndexByte(key[s+1:], '}'); e > 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func RandomSlot() int {
|
||||
return rand.Intn(slotNumber)
|
||||
}
|
||||
|
||||
// Slot returns a consistent slot number between 0 and 16383
|
||||
// for any given string key.
|
||||
func Slot(key string) int {
|
||||
if key == "" {
|
||||
return RandomSlot()
|
||||
}
|
||||
key = Key(key)
|
||||
return int(crc16sum(key)) % slotNumber
|
||||
}
|
||||
|
||||
func crc16sum(key string) (crc uint16) {
|
||||
for i := 0; i < len(key); i++ {
|
||||
crc = (crc << 8) ^ crc16tab[(byte(crc>>8)^key[i])&0x00ff]
|
||||
}
|
||||
return
|
||||
}
|
||||
207
vendor/github.com/redis/go-redis/v9/internal/hscan/hscan.go
generated
vendored
Normal file
207
vendor/github.com/redis/go-redis/v9/internal/hscan/hscan.go
generated
vendored
Normal file
@@ -0,0 +1,207 @@
|
||||
package hscan
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// decoderFunc represents decoding functions for default built-in types.
|
||||
type decoderFunc func(reflect.Value, string) error
|
||||
|
||||
// Scanner is the interface implemented by themselves,
|
||||
// which will override the decoding behavior of decoderFunc.
|
||||
type Scanner interface {
|
||||
ScanRedis(s string) error
|
||||
}
|
||||
|
||||
var (
|
||||
// List of built-in decoders indexed by their numeric constant values (eg: reflect.Bool = 1).
|
||||
decoders = []decoderFunc{
|
||||
reflect.Bool: decodeBool,
|
||||
reflect.Int: decodeInt,
|
||||
reflect.Int8: decodeInt8,
|
||||
reflect.Int16: decodeInt16,
|
||||
reflect.Int32: decodeInt32,
|
||||
reflect.Int64: decodeInt64,
|
||||
reflect.Uint: decodeUint,
|
||||
reflect.Uint8: decodeUint8,
|
||||
reflect.Uint16: decodeUint16,
|
||||
reflect.Uint32: decodeUint32,
|
||||
reflect.Uint64: decodeUint64,
|
||||
reflect.Float32: decodeFloat32,
|
||||
reflect.Float64: decodeFloat64,
|
||||
reflect.Complex64: decodeUnsupported,
|
||||
reflect.Complex128: decodeUnsupported,
|
||||
reflect.Array: decodeUnsupported,
|
||||
reflect.Chan: decodeUnsupported,
|
||||
reflect.Func: decodeUnsupported,
|
||||
reflect.Interface: decodeUnsupported,
|
||||
reflect.Map: decodeUnsupported,
|
||||
reflect.Ptr: decodeUnsupported,
|
||||
reflect.Slice: decodeSlice,
|
||||
reflect.String: decodeString,
|
||||
reflect.Struct: decodeUnsupported,
|
||||
reflect.UnsafePointer: decodeUnsupported,
|
||||
}
|
||||
|
||||
// Global map of struct field specs that is populated once for every new
|
||||
// struct type that is scanned. This caches the field types and the corresponding
|
||||
// decoder functions to avoid iterating through struct fields on subsequent scans.
|
||||
globalStructMap = newStructMap()
|
||||
)
|
||||
|
||||
func Struct(dst interface{}) (StructValue, error) {
|
||||
v := reflect.ValueOf(dst)
|
||||
|
||||
// The destination to scan into should be a struct pointer.
|
||||
if v.Kind() != reflect.Ptr || v.IsNil() {
|
||||
return StructValue{}, fmt.Errorf("redis.Scan(non-pointer %T)", dst)
|
||||
}
|
||||
|
||||
v = v.Elem()
|
||||
if v.Kind() != reflect.Struct {
|
||||
return StructValue{}, fmt.Errorf("redis.Scan(non-struct %T)", dst)
|
||||
}
|
||||
|
||||
return StructValue{
|
||||
spec: globalStructMap.get(v.Type()),
|
||||
value: v,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Scan scans the results from a key-value Redis map result set to a destination struct.
|
||||
// The Redis keys are matched to the struct's field with the `redis` tag.
|
||||
func Scan(dst interface{}, keys []interface{}, vals []interface{}) error {
|
||||
if len(keys) != len(vals) {
|
||||
return errors.New("args should have the same number of keys and vals")
|
||||
}
|
||||
|
||||
strct, err := Struct(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Iterate through the (key, value) sequence.
|
||||
for i := 0; i < len(vals); i++ {
|
||||
key, ok := keys[i].(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
val, ok := vals[i].(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := strct.Scan(key, val); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodeBool(f reflect.Value, s string) error {
|
||||
b, err := strconv.ParseBool(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.SetBool(b)
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodeInt8(f reflect.Value, s string) error {
|
||||
return decodeNumber(f, s, 8)
|
||||
}
|
||||
|
||||
func decodeInt16(f reflect.Value, s string) error {
|
||||
return decodeNumber(f, s, 16)
|
||||
}
|
||||
|
||||
func decodeInt32(f reflect.Value, s string) error {
|
||||
return decodeNumber(f, s, 32)
|
||||
}
|
||||
|
||||
func decodeInt64(f reflect.Value, s string) error {
|
||||
return decodeNumber(f, s, 64)
|
||||
}
|
||||
|
||||
func decodeInt(f reflect.Value, s string) error {
|
||||
return decodeNumber(f, s, 0)
|
||||
}
|
||||
|
||||
func decodeNumber(f reflect.Value, s string, bitSize int) error {
|
||||
v, err := strconv.ParseInt(s, 10, bitSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.SetInt(v)
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodeUint8(f reflect.Value, s string) error {
|
||||
return decodeUnsignedNumber(f, s, 8)
|
||||
}
|
||||
|
||||
func decodeUint16(f reflect.Value, s string) error {
|
||||
return decodeUnsignedNumber(f, s, 16)
|
||||
}
|
||||
|
||||
func decodeUint32(f reflect.Value, s string) error {
|
||||
return decodeUnsignedNumber(f, s, 32)
|
||||
}
|
||||
|
||||
func decodeUint64(f reflect.Value, s string) error {
|
||||
return decodeUnsignedNumber(f, s, 64)
|
||||
}
|
||||
|
||||
func decodeUint(f reflect.Value, s string) error {
|
||||
return decodeUnsignedNumber(f, s, 0)
|
||||
}
|
||||
|
||||
func decodeUnsignedNumber(f reflect.Value, s string, bitSize int) error {
|
||||
v, err := strconv.ParseUint(s, 10, bitSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.SetUint(v)
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodeFloat32(f reflect.Value, s string) error {
|
||||
v, err := strconv.ParseFloat(s, 32)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.SetFloat(v)
|
||||
return nil
|
||||
}
|
||||
|
||||
// although the default is float64, but we better define it.
|
||||
func decodeFloat64(f reflect.Value, s string) error {
|
||||
v, err := strconv.ParseFloat(s, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.SetFloat(v)
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodeString(f reflect.Value, s string) error {
|
||||
f.SetString(s)
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodeSlice(f reflect.Value, s string) error {
|
||||
// []byte slice ([]uint8).
|
||||
if f.Type().Elem().Kind() == reflect.Uint8 {
|
||||
f.SetBytes([]byte(s))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodeUnsupported(v reflect.Value, s string) error {
|
||||
return fmt.Errorf("redis.Scan(unsupported %s)", v.Type())
|
||||
}
|
||||
125
vendor/github.com/redis/go-redis/v9/internal/hscan/structmap.go
generated
vendored
Normal file
125
vendor/github.com/redis/go-redis/v9/internal/hscan/structmap.go
generated
vendored
Normal file
@@ -0,0 +1,125 @@
|
||||
package hscan
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/redis/go-redis/v9/internal/util"
|
||||
)
|
||||
|
||||
// structMap contains the map of struct fields for target structs
|
||||
// indexed by the struct type.
|
||||
type structMap struct {
|
||||
m sync.Map
|
||||
}
|
||||
|
||||
func newStructMap() *structMap {
|
||||
return new(structMap)
|
||||
}
|
||||
|
||||
func (s *structMap) get(t reflect.Type) *structSpec {
|
||||
if v, ok := s.m.Load(t); ok {
|
||||
return v.(*structSpec)
|
||||
}
|
||||
|
||||
spec := newStructSpec(t, "redis")
|
||||
s.m.Store(t, spec)
|
||||
return spec
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// structSpec contains the list of all fields in a target struct.
|
||||
type structSpec struct {
|
||||
m map[string]*structField
|
||||
}
|
||||
|
||||
func (s *structSpec) set(tag string, sf *structField) {
|
||||
s.m[tag] = sf
|
||||
}
|
||||
|
||||
func newStructSpec(t reflect.Type, fieldTag string) *structSpec {
|
||||
numField := t.NumField()
|
||||
out := &structSpec{
|
||||
m: make(map[string]*structField, numField),
|
||||
}
|
||||
|
||||
for i := 0; i < numField; i++ {
|
||||
f := t.Field(i)
|
||||
|
||||
tag := f.Tag.Get(fieldTag)
|
||||
if tag == "" || tag == "-" {
|
||||
continue
|
||||
}
|
||||
|
||||
tag = strings.Split(tag, ",")[0]
|
||||
if tag == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Use the built-in decoder.
|
||||
kind := f.Type.Kind()
|
||||
if kind == reflect.Pointer {
|
||||
kind = f.Type.Elem().Kind()
|
||||
}
|
||||
out.set(tag, &structField{index: i, fn: decoders[kind]})
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// structField represents a single field in a target struct.
|
||||
type structField struct {
|
||||
index int
|
||||
fn decoderFunc
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
type StructValue struct {
|
||||
spec *structSpec
|
||||
value reflect.Value
|
||||
}
|
||||
|
||||
func (s StructValue) Scan(key string, value string) error {
|
||||
field, ok := s.spec.m[key]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
v := s.value.Field(field.index)
|
||||
isPtr := v.Kind() == reflect.Ptr
|
||||
|
||||
if isPtr && v.IsNil() {
|
||||
v.Set(reflect.New(v.Type().Elem()))
|
||||
}
|
||||
if !isPtr && v.Type().Name() != "" && v.CanAddr() {
|
||||
v = v.Addr()
|
||||
isPtr = true
|
||||
}
|
||||
|
||||
if isPtr && v.Type().NumMethod() > 0 && v.CanInterface() {
|
||||
switch scan := v.Interface().(type) {
|
||||
case Scanner:
|
||||
return scan.ScanRedis(value)
|
||||
case encoding.TextUnmarshaler:
|
||||
return scan.UnmarshalText(util.StringToBytes(value))
|
||||
}
|
||||
}
|
||||
|
||||
if isPtr {
|
||||
v = v.Elem()
|
||||
}
|
||||
|
||||
if err := field.fn(v, value); err != nil {
|
||||
t := s.value.Type()
|
||||
return fmt.Errorf("cannot scan redis.result %s into struct field %s.%s of type %s, error-%s",
|
||||
value, t.Name(), t.Field(field.index).Name, t.Field(field.index).Type, err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
29
vendor/github.com/redis/go-redis/v9/internal/internal.go
generated
vendored
Normal file
29
vendor/github.com/redis/go-redis/v9/internal/internal.go
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/redis/go-redis/v9/internal/rand"
|
||||
)
|
||||
|
||||
func RetryBackoff(retry int, minBackoff, maxBackoff time.Duration) time.Duration {
|
||||
if retry < 0 {
|
||||
panic("not reached")
|
||||
}
|
||||
if minBackoff == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
d := minBackoff << uint(retry)
|
||||
if d < minBackoff {
|
||||
return maxBackoff
|
||||
}
|
||||
|
||||
d = minBackoff + time.Duration(rand.Int63n(int64(d)))
|
||||
|
||||
if d > maxBackoff || d < minBackoff {
|
||||
d = maxBackoff
|
||||
}
|
||||
|
||||
return d
|
||||
}
|
||||
26
vendor/github.com/redis/go-redis/v9/internal/log.go
generated
vendored
Normal file
26
vendor/github.com/redis/go-redis/v9/internal/log.go
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
type Logging interface {
|
||||
Printf(ctx context.Context, format string, v ...interface{})
|
||||
}
|
||||
|
||||
type logger struct {
|
||||
log *log.Logger
|
||||
}
|
||||
|
||||
func (l *logger) Printf(ctx context.Context, format string, v ...interface{}) {
|
||||
_ = l.log.Output(2, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
// Logger calls Output to print to the stderr.
|
||||
// Arguments are handled in the manner of fmt.Print.
|
||||
var Logger Logging = &logger{
|
||||
log: log.New(os.Stderr, "redis: ", log.LstdFlags|log.Lshortfile),
|
||||
}
|
||||
63
vendor/github.com/redis/go-redis/v9/internal/once.go
generated
vendored
Normal file
63
vendor/github.com/redis/go-redis/v9/internal/once.go
generated
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
Copyright 2014 The Camlistore Authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// A Once will perform a successful action exactly once.
|
||||
//
|
||||
// Unlike a sync.Once, this Once's func returns an error
|
||||
// and is re-armed on failure.
|
||||
type Once struct {
|
||||
m sync.Mutex
|
||||
done uint32
|
||||
}
|
||||
|
||||
// Do calls the function f if and only if Do has not been invoked
|
||||
// without error for this instance of Once. In other words, given
|
||||
//
|
||||
// var once Once
|
||||
//
|
||||
// if once.Do(f) is called multiple times, only the first call will
|
||||
// invoke f, even if f has a different value in each invocation unless
|
||||
// f returns an error. A new instance of Once is required for each
|
||||
// function to execute.
|
||||
//
|
||||
// Do is intended for initialization that must be run exactly once. Since f
|
||||
// is niladic, it may be necessary to use a function literal to capture the
|
||||
// arguments to a function to be invoked by Do:
|
||||
//
|
||||
// err := config.once.Do(func() error { return config.init(filename) })
|
||||
func (o *Once) Do(f func() error) error {
|
||||
if atomic.LoadUint32(&o.done) == 1 {
|
||||
return nil
|
||||
}
|
||||
// Slow-path.
|
||||
o.m.Lock()
|
||||
defer o.m.Unlock()
|
||||
var err error
|
||||
if o.done == 0 {
|
||||
err = f()
|
||||
if err == nil {
|
||||
atomic.StoreUint32(&o.done, 1)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
137
vendor/github.com/redis/go-redis/v9/internal/pool/conn.go
generated
vendored
Normal file
137
vendor/github.com/redis/go-redis/v9/internal/pool/conn.go
generated
vendored
Normal file
@@ -0,0 +1,137 @@
|
||||
package pool
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"net"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/redis/go-redis/v9/internal/proto"
|
||||
)
|
||||
|
||||
var noDeadline = time.Time{}
|
||||
|
||||
type Conn struct {
|
||||
usedAt int64 // atomic
|
||||
netConn net.Conn
|
||||
|
||||
rd *proto.Reader
|
||||
bw *bufio.Writer
|
||||
wr *proto.Writer
|
||||
|
||||
Inited bool
|
||||
pooled bool
|
||||
createdAt time.Time
|
||||
|
||||
onClose func() error
|
||||
}
|
||||
|
||||
func NewConn(netConn net.Conn) *Conn {
|
||||
cn := &Conn{
|
||||
netConn: netConn,
|
||||
createdAt: time.Now(),
|
||||
}
|
||||
cn.rd = proto.NewReader(netConn)
|
||||
cn.bw = bufio.NewWriter(netConn)
|
||||
cn.wr = proto.NewWriter(cn.bw)
|
||||
cn.SetUsedAt(time.Now())
|
||||
return cn
|
||||
}
|
||||
|
||||
func (cn *Conn) UsedAt() time.Time {
|
||||
unix := atomic.LoadInt64(&cn.usedAt)
|
||||
return time.Unix(unix, 0)
|
||||
}
|
||||
|
||||
func (cn *Conn) SetUsedAt(tm time.Time) {
|
||||
atomic.StoreInt64(&cn.usedAt, tm.Unix())
|
||||
}
|
||||
|
||||
func (cn *Conn) SetOnClose(fn func() error) {
|
||||
cn.onClose = fn
|
||||
}
|
||||
|
||||
func (cn *Conn) SetNetConn(netConn net.Conn) {
|
||||
cn.netConn = netConn
|
||||
cn.rd.Reset(netConn)
|
||||
cn.bw.Reset(netConn)
|
||||
}
|
||||
|
||||
func (cn *Conn) Write(b []byte) (int, error) {
|
||||
return cn.netConn.Write(b)
|
||||
}
|
||||
|
||||
func (cn *Conn) RemoteAddr() net.Addr {
|
||||
if cn.netConn != nil {
|
||||
return cn.netConn.RemoteAddr()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cn *Conn) WithReader(
|
||||
ctx context.Context, timeout time.Duration, fn func(rd *proto.Reader) error,
|
||||
) error {
|
||||
if timeout >= 0 {
|
||||
if err := cn.netConn.SetReadDeadline(cn.deadline(ctx, timeout)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return fn(cn.rd)
|
||||
}
|
||||
|
||||
func (cn *Conn) WithWriter(
|
||||
ctx context.Context, timeout time.Duration, fn func(wr *proto.Writer) error,
|
||||
) error {
|
||||
if timeout >= 0 {
|
||||
if err := cn.netConn.SetWriteDeadline(cn.deadline(ctx, timeout)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if cn.bw.Buffered() > 0 {
|
||||
cn.bw.Reset(cn.netConn)
|
||||
}
|
||||
|
||||
if err := fn(cn.wr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return cn.bw.Flush()
|
||||
}
|
||||
|
||||
func (cn *Conn) Close() error {
|
||||
if cn.onClose != nil {
|
||||
// ignore error
|
||||
_ = cn.onClose()
|
||||
}
|
||||
return cn.netConn.Close()
|
||||
}
|
||||
|
||||
func (cn *Conn) deadline(ctx context.Context, timeout time.Duration) time.Time {
|
||||
tm := time.Now()
|
||||
cn.SetUsedAt(tm)
|
||||
|
||||
if timeout > 0 {
|
||||
tm = tm.Add(timeout)
|
||||
}
|
||||
|
||||
if ctx != nil {
|
||||
deadline, ok := ctx.Deadline()
|
||||
if ok {
|
||||
if timeout == 0 {
|
||||
return deadline
|
||||
}
|
||||
if deadline.Before(tm) {
|
||||
return deadline
|
||||
}
|
||||
return tm
|
||||
}
|
||||
}
|
||||
|
||||
if timeout > 0 {
|
||||
return tm
|
||||
}
|
||||
|
||||
return noDeadline
|
||||
}
|
||||
49
vendor/github.com/redis/go-redis/v9/internal/pool/conn_check.go
generated
vendored
Normal file
49
vendor/github.com/redis/go-redis/v9/internal/pool/conn_check.go
generated
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
//go:build linux || darwin || dragonfly || freebsd || netbsd || openbsd || solaris || illumos
|
||||
|
||||
package pool
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
var errUnexpectedRead = errors.New("unexpected read from socket")
|
||||
|
||||
func connCheck(conn net.Conn) error {
|
||||
// Reset previous timeout.
|
||||
_ = conn.SetDeadline(time.Time{})
|
||||
|
||||
sysConn, ok := conn.(syscall.Conn)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
rawConn, err := sysConn.SyscallConn()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var sysErr error
|
||||
|
||||
if err := rawConn.Read(func(fd uintptr) bool {
|
||||
var buf [1]byte
|
||||
n, err := syscall.Read(int(fd), buf[:])
|
||||
switch {
|
||||
case n == 0 && err == nil:
|
||||
sysErr = io.EOF
|
||||
case n > 0:
|
||||
sysErr = errUnexpectedRead
|
||||
case err == syscall.EAGAIN || err == syscall.EWOULDBLOCK:
|
||||
sysErr = nil
|
||||
default:
|
||||
sysErr = err
|
||||
}
|
||||
return true
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return sysErr
|
||||
}
|
||||
9
vendor/github.com/redis/go-redis/v9/internal/pool/conn_check_dummy.go
generated
vendored
Normal file
9
vendor/github.com/redis/go-redis/v9/internal/pool/conn_check_dummy.go
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
//go:build !linux && !darwin && !dragonfly && !freebsd && !netbsd && !openbsd && !solaris && !illumos
|
||||
|
||||
package pool
|
||||
|
||||
import "net"
|
||||
|
||||
func connCheck(conn net.Conn) error {
|
||||
return nil
|
||||
}
|
||||
532
vendor/github.com/redis/go-redis/v9/internal/pool/pool.go
generated
vendored
Normal file
532
vendor/github.com/redis/go-redis/v9/internal/pool/pool.go
generated
vendored
Normal file
@@ -0,0 +1,532 @@
|
||||
package pool
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/redis/go-redis/v9/internal"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrClosed performs any operation on the closed client will return this error.
|
||||
ErrClosed = errors.New("redis: client is closed")
|
||||
|
||||
// ErrPoolExhausted is returned from a pool connection method
|
||||
// when the maximum number of database connections in the pool has been reached.
|
||||
ErrPoolExhausted = errors.New("redis: connection pool exhausted")
|
||||
|
||||
// ErrPoolTimeout timed out waiting to get a connection from the connection pool.
|
||||
ErrPoolTimeout = errors.New("redis: connection pool timeout")
|
||||
)
|
||||
|
||||
var timers = sync.Pool{
|
||||
New: func() interface{} {
|
||||
t := time.NewTimer(time.Hour)
|
||||
t.Stop()
|
||||
return t
|
||||
},
|
||||
}
|
||||
|
||||
// Stats contains pool state information and accumulated stats.
|
||||
type Stats struct {
|
||||
Hits uint32 // number of times free connection was found in the pool
|
||||
Misses uint32 // number of times free connection was NOT found in the pool
|
||||
Timeouts uint32 // number of times a wait timeout occurred
|
||||
WaitCount uint32 // number of times a connection was waited
|
||||
WaitDurationNs int64 // total time spent for waiting a connection in nanoseconds
|
||||
|
||||
TotalConns uint32 // number of total connections in the pool
|
||||
IdleConns uint32 // number of idle connections in the pool
|
||||
StaleConns uint32 // number of stale connections removed from the pool
|
||||
}
|
||||
|
||||
type Pooler interface {
|
||||
NewConn(context.Context) (*Conn, error)
|
||||
CloseConn(*Conn) error
|
||||
|
||||
Get(context.Context) (*Conn, error)
|
||||
Put(context.Context, *Conn)
|
||||
Remove(context.Context, *Conn, error)
|
||||
|
||||
Len() int
|
||||
IdleLen() int
|
||||
Stats() *Stats
|
||||
|
||||
Close() error
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
Dialer func(context.Context) (net.Conn, error)
|
||||
|
||||
PoolFIFO bool
|
||||
PoolSize int
|
||||
DialTimeout time.Duration
|
||||
PoolTimeout time.Duration
|
||||
MinIdleConns int
|
||||
MaxIdleConns int
|
||||
MaxActiveConns int
|
||||
ConnMaxIdleTime time.Duration
|
||||
ConnMaxLifetime time.Duration
|
||||
}
|
||||
|
||||
type lastDialErrorWrap struct {
|
||||
err error
|
||||
}
|
||||
|
||||
type ConnPool struct {
|
||||
cfg *Options
|
||||
|
||||
dialErrorsNum uint32 // atomic
|
||||
lastDialError atomic.Value
|
||||
|
||||
queue chan struct{}
|
||||
|
||||
connsMu sync.Mutex
|
||||
conns []*Conn
|
||||
idleConns []*Conn
|
||||
|
||||
poolSize int
|
||||
idleConnsLen int
|
||||
|
||||
stats Stats
|
||||
waitDurationNs atomic.Int64
|
||||
|
||||
_closed uint32 // atomic
|
||||
}
|
||||
|
||||
var _ Pooler = (*ConnPool)(nil)
|
||||
|
||||
func NewConnPool(opt *Options) *ConnPool {
|
||||
p := &ConnPool{
|
||||
cfg: opt,
|
||||
|
||||
queue: make(chan struct{}, opt.PoolSize),
|
||||
conns: make([]*Conn, 0, opt.PoolSize),
|
||||
idleConns: make([]*Conn, 0, opt.PoolSize),
|
||||
}
|
||||
|
||||
p.connsMu.Lock()
|
||||
p.checkMinIdleConns()
|
||||
p.connsMu.Unlock()
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *ConnPool) checkMinIdleConns() {
|
||||
if p.cfg.MinIdleConns == 0 {
|
||||
return
|
||||
}
|
||||
for p.poolSize < p.cfg.PoolSize && p.idleConnsLen < p.cfg.MinIdleConns {
|
||||
select {
|
||||
case p.queue <- struct{}{}:
|
||||
p.poolSize++
|
||||
p.idleConnsLen++
|
||||
|
||||
go func() {
|
||||
err := p.addIdleConn()
|
||||
if err != nil && err != ErrClosed {
|
||||
p.connsMu.Lock()
|
||||
p.poolSize--
|
||||
p.idleConnsLen--
|
||||
p.connsMu.Unlock()
|
||||
}
|
||||
|
||||
p.freeTurn()
|
||||
}()
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ConnPool) addIdleConn() error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), p.cfg.DialTimeout)
|
||||
defer cancel()
|
||||
|
||||
cn, err := p.dialConn(ctx, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.connsMu.Lock()
|
||||
defer p.connsMu.Unlock()
|
||||
|
||||
// It is not allowed to add new connections to the closed connection pool.
|
||||
if p.closed() {
|
||||
_ = cn.Close()
|
||||
return ErrClosed
|
||||
}
|
||||
|
||||
p.conns = append(p.conns, cn)
|
||||
p.idleConns = append(p.idleConns, cn)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *ConnPool) NewConn(ctx context.Context) (*Conn, error) {
|
||||
return p.newConn(ctx, false)
|
||||
}
|
||||
|
||||
func (p *ConnPool) newConn(ctx context.Context, pooled bool) (*Conn, error) {
|
||||
if p.closed() {
|
||||
return nil, ErrClosed
|
||||
}
|
||||
|
||||
p.connsMu.Lock()
|
||||
if p.cfg.MaxActiveConns > 0 && p.poolSize >= p.cfg.MaxActiveConns {
|
||||
p.connsMu.Unlock()
|
||||
return nil, ErrPoolExhausted
|
||||
}
|
||||
p.connsMu.Unlock()
|
||||
|
||||
cn, err := p.dialConn(ctx, pooled)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p.connsMu.Lock()
|
||||
defer p.connsMu.Unlock()
|
||||
|
||||
if p.cfg.MaxActiveConns > 0 && p.poolSize >= p.cfg.MaxActiveConns {
|
||||
_ = cn.Close()
|
||||
return nil, ErrPoolExhausted
|
||||
}
|
||||
|
||||
p.conns = append(p.conns, cn)
|
||||
if pooled {
|
||||
// If pool is full remove the cn on next Put.
|
||||
if p.poolSize >= p.cfg.PoolSize {
|
||||
cn.pooled = false
|
||||
} else {
|
||||
p.poolSize++
|
||||
}
|
||||
}
|
||||
|
||||
return cn, nil
|
||||
}
|
||||
|
||||
func (p *ConnPool) dialConn(ctx context.Context, pooled bool) (*Conn, error) {
|
||||
if p.closed() {
|
||||
return nil, ErrClosed
|
||||
}
|
||||
|
||||
if atomic.LoadUint32(&p.dialErrorsNum) >= uint32(p.cfg.PoolSize) {
|
||||
return nil, p.getLastDialError()
|
||||
}
|
||||
|
||||
netConn, err := p.cfg.Dialer(ctx)
|
||||
if err != nil {
|
||||
p.setLastDialError(err)
|
||||
if atomic.AddUint32(&p.dialErrorsNum, 1) == uint32(p.cfg.PoolSize) {
|
||||
go p.tryDial()
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cn := NewConn(netConn)
|
||||
cn.pooled = pooled
|
||||
return cn, nil
|
||||
}
|
||||
|
||||
func (p *ConnPool) tryDial() {
|
||||
for {
|
||||
if p.closed() {
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), p.cfg.DialTimeout)
|
||||
|
||||
conn, err := p.cfg.Dialer(ctx)
|
||||
if err != nil {
|
||||
p.setLastDialError(err)
|
||||
time.Sleep(time.Second)
|
||||
cancel()
|
||||
continue
|
||||
}
|
||||
|
||||
atomic.StoreUint32(&p.dialErrorsNum, 0)
|
||||
_ = conn.Close()
|
||||
cancel()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ConnPool) setLastDialError(err error) {
|
||||
p.lastDialError.Store(&lastDialErrorWrap{err: err})
|
||||
}
|
||||
|
||||
func (p *ConnPool) getLastDialError() error {
|
||||
err, _ := p.lastDialError.Load().(*lastDialErrorWrap)
|
||||
if err != nil {
|
||||
return err.err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get returns existed connection from the pool or creates a new one.
|
||||
func (p *ConnPool) Get(ctx context.Context) (*Conn, error) {
|
||||
if p.closed() {
|
||||
return nil, ErrClosed
|
||||
}
|
||||
|
||||
if err := p.waitTurn(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for {
|
||||
p.connsMu.Lock()
|
||||
cn, err := p.popIdle()
|
||||
p.connsMu.Unlock()
|
||||
|
||||
if err != nil {
|
||||
p.freeTurn()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if cn == nil {
|
||||
break
|
||||
}
|
||||
|
||||
if !p.isHealthyConn(cn) {
|
||||
_ = p.CloseConn(cn)
|
||||
continue
|
||||
}
|
||||
|
||||
atomic.AddUint32(&p.stats.Hits, 1)
|
||||
return cn, nil
|
||||
}
|
||||
|
||||
atomic.AddUint32(&p.stats.Misses, 1)
|
||||
|
||||
newcn, err := p.newConn(ctx, true)
|
||||
if err != nil {
|
||||
p.freeTurn()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newcn, nil
|
||||
}
|
||||
|
||||
func (p *ConnPool) waitTurn(ctx context.Context) error {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
select {
|
||||
case p.queue <- struct{}{}:
|
||||
return nil
|
||||
default:
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
timer := timers.Get().(*time.Timer)
|
||||
defer timers.Put(timer)
|
||||
timer.Reset(p.cfg.PoolTimeout)
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
if !timer.Stop() {
|
||||
<-timer.C
|
||||
}
|
||||
return ctx.Err()
|
||||
case p.queue <- struct{}{}:
|
||||
p.waitDurationNs.Add(time.Since(start).Nanoseconds())
|
||||
atomic.AddUint32(&p.stats.WaitCount, 1)
|
||||
if !timer.Stop() {
|
||||
<-timer.C
|
||||
}
|
||||
return nil
|
||||
case <-timer.C:
|
||||
atomic.AddUint32(&p.stats.Timeouts, 1)
|
||||
return ErrPoolTimeout
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ConnPool) freeTurn() {
|
||||
<-p.queue
|
||||
}
|
||||
|
||||
func (p *ConnPool) popIdle() (*Conn, error) {
|
||||
if p.closed() {
|
||||
return nil, ErrClosed
|
||||
}
|
||||
n := len(p.idleConns)
|
||||
if n == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var cn *Conn
|
||||
if p.cfg.PoolFIFO {
|
||||
cn = p.idleConns[0]
|
||||
copy(p.idleConns, p.idleConns[1:])
|
||||
p.idleConns = p.idleConns[:n-1]
|
||||
} else {
|
||||
idx := n - 1
|
||||
cn = p.idleConns[idx]
|
||||
p.idleConns = p.idleConns[:idx]
|
||||
}
|
||||
p.idleConnsLen--
|
||||
p.checkMinIdleConns()
|
||||
return cn, nil
|
||||
}
|
||||
|
||||
func (p *ConnPool) Put(ctx context.Context, cn *Conn) {
|
||||
if cn.rd.Buffered() > 0 {
|
||||
internal.Logger.Printf(ctx, "Conn has unread data")
|
||||
p.Remove(ctx, cn, BadConnError{})
|
||||
return
|
||||
}
|
||||
|
||||
if !cn.pooled {
|
||||
p.Remove(ctx, cn, nil)
|
||||
return
|
||||
}
|
||||
|
||||
var shouldCloseConn bool
|
||||
|
||||
p.connsMu.Lock()
|
||||
|
||||
if p.cfg.MaxIdleConns == 0 || p.idleConnsLen < p.cfg.MaxIdleConns {
|
||||
p.idleConns = append(p.idleConns, cn)
|
||||
p.idleConnsLen++
|
||||
} else {
|
||||
p.removeConn(cn)
|
||||
shouldCloseConn = true
|
||||
}
|
||||
|
||||
p.connsMu.Unlock()
|
||||
|
||||
p.freeTurn()
|
||||
|
||||
if shouldCloseConn {
|
||||
_ = p.closeConn(cn)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ConnPool) Remove(_ context.Context, cn *Conn, reason error) {
|
||||
p.removeConnWithLock(cn)
|
||||
p.freeTurn()
|
||||
_ = p.closeConn(cn)
|
||||
}
|
||||
|
||||
func (p *ConnPool) CloseConn(cn *Conn) error {
|
||||
p.removeConnWithLock(cn)
|
||||
return p.closeConn(cn)
|
||||
}
|
||||
|
||||
func (p *ConnPool) removeConnWithLock(cn *Conn) {
|
||||
p.connsMu.Lock()
|
||||
defer p.connsMu.Unlock()
|
||||
p.removeConn(cn)
|
||||
}
|
||||
|
||||
func (p *ConnPool) removeConn(cn *Conn) {
|
||||
for i, c := range p.conns {
|
||||
if c == cn {
|
||||
p.conns = append(p.conns[:i], p.conns[i+1:]...)
|
||||
if cn.pooled {
|
||||
p.poolSize--
|
||||
p.checkMinIdleConns()
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
atomic.AddUint32(&p.stats.StaleConns, 1)
|
||||
}
|
||||
|
||||
func (p *ConnPool) closeConn(cn *Conn) error {
|
||||
return cn.Close()
|
||||
}
|
||||
|
||||
// Len returns total number of connections.
|
||||
func (p *ConnPool) Len() int {
|
||||
p.connsMu.Lock()
|
||||
n := len(p.conns)
|
||||
p.connsMu.Unlock()
|
||||
return n
|
||||
}
|
||||
|
||||
// IdleLen returns number of idle connections.
|
||||
func (p *ConnPool) IdleLen() int {
|
||||
p.connsMu.Lock()
|
||||
n := p.idleConnsLen
|
||||
p.connsMu.Unlock()
|
||||
return n
|
||||
}
|
||||
|
||||
func (p *ConnPool) Stats() *Stats {
|
||||
return &Stats{
|
||||
Hits: atomic.LoadUint32(&p.stats.Hits),
|
||||
Misses: atomic.LoadUint32(&p.stats.Misses),
|
||||
Timeouts: atomic.LoadUint32(&p.stats.Timeouts),
|
||||
WaitCount: atomic.LoadUint32(&p.stats.WaitCount),
|
||||
WaitDurationNs: p.waitDurationNs.Load(),
|
||||
|
||||
TotalConns: uint32(p.Len()),
|
||||
IdleConns: uint32(p.IdleLen()),
|
||||
StaleConns: atomic.LoadUint32(&p.stats.StaleConns),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ConnPool) closed() bool {
|
||||
return atomic.LoadUint32(&p._closed) == 1
|
||||
}
|
||||
|
||||
func (p *ConnPool) Filter(fn func(*Conn) bool) error {
|
||||
p.connsMu.Lock()
|
||||
defer p.connsMu.Unlock()
|
||||
|
||||
var firstErr error
|
||||
for _, cn := range p.conns {
|
||||
if fn(cn) {
|
||||
if err := p.closeConn(cn); err != nil && firstErr == nil {
|
||||
firstErr = err
|
||||
}
|
||||
}
|
||||
}
|
||||
return firstErr
|
||||
}
|
||||
|
||||
func (p *ConnPool) Close() error {
|
||||
if !atomic.CompareAndSwapUint32(&p._closed, 0, 1) {
|
||||
return ErrClosed
|
||||
}
|
||||
|
||||
var firstErr error
|
||||
p.connsMu.Lock()
|
||||
for _, cn := range p.conns {
|
||||
if err := p.closeConn(cn); err != nil && firstErr == nil {
|
||||
firstErr = err
|
||||
}
|
||||
}
|
||||
p.conns = nil
|
||||
p.poolSize = 0
|
||||
p.idleConns = nil
|
||||
p.idleConnsLen = 0
|
||||
p.connsMu.Unlock()
|
||||
|
||||
return firstErr
|
||||
}
|
||||
|
||||
func (p *ConnPool) isHealthyConn(cn *Conn) bool {
|
||||
now := time.Now()
|
||||
|
||||
if p.cfg.ConnMaxLifetime > 0 && now.Sub(cn.createdAt) >= p.cfg.ConnMaxLifetime {
|
||||
return false
|
||||
}
|
||||
if p.cfg.ConnMaxIdleTime > 0 && now.Sub(cn.UsedAt()) >= p.cfg.ConnMaxIdleTime {
|
||||
return false
|
||||
}
|
||||
|
||||
if connCheck(cn.netConn) != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
cn.SetUsedAt(now)
|
||||
return true
|
||||
}
|
||||
58
vendor/github.com/redis/go-redis/v9/internal/pool/pool_single.go
generated
vendored
Normal file
58
vendor/github.com/redis/go-redis/v9/internal/pool/pool_single.go
generated
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
package pool
|
||||
|
||||
import "context"
|
||||
|
||||
type SingleConnPool struct {
|
||||
pool Pooler
|
||||
cn *Conn
|
||||
stickyErr error
|
||||
}
|
||||
|
||||
var _ Pooler = (*SingleConnPool)(nil)
|
||||
|
||||
func NewSingleConnPool(pool Pooler, cn *Conn) *SingleConnPool {
|
||||
return &SingleConnPool{
|
||||
pool: pool,
|
||||
cn: cn,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *SingleConnPool) NewConn(ctx context.Context) (*Conn, error) {
|
||||
return p.pool.NewConn(ctx)
|
||||
}
|
||||
|
||||
func (p *SingleConnPool) CloseConn(cn *Conn) error {
|
||||
return p.pool.CloseConn(cn)
|
||||
}
|
||||
|
||||
func (p *SingleConnPool) Get(ctx context.Context) (*Conn, error) {
|
||||
if p.stickyErr != nil {
|
||||
return nil, p.stickyErr
|
||||
}
|
||||
return p.cn, nil
|
||||
}
|
||||
|
||||
func (p *SingleConnPool) Put(ctx context.Context, cn *Conn) {}
|
||||
|
||||
func (p *SingleConnPool) Remove(ctx context.Context, cn *Conn, reason error) {
|
||||
p.cn = nil
|
||||
p.stickyErr = reason
|
||||
}
|
||||
|
||||
func (p *SingleConnPool) Close() error {
|
||||
p.cn = nil
|
||||
p.stickyErr = ErrClosed
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *SingleConnPool) Len() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (p *SingleConnPool) IdleLen() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (p *SingleConnPool) Stats() *Stats {
|
||||
return &Stats{}
|
||||
}
|
||||
201
vendor/github.com/redis/go-redis/v9/internal/pool/pool_sticky.go
generated
vendored
Normal file
201
vendor/github.com/redis/go-redis/v9/internal/pool/pool_sticky.go
generated
vendored
Normal file
@@ -0,0 +1,201 @@
|
||||
package pool
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
const (
|
||||
stateDefault = 0
|
||||
stateInited = 1
|
||||
stateClosed = 2
|
||||
)
|
||||
|
||||
type BadConnError struct {
|
||||
wrapped error
|
||||
}
|
||||
|
||||
var _ error = (*BadConnError)(nil)
|
||||
|
||||
func (e BadConnError) Error() string {
|
||||
s := "redis: Conn is in a bad state"
|
||||
if e.wrapped != nil {
|
||||
s += ": " + e.wrapped.Error()
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (e BadConnError) Unwrap() error {
|
||||
return e.wrapped
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
type StickyConnPool struct {
|
||||
pool Pooler
|
||||
shared int32 // atomic
|
||||
|
||||
state uint32 // atomic
|
||||
ch chan *Conn
|
||||
|
||||
_badConnError atomic.Value
|
||||
}
|
||||
|
||||
var _ Pooler = (*StickyConnPool)(nil)
|
||||
|
||||
func NewStickyConnPool(pool Pooler) *StickyConnPool {
|
||||
p, ok := pool.(*StickyConnPool)
|
||||
if !ok {
|
||||
p = &StickyConnPool{
|
||||
pool: pool,
|
||||
ch: make(chan *Conn, 1),
|
||||
}
|
||||
}
|
||||
atomic.AddInt32(&p.shared, 1)
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *StickyConnPool) NewConn(ctx context.Context) (*Conn, error) {
|
||||
return p.pool.NewConn(ctx)
|
||||
}
|
||||
|
||||
func (p *StickyConnPool) CloseConn(cn *Conn) error {
|
||||
return p.pool.CloseConn(cn)
|
||||
}
|
||||
|
||||
func (p *StickyConnPool) Get(ctx context.Context) (*Conn, error) {
|
||||
// In worst case this races with Close which is not a very common operation.
|
||||
for i := 0; i < 1000; i++ {
|
||||
switch atomic.LoadUint32(&p.state) {
|
||||
case stateDefault:
|
||||
cn, err := p.pool.Get(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if atomic.CompareAndSwapUint32(&p.state, stateDefault, stateInited) {
|
||||
return cn, nil
|
||||
}
|
||||
p.pool.Remove(ctx, cn, ErrClosed)
|
||||
case stateInited:
|
||||
if err := p.badConnError(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cn, ok := <-p.ch
|
||||
if !ok {
|
||||
return nil, ErrClosed
|
||||
}
|
||||
return cn, nil
|
||||
case stateClosed:
|
||||
return nil, ErrClosed
|
||||
default:
|
||||
panic("not reached")
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("redis: StickyConnPool.Get: infinite loop")
|
||||
}
|
||||
|
||||
func (p *StickyConnPool) Put(ctx context.Context, cn *Conn) {
|
||||
defer func() {
|
||||
if recover() != nil {
|
||||
p.freeConn(ctx, cn)
|
||||
}
|
||||
}()
|
||||
p.ch <- cn
|
||||
}
|
||||
|
||||
func (p *StickyConnPool) freeConn(ctx context.Context, cn *Conn) {
|
||||
if err := p.badConnError(); err != nil {
|
||||
p.pool.Remove(ctx, cn, err)
|
||||
} else {
|
||||
p.pool.Put(ctx, cn)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *StickyConnPool) Remove(ctx context.Context, cn *Conn, reason error) {
|
||||
defer func() {
|
||||
if recover() != nil {
|
||||
p.pool.Remove(ctx, cn, ErrClosed)
|
||||
}
|
||||
}()
|
||||
p._badConnError.Store(BadConnError{wrapped: reason})
|
||||
p.ch <- cn
|
||||
}
|
||||
|
||||
func (p *StickyConnPool) Close() error {
|
||||
if shared := atomic.AddInt32(&p.shared, -1); shared > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for i := 0; i < 1000; i++ {
|
||||
state := atomic.LoadUint32(&p.state)
|
||||
if state == stateClosed {
|
||||
return ErrClosed
|
||||
}
|
||||
if atomic.CompareAndSwapUint32(&p.state, state, stateClosed) {
|
||||
close(p.ch)
|
||||
cn, ok := <-p.ch
|
||||
if ok {
|
||||
p.freeConn(context.TODO(), cn)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return errors.New("redis: StickyConnPool.Close: infinite loop")
|
||||
}
|
||||
|
||||
func (p *StickyConnPool) Reset(ctx context.Context) error {
|
||||
if p.badConnError() == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
select {
|
||||
case cn, ok := <-p.ch:
|
||||
if !ok {
|
||||
return ErrClosed
|
||||
}
|
||||
p.pool.Remove(ctx, cn, ErrClosed)
|
||||
p._badConnError.Store(BadConnError{wrapped: nil})
|
||||
default:
|
||||
return errors.New("redis: StickyConnPool does not have a Conn")
|
||||
}
|
||||
|
||||
if !atomic.CompareAndSwapUint32(&p.state, stateInited, stateDefault) {
|
||||
state := atomic.LoadUint32(&p.state)
|
||||
return fmt.Errorf("redis: invalid StickyConnPool state: %d", state)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *StickyConnPool) badConnError() error {
|
||||
if v := p._badConnError.Load(); v != nil {
|
||||
if err := v.(BadConnError); err.wrapped != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *StickyConnPool) Len() int {
|
||||
switch atomic.LoadUint32(&p.state) {
|
||||
case stateDefault:
|
||||
return 0
|
||||
case stateInited:
|
||||
return 1
|
||||
case stateClosed:
|
||||
return 0
|
||||
default:
|
||||
panic("not reached")
|
||||
}
|
||||
}
|
||||
|
||||
func (p *StickyConnPool) IdleLen() int {
|
||||
return len(p.ch)
|
||||
}
|
||||
|
||||
func (p *StickyConnPool) Stats() *Stats {
|
||||
return &Stats{}
|
||||
}
|
||||
552
vendor/github.com/redis/go-redis/v9/internal/proto/reader.go
generated
vendored
Normal file
552
vendor/github.com/redis/go-redis/v9/internal/proto/reader.go
generated
vendored
Normal file
@@ -0,0 +1,552 @@
|
||||
package proto
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"math/big"
|
||||
"strconv"
|
||||
|
||||
"github.com/redis/go-redis/v9/internal/util"
|
||||
)
|
||||
|
||||
// redis resp protocol data type.
|
||||
const (
|
||||
RespStatus = '+' // +<string>\r\n
|
||||
RespError = '-' // -<string>\r\n
|
||||
RespString = '$' // $<length>\r\n<bytes>\r\n
|
||||
RespInt = ':' // :<number>\r\n
|
||||
RespNil = '_' // _\r\n
|
||||
RespFloat = ',' // ,<floating-point-number>\r\n (golang float)
|
||||
RespBool = '#' // true: #t\r\n false: #f\r\n
|
||||
RespBlobError = '!' // !<length>\r\n<bytes>\r\n
|
||||
RespVerbatim = '=' // =<length>\r\nFORMAT:<bytes>\r\n
|
||||
RespBigInt = '(' // (<big number>\r\n
|
||||
RespArray = '*' // *<len>\r\n... (same as resp2)
|
||||
RespMap = '%' // %<len>\r\n(key)\r\n(value)\r\n... (golang map)
|
||||
RespSet = '~' // ~<len>\r\n... (same as Array)
|
||||
RespAttr = '|' // |<len>\r\n(key)\r\n(value)\r\n... + command reply
|
||||
RespPush = '>' // ><len>\r\n... (same as Array)
|
||||
)
|
||||
|
||||
// Not used temporarily.
|
||||
// Redis has not used these two data types for the time being, and will implement them later.
|
||||
// Streamed = "EOF:"
|
||||
// StreamedAggregated = '?'
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const Nil = RedisError("redis: nil") // nolint:errname
|
||||
|
||||
type RedisError string
|
||||
|
||||
func (e RedisError) Error() string { return string(e) }
|
||||
|
||||
func (RedisError) RedisError() {}
|
||||
|
||||
func ParseErrorReply(line []byte) error {
|
||||
return RedisError(line[1:])
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
type Reader struct {
|
||||
rd *bufio.Reader
|
||||
}
|
||||
|
||||
func NewReader(rd io.Reader) *Reader {
|
||||
return &Reader{
|
||||
rd: bufio.NewReader(rd),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Reader) Buffered() int {
|
||||
return r.rd.Buffered()
|
||||
}
|
||||
|
||||
func (r *Reader) Peek(n int) ([]byte, error) {
|
||||
return r.rd.Peek(n)
|
||||
}
|
||||
|
||||
func (r *Reader) Reset(rd io.Reader) {
|
||||
r.rd.Reset(rd)
|
||||
}
|
||||
|
||||
// PeekReplyType returns the data type of the next response without advancing the Reader,
|
||||
// and discard the attribute type.
|
||||
func (r *Reader) PeekReplyType() (byte, error) {
|
||||
b, err := r.rd.Peek(1)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if b[0] == RespAttr {
|
||||
if err = r.DiscardNext(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return r.PeekReplyType()
|
||||
}
|
||||
return b[0], nil
|
||||
}
|
||||
|
||||
// ReadLine Return a valid reply, it will check the protocol or redis error,
|
||||
// and discard the attribute type.
|
||||
func (r *Reader) ReadLine() ([]byte, error) {
|
||||
line, err := r.readLine()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch line[0] {
|
||||
case RespError:
|
||||
return nil, ParseErrorReply(line)
|
||||
case RespNil:
|
||||
return nil, Nil
|
||||
case RespBlobError:
|
||||
var blobErr string
|
||||
blobErr, err = r.readStringReply(line)
|
||||
if err == nil {
|
||||
err = RedisError(blobErr)
|
||||
}
|
||||
return nil, err
|
||||
case RespAttr:
|
||||
if err = r.Discard(line); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return r.ReadLine()
|
||||
}
|
||||
|
||||
// Compatible with RESP2
|
||||
if IsNilReply(line) {
|
||||
return nil, Nil
|
||||
}
|
||||
|
||||
return line, nil
|
||||
}
|
||||
|
||||
// readLine returns an error if:
|
||||
// - there is a pending read error;
|
||||
// - or line does not end with \r\n.
|
||||
func (r *Reader) readLine() ([]byte, error) {
|
||||
b, err := r.rd.ReadSlice('\n')
|
||||
if err != nil {
|
||||
if err != bufio.ErrBufferFull {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
full := make([]byte, len(b))
|
||||
copy(full, b)
|
||||
|
||||
b, err = r.rd.ReadBytes('\n')
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
full = append(full, b...) //nolint:makezero
|
||||
b = full
|
||||
}
|
||||
if len(b) <= 2 || b[len(b)-1] != '\n' || b[len(b)-2] != '\r' {
|
||||
return nil, fmt.Errorf("redis: invalid reply: %q", b)
|
||||
}
|
||||
return b[:len(b)-2], nil
|
||||
}
|
||||
|
||||
func (r *Reader) ReadReply() (interface{}, error) {
|
||||
line, err := r.ReadLine()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch line[0] {
|
||||
case RespStatus:
|
||||
return string(line[1:]), nil
|
||||
case RespInt:
|
||||
return util.ParseInt(line[1:], 10, 64)
|
||||
case RespFloat:
|
||||
return r.readFloat(line)
|
||||
case RespBool:
|
||||
return r.readBool(line)
|
||||
case RespBigInt:
|
||||
return r.readBigInt(line)
|
||||
|
||||
case RespString:
|
||||
return r.readStringReply(line)
|
||||
case RespVerbatim:
|
||||
return r.readVerb(line)
|
||||
|
||||
case RespArray, RespSet, RespPush:
|
||||
return r.readSlice(line)
|
||||
case RespMap:
|
||||
return r.readMap(line)
|
||||
}
|
||||
return nil, fmt.Errorf("redis: can't parse %.100q", line)
|
||||
}
|
||||
|
||||
func (r *Reader) readFloat(line []byte) (float64, error) {
|
||||
v := string(line[1:])
|
||||
switch string(line[1:]) {
|
||||
case "inf":
|
||||
return math.Inf(1), nil
|
||||
case "-inf":
|
||||
return math.Inf(-1), nil
|
||||
case "nan", "-nan":
|
||||
return math.NaN(), nil
|
||||
}
|
||||
return strconv.ParseFloat(v, 64)
|
||||
}
|
||||
|
||||
func (r *Reader) readBool(line []byte) (bool, error) {
|
||||
switch string(line[1:]) {
|
||||
case "t":
|
||||
return true, nil
|
||||
case "f":
|
||||
return false, nil
|
||||
}
|
||||
return false, fmt.Errorf("redis: can't parse bool reply: %q", line)
|
||||
}
|
||||
|
||||
func (r *Reader) readBigInt(line []byte) (*big.Int, error) {
|
||||
i := new(big.Int)
|
||||
if i, ok := i.SetString(string(line[1:]), 10); ok {
|
||||
return i, nil
|
||||
}
|
||||
return nil, fmt.Errorf("redis: can't parse bigInt reply: %q", line)
|
||||
}
|
||||
|
||||
func (r *Reader) readStringReply(line []byte) (string, error) {
|
||||
n, err := replyLen(line)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
b := make([]byte, n+2)
|
||||
_, err = io.ReadFull(r.rd, b)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return util.BytesToString(b[:n]), nil
|
||||
}
|
||||
|
||||
func (r *Reader) readVerb(line []byte) (string, error) {
|
||||
s, err := r.readStringReply(line)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(s) < 4 || s[3] != ':' {
|
||||
return "", fmt.Errorf("redis: can't parse verbatim string reply: %q", line)
|
||||
}
|
||||
return s[4:], nil
|
||||
}
|
||||
|
||||
func (r *Reader) readSlice(line []byte) ([]interface{}, error) {
|
||||
n, err := replyLen(line)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
val := make([]interface{}, n)
|
||||
for i := 0; i < len(val); i++ {
|
||||
v, err := r.ReadReply()
|
||||
if err != nil {
|
||||
if err == Nil {
|
||||
val[i] = nil
|
||||
continue
|
||||
}
|
||||
if err, ok := err.(RedisError); ok {
|
||||
val[i] = err
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
val[i] = v
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
||||
func (r *Reader) readMap(line []byte) (map[interface{}]interface{}, error) {
|
||||
n, err := replyLen(line)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m := make(map[interface{}]interface{}, n)
|
||||
for i := 0; i < n; i++ {
|
||||
k, err := r.ReadReply()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v, err := r.ReadReply()
|
||||
if err != nil {
|
||||
if err == Nil {
|
||||
m[k] = nil
|
||||
continue
|
||||
}
|
||||
if err, ok := err.(RedisError); ok {
|
||||
m[k] = err
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
m[k] = v
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// -------------------------------
|
||||
|
||||
func (r *Reader) ReadInt() (int64, error) {
|
||||
line, err := r.ReadLine()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
switch line[0] {
|
||||
case RespInt, RespStatus:
|
||||
return util.ParseInt(line[1:], 10, 64)
|
||||
case RespString:
|
||||
s, err := r.readStringReply(line)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return util.ParseInt([]byte(s), 10, 64)
|
||||
case RespBigInt:
|
||||
b, err := r.readBigInt(line)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if !b.IsInt64() {
|
||||
return 0, fmt.Errorf("bigInt(%s) value out of range", b.String())
|
||||
}
|
||||
return b.Int64(), nil
|
||||
}
|
||||
return 0, fmt.Errorf("redis: can't parse int reply: %.100q", line)
|
||||
}
|
||||
|
||||
func (r *Reader) ReadUint() (uint64, error) {
|
||||
line, err := r.ReadLine()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
switch line[0] {
|
||||
case RespInt, RespStatus:
|
||||
return util.ParseUint(line[1:], 10, 64)
|
||||
case RespString:
|
||||
s, err := r.readStringReply(line)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return util.ParseUint([]byte(s), 10, 64)
|
||||
case RespBigInt:
|
||||
b, err := r.readBigInt(line)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if !b.IsUint64() {
|
||||
return 0, fmt.Errorf("bigInt(%s) value out of range", b.String())
|
||||
}
|
||||
return b.Uint64(), nil
|
||||
}
|
||||
return 0, fmt.Errorf("redis: can't parse uint reply: %.100q", line)
|
||||
}
|
||||
|
||||
func (r *Reader) ReadFloat() (float64, error) {
|
||||
line, err := r.ReadLine()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
switch line[0] {
|
||||
case RespFloat:
|
||||
return r.readFloat(line)
|
||||
case RespStatus:
|
||||
return strconv.ParseFloat(string(line[1:]), 64)
|
||||
case RespString:
|
||||
s, err := r.readStringReply(line)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return strconv.ParseFloat(s, 64)
|
||||
}
|
||||
return 0, fmt.Errorf("redis: can't parse float reply: %.100q", line)
|
||||
}
|
||||
|
||||
func (r *Reader) ReadString() (string, error) {
|
||||
line, err := r.ReadLine()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
switch line[0] {
|
||||
case RespStatus, RespInt, RespFloat:
|
||||
return string(line[1:]), nil
|
||||
case RespString:
|
||||
return r.readStringReply(line)
|
||||
case RespBool:
|
||||
b, err := r.readBool(line)
|
||||
return strconv.FormatBool(b), err
|
||||
case RespVerbatim:
|
||||
return r.readVerb(line)
|
||||
case RespBigInt:
|
||||
b, err := r.readBigInt(line)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return b.String(), nil
|
||||
}
|
||||
return "", fmt.Errorf("redis: can't parse reply=%.100q reading string", line)
|
||||
}
|
||||
|
||||
func (r *Reader) ReadBool() (bool, error) {
|
||||
s, err := r.ReadString()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return s == "OK" || s == "1" || s == "true", nil
|
||||
}
|
||||
|
||||
func (r *Reader) ReadSlice() ([]interface{}, error) {
|
||||
line, err := r.ReadLine()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return r.readSlice(line)
|
||||
}
|
||||
|
||||
// ReadFixedArrayLen read fixed array length.
|
||||
func (r *Reader) ReadFixedArrayLen(fixedLen int) error {
|
||||
n, err := r.ReadArrayLen()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n != fixedLen {
|
||||
return fmt.Errorf("redis: got %d elements in the array, wanted %d", n, fixedLen)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadArrayLen Read and return the length of the array.
|
||||
func (r *Reader) ReadArrayLen() (int, error) {
|
||||
line, err := r.ReadLine()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
switch line[0] {
|
||||
case RespArray, RespSet, RespPush:
|
||||
return replyLen(line)
|
||||
default:
|
||||
return 0, fmt.Errorf("redis: can't parse array/set/push reply: %.100q", line)
|
||||
}
|
||||
}
|
||||
|
||||
// ReadFixedMapLen reads fixed map length.
|
||||
func (r *Reader) ReadFixedMapLen(fixedLen int) error {
|
||||
n, err := r.ReadMapLen()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n != fixedLen {
|
||||
return fmt.Errorf("redis: got %d elements in the map, wanted %d", n, fixedLen)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadMapLen reads the length of the map type.
|
||||
// If responding to the array type (RespArray/RespSet/RespPush),
|
||||
// it must be a multiple of 2 and return n/2.
|
||||
// Other types will return an error.
|
||||
func (r *Reader) ReadMapLen() (int, error) {
|
||||
line, err := r.ReadLine()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
switch line[0] {
|
||||
case RespMap:
|
||||
return replyLen(line)
|
||||
case RespArray, RespSet, RespPush:
|
||||
// Some commands and RESP2 protocol may respond to array types.
|
||||
n, err := replyLen(line)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if n%2 != 0 {
|
||||
return 0, fmt.Errorf("redis: the length of the array must be a multiple of 2, got: %d", n)
|
||||
}
|
||||
return n / 2, nil
|
||||
default:
|
||||
return 0, fmt.Errorf("redis: can't parse map reply: %.100q", line)
|
||||
}
|
||||
}
|
||||
|
||||
// DiscardNext read and discard the data represented by the next line.
|
||||
func (r *Reader) DiscardNext() error {
|
||||
line, err := r.readLine()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return r.Discard(line)
|
||||
}
|
||||
|
||||
// Discard the data represented by line.
|
||||
func (r *Reader) Discard(line []byte) (err error) {
|
||||
if len(line) == 0 {
|
||||
return errors.New("redis: invalid line")
|
||||
}
|
||||
switch line[0] {
|
||||
case RespStatus, RespError, RespInt, RespNil, RespFloat, RespBool, RespBigInt:
|
||||
return nil
|
||||
}
|
||||
|
||||
n, err := replyLen(line)
|
||||
if err != nil && err != Nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch line[0] {
|
||||
case RespBlobError, RespString, RespVerbatim:
|
||||
// +\r\n
|
||||
_, err = r.rd.Discard(n + 2)
|
||||
return err
|
||||
case RespArray, RespSet, RespPush:
|
||||
for i := 0; i < n; i++ {
|
||||
if err = r.DiscardNext(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
case RespMap, RespAttr:
|
||||
// Read key & value.
|
||||
for i := 0; i < n*2; i++ {
|
||||
if err = r.DiscardNext(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("redis: can't parse %.100q", line)
|
||||
}
|
||||
|
||||
func replyLen(line []byte) (n int, err error) {
|
||||
n, err = util.Atoi(line[1:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if n < -1 {
|
||||
return 0, fmt.Errorf("redis: invalid reply: %q", line)
|
||||
}
|
||||
|
||||
switch line[0] {
|
||||
case RespString, RespVerbatim, RespBlobError,
|
||||
RespArray, RespSet, RespPush, RespMap, RespAttr:
|
||||
if n == -1 {
|
||||
return 0, Nil
|
||||
}
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// IsNilReply detects redis.Nil of RESP2.
|
||||
func IsNilReply(line []byte) bool {
|
||||
return len(line) == 3 &&
|
||||
(line[0] == RespString || line[0] == RespArray) &&
|
||||
line[1] == '-' && line[2] == '1'
|
||||
}
|
||||
185
vendor/github.com/redis/go-redis/v9/internal/proto/scan.go
generated
vendored
Normal file
185
vendor/github.com/redis/go-redis/v9/internal/proto/scan.go
generated
vendored
Normal file
@@ -0,0 +1,185 @@
|
||||
package proto
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"fmt"
|
||||
"net"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/redis/go-redis/v9/internal/util"
|
||||
)
|
||||
|
||||
// Scan parses bytes `b` to `v` with appropriate type.
|
||||
//
|
||||
//nolint:gocyclo
|
||||
func Scan(b []byte, v interface{}) error {
|
||||
switch v := v.(type) {
|
||||
case nil:
|
||||
return fmt.Errorf("redis: Scan(nil)")
|
||||
case *string:
|
||||
*v = util.BytesToString(b)
|
||||
return nil
|
||||
case *[]byte:
|
||||
*v = b
|
||||
return nil
|
||||
case *int:
|
||||
var err error
|
||||
*v, err = util.Atoi(b)
|
||||
return err
|
||||
case *int8:
|
||||
n, err := util.ParseInt(b, 10, 8)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*v = int8(n)
|
||||
return nil
|
||||
case *int16:
|
||||
n, err := util.ParseInt(b, 10, 16)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*v = int16(n)
|
||||
return nil
|
||||
case *int32:
|
||||
n, err := util.ParseInt(b, 10, 32)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*v = int32(n)
|
||||
return nil
|
||||
case *int64:
|
||||
n, err := util.ParseInt(b, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*v = n
|
||||
return nil
|
||||
case *uint:
|
||||
n, err := util.ParseUint(b, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*v = uint(n)
|
||||
return nil
|
||||
case *uint8:
|
||||
n, err := util.ParseUint(b, 10, 8)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*v = uint8(n)
|
||||
return nil
|
||||
case *uint16:
|
||||
n, err := util.ParseUint(b, 10, 16)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*v = uint16(n)
|
||||
return nil
|
||||
case *uint32:
|
||||
n, err := util.ParseUint(b, 10, 32)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*v = uint32(n)
|
||||
return nil
|
||||
case *uint64:
|
||||
n, err := util.ParseUint(b, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*v = n
|
||||
return nil
|
||||
case *float32:
|
||||
n, err := util.ParseFloat(b, 32)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*v = float32(n)
|
||||
return err
|
||||
case *float64:
|
||||
var err error
|
||||
*v, err = util.ParseFloat(b, 64)
|
||||
return err
|
||||
case *bool:
|
||||
*v = len(b) == 1 && b[0] == '1'
|
||||
return nil
|
||||
case *time.Time:
|
||||
var err error
|
||||
*v, err = time.Parse(time.RFC3339Nano, util.BytesToString(b))
|
||||
return err
|
||||
case *time.Duration:
|
||||
n, err := util.ParseInt(b, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*v = time.Duration(n)
|
||||
return nil
|
||||
case encoding.BinaryUnmarshaler:
|
||||
return v.UnmarshalBinary(b)
|
||||
case *net.IP:
|
||||
*v = b
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf(
|
||||
"redis: can't unmarshal %T (consider implementing BinaryUnmarshaler)", v)
|
||||
}
|
||||
}
|
||||
|
||||
func ScanSlice(data []string, slice interface{}) error {
|
||||
v := reflect.ValueOf(slice)
|
||||
if !v.IsValid() {
|
||||
return fmt.Errorf("redis: ScanSlice(nil)")
|
||||
}
|
||||
if v.Kind() != reflect.Ptr {
|
||||
return fmt.Errorf("redis: ScanSlice(non-pointer %T)", slice)
|
||||
}
|
||||
v = v.Elem()
|
||||
if v.Kind() != reflect.Slice {
|
||||
return fmt.Errorf("redis: ScanSlice(non-slice %T)", slice)
|
||||
}
|
||||
|
||||
next := makeSliceNextElemFunc(v)
|
||||
for i, s := range data {
|
||||
elem := next()
|
||||
if err := Scan([]byte(s), elem.Addr().Interface()); err != nil {
|
||||
err = fmt.Errorf("redis: ScanSlice index=%d value=%q failed: %w", i, s, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func makeSliceNextElemFunc(v reflect.Value) func() reflect.Value {
|
||||
elemType := v.Type().Elem()
|
||||
|
||||
if elemType.Kind() == reflect.Ptr {
|
||||
elemType = elemType.Elem()
|
||||
return func() reflect.Value {
|
||||
if v.Len() < v.Cap() {
|
||||
v.Set(v.Slice(0, v.Len()+1))
|
||||
elem := v.Index(v.Len() - 1)
|
||||
if elem.IsNil() {
|
||||
elem.Set(reflect.New(elemType))
|
||||
}
|
||||
return elem.Elem()
|
||||
}
|
||||
|
||||
elem := reflect.New(elemType)
|
||||
v.Set(reflect.Append(v, elem))
|
||||
return elem.Elem()
|
||||
}
|
||||
}
|
||||
|
||||
zero := reflect.Zero(elemType)
|
||||
return func() reflect.Value {
|
||||
if v.Len() < v.Cap() {
|
||||
v.Set(v.Slice(0, v.Len()+1))
|
||||
return v.Index(v.Len() - 1)
|
||||
}
|
||||
|
||||
v.Set(reflect.Append(v, zero))
|
||||
return v.Index(v.Len() - 1)
|
||||
}
|
||||
}
|
||||
242
vendor/github.com/redis/go-redis/v9/internal/proto/writer.go
generated
vendored
Normal file
242
vendor/github.com/redis/go-redis/v9/internal/proto/writer.go
generated
vendored
Normal file
@@ -0,0 +1,242 @@
|
||||
package proto
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/redis/go-redis/v9/internal/util"
|
||||
)
|
||||
|
||||
type writer interface {
|
||||
io.Writer
|
||||
io.ByteWriter
|
||||
// WriteString implement io.StringWriter.
|
||||
WriteString(s string) (n int, err error)
|
||||
}
|
||||
|
||||
type Writer struct {
|
||||
writer
|
||||
|
||||
lenBuf []byte
|
||||
numBuf []byte
|
||||
}
|
||||
|
||||
func NewWriter(wr writer) *Writer {
|
||||
return &Writer{
|
||||
writer: wr,
|
||||
|
||||
lenBuf: make([]byte, 64),
|
||||
numBuf: make([]byte, 64),
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Writer) WriteArgs(args []interface{}) error {
|
||||
if err := w.WriteByte(RespArray); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := w.writeLen(len(args)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, arg := range args {
|
||||
if err := w.WriteArg(arg); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Writer) writeLen(n int) error {
|
||||
w.lenBuf = strconv.AppendUint(w.lenBuf[:0], uint64(n), 10)
|
||||
w.lenBuf = append(w.lenBuf, '\r', '\n')
|
||||
_, err := w.Write(w.lenBuf)
|
||||
return err
|
||||
}
|
||||
|
||||
func (w *Writer) WriteArg(v interface{}) error {
|
||||
switch v := v.(type) {
|
||||
case nil:
|
||||
return w.string("")
|
||||
case string:
|
||||
return w.string(v)
|
||||
case *string:
|
||||
if v == nil {
|
||||
return w.string("")
|
||||
}
|
||||
return w.string(*v)
|
||||
case []byte:
|
||||
return w.bytes(v)
|
||||
case int:
|
||||
return w.int(int64(v))
|
||||
case *int:
|
||||
if v == nil {
|
||||
return w.int(0)
|
||||
}
|
||||
return w.int(int64(*v))
|
||||
case int8:
|
||||
return w.int(int64(v))
|
||||
case *int8:
|
||||
if v == nil {
|
||||
return w.int(0)
|
||||
}
|
||||
return w.int(int64(*v))
|
||||
case int16:
|
||||
return w.int(int64(v))
|
||||
case *int16:
|
||||
if v == nil {
|
||||
return w.int(0)
|
||||
}
|
||||
return w.int(int64(*v))
|
||||
case int32:
|
||||
return w.int(int64(v))
|
||||
case *int32:
|
||||
if v == nil {
|
||||
return w.int(0)
|
||||
}
|
||||
return w.int(int64(*v))
|
||||
case int64:
|
||||
return w.int(v)
|
||||
case *int64:
|
||||
if v == nil {
|
||||
return w.int(0)
|
||||
}
|
||||
return w.int(*v)
|
||||
case uint:
|
||||
return w.uint(uint64(v))
|
||||
case *uint:
|
||||
if v == nil {
|
||||
return w.uint(0)
|
||||
}
|
||||
return w.uint(uint64(*v))
|
||||
case uint8:
|
||||
return w.uint(uint64(v))
|
||||
case *uint8:
|
||||
if v == nil {
|
||||
return w.string("")
|
||||
}
|
||||
return w.uint(uint64(*v))
|
||||
case uint16:
|
||||
return w.uint(uint64(v))
|
||||
case *uint16:
|
||||
if v == nil {
|
||||
return w.uint(0)
|
||||
}
|
||||
return w.uint(uint64(*v))
|
||||
case uint32:
|
||||
return w.uint(uint64(v))
|
||||
case *uint32:
|
||||
if v == nil {
|
||||
return w.uint(0)
|
||||
}
|
||||
return w.uint(uint64(*v))
|
||||
case uint64:
|
||||
return w.uint(v)
|
||||
case *uint64:
|
||||
if v == nil {
|
||||
return w.uint(0)
|
||||
}
|
||||
return w.uint(*v)
|
||||
case float32:
|
||||
return w.float(float64(v))
|
||||
case *float32:
|
||||
if v == nil {
|
||||
return w.float(0)
|
||||
}
|
||||
return w.float(float64(*v))
|
||||
case float64:
|
||||
return w.float(v)
|
||||
case *float64:
|
||||
if v == nil {
|
||||
return w.float(0)
|
||||
}
|
||||
return w.float(*v)
|
||||
case bool:
|
||||
if v {
|
||||
return w.int(1)
|
||||
}
|
||||
return w.int(0)
|
||||
case *bool:
|
||||
if v == nil {
|
||||
return w.int(0)
|
||||
}
|
||||
if *v {
|
||||
return w.int(1)
|
||||
}
|
||||
return w.int(0)
|
||||
case time.Time:
|
||||
w.numBuf = v.AppendFormat(w.numBuf[:0], time.RFC3339Nano)
|
||||
return w.bytes(w.numBuf)
|
||||
case *time.Time:
|
||||
if v == nil {
|
||||
v = &time.Time{}
|
||||
}
|
||||
w.numBuf = v.AppendFormat(w.numBuf[:0], time.RFC3339Nano)
|
||||
return w.bytes(w.numBuf)
|
||||
case time.Duration:
|
||||
return w.int(v.Nanoseconds())
|
||||
case *time.Duration:
|
||||
if v == nil {
|
||||
return w.int(0)
|
||||
}
|
||||
return w.int(v.Nanoseconds())
|
||||
case encoding.BinaryMarshaler:
|
||||
b, err := v.MarshalBinary()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return w.bytes(b)
|
||||
case net.IP:
|
||||
return w.bytes(v)
|
||||
default:
|
||||
return fmt.Errorf(
|
||||
"redis: can't marshal %T (implement encoding.BinaryMarshaler)", v)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Writer) bytes(b []byte) error {
|
||||
if err := w.WriteByte(RespString); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := w.writeLen(len(b)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := w.Write(b); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return w.crlf()
|
||||
}
|
||||
|
||||
func (w *Writer) string(s string) error {
|
||||
return w.bytes(util.StringToBytes(s))
|
||||
}
|
||||
|
||||
func (w *Writer) uint(n uint64) error {
|
||||
w.numBuf = strconv.AppendUint(w.numBuf[:0], n, 10)
|
||||
return w.bytes(w.numBuf)
|
||||
}
|
||||
|
||||
func (w *Writer) int(n int64) error {
|
||||
w.numBuf = strconv.AppendInt(w.numBuf[:0], n, 10)
|
||||
return w.bytes(w.numBuf)
|
||||
}
|
||||
|
||||
func (w *Writer) float(f float64) error {
|
||||
w.numBuf = strconv.AppendFloat(w.numBuf[:0], f, 'f', -1, 64)
|
||||
return w.bytes(w.numBuf)
|
||||
}
|
||||
|
||||
func (w *Writer) crlf() error {
|
||||
if err := w.WriteByte('\r'); err != nil {
|
||||
return err
|
||||
}
|
||||
return w.WriteByte('\n')
|
||||
}
|
||||
50
vendor/github.com/redis/go-redis/v9/internal/rand/rand.go
generated
vendored
Normal file
50
vendor/github.com/redis/go-redis/v9/internal/rand/rand.go
generated
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
package rand
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Int returns a non-negative pseudo-random int.
|
||||
func Int() int { return pseudo.Int() }
|
||||
|
||||
// Intn returns, as an int, a non-negative pseudo-random number in [0,n).
|
||||
// It panics if n <= 0.
|
||||
func Intn(n int) int { return pseudo.Intn(n) }
|
||||
|
||||
// Int63n returns, as an int64, a non-negative pseudo-random number in [0,n).
|
||||
// It panics if n <= 0.
|
||||
func Int63n(n int64) int64 { return pseudo.Int63n(n) }
|
||||
|
||||
// Perm returns, as a slice of n ints, a pseudo-random permutation of the integers [0,n).
|
||||
func Perm(n int) []int { return pseudo.Perm(n) }
|
||||
|
||||
// Seed uses the provided seed value to initialize the default Source to a
|
||||
// deterministic state. If Seed is not called, the generator behaves as if
|
||||
// seeded by Seed(1).
|
||||
func Seed(n int64) { pseudo.Seed(n) }
|
||||
|
||||
var pseudo = rand.New(&source{src: rand.NewSource(1)})
|
||||
|
||||
type source struct {
|
||||
src rand.Source
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func (s *source) Int63() int64 {
|
||||
s.mu.Lock()
|
||||
n := s.src.Int63()
|
||||
s.mu.Unlock()
|
||||
return n
|
||||
}
|
||||
|
||||
func (s *source) Seed(seed int64) {
|
||||
s.mu.Lock()
|
||||
s.src.Seed(seed)
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
// Shuffle pseudo-randomizes the order of elements.
|
||||
// n is the number of elements.
|
||||
// swap swaps the elements with indexes i and j.
|
||||
func Shuffle(n int, swap func(i, j int)) { pseudo.Shuffle(n, swap) }
|
||||
113
vendor/github.com/redis/go-redis/v9/internal/util.go
generated
vendored
Normal file
113
vendor/github.com/redis/go-redis/v9/internal/util.go
generated
vendored
Normal file
@@ -0,0 +1,113 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/redis/go-redis/v9/internal/util"
|
||||
)
|
||||
|
||||
func Sleep(ctx context.Context, dur time.Duration) error {
|
||||
t := time.NewTimer(dur)
|
||||
defer t.Stop()
|
||||
|
||||
select {
|
||||
case <-t.C:
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
func ToLower(s string) string {
|
||||
if isLower(s) {
|
||||
return s
|
||||
}
|
||||
|
||||
b := make([]byte, len(s))
|
||||
for i := range b {
|
||||
c := s[i]
|
||||
if c >= 'A' && c <= 'Z' {
|
||||
c += 'a' - 'A'
|
||||
}
|
||||
b[i] = c
|
||||
}
|
||||
return util.BytesToString(b)
|
||||
}
|
||||
|
||||
func isLower(s string) bool {
|
||||
for i := 0; i < len(s); i++ {
|
||||
c := s[i]
|
||||
if c >= 'A' && c <= 'Z' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func ReplaceSpaces(s string) string {
|
||||
return strings.ReplaceAll(s, " ", "-")
|
||||
}
|
||||
|
||||
func GetAddr(addr string) string {
|
||||
ind := strings.LastIndexByte(addr, ':')
|
||||
if ind == -1 {
|
||||
return ""
|
||||
}
|
||||
|
||||
if strings.IndexByte(addr, '.') != -1 {
|
||||
return addr
|
||||
}
|
||||
|
||||
if addr[0] == '[' {
|
||||
return addr
|
||||
}
|
||||
return net.JoinHostPort(addr[:ind], addr[ind+1:])
|
||||
}
|
||||
|
||||
func ToInteger(val interface{}) int {
|
||||
switch v := val.(type) {
|
||||
case int:
|
||||
return v
|
||||
case int64:
|
||||
return int(v)
|
||||
case string:
|
||||
i, _ := strconv.Atoi(v)
|
||||
return i
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func ToFloat(val interface{}) float64 {
|
||||
switch v := val.(type) {
|
||||
case float64:
|
||||
return v
|
||||
case string:
|
||||
f, _ := strconv.ParseFloat(v, 64)
|
||||
return f
|
||||
default:
|
||||
return 0.0
|
||||
}
|
||||
}
|
||||
|
||||
func ToString(val interface{}) string {
|
||||
if str, ok := val.(string); ok {
|
||||
return str
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func ToStringSlice(val interface{}) []string {
|
||||
if arr, ok := val.([]interface{}); ok {
|
||||
result := make([]string, len(arr))
|
||||
for i, v := range arr {
|
||||
result[i] = ToString(v)
|
||||
}
|
||||
return result
|
||||
}
|
||||
return nil
|
||||
}
|
||||
30
vendor/github.com/redis/go-redis/v9/internal/util/convert.go
generated
vendored
Normal file
30
vendor/github.com/redis/go-redis/v9/internal/util/convert.go
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// ParseFloat parses a Redis RESP3 float reply into a Go float64,
|
||||
// handling "inf", "-inf", "nan" per Redis conventions.
|
||||
func ParseStringToFloat(s string) (float64, error) {
|
||||
switch s {
|
||||
case "inf":
|
||||
return math.Inf(1), nil
|
||||
case "-inf":
|
||||
return math.Inf(-1), nil
|
||||
case "nan", "-nan":
|
||||
return math.NaN(), nil
|
||||
}
|
||||
return strconv.ParseFloat(s, 64)
|
||||
}
|
||||
|
||||
// MustParseFloat is like ParseFloat but panics on parse errors.
|
||||
func MustParseFloat(s string) float64 {
|
||||
f, err := ParseStringToFloat(s)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("redis: failed to parse float %q: %v", s, err))
|
||||
}
|
||||
return f
|
||||
}
|
||||
11
vendor/github.com/redis/go-redis/v9/internal/util/safe.go
generated
vendored
Normal file
11
vendor/github.com/redis/go-redis/v9/internal/util/safe.go
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
//go:build appengine
|
||||
|
||||
package util
|
||||
|
||||
func BytesToString(b []byte) string {
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func StringToBytes(s string) []byte {
|
||||
return []byte(s)
|
||||
}
|
||||
19
vendor/github.com/redis/go-redis/v9/internal/util/strconv.go
generated
vendored
Normal file
19
vendor/github.com/redis/go-redis/v9/internal/util/strconv.go
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
package util
|
||||
|
||||
import "strconv"
|
||||
|
||||
func Atoi(b []byte) (int, error) {
|
||||
return strconv.Atoi(BytesToString(b))
|
||||
}
|
||||
|
||||
func ParseInt(b []byte, base int, bitSize int) (int64, error) {
|
||||
return strconv.ParseInt(BytesToString(b), base, bitSize)
|
||||
}
|
||||
|
||||
func ParseUint(b []byte, base int, bitSize int) (uint64, error) {
|
||||
return strconv.ParseUint(BytesToString(b), base, bitSize)
|
||||
}
|
||||
|
||||
func ParseFloat(b []byte, bitSize int) (float64, error) {
|
||||
return strconv.ParseFloat(BytesToString(b), bitSize)
|
||||
}
|
||||
5
vendor/github.com/redis/go-redis/v9/internal/util/type.go
generated
vendored
Normal file
5
vendor/github.com/redis/go-redis/v9/internal/util/type.go
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
package util
|
||||
|
||||
func ToPtr[T any](v T) *T {
|
||||
return &v
|
||||
}
|
||||
22
vendor/github.com/redis/go-redis/v9/internal/util/unsafe.go
generated
vendored
Normal file
22
vendor/github.com/redis/go-redis/v9/internal/util/unsafe.go
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
//go:build !appengine
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// BytesToString converts byte slice to string.
|
||||
func BytesToString(b []byte) string {
|
||||
return *(*string)(unsafe.Pointer(&b))
|
||||
}
|
||||
|
||||
// StringToBytes converts string to byte slice.
|
||||
func StringToBytes(s string) []byte {
|
||||
return *(*[]byte)(unsafe.Pointer(
|
||||
&struct {
|
||||
string
|
||||
Cap int
|
||||
}{s, len(s)},
|
||||
))
|
||||
}
|
||||
66
vendor/github.com/redis/go-redis/v9/iterator.go
generated
vendored
Normal file
66
vendor/github.com/redis/go-redis/v9/iterator.go
generated
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
// ScanIterator is used to incrementally iterate over a collection of elements.
|
||||
type ScanIterator struct {
|
||||
cmd *ScanCmd
|
||||
pos int
|
||||
}
|
||||
|
||||
// Err returns the last iterator error, if any.
|
||||
func (it *ScanIterator) Err() error {
|
||||
return it.cmd.Err()
|
||||
}
|
||||
|
||||
// Next advances the cursor and returns true if more values can be read.
|
||||
func (it *ScanIterator) Next(ctx context.Context) bool {
|
||||
// Instantly return on errors.
|
||||
if it.cmd.Err() != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Advance cursor, check if we are still within range.
|
||||
if it.pos < len(it.cmd.page) {
|
||||
it.pos++
|
||||
return true
|
||||
}
|
||||
|
||||
for {
|
||||
// Return if there is no more data to fetch.
|
||||
if it.cmd.cursor == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
// Fetch next page.
|
||||
switch it.cmd.args[0] {
|
||||
case "scan", "qscan":
|
||||
it.cmd.args[1] = it.cmd.cursor
|
||||
default:
|
||||
it.cmd.args[2] = it.cmd.cursor
|
||||
}
|
||||
|
||||
err := it.cmd.process(ctx, it.cmd)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
it.pos = 1
|
||||
|
||||
// Redis can occasionally return empty page.
|
||||
if len(it.cmd.page) > 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Val returns the key/field at the current cursor position.
|
||||
func (it *ScanIterator) Val() string {
|
||||
var v string
|
||||
if it.cmd.Err() == nil && it.pos > 0 && it.pos <= len(it.cmd.page) {
|
||||
v = it.cmd.page[it.pos-1]
|
||||
}
|
||||
return v
|
||||
}
|
||||
599
vendor/github.com/redis/go-redis/v9/json.go
generated
vendored
Normal file
599
vendor/github.com/redis/go-redis/v9/json.go
generated
vendored
Normal file
@@ -0,0 +1,599 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/redis/go-redis/v9/internal/proto"
|
||||
"github.com/redis/go-redis/v9/internal/util"
|
||||
)
|
||||
|
||||
// -------------------------------------------
|
||||
|
||||
type JSONCmdable interface {
|
||||
JSONArrAppend(ctx context.Context, key, path string, values ...interface{}) *IntSliceCmd
|
||||
JSONArrIndex(ctx context.Context, key, path string, value ...interface{}) *IntSliceCmd
|
||||
JSONArrIndexWithArgs(ctx context.Context, key, path string, options *JSONArrIndexArgs, value ...interface{}) *IntSliceCmd
|
||||
JSONArrInsert(ctx context.Context, key, path string, index int64, values ...interface{}) *IntSliceCmd
|
||||
JSONArrLen(ctx context.Context, key, path string) *IntSliceCmd
|
||||
JSONArrPop(ctx context.Context, key, path string, index int) *StringSliceCmd
|
||||
JSONArrTrim(ctx context.Context, key, path string) *IntSliceCmd
|
||||
JSONArrTrimWithArgs(ctx context.Context, key, path string, options *JSONArrTrimArgs) *IntSliceCmd
|
||||
JSONClear(ctx context.Context, key, path string) *IntCmd
|
||||
JSONDebugMemory(ctx context.Context, key, path string) *IntCmd
|
||||
JSONDel(ctx context.Context, key, path string) *IntCmd
|
||||
JSONForget(ctx context.Context, key, path string) *IntCmd
|
||||
JSONGet(ctx context.Context, key string, paths ...string) *JSONCmd
|
||||
JSONGetWithArgs(ctx context.Context, key string, options *JSONGetArgs, paths ...string) *JSONCmd
|
||||
JSONMerge(ctx context.Context, key, path string, value string) *StatusCmd
|
||||
JSONMSetArgs(ctx context.Context, docs []JSONSetArgs) *StatusCmd
|
||||
JSONMSet(ctx context.Context, params ...interface{}) *StatusCmd
|
||||
JSONMGet(ctx context.Context, path string, keys ...string) *JSONSliceCmd
|
||||
JSONNumIncrBy(ctx context.Context, key, path string, value float64) *JSONCmd
|
||||
JSONObjKeys(ctx context.Context, key, path string) *SliceCmd
|
||||
JSONObjLen(ctx context.Context, key, path string) *IntPointerSliceCmd
|
||||
JSONSet(ctx context.Context, key, path string, value interface{}) *StatusCmd
|
||||
JSONSetMode(ctx context.Context, key, path string, value interface{}, mode string) *StatusCmd
|
||||
JSONStrAppend(ctx context.Context, key, path, value string) *IntPointerSliceCmd
|
||||
JSONStrLen(ctx context.Context, key, path string) *IntPointerSliceCmd
|
||||
JSONToggle(ctx context.Context, key, path string) *IntPointerSliceCmd
|
||||
JSONType(ctx context.Context, key, path string) *JSONSliceCmd
|
||||
}
|
||||
|
||||
type JSONSetArgs struct {
|
||||
Key string
|
||||
Path string
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
type JSONArrIndexArgs struct {
|
||||
Start int
|
||||
Stop *int
|
||||
}
|
||||
|
||||
type JSONArrTrimArgs struct {
|
||||
Start int
|
||||
Stop *int
|
||||
}
|
||||
|
||||
type JSONCmd struct {
|
||||
baseCmd
|
||||
val string
|
||||
expanded interface{}
|
||||
}
|
||||
|
||||
var _ Cmder = (*JSONCmd)(nil)
|
||||
|
||||
func newJSONCmd(ctx context.Context, args ...interface{}) *JSONCmd {
|
||||
return &JSONCmd{
|
||||
baseCmd: baseCmd{
|
||||
ctx: ctx,
|
||||
args: args,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *JSONCmd) String() string {
|
||||
return cmdString(cmd, cmd.val)
|
||||
}
|
||||
|
||||
func (cmd *JSONCmd) SetVal(val string) {
|
||||
cmd.val = val
|
||||
}
|
||||
|
||||
func (cmd *JSONCmd) Val() string {
|
||||
if len(cmd.val) == 0 && cmd.expanded != nil {
|
||||
val, err := json.Marshal(cmd.expanded)
|
||||
if err != nil {
|
||||
cmd.SetErr(err)
|
||||
return ""
|
||||
}
|
||||
return string(val)
|
||||
|
||||
} else {
|
||||
return cmd.val
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *JSONCmd) Result() (string, error) {
|
||||
return cmd.Val(), cmd.Err()
|
||||
}
|
||||
|
||||
func (cmd *JSONCmd) Expanded() (interface{}, error) {
|
||||
if len(cmd.val) != 0 && cmd.expanded == nil {
|
||||
err := json.Unmarshal([]byte(cmd.val), &cmd.expanded)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return cmd.expanded, nil
|
||||
}
|
||||
|
||||
func (cmd *JSONCmd) readReply(rd *proto.Reader) error {
|
||||
// nil response from JSON.(M)GET (cmd.baseCmd.err will be "redis: nil")
|
||||
if cmd.baseCmd.Err() == Nil {
|
||||
cmd.val = ""
|
||||
return Nil
|
||||
}
|
||||
|
||||
if readType, err := rd.PeekReplyType(); err != nil {
|
||||
return err
|
||||
} else if readType == proto.RespArray {
|
||||
|
||||
size, err := rd.ReadArrayLen()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
expanded := make([]interface{}, size)
|
||||
|
||||
for i := 0; i < size; i++ {
|
||||
if expanded[i], err = rd.ReadReply(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
cmd.expanded = expanded
|
||||
|
||||
} else {
|
||||
if str, err := rd.ReadString(); err != nil && err != Nil {
|
||||
return err
|
||||
} else if str == "" || err == Nil {
|
||||
cmd.val = ""
|
||||
} else {
|
||||
cmd.val = str
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// -------------------------------------------
|
||||
|
||||
type JSONSliceCmd struct {
|
||||
baseCmd
|
||||
val []interface{}
|
||||
}
|
||||
|
||||
func NewJSONSliceCmd(ctx context.Context, args ...interface{}) *JSONSliceCmd {
|
||||
return &JSONSliceCmd{
|
||||
baseCmd: baseCmd{
|
||||
ctx: ctx,
|
||||
args: args,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *JSONSliceCmd) String() string {
|
||||
return cmdString(cmd, cmd.val)
|
||||
}
|
||||
|
||||
func (cmd *JSONSliceCmd) SetVal(val []interface{}) {
|
||||
cmd.val = val
|
||||
}
|
||||
|
||||
func (cmd *JSONSliceCmd) Val() []interface{} {
|
||||
return cmd.val
|
||||
}
|
||||
|
||||
func (cmd *JSONSliceCmd) Result() ([]interface{}, error) {
|
||||
return cmd.val, cmd.err
|
||||
}
|
||||
|
||||
func (cmd *JSONSliceCmd) readReply(rd *proto.Reader) error {
|
||||
if cmd.baseCmd.Err() == Nil {
|
||||
cmd.val = nil
|
||||
return Nil
|
||||
}
|
||||
|
||||
if readType, err := rd.PeekReplyType(); err != nil {
|
||||
return err
|
||||
} else if readType == proto.RespArray {
|
||||
response, err := rd.ReadReply()
|
||||
if err != nil {
|
||||
return nil
|
||||
} else {
|
||||
cmd.val = response.([]interface{})
|
||||
}
|
||||
|
||||
} else {
|
||||
n, err := rd.ReadArrayLen()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd.val = make([]interface{}, n)
|
||||
for i := 0; i < len(cmd.val); i++ {
|
||||
switch s, err := rd.ReadString(); {
|
||||
case err == Nil:
|
||||
cmd.val[i] = ""
|
||||
case err != nil:
|
||||
return err
|
||||
default:
|
||||
cmd.val[i] = s
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
*
|
||||
* IntPointerSliceCmd
|
||||
* used to represent a RedisJSON response where the result is either an integer or nil
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
type IntPointerSliceCmd struct {
|
||||
baseCmd
|
||||
val []*int64
|
||||
}
|
||||
|
||||
// NewIntPointerSliceCmd initialises an IntPointerSliceCmd
|
||||
func NewIntPointerSliceCmd(ctx context.Context, args ...interface{}) *IntPointerSliceCmd {
|
||||
return &IntPointerSliceCmd{
|
||||
baseCmd: baseCmd{
|
||||
ctx: ctx,
|
||||
args: args,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *IntPointerSliceCmd) String() string {
|
||||
return cmdString(cmd, cmd.val)
|
||||
}
|
||||
|
||||
func (cmd *IntPointerSliceCmd) SetVal(val []*int64) {
|
||||
cmd.val = val
|
||||
}
|
||||
|
||||
func (cmd *IntPointerSliceCmd) Val() []*int64 {
|
||||
return cmd.val
|
||||
}
|
||||
|
||||
func (cmd *IntPointerSliceCmd) Result() ([]*int64, error) {
|
||||
return cmd.val, cmd.err
|
||||
}
|
||||
|
||||
func (cmd *IntPointerSliceCmd) readReply(rd *proto.Reader) error {
|
||||
n, err := rd.ReadArrayLen()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd.val = make([]*int64, n)
|
||||
|
||||
for i := 0; i < len(cmd.val); i++ {
|
||||
val, err := rd.ReadInt()
|
||||
if err != nil && err != Nil {
|
||||
return err
|
||||
} else if err != Nil {
|
||||
cmd.val[i] = &val
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// JSONArrAppend adds the provided JSON values to the end of the array at the given path.
|
||||
// For more information, see https://redis.io/commands/json.arrappend
|
||||
func (c cmdable) JSONArrAppend(ctx context.Context, key, path string, values ...interface{}) *IntSliceCmd {
|
||||
args := []interface{}{"JSON.ARRAPPEND", key, path}
|
||||
args = append(args, values...)
|
||||
cmd := NewIntSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// JSONArrIndex searches for the first occurrence of the provided JSON value in the array at the given path.
|
||||
// For more information, see https://redis.io/commands/json.arrindex
|
||||
func (c cmdable) JSONArrIndex(ctx context.Context, key, path string, value ...interface{}) *IntSliceCmd {
|
||||
args := []interface{}{"JSON.ARRINDEX", key, path}
|
||||
args = append(args, value...)
|
||||
cmd := NewIntSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// JSONArrIndexWithArgs searches for the first occurrence of a JSON value in an array while allowing the start and
|
||||
// stop options to be provided.
|
||||
// For more information, see https://redis.io/commands/json.arrindex
|
||||
func (c cmdable) JSONArrIndexWithArgs(ctx context.Context, key, path string, options *JSONArrIndexArgs, value ...interface{}) *IntSliceCmd {
|
||||
args := []interface{}{"JSON.ARRINDEX", key, path}
|
||||
args = append(args, value...)
|
||||
|
||||
if options != nil {
|
||||
args = append(args, options.Start)
|
||||
if options.Stop != nil {
|
||||
args = append(args, *options.Stop)
|
||||
}
|
||||
}
|
||||
cmd := NewIntSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// JSONArrInsert inserts the JSON values into the array at the specified path before the index (shifts to the right).
|
||||
// For more information, see https://redis.io/commands/json.arrinsert
|
||||
func (c cmdable) JSONArrInsert(ctx context.Context, key, path string, index int64, values ...interface{}) *IntSliceCmd {
|
||||
args := []interface{}{"JSON.ARRINSERT", key, path, index}
|
||||
args = append(args, values...)
|
||||
cmd := NewIntSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// JSONArrLen reports the length of the JSON array at the specified path in the given key.
|
||||
// For more information, see https://redis.io/commands/json.arrlen
|
||||
func (c cmdable) JSONArrLen(ctx context.Context, key, path string) *IntSliceCmd {
|
||||
args := []interface{}{"JSON.ARRLEN", key, path}
|
||||
cmd := NewIntSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// JSONArrPop removes and returns an element from the specified index in the array.
|
||||
// For more information, see https://redis.io/commands/json.arrpop
|
||||
func (c cmdable) JSONArrPop(ctx context.Context, key, path string, index int) *StringSliceCmd {
|
||||
args := []interface{}{"JSON.ARRPOP", key, path, index}
|
||||
cmd := NewStringSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// JSONArrTrim trims an array to contain only the specified inclusive range of elements.
|
||||
// For more information, see https://redis.io/commands/json.arrtrim
|
||||
func (c cmdable) JSONArrTrim(ctx context.Context, key, path string) *IntSliceCmd {
|
||||
args := []interface{}{"JSON.ARRTRIM", key, path}
|
||||
cmd := NewIntSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// JSONArrTrimWithArgs trims an array to contain only the specified inclusive range of elements.
|
||||
// For more information, see https://redis.io/commands/json.arrtrim
|
||||
func (c cmdable) JSONArrTrimWithArgs(ctx context.Context, key, path string, options *JSONArrTrimArgs) *IntSliceCmd {
|
||||
args := []interface{}{"JSON.ARRTRIM", key, path}
|
||||
|
||||
if options != nil {
|
||||
args = append(args, options.Start)
|
||||
|
||||
if options.Stop != nil {
|
||||
args = append(args, *options.Stop)
|
||||
}
|
||||
}
|
||||
cmd := NewIntSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// JSONClear clears container values (arrays/objects) and sets numeric values to 0.
|
||||
// For more information, see https://redis.io/commands/json.clear
|
||||
func (c cmdable) JSONClear(ctx context.Context, key, path string) *IntCmd {
|
||||
args := []interface{}{"JSON.CLEAR", key, path}
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// JSONDebugMemory reports a value's memory usage in bytes (unimplemented)
|
||||
// For more information, see https://redis.io/commands/json.debug-memory
|
||||
func (c cmdable) JSONDebugMemory(ctx context.Context, key, path string) *IntCmd {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
// JSONDel deletes a value.
|
||||
// For more information, see https://redis.io/commands/json.del
|
||||
func (c cmdable) JSONDel(ctx context.Context, key, path string) *IntCmd {
|
||||
args := []interface{}{"JSON.DEL", key, path}
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// JSONForget deletes a value.
|
||||
// For more information, see https://redis.io/commands/json.forget
|
||||
func (c cmdable) JSONForget(ctx context.Context, key, path string) *IntCmd {
|
||||
args := []interface{}{"JSON.FORGET", key, path}
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// JSONGet returns the value at path in JSON serialized form. JSON.GET returns an
|
||||
// array of strings. This function parses out the wrapping array but leaves the
|
||||
// internal strings unprocessed by default (see Val())
|
||||
// For more information - https://redis.io/commands/json.get/
|
||||
func (c cmdable) JSONGet(ctx context.Context, key string, paths ...string) *JSONCmd {
|
||||
args := make([]interface{}, len(paths)+2)
|
||||
args[0] = "JSON.GET"
|
||||
args[1] = key
|
||||
for n, path := range paths {
|
||||
args[n+2] = path
|
||||
}
|
||||
cmd := newJSONCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
type JSONGetArgs struct {
|
||||
Indent string
|
||||
Newline string
|
||||
Space string
|
||||
}
|
||||
|
||||
// JSONGetWithArgs - Retrieves the value of a key from a JSON document.
|
||||
// This function also allows for specifying additional options such as:
|
||||
// Indention, NewLine and Space
|
||||
// For more information - https://redis.io/commands/json.get/
|
||||
func (c cmdable) JSONGetWithArgs(ctx context.Context, key string, options *JSONGetArgs, paths ...string) *JSONCmd {
|
||||
args := []interface{}{"JSON.GET", key}
|
||||
if options != nil {
|
||||
if options.Indent != "" {
|
||||
args = append(args, "INDENT", options.Indent)
|
||||
}
|
||||
if options.Newline != "" {
|
||||
args = append(args, "NEWLINE", options.Newline)
|
||||
}
|
||||
if options.Space != "" {
|
||||
args = append(args, "SPACE", options.Space)
|
||||
}
|
||||
for _, path := range paths {
|
||||
args = append(args, path)
|
||||
}
|
||||
}
|
||||
cmd := newJSONCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// JSONMerge merges a given JSON value into matching paths.
|
||||
// For more information, see https://redis.io/commands/json.merge
|
||||
func (c cmdable) JSONMerge(ctx context.Context, key, path string, value string) *StatusCmd {
|
||||
args := []interface{}{"JSON.MERGE", key, path, value}
|
||||
cmd := NewStatusCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// JSONMGet returns the values at the specified path from multiple key arguments.
|
||||
// Note - the arguments are reversed when compared with `JSON.MGET` as we want
|
||||
// to follow the pattern of having the last argument be variable.
|
||||
// For more information, see https://redis.io/commands/json.mget
|
||||
func (c cmdable) JSONMGet(ctx context.Context, path string, keys ...string) *JSONSliceCmd {
|
||||
args := make([]interface{}, len(keys)+1)
|
||||
args[0] = "JSON.MGET"
|
||||
for n, key := range keys {
|
||||
args[n+1] = key
|
||||
}
|
||||
args = append(args, path)
|
||||
cmd := NewJSONSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// JSONMSetArgs sets or updates one or more JSON values according to the specified key-path-value triplets.
|
||||
// For more information, see https://redis.io/commands/json.mset
|
||||
func (c cmdable) JSONMSetArgs(ctx context.Context, docs []JSONSetArgs) *StatusCmd {
|
||||
args := []interface{}{"JSON.MSET"}
|
||||
for _, doc := range docs {
|
||||
args = append(args, doc.Key, doc.Path, doc.Value)
|
||||
}
|
||||
cmd := NewStatusCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) JSONMSet(ctx context.Context, params ...interface{}) *StatusCmd {
|
||||
args := []interface{}{"JSON.MSET"}
|
||||
args = append(args, params...)
|
||||
cmd := NewStatusCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// JSONNumIncrBy increments the number value stored at the specified path by the provided number.
|
||||
// For more information, see https://redis.io/docs/latest/commands/json.numincrby/
|
||||
func (c cmdable) JSONNumIncrBy(ctx context.Context, key, path string, value float64) *JSONCmd {
|
||||
args := []interface{}{"JSON.NUMINCRBY", key, path, value}
|
||||
cmd := newJSONCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// JSONObjKeys returns the keys in the object that's referenced by the specified path.
|
||||
// For more information, see https://redis.io/commands/json.objkeys
|
||||
func (c cmdable) JSONObjKeys(ctx context.Context, key, path string) *SliceCmd {
|
||||
args := []interface{}{"JSON.OBJKEYS", key, path}
|
||||
cmd := NewSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// JSONObjLen reports the number of keys in the JSON object at the specified path in the given key.
|
||||
// For more information, see https://redis.io/commands/json.objlen
|
||||
func (c cmdable) JSONObjLen(ctx context.Context, key, path string) *IntPointerSliceCmd {
|
||||
args := []interface{}{"JSON.OBJLEN", key, path}
|
||||
cmd := NewIntPointerSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// JSONSet sets the JSON value at the given path in the given key. The value must be something that
|
||||
// can be marshaled to JSON (using encoding/JSON) unless the argument is a string or a []byte when we assume that
|
||||
// it can be passed directly as JSON.
|
||||
// For more information, see https://redis.io/commands/json.set
|
||||
func (c cmdable) JSONSet(ctx context.Context, key, path string, value interface{}) *StatusCmd {
|
||||
return c.JSONSetMode(ctx, key, path, value, "")
|
||||
}
|
||||
|
||||
// JSONSetMode sets the JSON value at the given path in the given key and allows the mode to be set
|
||||
// (the mode value must be "XX" or "NX"). The value must be something that can be marshaled to JSON (using encoding/JSON) unless
|
||||
// the argument is a string or []byte when we assume that it can be passed directly as JSON.
|
||||
// For more information, see https://redis.io/commands/json.set
|
||||
func (c cmdable) JSONSetMode(ctx context.Context, key, path string, value interface{}, mode string) *StatusCmd {
|
||||
var bytes []byte
|
||||
var err error
|
||||
switch v := value.(type) {
|
||||
case string:
|
||||
bytes = []byte(v)
|
||||
case []byte:
|
||||
bytes = v
|
||||
default:
|
||||
bytes, err = json.Marshal(v)
|
||||
}
|
||||
args := []interface{}{"JSON.SET", key, path, util.BytesToString(bytes)}
|
||||
if mode != "" {
|
||||
switch strings.ToUpper(mode) {
|
||||
case "XX", "NX":
|
||||
args = append(args, strings.ToUpper(mode))
|
||||
|
||||
default:
|
||||
panic("redis: JSON.SET mode must be NX or XX")
|
||||
}
|
||||
}
|
||||
cmd := NewStatusCmd(ctx, args...)
|
||||
if err != nil {
|
||||
cmd.SetErr(err)
|
||||
} else {
|
||||
_ = c(ctx, cmd)
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
// JSONStrAppend appends the JSON-string values to the string at the specified path.
|
||||
// For more information, see https://redis.io/commands/json.strappend
|
||||
func (c cmdable) JSONStrAppend(ctx context.Context, key, path, value string) *IntPointerSliceCmd {
|
||||
args := []interface{}{"JSON.STRAPPEND", key, path, value}
|
||||
cmd := NewIntPointerSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// JSONStrLen reports the length of the JSON String at the specified path in the given key.
|
||||
// For more information, see https://redis.io/commands/json.strlen
|
||||
func (c cmdable) JSONStrLen(ctx context.Context, key, path string) *IntPointerSliceCmd {
|
||||
args := []interface{}{"JSON.STRLEN", key, path}
|
||||
cmd := NewIntPointerSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// JSONToggle toggles a Boolean value stored at the specified path.
|
||||
// For more information, see https://redis.io/commands/json.toggle
|
||||
func (c cmdable) JSONToggle(ctx context.Context, key, path string) *IntPointerSliceCmd {
|
||||
args := []interface{}{"JSON.TOGGLE", key, path}
|
||||
cmd := NewIntPointerSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// JSONType reports the type of JSON value at the specified path.
|
||||
// For more information, see https://redis.io/commands/json.type
|
||||
func (c cmdable) JSONType(ctx context.Context, key, path string) *JSONSliceCmd {
|
||||
args := []interface{}{"JSON.TYPE", key, path}
|
||||
cmd := NewJSONSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
289
vendor/github.com/redis/go-redis/v9/list_commands.go
generated
vendored
Normal file
289
vendor/github.com/redis/go-redis/v9/list_commands.go
generated
vendored
Normal file
@@ -0,0 +1,289 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ListCmdable interface {
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
func (c cmdable) BLPop(ctx context.Context, timeout time.Duration, keys ...string) *StringSliceCmd {
|
||||
args := make([]interface{}, 1+len(keys)+1)
|
||||
args[0] = "blpop"
|
||||
for i, key := range keys {
|
||||
args[1+i] = key
|
||||
}
|
||||
args[len(args)-1] = formatSec(ctx, timeout)
|
||||
cmd := NewStringSliceCmd(ctx, args...)
|
||||
cmd.setReadTimeout(timeout)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) BLMPop(ctx context.Context, timeout time.Duration, direction string, count int64, keys ...string) *KeyValuesCmd {
|
||||
args := make([]interface{}, 3+len(keys), 6+len(keys))
|
||||
args[0] = "blmpop"
|
||||
args[1] = formatSec(ctx, timeout)
|
||||
args[2] = len(keys)
|
||||
for i, key := range keys {
|
||||
args[3+i] = key
|
||||
}
|
||||
args = append(args, strings.ToLower(direction), "count", count)
|
||||
cmd := NewKeyValuesCmd(ctx, args...)
|
||||
cmd.setReadTimeout(timeout)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) BRPop(ctx context.Context, timeout time.Duration, keys ...string) *StringSliceCmd {
|
||||
args := make([]interface{}, 1+len(keys)+1)
|
||||
args[0] = "brpop"
|
||||
for i, key := range keys {
|
||||
args[1+i] = key
|
||||
}
|
||||
args[len(keys)+1] = formatSec(ctx, timeout)
|
||||
cmd := NewStringSliceCmd(ctx, args...)
|
||||
cmd.setReadTimeout(timeout)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) BRPopLPush(ctx context.Context, source, destination string, timeout time.Duration) *StringCmd {
|
||||
cmd := NewStringCmd(
|
||||
ctx,
|
||||
"brpoplpush",
|
||||
source,
|
||||
destination,
|
||||
formatSec(ctx, timeout),
|
||||
)
|
||||
cmd.setReadTimeout(timeout)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) LIndex(ctx context.Context, key string, index int64) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "lindex", key, index)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// LMPop Pops one or more elements from the first non-empty list key from the list of provided key names.
|
||||
// direction: left or right, count: > 0
|
||||
// example: client.LMPop(ctx, "left", 3, "key1", "key2")
|
||||
func (c cmdable) LMPop(ctx context.Context, direction string, count int64, keys ...string) *KeyValuesCmd {
|
||||
args := make([]interface{}, 2+len(keys), 5+len(keys))
|
||||
args[0] = "lmpop"
|
||||
args[1] = len(keys)
|
||||
for i, key := range keys {
|
||||
args[2+i] = key
|
||||
}
|
||||
args = append(args, strings.ToLower(direction), "count", count)
|
||||
cmd := NewKeyValuesCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) LInsert(ctx context.Context, key, op string, pivot, value interface{}) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "linsert", key, op, pivot, value)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) LInsertBefore(ctx context.Context, key string, pivot, value interface{}) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "linsert", key, "before", pivot, value)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) LInsertAfter(ctx context.Context, key string, pivot, value interface{}) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "linsert", key, "after", pivot, value)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) LLen(ctx context.Context, key string) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "llen", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) LPop(ctx context.Context, key string) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "lpop", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) LPopCount(ctx context.Context, key string, count int) *StringSliceCmd {
|
||||
cmd := NewStringSliceCmd(ctx, "lpop", key, count)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
type LPosArgs struct {
|
||||
Rank, MaxLen int64
|
||||
}
|
||||
|
||||
func (c cmdable) LPos(ctx context.Context, key string, value string, a LPosArgs) *IntCmd {
|
||||
args := []interface{}{"lpos", key, value}
|
||||
if a.Rank != 0 {
|
||||
args = append(args, "rank", a.Rank)
|
||||
}
|
||||
if a.MaxLen != 0 {
|
||||
args = append(args, "maxlen", a.MaxLen)
|
||||
}
|
||||
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) LPosCount(ctx context.Context, key string, value string, count int64, a LPosArgs) *IntSliceCmd {
|
||||
args := []interface{}{"lpos", key, value, "count", count}
|
||||
if a.Rank != 0 {
|
||||
args = append(args, "rank", a.Rank)
|
||||
}
|
||||
if a.MaxLen != 0 {
|
||||
args = append(args, "maxlen", a.MaxLen)
|
||||
}
|
||||
cmd := NewIntSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) LPush(ctx context.Context, key string, values ...interface{}) *IntCmd {
|
||||
args := make([]interface{}, 2, 2+len(values))
|
||||
args[0] = "lpush"
|
||||
args[1] = key
|
||||
args = appendArgs(args, values)
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) LPushX(ctx context.Context, key string, values ...interface{}) *IntCmd {
|
||||
args := make([]interface{}, 2, 2+len(values))
|
||||
args[0] = "lpushx"
|
||||
args[1] = key
|
||||
args = appendArgs(args, values)
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) LRange(ctx context.Context, key string, start, stop int64) *StringSliceCmd {
|
||||
cmd := NewStringSliceCmd(
|
||||
ctx,
|
||||
"lrange",
|
||||
key,
|
||||
start,
|
||||
stop,
|
||||
)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) LRem(ctx context.Context, key string, count int64, value interface{}) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "lrem", key, count, value)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) LSet(ctx context.Context, key string, index int64, value interface{}) *StatusCmd {
|
||||
cmd := NewStatusCmd(ctx, "lset", key, index, value)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) LTrim(ctx context.Context, key string, start, stop int64) *StatusCmd {
|
||||
cmd := NewStatusCmd(
|
||||
ctx,
|
||||
"ltrim",
|
||||
key,
|
||||
start,
|
||||
stop,
|
||||
)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) RPop(ctx context.Context, key string) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "rpop", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) RPopCount(ctx context.Context, key string, count int) *StringSliceCmd {
|
||||
cmd := NewStringSliceCmd(ctx, "rpop", key, count)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) RPopLPush(ctx context.Context, source, destination string) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "rpoplpush", source, destination)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) RPush(ctx context.Context, key string, values ...interface{}) *IntCmd {
|
||||
args := make([]interface{}, 2, 2+len(values))
|
||||
args[0] = "rpush"
|
||||
args[1] = key
|
||||
args = appendArgs(args, values)
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) RPushX(ctx context.Context, key string, values ...interface{}) *IntCmd {
|
||||
args := make([]interface{}, 2, 2+len(values))
|
||||
args[0] = "rpushx"
|
||||
args[1] = key
|
||||
args = appendArgs(args, values)
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) LMove(ctx context.Context, source, destination, srcpos, destpos string) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "lmove", source, destination, srcpos, destpos)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) BLMove(
|
||||
ctx context.Context, source, destination, srcpos, destpos string, timeout time.Duration,
|
||||
) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "blmove", source, destination, srcpos, destpos, formatSec(ctx, timeout))
|
||||
cmd.setReadTimeout(timeout)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
596
vendor/github.com/redis/go-redis/v9/options.go
generated
vendored
Normal file
596
vendor/github.com/redis/go-redis/v9/options.go
generated
vendored
Normal file
@@ -0,0 +1,596 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/redis/go-redis/v9/auth"
|
||||
"github.com/redis/go-redis/v9/internal/pool"
|
||||
)
|
||||
|
||||
// Limiter is the interface of a rate limiter or a circuit breaker.
|
||||
type Limiter interface {
|
||||
// Allow returns nil if operation is allowed or an error otherwise.
|
||||
// If operation is allowed client must ReportResult of the operation
|
||||
// whether it is a success or a failure.
|
||||
Allow() error
|
||||
// ReportResult reports the result of the previously allowed operation.
|
||||
// nil indicates a success, non-nil error usually indicates a failure.
|
||||
ReportResult(result error)
|
||||
}
|
||||
|
||||
// Options keeps the settings to set up redis connection.
|
||||
type Options struct {
|
||||
|
||||
// Network type, either tcp or unix.
|
||||
//
|
||||
// default: is tcp.
|
||||
Network string
|
||||
|
||||
// Addr is the address formated as host:port
|
||||
Addr string
|
||||
|
||||
// ClientName will execute the `CLIENT SETNAME ClientName` command for each conn.
|
||||
ClientName string
|
||||
|
||||
// Dialer creates new network connection and has priority over
|
||||
// Network and Addr options.
|
||||
Dialer func(ctx context.Context, network, addr string) (net.Conn, error)
|
||||
|
||||
// Hook that is called when new connection is established.
|
||||
OnConnect func(ctx context.Context, cn *Conn) error
|
||||
|
||||
// Protocol 2 or 3. Use the version to negotiate RESP version with redis-server.
|
||||
//
|
||||
// default: 3.
|
||||
Protocol int
|
||||
|
||||
// Username is used to authenticate the current connection
|
||||
// with one of the connections defined in the ACL list when connecting
|
||||
// to a Redis 6.0 instance, or greater, that is using the Redis ACL system.
|
||||
Username string
|
||||
|
||||
// Password is an optional password. Must match the password specified in the
|
||||
// `requirepass` server configuration option (if connecting to a Redis 5.0 instance, or lower),
|
||||
// or the User Password when connecting to a Redis 6.0 instance, or greater,
|
||||
// that is using the Redis ACL system.
|
||||
Password string
|
||||
|
||||
// CredentialsProvider allows the username and password to be updated
|
||||
// before reconnecting. It should return the current username and password.
|
||||
CredentialsProvider func() (username string, password string)
|
||||
|
||||
// CredentialsProviderContext is an enhanced parameter of CredentialsProvider,
|
||||
// done to maintain API compatibility. In the future,
|
||||
// there might be a merge between CredentialsProviderContext and CredentialsProvider.
|
||||
// There will be a conflict between them; if CredentialsProviderContext exists, we will ignore CredentialsProvider.
|
||||
CredentialsProviderContext func(ctx context.Context) (username string, password string, err error)
|
||||
|
||||
// StreamingCredentialsProvider is used to retrieve the credentials
|
||||
// for the connection from an external source. Those credentials may change
|
||||
// during the connection lifetime. This is useful for managed identity
|
||||
// scenarios where the credentials are retrieved from an external source.
|
||||
//
|
||||
// Currently, this is a placeholder for the future implementation.
|
||||
StreamingCredentialsProvider auth.StreamingCredentialsProvider
|
||||
|
||||
// DB is the database to be selected after connecting to the server.
|
||||
DB int
|
||||
|
||||
// MaxRetries is the maximum number of retries before giving up.
|
||||
// -1 (not 0) disables retries.
|
||||
//
|
||||
// default: 3 retries
|
||||
MaxRetries int
|
||||
|
||||
// MinRetryBackoff is the minimum backoff between each retry.
|
||||
// -1 disables backoff.
|
||||
//
|
||||
// default: 8 milliseconds
|
||||
MinRetryBackoff time.Duration
|
||||
|
||||
// MaxRetryBackoff is the maximum backoff between each retry.
|
||||
// -1 disables backoff.
|
||||
// default: 512 milliseconds;
|
||||
MaxRetryBackoff time.Duration
|
||||
|
||||
// DialTimeout for establishing new connections.
|
||||
//
|
||||
// default: 5 seconds
|
||||
DialTimeout time.Duration
|
||||
|
||||
// ReadTimeout for socket reads. If reached, commands will fail
|
||||
// with a timeout instead of blocking. Supported values:
|
||||
//
|
||||
// - `-1` - no timeout (block indefinitely).
|
||||
// - `-2` - disables SetReadDeadline calls completely.
|
||||
//
|
||||
// default: 3 seconds
|
||||
ReadTimeout time.Duration
|
||||
|
||||
// WriteTimeout for socket writes. If reached, commands will fail
|
||||
// with a timeout instead of blocking. Supported values:
|
||||
//
|
||||
// - `-1` - no timeout (block indefinitely).
|
||||
// - `-2` - disables SetWriteDeadline calls completely.
|
||||
//
|
||||
// default: 3 seconds
|
||||
WriteTimeout time.Duration
|
||||
|
||||
// ContextTimeoutEnabled controls whether the client respects context timeouts and deadlines.
|
||||
// See https://redis.uptrace.dev/guide/go-redis-debugging.html#timeouts
|
||||
ContextTimeoutEnabled bool
|
||||
|
||||
// PoolFIFO type of connection pool.
|
||||
//
|
||||
// - true for FIFO pool
|
||||
// - false for LIFO pool.
|
||||
//
|
||||
// Note that FIFO has slightly higher overhead compared to LIFO,
|
||||
// but it helps closing idle connections faster reducing the pool size.
|
||||
PoolFIFO bool
|
||||
|
||||
// PoolSize is the base number of socket connections.
|
||||
// Default is 10 connections per every available CPU as reported by runtime.GOMAXPROCS.
|
||||
// If there is not enough connections in the pool, new connections will be allocated in excess of PoolSize,
|
||||
// you can limit it through MaxActiveConns
|
||||
//
|
||||
// default: 10 * runtime.GOMAXPROCS(0)
|
||||
PoolSize int
|
||||
|
||||
// PoolTimeout is the amount of time client waits for connection if all connections
|
||||
// are busy before returning an error.
|
||||
//
|
||||
// default: ReadTimeout + 1 second
|
||||
PoolTimeout time.Duration
|
||||
|
||||
// MinIdleConns is the minimum number of idle connections which is useful when establishing
|
||||
// new connection is slow. The idle connections are not closed by default.
|
||||
//
|
||||
// default: 0
|
||||
MinIdleConns int
|
||||
|
||||
// MaxIdleConns is the maximum number of idle connections.
|
||||
// The idle connections are not closed by default.
|
||||
//
|
||||
// default: 0
|
||||
MaxIdleConns int
|
||||
|
||||
// MaxActiveConns is the maximum number of connections allocated by the pool at a given time.
|
||||
// When zero, there is no limit on the number of connections in the pool.
|
||||
// If the pool is full, the next call to Get() will block until a connection is released.
|
||||
MaxActiveConns int
|
||||
|
||||
// ConnMaxIdleTime is the maximum amount of time a connection may be idle.
|
||||
// Should be less than server's timeout.
|
||||
//
|
||||
// Expired connections may be closed lazily before reuse.
|
||||
// If d <= 0, connections are not closed due to a connection's idle time.
|
||||
// -1 disables idle timeout check.
|
||||
//
|
||||
// default: 30 minutes
|
||||
ConnMaxIdleTime time.Duration
|
||||
|
||||
// ConnMaxLifetime is the maximum amount of time a connection may be reused.
|
||||
//
|
||||
// Expired connections may be closed lazily before reuse.
|
||||
// If <= 0, connections are not closed due to a connection's age.
|
||||
//
|
||||
// default: 0
|
||||
ConnMaxLifetime time.Duration
|
||||
|
||||
// TLSConfig to use. When set, TLS will be negotiated.
|
||||
TLSConfig *tls.Config
|
||||
|
||||
// Limiter interface used to implement circuit breaker or rate limiter.
|
||||
Limiter Limiter
|
||||
|
||||
// readOnly enables read only queries on slave/follower nodes.
|
||||
readOnly bool
|
||||
|
||||
// DisableIndentity - Disable set-lib on connect.
|
||||
//
|
||||
// default: false
|
||||
//
|
||||
// Deprecated: Use DisableIdentity instead.
|
||||
DisableIndentity bool
|
||||
|
||||
// DisableIdentity is used to disable CLIENT SETINFO command on connect.
|
||||
//
|
||||
// default: false
|
||||
DisableIdentity bool
|
||||
|
||||
// Add suffix to client name. Default is empty.
|
||||
// IdentitySuffix - add suffix to client name.
|
||||
IdentitySuffix string
|
||||
|
||||
// UnstableResp3 enables Unstable mode for Redis Search module with RESP3.
|
||||
// When unstable mode is enabled, the client will use RESP3 protocol and only be able to use RawResult
|
||||
UnstableResp3 bool
|
||||
}
|
||||
|
||||
func (opt *Options) init() {
|
||||
if opt.Addr == "" {
|
||||
opt.Addr = "localhost:6379"
|
||||
}
|
||||
if opt.Network == "" {
|
||||
if strings.HasPrefix(opt.Addr, "/") {
|
||||
opt.Network = "unix"
|
||||
} else {
|
||||
opt.Network = "tcp"
|
||||
}
|
||||
}
|
||||
if opt.Protocol < 2 {
|
||||
opt.Protocol = 3
|
||||
}
|
||||
if opt.DialTimeout == 0 {
|
||||
opt.DialTimeout = 5 * time.Second
|
||||
}
|
||||
if opt.Dialer == nil {
|
||||
opt.Dialer = NewDialer(opt)
|
||||
}
|
||||
if opt.PoolSize == 0 {
|
||||
opt.PoolSize = 10 * runtime.GOMAXPROCS(0)
|
||||
}
|
||||
switch opt.ReadTimeout {
|
||||
case -2:
|
||||
opt.ReadTimeout = -1
|
||||
case -1:
|
||||
opt.ReadTimeout = 0
|
||||
case 0:
|
||||
opt.ReadTimeout = 3 * time.Second
|
||||
}
|
||||
switch opt.WriteTimeout {
|
||||
case -2:
|
||||
opt.WriteTimeout = -1
|
||||
case -1:
|
||||
opt.WriteTimeout = 0
|
||||
case 0:
|
||||
opt.WriteTimeout = opt.ReadTimeout
|
||||
}
|
||||
if opt.PoolTimeout == 0 {
|
||||
if opt.ReadTimeout > 0 {
|
||||
opt.PoolTimeout = opt.ReadTimeout + time.Second
|
||||
} else {
|
||||
opt.PoolTimeout = 30 * time.Second
|
||||
}
|
||||
}
|
||||
if opt.ConnMaxIdleTime == 0 {
|
||||
opt.ConnMaxIdleTime = 30 * time.Minute
|
||||
}
|
||||
|
||||
switch opt.MaxRetries {
|
||||
case -1:
|
||||
opt.MaxRetries = 0
|
||||
case 0:
|
||||
opt.MaxRetries = 3
|
||||
}
|
||||
switch opt.MinRetryBackoff {
|
||||
case -1:
|
||||
opt.MinRetryBackoff = 0
|
||||
case 0:
|
||||
opt.MinRetryBackoff = 8 * time.Millisecond
|
||||
}
|
||||
switch opt.MaxRetryBackoff {
|
||||
case -1:
|
||||
opt.MaxRetryBackoff = 0
|
||||
case 0:
|
||||
opt.MaxRetryBackoff = 512 * time.Millisecond
|
||||
}
|
||||
}
|
||||
|
||||
func (opt *Options) clone() *Options {
|
||||
clone := *opt
|
||||
return &clone
|
||||
}
|
||||
|
||||
// NewDialer returns a function that will be used as the default dialer
|
||||
// when none is specified in Options.Dialer.
|
||||
func NewDialer(opt *Options) func(context.Context, string, string) (net.Conn, error) {
|
||||
return func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
netDialer := &net.Dialer{
|
||||
Timeout: opt.DialTimeout,
|
||||
KeepAlive: 5 * time.Minute,
|
||||
}
|
||||
if opt.TLSConfig == nil {
|
||||
return netDialer.DialContext(ctx, network, addr)
|
||||
}
|
||||
return tls.DialWithDialer(netDialer, network, addr, opt.TLSConfig)
|
||||
}
|
||||
}
|
||||
|
||||
// ParseURL parses a URL into Options that can be used to connect to Redis.
|
||||
// Scheme is required.
|
||||
// There are two connection types: by tcp socket and by unix socket.
|
||||
// Tcp connection:
|
||||
//
|
||||
// redis://<user>:<password>@<host>:<port>/<db_number>
|
||||
//
|
||||
// Unix connection:
|
||||
//
|
||||
// unix://<user>:<password>@</path/to/redis.sock>?db=<db_number>
|
||||
//
|
||||
// Most Option fields can be set using query parameters, with the following restrictions:
|
||||
// - field names are mapped using snake-case conversion: to set MaxRetries, use max_retries
|
||||
// - only scalar type fields are supported (bool, int, time.Duration)
|
||||
// - for time.Duration fields, values must be a valid input for time.ParseDuration();
|
||||
// additionally a plain integer as value (i.e. without unit) is interpreted as seconds
|
||||
// - to disable a duration field, use value less than or equal to 0; to use the default
|
||||
// value, leave the value blank or remove the parameter
|
||||
// - only the last value is interpreted if a parameter is given multiple times
|
||||
// - fields "network", "addr", "username" and "password" can only be set using other
|
||||
// URL attributes (scheme, host, userinfo, resp.), query parameters using these
|
||||
// names will be treated as unknown parameters
|
||||
// - unknown parameter names will result in an error
|
||||
// - use "skip_verify=true" to ignore TLS certificate validation
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// redis://user:password@localhost:6789/3?dial_timeout=3&db=1&read_timeout=6s&max_retries=2
|
||||
// is equivalent to:
|
||||
// &Options{
|
||||
// Network: "tcp",
|
||||
// Addr: "localhost:6789",
|
||||
// DB: 1, // path "/3" was overridden by "&db=1"
|
||||
// DialTimeout: 3 * time.Second, // no time unit = seconds
|
||||
// ReadTimeout: 6 * time.Second,
|
||||
// MaxRetries: 2,
|
||||
// }
|
||||
func ParseURL(redisURL string) (*Options, error) {
|
||||
u, err := url.Parse(redisURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch u.Scheme {
|
||||
case "redis", "rediss":
|
||||
return setupTCPConn(u)
|
||||
case "unix":
|
||||
return setupUnixConn(u)
|
||||
default:
|
||||
return nil, fmt.Errorf("redis: invalid URL scheme: %s", u.Scheme)
|
||||
}
|
||||
}
|
||||
|
||||
func setupTCPConn(u *url.URL) (*Options, error) {
|
||||
o := &Options{Network: "tcp"}
|
||||
|
||||
o.Username, o.Password = getUserPassword(u)
|
||||
|
||||
h, p := getHostPortWithDefaults(u)
|
||||
o.Addr = net.JoinHostPort(h, p)
|
||||
|
||||
f := strings.FieldsFunc(u.Path, func(r rune) bool {
|
||||
return r == '/'
|
||||
})
|
||||
switch len(f) {
|
||||
case 0:
|
||||
o.DB = 0
|
||||
case 1:
|
||||
var err error
|
||||
if o.DB, err = strconv.Atoi(f[0]); err != nil {
|
||||
return nil, fmt.Errorf("redis: invalid database number: %q", f[0])
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("redis: invalid URL path: %s", u.Path)
|
||||
}
|
||||
|
||||
if u.Scheme == "rediss" {
|
||||
o.TLSConfig = &tls.Config{
|
||||
ServerName: h,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
}
|
||||
}
|
||||
|
||||
return setupConnParams(u, o)
|
||||
}
|
||||
|
||||
// getHostPortWithDefaults is a helper function that splits the url into
|
||||
// a host and a port. If the host is missing, it defaults to localhost
|
||||
// and if the port is missing, it defaults to 6379.
|
||||
func getHostPortWithDefaults(u *url.URL) (string, string) {
|
||||
host, port, err := net.SplitHostPort(u.Host)
|
||||
if err != nil {
|
||||
host = u.Host
|
||||
}
|
||||
if host == "" {
|
||||
host = "localhost"
|
||||
}
|
||||
if port == "" {
|
||||
port = "6379"
|
||||
}
|
||||
return host, port
|
||||
}
|
||||
|
||||
func setupUnixConn(u *url.URL) (*Options, error) {
|
||||
o := &Options{
|
||||
Network: "unix",
|
||||
}
|
||||
|
||||
if strings.TrimSpace(u.Path) == "" { // path is required with unix connection
|
||||
return nil, errors.New("redis: empty unix socket path")
|
||||
}
|
||||
o.Addr = u.Path
|
||||
o.Username, o.Password = getUserPassword(u)
|
||||
return setupConnParams(u, o)
|
||||
}
|
||||
|
||||
type queryOptions struct {
|
||||
q url.Values
|
||||
err error
|
||||
}
|
||||
|
||||
func (o *queryOptions) has(name string) bool {
|
||||
return len(o.q[name]) > 0
|
||||
}
|
||||
|
||||
func (o *queryOptions) string(name string) string {
|
||||
vs := o.q[name]
|
||||
if len(vs) == 0 {
|
||||
return ""
|
||||
}
|
||||
delete(o.q, name) // enable detection of unknown parameters
|
||||
return vs[len(vs)-1]
|
||||
}
|
||||
|
||||
func (o *queryOptions) strings(name string) []string {
|
||||
vs := o.q[name]
|
||||
delete(o.q, name)
|
||||
return vs
|
||||
}
|
||||
|
||||
func (o *queryOptions) int(name string) int {
|
||||
s := o.string(name)
|
||||
if s == "" {
|
||||
return 0
|
||||
}
|
||||
i, err := strconv.Atoi(s)
|
||||
if err == nil {
|
||||
return i
|
||||
}
|
||||
if o.err == nil {
|
||||
o.err = fmt.Errorf("redis: invalid %s number: %s", name, err)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (o *queryOptions) duration(name string) time.Duration {
|
||||
s := o.string(name)
|
||||
if s == "" {
|
||||
return 0
|
||||
}
|
||||
// try plain number first
|
||||
if i, err := strconv.Atoi(s); err == nil {
|
||||
if i <= 0 {
|
||||
// disable timeouts
|
||||
return -1
|
||||
}
|
||||
return time.Duration(i) * time.Second
|
||||
}
|
||||
dur, err := time.ParseDuration(s)
|
||||
if err == nil {
|
||||
return dur
|
||||
}
|
||||
if o.err == nil {
|
||||
o.err = fmt.Errorf("redis: invalid %s duration: %w", name, err)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (o *queryOptions) bool(name string) bool {
|
||||
switch s := o.string(name); s {
|
||||
case "true", "1":
|
||||
return true
|
||||
case "false", "0", "":
|
||||
return false
|
||||
default:
|
||||
if o.err == nil {
|
||||
o.err = fmt.Errorf("redis: invalid %s boolean: expected true/false/1/0 or an empty string, got %q", name, s)
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (o *queryOptions) remaining() []string {
|
||||
if len(o.q) == 0 {
|
||||
return nil
|
||||
}
|
||||
keys := make([]string, 0, len(o.q))
|
||||
for k := range o.q {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
return keys
|
||||
}
|
||||
|
||||
// setupConnParams converts query parameters in u to option value in o.
|
||||
func setupConnParams(u *url.URL, o *Options) (*Options, error) {
|
||||
q := queryOptions{q: u.Query()}
|
||||
|
||||
// compat: a future major release may use q.int("db")
|
||||
if tmp := q.string("db"); tmp != "" {
|
||||
db, err := strconv.Atoi(tmp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("redis: invalid database number: %w", err)
|
||||
}
|
||||
o.DB = db
|
||||
}
|
||||
|
||||
o.Protocol = q.int("protocol")
|
||||
o.ClientName = q.string("client_name")
|
||||
o.MaxRetries = q.int("max_retries")
|
||||
o.MinRetryBackoff = q.duration("min_retry_backoff")
|
||||
o.MaxRetryBackoff = q.duration("max_retry_backoff")
|
||||
o.DialTimeout = q.duration("dial_timeout")
|
||||
o.ReadTimeout = q.duration("read_timeout")
|
||||
o.WriteTimeout = q.duration("write_timeout")
|
||||
o.PoolFIFO = q.bool("pool_fifo")
|
||||
o.PoolSize = q.int("pool_size")
|
||||
o.PoolTimeout = q.duration("pool_timeout")
|
||||
o.MinIdleConns = q.int("min_idle_conns")
|
||||
o.MaxIdleConns = q.int("max_idle_conns")
|
||||
o.MaxActiveConns = q.int("max_active_conns")
|
||||
if q.has("conn_max_idle_time") {
|
||||
o.ConnMaxIdleTime = q.duration("conn_max_idle_time")
|
||||
} else {
|
||||
o.ConnMaxIdleTime = q.duration("idle_timeout")
|
||||
}
|
||||
if q.has("conn_max_lifetime") {
|
||||
o.ConnMaxLifetime = q.duration("conn_max_lifetime")
|
||||
} else {
|
||||
o.ConnMaxLifetime = q.duration("max_conn_age")
|
||||
}
|
||||
if q.err != nil {
|
||||
return nil, q.err
|
||||
}
|
||||
if o.TLSConfig != nil && q.has("skip_verify") {
|
||||
o.TLSConfig.InsecureSkipVerify = q.bool("skip_verify")
|
||||
}
|
||||
|
||||
// any parameters left?
|
||||
if r := q.remaining(); len(r) > 0 {
|
||||
return nil, fmt.Errorf("redis: unexpected option: %s", strings.Join(r, ", "))
|
||||
}
|
||||
|
||||
return o, nil
|
||||
}
|
||||
|
||||
func getUserPassword(u *url.URL) (string, string) {
|
||||
var user, password string
|
||||
if u.User != nil {
|
||||
user = u.User.Username()
|
||||
if p, ok := u.User.Password(); ok {
|
||||
password = p
|
||||
}
|
||||
}
|
||||
return user, password
|
||||
}
|
||||
|
||||
func newConnPool(
|
||||
opt *Options,
|
||||
dialer func(ctx context.Context, network, addr string) (net.Conn, error),
|
||||
) *pool.ConnPool {
|
||||
return pool.NewConnPool(&pool.Options{
|
||||
Dialer: func(ctx context.Context) (net.Conn, error) {
|
||||
return dialer(ctx, opt.Network, opt.Addr)
|
||||
},
|
||||
PoolFIFO: opt.PoolFIFO,
|
||||
PoolSize: opt.PoolSize,
|
||||
PoolTimeout: opt.PoolTimeout,
|
||||
DialTimeout: opt.DialTimeout,
|
||||
MinIdleConns: opt.MinIdleConns,
|
||||
MaxIdleConns: opt.MaxIdleConns,
|
||||
MaxActiveConns: opt.MaxActiveConns,
|
||||
ConnMaxIdleTime: opt.ConnMaxIdleTime,
|
||||
ConnMaxLifetime: opt.ConnMaxLifetime,
|
||||
})
|
||||
}
|
||||
2047
vendor/github.com/redis/go-redis/v9/osscluster.go
generated
vendored
Normal file
2047
vendor/github.com/redis/go-redis/v9/osscluster.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
109
vendor/github.com/redis/go-redis/v9/osscluster_commands.go
generated
vendored
Normal file
109
vendor/github.com/redis/go-redis/v9/osscluster_commands.go
generated
vendored
Normal file
@@ -0,0 +1,109 @@
|
||||
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
|
||||
}
|
||||
cmd := NewBoolSliceCmd(ctx, args...)
|
||||
|
||||
result := make([]bool, len(hashes))
|
||||
for i := range result {
|
||||
result[i] = true
|
||||
}
|
||||
|
||||
_ = 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
|
||||
})
|
||||
return cmd
|
||||
}
|
||||
121
vendor/github.com/redis/go-redis/v9/pipeline.go
generated
vendored
Normal file
121
vendor/github.com/redis/go-redis/v9/pipeline.go
generated
vendored
Normal file
@@ -0,0 +1,121 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
)
|
||||
|
||||
type pipelineExecer func(context.Context, []Cmder) error
|
||||
|
||||
// Pipeliner is an mechanism to realise Redis Pipeline technique.
|
||||
//
|
||||
// Pipelining is a technique to extremely speed up processing by packing
|
||||
// operations to batches, send them at once to Redis and read a replies in a
|
||||
// single step.
|
||||
// See https://redis.io/topics/pipelining
|
||||
//
|
||||
// Pay attention, that Pipeline is not a transaction, so you can get unexpected
|
||||
// results in case of big pipelines and small read/write timeouts.
|
||||
// Redis client has retransmission logic in case of timeouts, pipeline
|
||||
// can be retransmitted and commands can be executed more then once.
|
||||
// To avoid this: it is good idea to use reasonable bigger read/write timeouts
|
||||
// depends of your batch size and/or use TxPipeline.
|
||||
type Pipeliner interface {
|
||||
StatefulCmdable
|
||||
|
||||
// Len is to obtain the number of commands in the pipeline that have not yet been executed.
|
||||
Len() int
|
||||
|
||||
// Do is an API for executing any command.
|
||||
// If a certain Redis command is not yet supported, you can use Do to execute it.
|
||||
Do(ctx context.Context, args ...interface{}) *Cmd
|
||||
|
||||
// Process is to put the commands to be executed into the pipeline buffer.
|
||||
Process(ctx context.Context, cmd Cmder) error
|
||||
|
||||
// Discard is to discard all commands in the cache that have not yet been executed.
|
||||
Discard()
|
||||
|
||||
// Exec is to send all the commands buffered in the pipeline to the redis-server.
|
||||
Exec(ctx context.Context) ([]Cmder, error)
|
||||
}
|
||||
|
||||
var _ Pipeliner = (*Pipeline)(nil)
|
||||
|
||||
// Pipeline implements pipelining as described in
|
||||
// http://redis.io/topics/pipelining.
|
||||
// Please note: it is not safe for concurrent use by multiple goroutines.
|
||||
type Pipeline struct {
|
||||
cmdable
|
||||
statefulCmdable
|
||||
|
||||
exec pipelineExecer
|
||||
cmds []Cmder
|
||||
}
|
||||
|
||||
func (c *Pipeline) init() {
|
||||
c.cmdable = c.Process
|
||||
c.statefulCmdable = c.Process
|
||||
}
|
||||
|
||||
// Len returns the number of queued commands.
|
||||
func (c *Pipeline) Len() int {
|
||||
return len(c.cmds)
|
||||
}
|
||||
|
||||
// Do queues the custom command for later execution.
|
||||
func (c *Pipeline) Do(ctx context.Context, args ...interface{}) *Cmd {
|
||||
cmd := NewCmd(ctx, args...)
|
||||
if len(args) == 0 {
|
||||
cmd.SetErr(errors.New("redis: please enter the command to be executed"))
|
||||
return cmd
|
||||
}
|
||||
_ = c.Process(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Process queues the cmd for later execution.
|
||||
func (c *Pipeline) Process(ctx context.Context, cmd Cmder) error {
|
||||
c.cmds = append(c.cmds, cmd)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Discard resets the pipeline and discards queued commands.
|
||||
func (c *Pipeline) Discard() {
|
||||
c.cmds = c.cmds[:0]
|
||||
}
|
||||
|
||||
// Exec executes all previously queued commands using one
|
||||
// client-server roundtrip.
|
||||
//
|
||||
// Exec always returns list of commands and error of the first failed
|
||||
// command if any.
|
||||
func (c *Pipeline) Exec(ctx context.Context) ([]Cmder, error) {
|
||||
if len(c.cmds) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
cmds := c.cmds
|
||||
c.cmds = nil
|
||||
|
||||
return cmds, c.exec(ctx, cmds)
|
||||
}
|
||||
|
||||
func (c *Pipeline) Pipelined(ctx context.Context, fn func(Pipeliner) error) ([]Cmder, error) {
|
||||
if err := fn(c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.Exec(ctx)
|
||||
}
|
||||
|
||||
func (c *Pipeline) Pipeline() Pipeliner {
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Pipeline) TxPipelined(ctx context.Context, fn func(Pipeliner) error) ([]Cmder, error) {
|
||||
return c.Pipelined(ctx, fn)
|
||||
}
|
||||
|
||||
func (c *Pipeline) TxPipeline() Pipeliner {
|
||||
return c
|
||||
}
|
||||
1433
vendor/github.com/redis/go-redis/v9/probabilistic.go
generated
vendored
Normal file
1433
vendor/github.com/redis/go-redis/v9/probabilistic.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
732
vendor/github.com/redis/go-redis/v9/pubsub.go
generated
vendored
Normal file
732
vendor/github.com/redis/go-redis/v9/pubsub.go
generated
vendored
Normal file
@@ -0,0 +1,732 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/redis/go-redis/v9/internal"
|
||||
"github.com/redis/go-redis/v9/internal/pool"
|
||||
"github.com/redis/go-redis/v9/internal/proto"
|
||||
)
|
||||
|
||||
// PubSub implements Pub/Sub commands as described in
|
||||
// http://redis.io/topics/pubsub. Message receiving is NOT safe
|
||||
// for concurrent use by multiple goroutines.
|
||||
//
|
||||
// PubSub automatically reconnects to Redis Server and resubscribes
|
||||
// to the channels in case of network errors.
|
||||
type PubSub struct {
|
||||
opt *Options
|
||||
|
||||
newConn func(ctx context.Context, channels []string) (*pool.Conn, error)
|
||||
closeConn func(*pool.Conn) error
|
||||
|
||||
mu sync.Mutex
|
||||
cn *pool.Conn
|
||||
channels map[string]struct{}
|
||||
patterns map[string]struct{}
|
||||
schannels map[string]struct{}
|
||||
|
||||
closed bool
|
||||
exit chan struct{}
|
||||
|
||||
cmd *Cmd
|
||||
|
||||
chOnce sync.Once
|
||||
msgCh *channel
|
||||
allCh *channel
|
||||
}
|
||||
|
||||
func (c *PubSub) init() {
|
||||
c.exit = make(chan struct{})
|
||||
}
|
||||
|
||||
func (c *PubSub) String() string {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
channels := mapKeys(c.channels)
|
||||
channels = append(channels, mapKeys(c.patterns)...)
|
||||
channels = append(channels, mapKeys(c.schannels)...)
|
||||
return fmt.Sprintf("PubSub(%s)", strings.Join(channels, ", "))
|
||||
}
|
||||
|
||||
func (c *PubSub) connWithLock(ctx context.Context) (*pool.Conn, error) {
|
||||
c.mu.Lock()
|
||||
cn, err := c.conn(ctx, nil)
|
||||
c.mu.Unlock()
|
||||
return cn, err
|
||||
}
|
||||
|
||||
func (c *PubSub) conn(ctx context.Context, newChannels []string) (*pool.Conn, error) {
|
||||
if c.closed {
|
||||
return nil, pool.ErrClosed
|
||||
}
|
||||
if c.cn != nil {
|
||||
return c.cn, nil
|
||||
}
|
||||
|
||||
channels := mapKeys(c.channels)
|
||||
channels = append(channels, newChannels...)
|
||||
|
||||
cn, err := c.newConn(ctx, channels)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := c.resubscribe(ctx, cn); err != nil {
|
||||
_ = c.closeConn(cn)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.cn = cn
|
||||
return cn, nil
|
||||
}
|
||||
|
||||
func (c *PubSub) writeCmd(ctx context.Context, cn *pool.Conn, cmd Cmder) error {
|
||||
return cn.WithWriter(ctx, c.opt.WriteTimeout, func(wr *proto.Writer) error {
|
||||
return writeCmd(wr, cmd)
|
||||
})
|
||||
}
|
||||
|
||||
func (c *PubSub) resubscribe(ctx context.Context, cn *pool.Conn) error {
|
||||
var firstErr error
|
||||
|
||||
if len(c.channels) > 0 {
|
||||
firstErr = c._subscribe(ctx, cn, "subscribe", mapKeys(c.channels))
|
||||
}
|
||||
|
||||
if len(c.patterns) > 0 {
|
||||
err := c._subscribe(ctx, cn, "psubscribe", mapKeys(c.patterns))
|
||||
if err != nil && firstErr == nil {
|
||||
firstErr = err
|
||||
}
|
||||
}
|
||||
|
||||
if len(c.schannels) > 0 {
|
||||
err := c._subscribe(ctx, cn, "ssubscribe", mapKeys(c.schannels))
|
||||
if err != nil && firstErr == nil {
|
||||
firstErr = err
|
||||
}
|
||||
}
|
||||
|
||||
return firstErr
|
||||
}
|
||||
|
||||
func mapKeys(m map[string]struct{}) []string {
|
||||
s := make([]string, len(m))
|
||||
i := 0
|
||||
for k := range m {
|
||||
s[i] = k
|
||||
i++
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (c *PubSub) _subscribe(
|
||||
ctx context.Context, cn *pool.Conn, redisCmd string, channels []string,
|
||||
) error {
|
||||
args := make([]interface{}, 0, 1+len(channels))
|
||||
args = append(args, redisCmd)
|
||||
for _, channel := range channels {
|
||||
args = append(args, channel)
|
||||
}
|
||||
cmd := NewSliceCmd(ctx, args...)
|
||||
return c.writeCmd(ctx, cn, cmd)
|
||||
}
|
||||
|
||||
func (c *PubSub) releaseConnWithLock(
|
||||
ctx context.Context,
|
||||
cn *pool.Conn,
|
||||
err error,
|
||||
allowTimeout bool,
|
||||
) {
|
||||
c.mu.Lock()
|
||||
c.releaseConn(ctx, cn, err, allowTimeout)
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
func (c *PubSub) releaseConn(ctx context.Context, cn *pool.Conn, err error, allowTimeout bool) {
|
||||
if c.cn != cn {
|
||||
return
|
||||
}
|
||||
if isBadConn(err, allowTimeout, c.opt.Addr) {
|
||||
c.reconnect(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *PubSub) reconnect(ctx context.Context, reason error) {
|
||||
_ = c.closeTheCn(reason)
|
||||
_, _ = c.conn(ctx, nil)
|
||||
}
|
||||
|
||||
func (c *PubSub) closeTheCn(reason error) error {
|
||||
if c.cn == nil {
|
||||
return nil
|
||||
}
|
||||
if !c.closed {
|
||||
internal.Logger.Printf(c.getContext(), "redis: discarding bad PubSub connection: %s", reason)
|
||||
}
|
||||
err := c.closeConn(c.cn)
|
||||
c.cn = nil
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *PubSub) Close() error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if c.closed {
|
||||
return pool.ErrClosed
|
||||
}
|
||||
c.closed = true
|
||||
close(c.exit)
|
||||
|
||||
return c.closeTheCn(pool.ErrClosed)
|
||||
}
|
||||
|
||||
// Subscribe the client to the specified channels. It returns
|
||||
// empty subscription if there are no channels.
|
||||
func (c *PubSub) Subscribe(ctx context.Context, channels ...string) error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
err := c.subscribe(ctx, "subscribe", channels...)
|
||||
if c.channels == nil {
|
||||
c.channels = make(map[string]struct{})
|
||||
}
|
||||
for _, s := range channels {
|
||||
c.channels[s] = struct{}{}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// PSubscribe the client to the given patterns. It returns
|
||||
// empty subscription if there are no patterns.
|
||||
func (c *PubSub) PSubscribe(ctx context.Context, patterns ...string) error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
err := c.subscribe(ctx, "psubscribe", patterns...)
|
||||
if c.patterns == nil {
|
||||
c.patterns = make(map[string]struct{})
|
||||
}
|
||||
for _, s := range patterns {
|
||||
c.patterns[s] = struct{}{}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// SSubscribe Subscribes the client to the specified shard channels.
|
||||
func (c *PubSub) SSubscribe(ctx context.Context, channels ...string) error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
err := c.subscribe(ctx, "ssubscribe", channels...)
|
||||
if c.schannels == nil {
|
||||
c.schannels = make(map[string]struct{})
|
||||
}
|
||||
for _, s := range channels {
|
||||
c.schannels[s] = struct{}{}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Unsubscribe the client from the given channels, or from all of
|
||||
// them if none is given.
|
||||
func (c *PubSub) Unsubscribe(ctx context.Context, channels ...string) error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if len(channels) > 0 {
|
||||
for _, channel := range channels {
|
||||
delete(c.channels, channel)
|
||||
}
|
||||
} else {
|
||||
// Unsubscribe from all channels.
|
||||
for channel := range c.channels {
|
||||
delete(c.channels, channel)
|
||||
}
|
||||
}
|
||||
|
||||
err := c.subscribe(ctx, "unsubscribe", channels...)
|
||||
return err
|
||||
}
|
||||
|
||||
// PUnsubscribe the client from the given patterns, or from all of
|
||||
// them if none is given.
|
||||
func (c *PubSub) PUnsubscribe(ctx context.Context, patterns ...string) error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if len(patterns) > 0 {
|
||||
for _, pattern := range patterns {
|
||||
delete(c.patterns, pattern)
|
||||
}
|
||||
} else {
|
||||
// Unsubscribe from all patterns.
|
||||
for pattern := range c.patterns {
|
||||
delete(c.patterns, pattern)
|
||||
}
|
||||
}
|
||||
|
||||
err := c.subscribe(ctx, "punsubscribe", patterns...)
|
||||
return err
|
||||
}
|
||||
|
||||
// SUnsubscribe unsubscribes the client from the given shard channels,
|
||||
// or from all of them if none is given.
|
||||
func (c *PubSub) SUnsubscribe(ctx context.Context, channels ...string) error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if len(channels) > 0 {
|
||||
for _, channel := range channels {
|
||||
delete(c.schannels, channel)
|
||||
}
|
||||
} else {
|
||||
// Unsubscribe from all channels.
|
||||
for channel := range c.schannels {
|
||||
delete(c.schannels, channel)
|
||||
}
|
||||
}
|
||||
|
||||
err := c.subscribe(ctx, "sunsubscribe", channels...)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *PubSub) subscribe(ctx context.Context, redisCmd string, channels ...string) error {
|
||||
cn, err := c.conn(ctx, channels)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = c._subscribe(ctx, cn, redisCmd, channels)
|
||||
c.releaseConn(ctx, cn, err, false)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *PubSub) Ping(ctx context.Context, payload ...string) error {
|
||||
args := []interface{}{"ping"}
|
||||
if len(payload) == 1 {
|
||||
args = append(args, payload[0])
|
||||
}
|
||||
cmd := NewCmd(ctx, args...)
|
||||
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
cn, err := c.conn(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = c.writeCmd(ctx, cn, cmd)
|
||||
c.releaseConn(ctx, cn, err, false)
|
||||
return err
|
||||
}
|
||||
|
||||
// Subscription received after a successful subscription to channel.
|
||||
type Subscription struct {
|
||||
// Can be "subscribe", "unsubscribe", "psubscribe" or "punsubscribe".
|
||||
Kind string
|
||||
// Channel name we have subscribed to.
|
||||
Channel string
|
||||
// Number of channels we are currently subscribed to.
|
||||
Count int
|
||||
}
|
||||
|
||||
func (m *Subscription) String() string {
|
||||
return fmt.Sprintf("%s: %s", m.Kind, m.Channel)
|
||||
}
|
||||
|
||||
// Message received as result of a PUBLISH command issued by another client.
|
||||
type Message struct {
|
||||
Channel string
|
||||
Pattern string
|
||||
Payload string
|
||||
PayloadSlice []string
|
||||
}
|
||||
|
||||
func (m *Message) String() string {
|
||||
return fmt.Sprintf("Message<%s: %s>", m.Channel, m.Payload)
|
||||
}
|
||||
|
||||
// Pong received as result of a PING command issued by another client.
|
||||
type Pong struct {
|
||||
Payload string
|
||||
}
|
||||
|
||||
func (p *Pong) String() string {
|
||||
if p.Payload != "" {
|
||||
return fmt.Sprintf("Pong<%s>", p.Payload)
|
||||
}
|
||||
return "Pong"
|
||||
}
|
||||
|
||||
func (c *PubSub) newMessage(reply interface{}) (interface{}, error) {
|
||||
switch reply := reply.(type) {
|
||||
case string:
|
||||
return &Pong{
|
||||
Payload: reply,
|
||||
}, nil
|
||||
case []interface{}:
|
||||
switch kind := reply[0].(string); kind {
|
||||
case "subscribe", "unsubscribe", "psubscribe", "punsubscribe", "ssubscribe", "sunsubscribe":
|
||||
// Can be nil in case of "unsubscribe".
|
||||
channel, _ := reply[1].(string)
|
||||
return &Subscription{
|
||||
Kind: kind,
|
||||
Channel: channel,
|
||||
Count: int(reply[2].(int64)),
|
||||
}, nil
|
||||
case "message", "smessage":
|
||||
switch payload := reply[2].(type) {
|
||||
case string:
|
||||
return &Message{
|
||||
Channel: reply[1].(string),
|
||||
Payload: payload,
|
||||
}, nil
|
||||
case []interface{}:
|
||||
ss := make([]string, len(payload))
|
||||
for i, s := range payload {
|
||||
ss[i] = s.(string)
|
||||
}
|
||||
return &Message{
|
||||
Channel: reply[1].(string),
|
||||
PayloadSlice: ss,
|
||||
}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("redis: unsupported pubsub message payload: %T", payload)
|
||||
}
|
||||
case "pmessage":
|
||||
return &Message{
|
||||
Pattern: reply[1].(string),
|
||||
Channel: reply[2].(string),
|
||||
Payload: reply[3].(string),
|
||||
}, nil
|
||||
case "pong":
|
||||
return &Pong{
|
||||
Payload: reply[1].(string),
|
||||
}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("redis: unsupported pubsub message: %q", kind)
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("redis: unsupported pubsub message: %#v", reply)
|
||||
}
|
||||
}
|
||||
|
||||
// ReceiveTimeout acts like Receive but returns an error if message
|
||||
// is not received in time. This is low-level API and in most cases
|
||||
// Channel should be used instead.
|
||||
func (c *PubSub) ReceiveTimeout(ctx context.Context, timeout time.Duration) (interface{}, error) {
|
||||
if c.cmd == nil {
|
||||
c.cmd = NewCmd(ctx)
|
||||
}
|
||||
|
||||
// Don't hold the lock to allow subscriptions and pings.
|
||||
|
||||
cn, err := c.connWithLock(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = cn.WithReader(ctx, timeout, func(rd *proto.Reader) error {
|
||||
return c.cmd.readReply(rd)
|
||||
})
|
||||
|
||||
c.releaseConnWithLock(ctx, cn, err, timeout > 0)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c.newMessage(c.cmd.Val())
|
||||
}
|
||||
|
||||
// Receive returns a message as a Subscription, Message, Pong or error.
|
||||
// See PubSub example for details. This is low-level API and in most cases
|
||||
// Channel should be used instead.
|
||||
func (c *PubSub) Receive(ctx context.Context) (interface{}, error) {
|
||||
return c.ReceiveTimeout(ctx, 0)
|
||||
}
|
||||
|
||||
// ReceiveMessage returns a Message or error ignoring Subscription and Pong
|
||||
// messages. This is low-level API and in most cases Channel should be used
|
||||
// instead.
|
||||
func (c *PubSub) ReceiveMessage(ctx context.Context) (*Message, error) {
|
||||
for {
|
||||
msg, err := c.Receive(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case *Subscription:
|
||||
// Ignore.
|
||||
case *Pong:
|
||||
// Ignore.
|
||||
case *Message:
|
||||
return msg, nil
|
||||
default:
|
||||
err := fmt.Errorf("redis: unknown message: %T", msg)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *PubSub) getContext() context.Context {
|
||||
if c.cmd != nil {
|
||||
return c.cmd.ctx
|
||||
}
|
||||
return context.Background()
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// Channel returns a Go channel for concurrently receiving messages.
|
||||
// The channel is closed together with the PubSub. If the Go channel
|
||||
// is blocked full for 1 minute the message is dropped.
|
||||
// Receive* APIs can not be used after channel is created.
|
||||
//
|
||||
// go-redis periodically sends ping messages to test connection health
|
||||
// and re-subscribes if ping can not received for 1 minute.
|
||||
func (c *PubSub) Channel(opts ...ChannelOption) <-chan *Message {
|
||||
c.chOnce.Do(func() {
|
||||
c.msgCh = newChannel(c, opts...)
|
||||
c.msgCh.initMsgChan()
|
||||
})
|
||||
if c.msgCh == nil {
|
||||
err := fmt.Errorf("redis: Channel can't be called after ChannelWithSubscriptions")
|
||||
panic(err)
|
||||
}
|
||||
return c.msgCh.msgCh
|
||||
}
|
||||
|
||||
// ChannelSize is like Channel, but creates a Go channel
|
||||
// with specified buffer size.
|
||||
//
|
||||
// Deprecated: use Channel(WithChannelSize(size)), remove in v9.
|
||||
func (c *PubSub) ChannelSize(size int) <-chan *Message {
|
||||
return c.Channel(WithChannelSize(size))
|
||||
}
|
||||
|
||||
// ChannelWithSubscriptions is like Channel, but message type can be either
|
||||
// *Subscription or *Message. Subscription messages can be used to detect
|
||||
// reconnections.
|
||||
//
|
||||
// ChannelWithSubscriptions can not be used together with Channel or ChannelSize.
|
||||
func (c *PubSub) ChannelWithSubscriptions(opts ...ChannelOption) <-chan interface{} {
|
||||
c.chOnce.Do(func() {
|
||||
c.allCh = newChannel(c, opts...)
|
||||
c.allCh.initAllChan()
|
||||
})
|
||||
if c.allCh == nil {
|
||||
err := fmt.Errorf("redis: ChannelWithSubscriptions can't be called after Channel")
|
||||
panic(err)
|
||||
}
|
||||
return c.allCh.allCh
|
||||
}
|
||||
|
||||
type ChannelOption func(c *channel)
|
||||
|
||||
// WithChannelSize specifies the Go chan size that is used to buffer incoming messages.
|
||||
//
|
||||
// The default is 100 messages.
|
||||
func WithChannelSize(size int) ChannelOption {
|
||||
return func(c *channel) {
|
||||
c.chanSize = size
|
||||
}
|
||||
}
|
||||
|
||||
// WithChannelHealthCheckInterval specifies the health check interval.
|
||||
// PubSub will ping Redis Server if it does not receive any messages within the interval.
|
||||
// To disable health check, use zero interval.
|
||||
//
|
||||
// The default is 3 seconds.
|
||||
func WithChannelHealthCheckInterval(d time.Duration) ChannelOption {
|
||||
return func(c *channel) {
|
||||
c.checkInterval = d
|
||||
}
|
||||
}
|
||||
|
||||
// WithChannelSendTimeout specifies the channel send timeout after which
|
||||
// the message is dropped.
|
||||
//
|
||||
// The default is 60 seconds.
|
||||
func WithChannelSendTimeout(d time.Duration) ChannelOption {
|
||||
return func(c *channel) {
|
||||
c.chanSendTimeout = d
|
||||
}
|
||||
}
|
||||
|
||||
type channel struct {
|
||||
pubSub *PubSub
|
||||
|
||||
msgCh chan *Message
|
||||
allCh chan interface{}
|
||||
ping chan struct{}
|
||||
|
||||
chanSize int
|
||||
chanSendTimeout time.Duration
|
||||
checkInterval time.Duration
|
||||
}
|
||||
|
||||
func newChannel(pubSub *PubSub, opts ...ChannelOption) *channel {
|
||||
c := &channel{
|
||||
pubSub: pubSub,
|
||||
|
||||
chanSize: 100,
|
||||
chanSendTimeout: time.Minute,
|
||||
checkInterval: 3 * time.Second,
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(c)
|
||||
}
|
||||
if c.checkInterval > 0 {
|
||||
c.initHealthCheck()
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *channel) initHealthCheck() {
|
||||
ctx := context.TODO()
|
||||
c.ping = make(chan struct{}, 1)
|
||||
|
||||
go func() {
|
||||
timer := time.NewTimer(time.Minute)
|
||||
timer.Stop()
|
||||
|
||||
for {
|
||||
timer.Reset(c.checkInterval)
|
||||
select {
|
||||
case <-c.ping:
|
||||
if !timer.Stop() {
|
||||
<-timer.C
|
||||
}
|
||||
case <-timer.C:
|
||||
if pingErr := c.pubSub.Ping(ctx); pingErr != nil {
|
||||
c.pubSub.mu.Lock()
|
||||
c.pubSub.reconnect(ctx, pingErr)
|
||||
c.pubSub.mu.Unlock()
|
||||
}
|
||||
case <-c.pubSub.exit:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// initMsgChan must be in sync with initAllChan.
|
||||
func (c *channel) initMsgChan() {
|
||||
ctx := context.TODO()
|
||||
c.msgCh = make(chan *Message, c.chanSize)
|
||||
|
||||
go func() {
|
||||
timer := time.NewTimer(time.Minute)
|
||||
timer.Stop()
|
||||
|
||||
var errCount int
|
||||
for {
|
||||
msg, err := c.pubSub.Receive(ctx)
|
||||
if err != nil {
|
||||
if err == pool.ErrClosed {
|
||||
close(c.msgCh)
|
||||
return
|
||||
}
|
||||
if errCount > 0 {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
errCount++
|
||||
continue
|
||||
}
|
||||
|
||||
errCount = 0
|
||||
|
||||
// Any message is as good as a ping.
|
||||
select {
|
||||
case c.ping <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case *Subscription:
|
||||
// Ignore.
|
||||
case *Pong:
|
||||
// Ignore.
|
||||
case *Message:
|
||||
timer.Reset(c.chanSendTimeout)
|
||||
select {
|
||||
case c.msgCh <- msg:
|
||||
if !timer.Stop() {
|
||||
<-timer.C
|
||||
}
|
||||
case <-timer.C:
|
||||
internal.Logger.Printf(
|
||||
ctx, "redis: %s channel is full for %s (message is dropped)",
|
||||
c, c.chanSendTimeout)
|
||||
}
|
||||
default:
|
||||
internal.Logger.Printf(ctx, "redis: unknown message type: %T", msg)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// initAllChan must be in sync with initMsgChan.
|
||||
func (c *channel) initAllChan() {
|
||||
ctx := context.TODO()
|
||||
c.allCh = make(chan interface{}, c.chanSize)
|
||||
|
||||
go func() {
|
||||
timer := time.NewTimer(time.Minute)
|
||||
timer.Stop()
|
||||
|
||||
var errCount int
|
||||
for {
|
||||
msg, err := c.pubSub.Receive(ctx)
|
||||
if err != nil {
|
||||
if err == pool.ErrClosed {
|
||||
close(c.allCh)
|
||||
return
|
||||
}
|
||||
if errCount > 0 {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
errCount++
|
||||
continue
|
||||
}
|
||||
|
||||
errCount = 0
|
||||
|
||||
// Any message is as good as a ping.
|
||||
select {
|
||||
case c.ping <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case *Pong:
|
||||
// Ignore.
|
||||
case *Subscription, *Message:
|
||||
timer.Reset(c.chanSendTimeout)
|
||||
select {
|
||||
case c.allCh <- msg:
|
||||
if !timer.Stop() {
|
||||
<-timer.C
|
||||
}
|
||||
case <-timer.C:
|
||||
internal.Logger.Printf(
|
||||
ctx, "redis: %s channel is full for %s (message is dropped)",
|
||||
c, c.chanSendTimeout)
|
||||
}
|
||||
default:
|
||||
internal.Logger.Printf(ctx, "redis: unknown message type: %T", msg)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
76
vendor/github.com/redis/go-redis/v9/pubsub_commands.go
generated
vendored
Normal file
76
vendor/github.com/redis/go-redis/v9/pubsub_commands.go
generated
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
package redis
|
||||
|
||||
import "context"
|
||||
|
||||
type PubSubCmdable interface {
|
||||
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
|
||||
}
|
||||
|
||||
// Publish posts the message to the channel.
|
||||
func (c cmdable) Publish(ctx context.Context, channel string, message interface{}) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "publish", channel, message)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) SPublish(ctx context.Context, channel string, message interface{}) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "spublish", channel, message)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) PubSubChannels(ctx context.Context, pattern string) *StringSliceCmd {
|
||||
args := []interface{}{"pubsub", "channels"}
|
||||
if pattern != "*" {
|
||||
args = append(args, pattern)
|
||||
}
|
||||
cmd := NewStringSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) PubSubNumSub(ctx context.Context, channels ...string) *MapStringIntCmd {
|
||||
args := make([]interface{}, 2+len(channels))
|
||||
args[0] = "pubsub"
|
||||
args[1] = "numsub"
|
||||
for i, channel := range channels {
|
||||
args[2+i] = channel
|
||||
}
|
||||
cmd := NewMapStringIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) PubSubShardChannels(ctx context.Context, pattern string) *StringSliceCmd {
|
||||
args := []interface{}{"pubsub", "shardchannels"}
|
||||
if pattern != "*" {
|
||||
args = append(args, pattern)
|
||||
}
|
||||
cmd := NewStringSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) PubSubShardNumSub(ctx context.Context, channels ...string) *MapStringIntCmd {
|
||||
args := make([]interface{}, 2+len(channels))
|
||||
args[0] = "pubsub"
|
||||
args[1] = "shardnumsub"
|
||||
for i, channel := range channels {
|
||||
args[2+i] = channel
|
||||
}
|
||||
cmd := NewMapStringIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) PubSubNumPat(ctx context.Context) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "pubsub", "numpat")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
963
vendor/github.com/redis/go-redis/v9/redis.go
generated
vendored
Normal file
963
vendor/github.com/redis/go-redis/v9/redis.go
generated
vendored
Normal file
@@ -0,0 +1,963 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/redis/go-redis/v9/auth"
|
||||
"github.com/redis/go-redis/v9/internal"
|
||||
"github.com/redis/go-redis/v9/internal/hscan"
|
||||
"github.com/redis/go-redis/v9/internal/pool"
|
||||
"github.com/redis/go-redis/v9/internal/proto"
|
||||
)
|
||||
|
||||
// Scanner internal/hscan.Scanner exposed interface.
|
||||
type Scanner = hscan.Scanner
|
||||
|
||||
// Nil reply returned by Redis when key does not exist.
|
||||
const Nil = proto.Nil
|
||||
|
||||
// SetLogger set custom log
|
||||
func SetLogger(logger internal.Logging) {
|
||||
internal.Logger = logger
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
type Hook interface {
|
||||
DialHook(next DialHook) DialHook
|
||||
ProcessHook(next ProcessHook) ProcessHook
|
||||
ProcessPipelineHook(next ProcessPipelineHook) ProcessPipelineHook
|
||||
}
|
||||
|
||||
type (
|
||||
DialHook func(ctx context.Context, network, addr string) (net.Conn, error)
|
||||
ProcessHook func(ctx context.Context, cmd Cmder) error
|
||||
ProcessPipelineHook func(ctx context.Context, cmds []Cmder) error
|
||||
)
|
||||
|
||||
type hooksMixin struct {
|
||||
hooksMu *sync.RWMutex
|
||||
|
||||
slice []Hook
|
||||
initial hooks
|
||||
current hooks
|
||||
}
|
||||
|
||||
func (hs *hooksMixin) initHooks(hooks hooks) {
|
||||
hs.hooksMu = new(sync.RWMutex)
|
||||
hs.initial = hooks
|
||||
hs.chain()
|
||||
}
|
||||
|
||||
type hooks struct {
|
||||
dial DialHook
|
||||
process ProcessHook
|
||||
pipeline ProcessPipelineHook
|
||||
txPipeline ProcessPipelineHook
|
||||
}
|
||||
|
||||
func (h *hooks) setDefaults() {
|
||||
if h.dial == nil {
|
||||
h.dial = func(ctx context.Context, network, addr string) (net.Conn, error) { return nil, nil }
|
||||
}
|
||||
if h.process == nil {
|
||||
h.process = func(ctx context.Context, cmd Cmder) error { return nil }
|
||||
}
|
||||
if h.pipeline == nil {
|
||||
h.pipeline = func(ctx context.Context, cmds []Cmder) error { return nil }
|
||||
}
|
||||
if h.txPipeline == nil {
|
||||
h.txPipeline = func(ctx context.Context, cmds []Cmder) error { return nil }
|
||||
}
|
||||
}
|
||||
|
||||
// AddHook is to add a hook to the queue.
|
||||
// Hook is a function executed during network connection, command execution, and pipeline,
|
||||
// it is a first-in-first-out stack queue (FIFO).
|
||||
// You need to execute the next hook in each hook, unless you want to terminate the execution of the command.
|
||||
// For example, you added hook-1, hook-2:
|
||||
//
|
||||
// client.AddHook(hook-1, hook-2)
|
||||
//
|
||||
// hook-1:
|
||||
//
|
||||
// func (Hook1) ProcessHook(next redis.ProcessHook) redis.ProcessHook {
|
||||
// return func(ctx context.Context, cmd Cmder) error {
|
||||
// print("hook-1 start")
|
||||
// next(ctx, cmd)
|
||||
// print("hook-1 end")
|
||||
// return nil
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// hook-2:
|
||||
//
|
||||
// func (Hook2) ProcessHook(next redis.ProcessHook) redis.ProcessHook {
|
||||
// return func(ctx context.Context, cmd redis.Cmder) error {
|
||||
// print("hook-2 start")
|
||||
// next(ctx, cmd)
|
||||
// print("hook-2 end")
|
||||
// return nil
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// The execution sequence is:
|
||||
//
|
||||
// hook-1 start -> hook-2 start -> exec redis cmd -> hook-2 end -> hook-1 end
|
||||
//
|
||||
// Please note: "next(ctx, cmd)" is very important, it will call the next hook,
|
||||
// if "next(ctx, cmd)" is not executed, the redis command will not be executed.
|
||||
func (hs *hooksMixin) AddHook(hook Hook) {
|
||||
hs.slice = append(hs.slice, hook)
|
||||
hs.chain()
|
||||
}
|
||||
|
||||
func (hs *hooksMixin) chain() {
|
||||
hs.initial.setDefaults()
|
||||
|
||||
hs.hooksMu.Lock()
|
||||
defer hs.hooksMu.Unlock()
|
||||
|
||||
hs.current.dial = hs.initial.dial
|
||||
hs.current.process = hs.initial.process
|
||||
hs.current.pipeline = hs.initial.pipeline
|
||||
hs.current.txPipeline = hs.initial.txPipeline
|
||||
|
||||
for i := len(hs.slice) - 1; i >= 0; i-- {
|
||||
if wrapped := hs.slice[i].DialHook(hs.current.dial); wrapped != nil {
|
||||
hs.current.dial = wrapped
|
||||
}
|
||||
if wrapped := hs.slice[i].ProcessHook(hs.current.process); wrapped != nil {
|
||||
hs.current.process = wrapped
|
||||
}
|
||||
if wrapped := hs.slice[i].ProcessPipelineHook(hs.current.pipeline); wrapped != nil {
|
||||
hs.current.pipeline = wrapped
|
||||
}
|
||||
if wrapped := hs.slice[i].ProcessPipelineHook(hs.current.txPipeline); wrapped != nil {
|
||||
hs.current.txPipeline = wrapped
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (hs *hooksMixin) clone() hooksMixin {
|
||||
hs.hooksMu.Lock()
|
||||
defer hs.hooksMu.Unlock()
|
||||
|
||||
clone := *hs
|
||||
l := len(clone.slice)
|
||||
clone.slice = clone.slice[:l:l]
|
||||
clone.hooksMu = new(sync.RWMutex)
|
||||
return clone
|
||||
}
|
||||
|
||||
func (hs *hooksMixin) withProcessHook(ctx context.Context, cmd Cmder, hook ProcessHook) error {
|
||||
for i := len(hs.slice) - 1; i >= 0; i-- {
|
||||
if wrapped := hs.slice[i].ProcessHook(hook); wrapped != nil {
|
||||
hook = wrapped
|
||||
}
|
||||
}
|
||||
return hook(ctx, cmd)
|
||||
}
|
||||
|
||||
func (hs *hooksMixin) withProcessPipelineHook(
|
||||
ctx context.Context, cmds []Cmder, hook ProcessPipelineHook,
|
||||
) error {
|
||||
for i := len(hs.slice) - 1; i >= 0; i-- {
|
||||
if wrapped := hs.slice[i].ProcessPipelineHook(hook); wrapped != nil {
|
||||
hook = wrapped
|
||||
}
|
||||
}
|
||||
return hook(ctx, cmds)
|
||||
}
|
||||
|
||||
func (hs *hooksMixin) dialHook(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
// Access to hs.current is guarded by a read-only lock since it may be mutated by AddHook(...)
|
||||
// while this dialer is concurrently accessed by the background connection pool population
|
||||
// routine when MinIdleConns > 0.
|
||||
hs.hooksMu.RLock()
|
||||
current := hs.current
|
||||
hs.hooksMu.RUnlock()
|
||||
|
||||
return current.dial(ctx, network, addr)
|
||||
}
|
||||
|
||||
func (hs *hooksMixin) processHook(ctx context.Context, cmd Cmder) error {
|
||||
return hs.current.process(ctx, cmd)
|
||||
}
|
||||
|
||||
func (hs *hooksMixin) processPipelineHook(ctx context.Context, cmds []Cmder) error {
|
||||
return hs.current.pipeline(ctx, cmds)
|
||||
}
|
||||
|
||||
func (hs *hooksMixin) processTxPipelineHook(ctx context.Context, cmds []Cmder) error {
|
||||
return hs.current.txPipeline(ctx, cmds)
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
type baseClient struct {
|
||||
opt *Options
|
||||
connPool pool.Pooler
|
||||
hooksMixin
|
||||
|
||||
onClose func() error // hook called when client is closed
|
||||
}
|
||||
|
||||
func (c *baseClient) clone() *baseClient {
|
||||
clone := *c
|
||||
return &clone
|
||||
}
|
||||
|
||||
func (c *baseClient) withTimeout(timeout time.Duration) *baseClient {
|
||||
opt := c.opt.clone()
|
||||
opt.ReadTimeout = timeout
|
||||
opt.WriteTimeout = timeout
|
||||
|
||||
clone := c.clone()
|
||||
clone.opt = opt
|
||||
|
||||
return clone
|
||||
}
|
||||
|
||||
func (c *baseClient) String() string {
|
||||
return fmt.Sprintf("Redis<%s db:%d>", c.getAddr(), c.opt.DB)
|
||||
}
|
||||
|
||||
func (c *baseClient) newConn(ctx context.Context) (*pool.Conn, error) {
|
||||
cn, err := c.connPool.NewConn(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = c.initConn(ctx, cn)
|
||||
if err != nil {
|
||||
_ = c.connPool.CloseConn(cn)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cn, nil
|
||||
}
|
||||
|
||||
func (c *baseClient) getConn(ctx context.Context) (*pool.Conn, error) {
|
||||
if c.opt.Limiter != nil {
|
||||
err := c.opt.Limiter.Allow()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
cn, err := c._getConn(ctx)
|
||||
if err != nil {
|
||||
if c.opt.Limiter != nil {
|
||||
c.opt.Limiter.ReportResult(err)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cn, nil
|
||||
}
|
||||
|
||||
func (c *baseClient) _getConn(ctx context.Context) (*pool.Conn, error) {
|
||||
cn, err := c.connPool.Get(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if cn.Inited {
|
||||
return cn, nil
|
||||
}
|
||||
|
||||
if err := c.initConn(ctx, cn); err != nil {
|
||||
c.connPool.Remove(ctx, cn, err)
|
||||
if err := errors.Unwrap(err); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cn, nil
|
||||
}
|
||||
|
||||
func (c *baseClient) newReAuthCredentialsListener(poolCn *pool.Conn) auth.CredentialsListener {
|
||||
return auth.NewReAuthCredentialsListener(
|
||||
c.reAuthConnection(poolCn),
|
||||
c.onAuthenticationErr(poolCn),
|
||||
)
|
||||
}
|
||||
|
||||
func (c *baseClient) reAuthConnection(poolCn *pool.Conn) func(credentials auth.Credentials) error {
|
||||
return func(credentials auth.Credentials) error {
|
||||
var err error
|
||||
username, password := credentials.BasicAuth()
|
||||
ctx := context.Background()
|
||||
connPool := pool.NewSingleConnPool(c.connPool, poolCn)
|
||||
// hooksMixin are intentionally empty here
|
||||
cn := newConn(c.opt, connPool, nil)
|
||||
|
||||
if username != "" {
|
||||
err = cn.AuthACL(ctx, username, password).Err()
|
||||
} else {
|
||||
err = cn.Auth(ctx, password).Err()
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
func (c *baseClient) onAuthenticationErr(poolCn *pool.Conn) func(err error) {
|
||||
return func(err error) {
|
||||
if err != nil {
|
||||
if isBadConn(err, false, c.opt.Addr) {
|
||||
// Close the connection to force a reconnection.
|
||||
err := c.connPool.CloseConn(poolCn)
|
||||
if err != nil {
|
||||
internal.Logger.Printf(context.Background(), "redis: failed to close connection: %v", err)
|
||||
// try to close the network connection directly
|
||||
// so that no resource is leaked
|
||||
err := poolCn.Close()
|
||||
if err != nil {
|
||||
internal.Logger.Printf(context.Background(), "redis: failed to close network connection: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
internal.Logger.Printf(context.Background(), "redis: re-authentication failed: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *baseClient) wrappedOnClose(newOnClose func() error) func() error {
|
||||
onClose := c.onClose
|
||||
return func() error {
|
||||
var firstErr error
|
||||
err := newOnClose()
|
||||
// Even if we have an error we would like to execute the onClose hook
|
||||
// if it exists. We will return the first error that occurred.
|
||||
// This is to keep error handling consistent with the rest of the code.
|
||||
if err != nil {
|
||||
firstErr = err
|
||||
}
|
||||
if onClose != nil {
|
||||
err = onClose()
|
||||
if err != nil && firstErr == nil {
|
||||
firstErr = err
|
||||
}
|
||||
}
|
||||
return firstErr
|
||||
}
|
||||
}
|
||||
|
||||
func (c *baseClient) initConn(ctx context.Context, cn *pool.Conn) error {
|
||||
if cn.Inited {
|
||||
return nil
|
||||
}
|
||||
|
||||
var err error
|
||||
cn.Inited = true
|
||||
connPool := pool.NewSingleConnPool(c.connPool, cn)
|
||||
conn := newConn(c.opt, connPool, &c.hooksMixin)
|
||||
|
||||
username, password := "", ""
|
||||
if c.opt.StreamingCredentialsProvider != nil {
|
||||
credentials, unsubscribeFromCredentialsProvider, err := c.opt.StreamingCredentialsProvider.
|
||||
Subscribe(c.newReAuthCredentialsListener(cn))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to subscribe to streaming credentials: %w", err)
|
||||
}
|
||||
c.onClose = c.wrappedOnClose(unsubscribeFromCredentialsProvider)
|
||||
cn.SetOnClose(unsubscribeFromCredentialsProvider)
|
||||
username, password = credentials.BasicAuth()
|
||||
} else if c.opt.CredentialsProviderContext != nil {
|
||||
username, password, err = c.opt.CredentialsProviderContext(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get credentials from context provider: %w", err)
|
||||
}
|
||||
} else if c.opt.CredentialsProvider != nil {
|
||||
username, password = c.opt.CredentialsProvider()
|
||||
} else if c.opt.Username != "" || c.opt.Password != "" {
|
||||
username, password = c.opt.Username, c.opt.Password
|
||||
}
|
||||
|
||||
// for redis-server versions that do not support the HELLO command,
|
||||
// RESP2 will continue to be used.
|
||||
if err = conn.Hello(ctx, c.opt.Protocol, username, password, c.opt.ClientName).Err(); err == nil {
|
||||
// Authentication successful with HELLO command
|
||||
} else if !isRedisError(err) {
|
||||
// When the server responds with the RESP protocol and the result is not a normal
|
||||
// execution result of the HELLO command, we consider it to be an indication that
|
||||
// the server does not support the HELLO command.
|
||||
// The server may be a redis-server that does not support the HELLO command,
|
||||
// or it could be DragonflyDB or a third-party redis-proxy. They all respond
|
||||
// with different error string results for unsupported commands, making it
|
||||
// difficult to rely on error strings to determine all results.
|
||||
return err
|
||||
} else if password != "" {
|
||||
// Try legacy AUTH command if HELLO failed
|
||||
if username != "" {
|
||||
err = conn.AuthACL(ctx, username, password).Err()
|
||||
} else {
|
||||
err = conn.Auth(ctx, password).Err()
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to authenticate: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
_, err = conn.Pipelined(ctx, func(pipe Pipeliner) error {
|
||||
if c.opt.DB > 0 {
|
||||
pipe.Select(ctx, c.opt.DB)
|
||||
}
|
||||
|
||||
if c.opt.readOnly {
|
||||
pipe.ReadOnly(ctx)
|
||||
}
|
||||
|
||||
if c.opt.ClientName != "" {
|
||||
pipe.ClientSetName(ctx, c.opt.ClientName)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to initialize connection options: %w", err)
|
||||
}
|
||||
|
||||
if !c.opt.DisableIdentity && !c.opt.DisableIndentity {
|
||||
libName := ""
|
||||
libVer := Version()
|
||||
if c.opt.IdentitySuffix != "" {
|
||||
libName = c.opt.IdentitySuffix
|
||||
}
|
||||
p := conn.Pipeline()
|
||||
p.ClientSetInfo(ctx, WithLibraryName(libName))
|
||||
p.ClientSetInfo(ctx, WithLibraryVersion(libVer))
|
||||
// Handle network errors (e.g. timeouts) in CLIENT SETINFO to avoid
|
||||
// out of order responses later on.
|
||||
if _, err = p.Exec(ctx); err != nil && !isRedisError(err) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if c.opt.OnConnect != nil {
|
||||
return c.opt.OnConnect(ctx, conn)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *baseClient) releaseConn(ctx context.Context, cn *pool.Conn, err error) {
|
||||
if c.opt.Limiter != nil {
|
||||
c.opt.Limiter.ReportResult(err)
|
||||
}
|
||||
|
||||
if isBadConn(err, false, c.opt.Addr) {
|
||||
c.connPool.Remove(ctx, cn, err)
|
||||
} else {
|
||||
c.connPool.Put(ctx, cn)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *baseClient) withConn(
|
||||
ctx context.Context, fn func(context.Context, *pool.Conn) error,
|
||||
) error {
|
||||
cn, err := c.getConn(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var fnErr error
|
||||
defer func() {
|
||||
c.releaseConn(ctx, cn, fnErr)
|
||||
}()
|
||||
|
||||
fnErr = fn(ctx, cn)
|
||||
|
||||
return fnErr
|
||||
}
|
||||
|
||||
func (c *baseClient) dial(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
return c.opt.Dialer(ctx, network, addr)
|
||||
}
|
||||
|
||||
func (c *baseClient) process(ctx context.Context, cmd Cmder) error {
|
||||
var lastErr error
|
||||
for attempt := 0; attempt <= c.opt.MaxRetries; attempt++ {
|
||||
attempt := attempt
|
||||
|
||||
retry, err := c._process(ctx, cmd, attempt)
|
||||
if err == nil || !retry {
|
||||
return err
|
||||
}
|
||||
|
||||
lastErr = err
|
||||
}
|
||||
return lastErr
|
||||
}
|
||||
|
||||
func (c *baseClient) assertUnstableCommand(cmd Cmder) bool {
|
||||
switch cmd.(type) {
|
||||
case *AggregateCmd, *FTInfoCmd, *FTSpellCheckCmd, *FTSearchCmd, *FTSynDumpCmd:
|
||||
if c.opt.UnstableResp3 {
|
||||
return true
|
||||
} else {
|
||||
panic("RESP3 responses for this command are disabled because they may still change. Please set the flag UnstableResp3 . See the [README](https://github.com/redis/go-redis/blob/master/README.md) and the release notes for guidance.")
|
||||
}
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (c *baseClient) _process(ctx context.Context, cmd Cmder, attempt int) (bool, error) {
|
||||
if attempt > 0 {
|
||||
if err := internal.Sleep(ctx, c.retryBackoff(attempt)); err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
retryTimeout := uint32(0)
|
||||
if err := c.withConn(ctx, func(ctx context.Context, cn *pool.Conn) error {
|
||||
if err := cn.WithWriter(c.context(ctx), c.opt.WriteTimeout, func(wr *proto.Writer) error {
|
||||
return writeCmd(wr, cmd)
|
||||
}); err != nil {
|
||||
atomic.StoreUint32(&retryTimeout, 1)
|
||||
return err
|
||||
}
|
||||
readReplyFunc := cmd.readReply
|
||||
// Apply unstable RESP3 search module.
|
||||
if c.opt.Protocol != 2 && c.assertUnstableCommand(cmd) {
|
||||
readReplyFunc = cmd.readRawReply
|
||||
}
|
||||
if err := cn.WithReader(c.context(ctx), c.cmdTimeout(cmd), readReplyFunc); err != nil {
|
||||
if cmd.readTimeout() == nil {
|
||||
atomic.StoreUint32(&retryTimeout, 1)
|
||||
} else {
|
||||
atomic.StoreUint32(&retryTimeout, 0)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
retry := shouldRetry(err, atomic.LoadUint32(&retryTimeout) == 1)
|
||||
return retry, err
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (c *baseClient) retryBackoff(attempt int) time.Duration {
|
||||
return internal.RetryBackoff(attempt, c.opt.MinRetryBackoff, c.opt.MaxRetryBackoff)
|
||||
}
|
||||
|
||||
func (c *baseClient) cmdTimeout(cmd Cmder) time.Duration {
|
||||
if timeout := cmd.readTimeout(); timeout != nil {
|
||||
t := *timeout
|
||||
if t == 0 {
|
||||
return 0
|
||||
}
|
||||
return t + 10*time.Second
|
||||
}
|
||||
return c.opt.ReadTimeout
|
||||
}
|
||||
|
||||
// context returns the context for the current connection.
|
||||
// If the context timeout is enabled, it returns the original context.
|
||||
// Otherwise, it returns a new background context.
|
||||
func (c *baseClient) context(ctx context.Context) context.Context {
|
||||
if c.opt.ContextTimeoutEnabled {
|
||||
return ctx
|
||||
}
|
||||
return context.Background()
|
||||
}
|
||||
|
||||
// Close closes the client, releasing any open resources.
|
||||
//
|
||||
// It is rare to Close a Client, as the Client is meant to be
|
||||
// long-lived and shared between many goroutines.
|
||||
func (c *baseClient) Close() error {
|
||||
var firstErr error
|
||||
if c.onClose != nil {
|
||||
if err := c.onClose(); err != nil {
|
||||
firstErr = err
|
||||
}
|
||||
}
|
||||
if err := c.connPool.Close(); err != nil && firstErr == nil {
|
||||
firstErr = err
|
||||
}
|
||||
return firstErr
|
||||
}
|
||||
|
||||
func (c *baseClient) getAddr() string {
|
||||
return c.opt.Addr
|
||||
}
|
||||
|
||||
func (c *baseClient) processPipeline(ctx context.Context, cmds []Cmder) error {
|
||||
if err := c.generalProcessPipeline(ctx, cmds, c.pipelineProcessCmds); err != nil {
|
||||
return err
|
||||
}
|
||||
return cmdsFirstErr(cmds)
|
||||
}
|
||||
|
||||
func (c *baseClient) processTxPipeline(ctx context.Context, cmds []Cmder) error {
|
||||
if err := c.generalProcessPipeline(ctx, cmds, c.txPipelineProcessCmds); err != nil {
|
||||
return err
|
||||
}
|
||||
return cmdsFirstErr(cmds)
|
||||
}
|
||||
|
||||
type pipelineProcessor func(context.Context, *pool.Conn, []Cmder) (bool, error)
|
||||
|
||||
func (c *baseClient) generalProcessPipeline(
|
||||
ctx context.Context, cmds []Cmder, p pipelineProcessor,
|
||||
) error {
|
||||
var lastErr error
|
||||
for attempt := 0; attempt <= c.opt.MaxRetries; attempt++ {
|
||||
if attempt > 0 {
|
||||
if err := internal.Sleep(ctx, c.retryBackoff(attempt)); err != nil {
|
||||
setCmdsErr(cmds, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Enable retries by default to retry dial errors returned by withConn.
|
||||
canRetry := true
|
||||
lastErr = c.withConn(ctx, func(ctx context.Context, cn *pool.Conn) error {
|
||||
var err error
|
||||
canRetry, err = p(ctx, cn, cmds)
|
||||
return err
|
||||
})
|
||||
if lastErr == nil || !canRetry || !shouldRetry(lastErr, true) {
|
||||
return lastErr
|
||||
}
|
||||
}
|
||||
return lastErr
|
||||
}
|
||||
|
||||
func (c *baseClient) pipelineProcessCmds(
|
||||
ctx context.Context, cn *pool.Conn, cmds []Cmder,
|
||||
) (bool, error) {
|
||||
if err := cn.WithWriter(c.context(ctx), c.opt.WriteTimeout, func(wr *proto.Writer) error {
|
||||
return writeCmds(wr, cmds)
|
||||
}); err != nil {
|
||||
setCmdsErr(cmds, err)
|
||||
return true, err
|
||||
}
|
||||
|
||||
if err := cn.WithReader(c.context(ctx), c.opt.ReadTimeout, func(rd *proto.Reader) error {
|
||||
return pipelineReadCmds(rd, cmds)
|
||||
}); err != nil {
|
||||
return true, err
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func pipelineReadCmds(rd *proto.Reader, cmds []Cmder) error {
|
||||
for i, cmd := range cmds {
|
||||
err := cmd.readReply(rd)
|
||||
cmd.SetErr(err)
|
||||
if err != nil && !isRedisError(err) {
|
||||
setCmdsErr(cmds[i+1:], err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Retry errors like "LOADING redis is loading the dataset in memory".
|
||||
return cmds[0].Err()
|
||||
}
|
||||
|
||||
func (c *baseClient) txPipelineProcessCmds(
|
||||
ctx context.Context, cn *pool.Conn, cmds []Cmder,
|
||||
) (bool, error) {
|
||||
if err := cn.WithWriter(c.context(ctx), c.opt.WriteTimeout, func(wr *proto.Writer) error {
|
||||
return writeCmds(wr, cmds)
|
||||
}); err != nil {
|
||||
setCmdsErr(cmds, err)
|
||||
return true, err
|
||||
}
|
||||
|
||||
if err := cn.WithReader(c.context(ctx), c.opt.ReadTimeout, func(rd *proto.Reader) error {
|
||||
statusCmd := cmds[0].(*StatusCmd)
|
||||
// Trim multi and exec.
|
||||
trimmedCmds := cmds[1 : len(cmds)-1]
|
||||
|
||||
if err := txPipelineReadQueued(rd, statusCmd, trimmedCmds); err != nil {
|
||||
setCmdsErr(cmds, err)
|
||||
return err
|
||||
}
|
||||
|
||||
return pipelineReadCmds(rd, trimmedCmds)
|
||||
}); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func txPipelineReadQueued(rd *proto.Reader, statusCmd *StatusCmd, cmds []Cmder) error {
|
||||
// Parse +OK.
|
||||
if err := statusCmd.readReply(rd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Parse +QUEUED.
|
||||
for range cmds {
|
||||
if err := statusCmd.readReply(rd); err != nil && !isRedisError(err) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Parse number of replies.
|
||||
line, err := rd.ReadLine()
|
||||
if err != nil {
|
||||
if err == Nil {
|
||||
err = TxFailedErr
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if line[0] != proto.RespArray {
|
||||
return fmt.Errorf("redis: expected '*', but got line %q", line)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// Client is a Redis client representing a pool of zero or more underlying connections.
|
||||
// It's safe for concurrent use by multiple goroutines.
|
||||
//
|
||||
// Client creates and frees connections automatically; it also maintains a free pool
|
||||
// of idle connections. You can control the pool size with Config.PoolSize option.
|
||||
type Client struct {
|
||||
*baseClient
|
||||
cmdable
|
||||
}
|
||||
|
||||
// NewClient returns a client to the Redis Server specified by Options.
|
||||
func NewClient(opt *Options) *Client {
|
||||
if opt == nil {
|
||||
panic("redis: NewClient nil options")
|
||||
}
|
||||
opt.init()
|
||||
|
||||
c := Client{
|
||||
baseClient: &baseClient{
|
||||
opt: opt,
|
||||
},
|
||||
}
|
||||
c.init()
|
||||
c.connPool = newConnPool(opt, c.dialHook)
|
||||
|
||||
return &c
|
||||
}
|
||||
|
||||
func (c *Client) init() {
|
||||
c.cmdable = c.Process
|
||||
c.initHooks(hooks{
|
||||
dial: c.baseClient.dial,
|
||||
process: c.baseClient.process,
|
||||
pipeline: c.baseClient.processPipeline,
|
||||
txPipeline: c.baseClient.processTxPipeline,
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Client) WithTimeout(timeout time.Duration) *Client {
|
||||
clone := *c
|
||||
clone.baseClient = c.baseClient.withTimeout(timeout)
|
||||
clone.init()
|
||||
return &clone
|
||||
}
|
||||
|
||||
func (c *Client) Conn() *Conn {
|
||||
return newConn(c.opt, pool.NewStickyConnPool(c.connPool), &c.hooksMixin)
|
||||
}
|
||||
|
||||
func (c *Client) Process(ctx context.Context, cmd Cmder) error {
|
||||
err := c.processHook(ctx, cmd)
|
||||
cmd.SetErr(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Options returns read-only Options that were used to create the client.
|
||||
func (c *Client) Options() *Options {
|
||||
return c.opt
|
||||
}
|
||||
|
||||
type PoolStats pool.Stats
|
||||
|
||||
// PoolStats returns connection pool stats.
|
||||
func (c *Client) PoolStats() *PoolStats {
|
||||
stats := c.connPool.Stats()
|
||||
return (*PoolStats)(stats)
|
||||
}
|
||||
|
||||
func (c *Client) Pipelined(ctx context.Context, fn func(Pipeliner) error) ([]Cmder, error) {
|
||||
return c.Pipeline().Pipelined(ctx, fn)
|
||||
}
|
||||
|
||||
func (c *Client) Pipeline() Pipeliner {
|
||||
pipe := Pipeline{
|
||||
exec: pipelineExecer(c.processPipelineHook),
|
||||
}
|
||||
pipe.init()
|
||||
return &pipe
|
||||
}
|
||||
|
||||
func (c *Client) TxPipelined(ctx context.Context, fn func(Pipeliner) error) ([]Cmder, error) {
|
||||
return c.TxPipeline().Pipelined(ctx, fn)
|
||||
}
|
||||
|
||||
// TxPipeline acts like Pipeline, but wraps queued commands with MULTI/EXEC.
|
||||
func (c *Client) TxPipeline() Pipeliner {
|
||||
pipe := Pipeline{
|
||||
exec: func(ctx context.Context, cmds []Cmder) error {
|
||||
cmds = wrapMultiExec(ctx, cmds)
|
||||
return c.processTxPipelineHook(ctx, cmds)
|
||||
},
|
||||
}
|
||||
pipe.init()
|
||||
return &pipe
|
||||
}
|
||||
|
||||
func (c *Client) pubSub() *PubSub {
|
||||
pubsub := &PubSub{
|
||||
opt: c.opt,
|
||||
|
||||
newConn: func(ctx context.Context, channels []string) (*pool.Conn, error) {
|
||||
return c.newConn(ctx)
|
||||
},
|
||||
closeConn: c.connPool.CloseConn,
|
||||
}
|
||||
pubsub.init()
|
||||
return pubsub
|
||||
}
|
||||
|
||||
// Subscribe subscribes the client to the specified channels.
|
||||
// Channels can be omitted to create empty subscription.
|
||||
// Note that this method does not wait on a response from Redis, so the
|
||||
// subscription may not be active immediately. To force the connection to wait,
|
||||
// you may call the Receive() method on the returned *PubSub like so:
|
||||
//
|
||||
// sub := client.Subscribe(queryResp)
|
||||
// iface, err := sub.Receive()
|
||||
// if err != nil {
|
||||
// // handle error
|
||||
// }
|
||||
//
|
||||
// // Should be *Subscription, but others are possible if other actions have been
|
||||
// // taken on sub since it was created.
|
||||
// switch iface.(type) {
|
||||
// case *Subscription:
|
||||
// // subscribe succeeded
|
||||
// case *Message:
|
||||
// // received first message
|
||||
// case *Pong:
|
||||
// // pong received
|
||||
// default:
|
||||
// // handle error
|
||||
// }
|
||||
//
|
||||
// ch := sub.Channel()
|
||||
func (c *Client) Subscribe(ctx context.Context, channels ...string) *PubSub {
|
||||
pubsub := c.pubSub()
|
||||
if len(channels) > 0 {
|
||||
_ = pubsub.Subscribe(ctx, channels...)
|
||||
}
|
||||
return pubsub
|
||||
}
|
||||
|
||||
// PSubscribe subscribes the client to the given patterns.
|
||||
// Patterns can be omitted to create empty subscription.
|
||||
func (c *Client) PSubscribe(ctx context.Context, channels ...string) *PubSub {
|
||||
pubsub := c.pubSub()
|
||||
if len(channels) > 0 {
|
||||
_ = pubsub.PSubscribe(ctx, channels...)
|
||||
}
|
||||
return pubsub
|
||||
}
|
||||
|
||||
// SSubscribe Subscribes the client to the specified shard channels.
|
||||
// Channels can be omitted to create empty subscription.
|
||||
func (c *Client) SSubscribe(ctx context.Context, channels ...string) *PubSub {
|
||||
pubsub := c.pubSub()
|
||||
if len(channels) > 0 {
|
||||
_ = pubsub.SSubscribe(ctx, channels...)
|
||||
}
|
||||
return pubsub
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// Conn represents a single Redis connection rather than a pool of connections.
|
||||
// Prefer running commands from Client unless there is a specific need
|
||||
// for a continuous single Redis connection.
|
||||
type Conn struct {
|
||||
baseClient
|
||||
cmdable
|
||||
statefulCmdable
|
||||
}
|
||||
|
||||
// newConn is a helper func to create a new Conn instance.
|
||||
// the Conn instance is not thread-safe and should not be shared between goroutines.
|
||||
// the parentHooks will be cloned, no need to clone before passing it.
|
||||
func newConn(opt *Options, connPool pool.Pooler, parentHooks *hooksMixin) *Conn {
|
||||
c := Conn{
|
||||
baseClient: baseClient{
|
||||
opt: opt,
|
||||
connPool: connPool,
|
||||
},
|
||||
}
|
||||
|
||||
if parentHooks != nil {
|
||||
c.hooksMixin = parentHooks.clone()
|
||||
}
|
||||
|
||||
c.cmdable = c.Process
|
||||
c.statefulCmdable = c.Process
|
||||
c.initHooks(hooks{
|
||||
dial: c.baseClient.dial,
|
||||
process: c.baseClient.process,
|
||||
pipeline: c.baseClient.processPipeline,
|
||||
txPipeline: c.baseClient.processTxPipeline,
|
||||
})
|
||||
|
||||
return &c
|
||||
}
|
||||
|
||||
func (c *Conn) Process(ctx context.Context, cmd Cmder) error {
|
||||
err := c.processHook(ctx, cmd)
|
||||
cmd.SetErr(err)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Conn) Pipelined(ctx context.Context, fn func(Pipeliner) error) ([]Cmder, error) {
|
||||
return c.Pipeline().Pipelined(ctx, fn)
|
||||
}
|
||||
|
||||
func (c *Conn) Pipeline() Pipeliner {
|
||||
pipe := Pipeline{
|
||||
exec: c.processPipelineHook,
|
||||
}
|
||||
pipe.init()
|
||||
return &pipe
|
||||
}
|
||||
|
||||
func (c *Conn) TxPipelined(ctx context.Context, fn func(Pipeliner) error) ([]Cmder, error) {
|
||||
return c.TxPipeline().Pipelined(ctx, fn)
|
||||
}
|
||||
|
||||
// TxPipeline acts like Pipeline, but wraps queued commands with MULTI/EXEC.
|
||||
func (c *Conn) TxPipeline() Pipeliner {
|
||||
pipe := Pipeline{
|
||||
exec: func(ctx context.Context, cmds []Cmder) error {
|
||||
cmds = wrapMultiExec(ctx, cmds)
|
||||
return c.processTxPipelineHook(ctx, cmds)
|
||||
},
|
||||
}
|
||||
pipe.init()
|
||||
return &pipe
|
||||
}
|
||||
196
vendor/github.com/redis/go-redis/v9/result.go
generated
vendored
Normal file
196
vendor/github.com/redis/go-redis/v9/result.go
generated
vendored
Normal file
@@ -0,0 +1,196 @@
|
||||
package redis
|
||||
|
||||
import "time"
|
||||
|
||||
// NewCmdResult returns a Cmd initialised with val and err for testing.
|
||||
func NewCmdResult(val interface{}, err error) *Cmd {
|
||||
var cmd Cmd
|
||||
cmd.val = val
|
||||
cmd.SetErr(err)
|
||||
return &cmd
|
||||
}
|
||||
|
||||
// NewSliceResult returns a SliceCmd initialised with val and err for testing.
|
||||
func NewSliceResult(val []interface{}, err error) *SliceCmd {
|
||||
var cmd SliceCmd
|
||||
cmd.val = val
|
||||
cmd.SetErr(err)
|
||||
return &cmd
|
||||
}
|
||||
|
||||
// NewStatusResult returns a StatusCmd initialised with val and err for testing.
|
||||
func NewStatusResult(val string, err error) *StatusCmd {
|
||||
var cmd StatusCmd
|
||||
cmd.val = val
|
||||
cmd.SetErr(err)
|
||||
return &cmd
|
||||
}
|
||||
|
||||
// NewIntResult returns an IntCmd initialised with val and err for testing.
|
||||
func NewIntResult(val int64, err error) *IntCmd {
|
||||
var cmd IntCmd
|
||||
cmd.val = val
|
||||
cmd.SetErr(err)
|
||||
return &cmd
|
||||
}
|
||||
|
||||
// NewDurationResult returns a DurationCmd initialised with val and err for testing.
|
||||
func NewDurationResult(val time.Duration, err error) *DurationCmd {
|
||||
var cmd DurationCmd
|
||||
cmd.val = val
|
||||
cmd.SetErr(err)
|
||||
return &cmd
|
||||
}
|
||||
|
||||
// NewBoolResult returns a BoolCmd initialised with val and err for testing.
|
||||
func NewBoolResult(val bool, err error) *BoolCmd {
|
||||
var cmd BoolCmd
|
||||
cmd.val = val
|
||||
cmd.SetErr(err)
|
||||
return &cmd
|
||||
}
|
||||
|
||||
// NewStringResult returns a StringCmd initialised with val and err for testing.
|
||||
func NewStringResult(val string, err error) *StringCmd {
|
||||
var cmd StringCmd
|
||||
cmd.val = val
|
||||
cmd.SetErr(err)
|
||||
return &cmd
|
||||
}
|
||||
|
||||
// NewFloatResult returns a FloatCmd initialised with val and err for testing.
|
||||
func NewFloatResult(val float64, err error) *FloatCmd {
|
||||
var cmd FloatCmd
|
||||
cmd.val = val
|
||||
cmd.SetErr(err)
|
||||
return &cmd
|
||||
}
|
||||
|
||||
// NewStringSliceResult returns a StringSliceCmd initialised with val and err for testing.
|
||||
func NewStringSliceResult(val []string, err error) *StringSliceCmd {
|
||||
var cmd StringSliceCmd
|
||||
cmd.val = val
|
||||
cmd.SetErr(err)
|
||||
return &cmd
|
||||
}
|
||||
|
||||
// NewBoolSliceResult returns a BoolSliceCmd initialised with val and err for testing.
|
||||
func NewBoolSliceResult(val []bool, err error) *BoolSliceCmd {
|
||||
var cmd BoolSliceCmd
|
||||
cmd.val = val
|
||||
cmd.SetErr(err)
|
||||
return &cmd
|
||||
}
|
||||
|
||||
// NewFloatSliceResult returns a FloatSliceCmd initialised with val and err for testing.
|
||||
func NewFloatSliceResult(val []float64, err error) *FloatSliceCmd {
|
||||
var cmd FloatSliceCmd
|
||||
cmd.val = val
|
||||
cmd.SetErr(err)
|
||||
return &cmd
|
||||
}
|
||||
|
||||
// NewMapStringStringResult returns a MapStringStringCmd initialised with val and err for testing.
|
||||
func NewMapStringStringResult(val map[string]string, err error) *MapStringStringCmd {
|
||||
var cmd MapStringStringCmd
|
||||
cmd.val = val
|
||||
cmd.SetErr(err)
|
||||
return &cmd
|
||||
}
|
||||
|
||||
// NewMapStringIntCmdResult returns a MapStringIntCmd initialised with val and err for testing.
|
||||
func NewMapStringIntCmdResult(val map[string]int64, err error) *MapStringIntCmd {
|
||||
var cmd MapStringIntCmd
|
||||
cmd.val = val
|
||||
cmd.SetErr(err)
|
||||
return &cmd
|
||||
}
|
||||
|
||||
// NewTimeCmdResult returns a TimeCmd initialised with val and err for testing.
|
||||
func NewTimeCmdResult(val time.Time, err error) *TimeCmd {
|
||||
var cmd TimeCmd
|
||||
cmd.val = val
|
||||
cmd.SetErr(err)
|
||||
return &cmd
|
||||
}
|
||||
|
||||
// NewZSliceCmdResult returns a ZSliceCmd initialised with val and err for testing.
|
||||
func NewZSliceCmdResult(val []Z, err error) *ZSliceCmd {
|
||||
var cmd ZSliceCmd
|
||||
cmd.val = val
|
||||
cmd.SetErr(err)
|
||||
return &cmd
|
||||
}
|
||||
|
||||
// NewZWithKeyCmdResult returns a ZWithKeyCmd initialised with val and err for testing.
|
||||
func NewZWithKeyCmdResult(val *ZWithKey, err error) *ZWithKeyCmd {
|
||||
var cmd ZWithKeyCmd
|
||||
cmd.val = val
|
||||
cmd.SetErr(err)
|
||||
return &cmd
|
||||
}
|
||||
|
||||
// NewScanCmdResult returns a ScanCmd initialised with val and err for testing.
|
||||
func NewScanCmdResult(keys []string, cursor uint64, err error) *ScanCmd {
|
||||
var cmd ScanCmd
|
||||
cmd.page = keys
|
||||
cmd.cursor = cursor
|
||||
cmd.SetErr(err)
|
||||
return &cmd
|
||||
}
|
||||
|
||||
// NewClusterSlotsCmdResult returns a ClusterSlotsCmd initialised with val and err for testing.
|
||||
func NewClusterSlotsCmdResult(val []ClusterSlot, err error) *ClusterSlotsCmd {
|
||||
var cmd ClusterSlotsCmd
|
||||
cmd.val = val
|
||||
cmd.SetErr(err)
|
||||
return &cmd
|
||||
}
|
||||
|
||||
// NewGeoLocationCmdResult returns a GeoLocationCmd initialised with val and err for testing.
|
||||
func NewGeoLocationCmdResult(val []GeoLocation, err error) *GeoLocationCmd {
|
||||
var cmd GeoLocationCmd
|
||||
cmd.locations = val
|
||||
cmd.SetErr(err)
|
||||
return &cmd
|
||||
}
|
||||
|
||||
// NewGeoPosCmdResult returns a GeoPosCmd initialised with val and err for testing.
|
||||
func NewGeoPosCmdResult(val []*GeoPos, err error) *GeoPosCmd {
|
||||
var cmd GeoPosCmd
|
||||
cmd.val = val
|
||||
cmd.SetErr(err)
|
||||
return &cmd
|
||||
}
|
||||
|
||||
// NewCommandsInfoCmdResult returns a CommandsInfoCmd initialised with val and err for testing.
|
||||
func NewCommandsInfoCmdResult(val map[string]*CommandInfo, err error) *CommandsInfoCmd {
|
||||
var cmd CommandsInfoCmd
|
||||
cmd.val = val
|
||||
cmd.SetErr(err)
|
||||
return &cmd
|
||||
}
|
||||
|
||||
// NewXMessageSliceCmdResult returns a XMessageSliceCmd initialised with val and err for testing.
|
||||
func NewXMessageSliceCmdResult(val []XMessage, err error) *XMessageSliceCmd {
|
||||
var cmd XMessageSliceCmd
|
||||
cmd.val = val
|
||||
cmd.SetErr(err)
|
||||
return &cmd
|
||||
}
|
||||
|
||||
// NewXStreamSliceCmdResult returns a XStreamSliceCmd initialised with val and err for testing.
|
||||
func NewXStreamSliceCmdResult(val []XStream, err error) *XStreamSliceCmd {
|
||||
var cmd XStreamSliceCmd
|
||||
cmd.val = val
|
||||
cmd.SetErr(err)
|
||||
return &cmd
|
||||
}
|
||||
|
||||
// NewXPendingResult returns a XPendingCmd initialised with val and err for testing.
|
||||
func NewXPendingResult(val *XPending, err error) *XPendingCmd {
|
||||
var cmd XPendingCmd
|
||||
cmd.val = val
|
||||
cmd.SetErr(err)
|
||||
return &cmd
|
||||
}
|
||||
891
vendor/github.com/redis/go-redis/v9/ring.go
generated
vendored
Normal file
891
vendor/github.com/redis/go-redis/v9/ring.go
generated
vendored
Normal file
@@ -0,0 +1,891 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/cespare/xxhash/v2"
|
||||
"github.com/dgryski/go-rendezvous" //nolint
|
||||
"github.com/redis/go-redis/v9/auth"
|
||||
|
||||
"github.com/redis/go-redis/v9/internal"
|
||||
"github.com/redis/go-redis/v9/internal/hashtag"
|
||||
"github.com/redis/go-redis/v9/internal/pool"
|
||||
"github.com/redis/go-redis/v9/internal/rand"
|
||||
)
|
||||
|
||||
var errRingShardsDown = errors.New("redis: all ring shards are down")
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
type ConsistentHash interface {
|
||||
Get(string) string
|
||||
}
|
||||
|
||||
type rendezvousWrapper struct {
|
||||
*rendezvous.Rendezvous
|
||||
}
|
||||
|
||||
func (w rendezvousWrapper) Get(key string) string {
|
||||
return w.Lookup(key)
|
||||
}
|
||||
|
||||
func newRendezvous(shards []string) ConsistentHash {
|
||||
return rendezvousWrapper{rendezvous.New(shards, xxhash.Sum64String)}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// RingOptions are used to configure a ring client and should be
|
||||
// passed to NewRing.
|
||||
type RingOptions struct {
|
||||
// Map of name => host:port addresses of ring shards.
|
||||
Addrs map[string]string
|
||||
|
||||
// NewClient creates a shard client with provided options.
|
||||
NewClient func(opt *Options) *Client
|
||||
|
||||
// ClientName will execute the `CLIENT SETNAME ClientName` command for each conn.
|
||||
ClientName string
|
||||
|
||||
// Frequency of PING commands sent to check shards availability.
|
||||
// Shard is considered down after 3 subsequent failed checks.
|
||||
HeartbeatFrequency time.Duration
|
||||
|
||||
// NewConsistentHash returns a consistent hash that is used
|
||||
// to distribute keys across the shards.
|
||||
//
|
||||
// See https://medium.com/@dgryski/consistent-hashing-algorithmic-tradeoffs-ef6b8e2fcae8
|
||||
// for consistent hashing algorithmic tradeoffs.
|
||||
NewConsistentHash func(shards []string) ConsistentHash
|
||||
|
||||
// Following options are copied from Options struct.
|
||||
|
||||
Dialer func(ctx context.Context, network, addr string) (net.Conn, error)
|
||||
OnConnect func(ctx context.Context, cn *Conn) error
|
||||
|
||||
Protocol int
|
||||
Username string
|
||||
Password string
|
||||
// CredentialsProvider allows the username and password to be updated
|
||||
// before reconnecting. It should return the current username and password.
|
||||
CredentialsProvider func() (username string, password string)
|
||||
|
||||
// CredentialsProviderContext is an enhanced parameter of CredentialsProvider,
|
||||
// done to maintain API compatibility. In the future,
|
||||
// there might be a merge between CredentialsProviderContext and CredentialsProvider.
|
||||
// There will be a conflict between them; if CredentialsProviderContext exists, we will ignore CredentialsProvider.
|
||||
CredentialsProviderContext func(ctx context.Context) (username string, password string, err error)
|
||||
|
||||
// StreamingCredentialsProvider is used to retrieve the credentials
|
||||
// for the connection from an external source. Those credentials may change
|
||||
// during the connection lifetime. This is useful for managed identity
|
||||
// scenarios where the credentials are retrieved from an external source.
|
||||
//
|
||||
// Currently, this is a placeholder for the future implementation.
|
||||
StreamingCredentialsProvider auth.StreamingCredentialsProvider
|
||||
DB int
|
||||
|
||||
MaxRetries int
|
||||
MinRetryBackoff time.Duration
|
||||
MaxRetryBackoff time.Duration
|
||||
|
||||
DialTimeout time.Duration
|
||||
ReadTimeout time.Duration
|
||||
WriteTimeout time.Duration
|
||||
ContextTimeoutEnabled bool
|
||||
|
||||
// PoolFIFO uses FIFO mode for each node connection pool GET/PUT (default LIFO).
|
||||
PoolFIFO bool
|
||||
|
||||
PoolSize int
|
||||
PoolTimeout time.Duration
|
||||
MinIdleConns int
|
||||
MaxIdleConns int
|
||||
MaxActiveConns int
|
||||
ConnMaxIdleTime time.Duration
|
||||
ConnMaxLifetime time.Duration
|
||||
|
||||
TLSConfig *tls.Config
|
||||
Limiter Limiter
|
||||
|
||||
// DisableIndentity - Disable set-lib on connect.
|
||||
//
|
||||
// default: false
|
||||
//
|
||||
// Deprecated: Use DisableIdentity instead.
|
||||
DisableIndentity bool
|
||||
|
||||
// DisableIdentity is used to disable CLIENT SETINFO command on connect.
|
||||
//
|
||||
// default: false
|
||||
DisableIdentity bool
|
||||
IdentitySuffix string
|
||||
UnstableResp3 bool
|
||||
}
|
||||
|
||||
func (opt *RingOptions) init() {
|
||||
if opt.NewClient == nil {
|
||||
opt.NewClient = func(opt *Options) *Client {
|
||||
return NewClient(opt)
|
||||
}
|
||||
}
|
||||
|
||||
if opt.HeartbeatFrequency == 0 {
|
||||
opt.HeartbeatFrequency = 500 * time.Millisecond
|
||||
}
|
||||
|
||||
if opt.NewConsistentHash == nil {
|
||||
opt.NewConsistentHash = newRendezvous
|
||||
}
|
||||
|
||||
switch opt.MaxRetries {
|
||||
case -1:
|
||||
opt.MaxRetries = 0
|
||||
case 0:
|
||||
opt.MaxRetries = 3
|
||||
}
|
||||
switch opt.MinRetryBackoff {
|
||||
case -1:
|
||||
opt.MinRetryBackoff = 0
|
||||
case 0:
|
||||
opt.MinRetryBackoff = 8 * time.Millisecond
|
||||
}
|
||||
switch opt.MaxRetryBackoff {
|
||||
case -1:
|
||||
opt.MaxRetryBackoff = 0
|
||||
case 0:
|
||||
opt.MaxRetryBackoff = 512 * time.Millisecond
|
||||
}
|
||||
}
|
||||
|
||||
func (opt *RingOptions) clientOptions() *Options {
|
||||
return &Options{
|
||||
ClientName: opt.ClientName,
|
||||
Dialer: opt.Dialer,
|
||||
OnConnect: opt.OnConnect,
|
||||
|
||||
Protocol: opt.Protocol,
|
||||
Username: opt.Username,
|
||||
Password: opt.Password,
|
||||
CredentialsProvider: opt.CredentialsProvider,
|
||||
CredentialsProviderContext: opt.CredentialsProviderContext,
|
||||
StreamingCredentialsProvider: opt.StreamingCredentialsProvider,
|
||||
DB: opt.DB,
|
||||
|
||||
MaxRetries: -1,
|
||||
|
||||
DialTimeout: opt.DialTimeout,
|
||||
ReadTimeout: opt.ReadTimeout,
|
||||
WriteTimeout: opt.WriteTimeout,
|
||||
ContextTimeoutEnabled: opt.ContextTimeoutEnabled,
|
||||
|
||||
PoolFIFO: opt.PoolFIFO,
|
||||
PoolSize: opt.PoolSize,
|
||||
PoolTimeout: opt.PoolTimeout,
|
||||
MinIdleConns: opt.MinIdleConns,
|
||||
MaxIdleConns: opt.MaxIdleConns,
|
||||
MaxActiveConns: opt.MaxActiveConns,
|
||||
ConnMaxIdleTime: opt.ConnMaxIdleTime,
|
||||
ConnMaxLifetime: opt.ConnMaxLifetime,
|
||||
|
||||
TLSConfig: opt.TLSConfig,
|
||||
Limiter: opt.Limiter,
|
||||
|
||||
DisableIdentity: opt.DisableIdentity,
|
||||
DisableIndentity: opt.DisableIndentity,
|
||||
|
||||
IdentitySuffix: opt.IdentitySuffix,
|
||||
UnstableResp3: opt.UnstableResp3,
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
type ringShard struct {
|
||||
Client *Client
|
||||
down int32
|
||||
addr string
|
||||
}
|
||||
|
||||
func newRingShard(opt *RingOptions, addr string) *ringShard {
|
||||
clopt := opt.clientOptions()
|
||||
clopt.Addr = addr
|
||||
|
||||
return &ringShard{
|
||||
Client: opt.NewClient(clopt),
|
||||
addr: addr,
|
||||
}
|
||||
}
|
||||
|
||||
func (shard *ringShard) String() string {
|
||||
var state string
|
||||
if shard.IsUp() {
|
||||
state = "up"
|
||||
} else {
|
||||
state = "down"
|
||||
}
|
||||
return fmt.Sprintf("%s is %s", shard.Client, state)
|
||||
}
|
||||
|
||||
func (shard *ringShard) IsDown() bool {
|
||||
const threshold = 3
|
||||
return atomic.LoadInt32(&shard.down) >= threshold
|
||||
}
|
||||
|
||||
func (shard *ringShard) IsUp() bool {
|
||||
return !shard.IsDown()
|
||||
}
|
||||
|
||||
// Vote votes to set shard state and returns true if state was changed.
|
||||
func (shard *ringShard) Vote(up bool) bool {
|
||||
if up {
|
||||
changed := shard.IsDown()
|
||||
atomic.StoreInt32(&shard.down, 0)
|
||||
return changed
|
||||
}
|
||||
|
||||
if shard.IsDown() {
|
||||
return false
|
||||
}
|
||||
|
||||
atomic.AddInt32(&shard.down, 1)
|
||||
return shard.IsDown()
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
type ringSharding struct {
|
||||
opt *RingOptions
|
||||
|
||||
mu sync.RWMutex
|
||||
shards *ringShards
|
||||
closed bool
|
||||
hash ConsistentHash
|
||||
numShard int
|
||||
onNewNode []func(rdb *Client)
|
||||
|
||||
// ensures exclusive access to SetAddrs so there is no need
|
||||
// to hold mu for the duration of potentially long shard creation
|
||||
setAddrsMu sync.Mutex
|
||||
}
|
||||
|
||||
type ringShards struct {
|
||||
m map[string]*ringShard
|
||||
list []*ringShard
|
||||
}
|
||||
|
||||
func newRingSharding(opt *RingOptions) *ringSharding {
|
||||
c := &ringSharding{
|
||||
opt: opt,
|
||||
}
|
||||
c.SetAddrs(opt.Addrs)
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *ringSharding) OnNewNode(fn func(rdb *Client)) {
|
||||
c.mu.Lock()
|
||||
c.onNewNode = append(c.onNewNode, fn)
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
// SetAddrs replaces the shards in use, such that you can increase and
|
||||
// decrease number of shards, that you use. It will reuse shards that
|
||||
// existed before and close the ones that will not be used anymore.
|
||||
func (c *ringSharding) SetAddrs(addrs map[string]string) {
|
||||
c.setAddrsMu.Lock()
|
||||
defer c.setAddrsMu.Unlock()
|
||||
|
||||
cleanup := func(shards map[string]*ringShard) {
|
||||
for addr, shard := range shards {
|
||||
if err := shard.Client.Close(); err != nil {
|
||||
internal.Logger.Printf(context.Background(), "shard.Close %s failed: %s", addr, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c.mu.RLock()
|
||||
if c.closed {
|
||||
c.mu.RUnlock()
|
||||
return
|
||||
}
|
||||
existing := c.shards
|
||||
c.mu.RUnlock()
|
||||
|
||||
shards, created, unused := c.newRingShards(addrs, existing)
|
||||
|
||||
c.mu.Lock()
|
||||
if c.closed {
|
||||
cleanup(created)
|
||||
c.mu.Unlock()
|
||||
return
|
||||
}
|
||||
c.shards = shards
|
||||
c.rebalanceLocked()
|
||||
c.mu.Unlock()
|
||||
|
||||
cleanup(unused)
|
||||
}
|
||||
|
||||
func (c *ringSharding) newRingShards(
|
||||
addrs map[string]string, existing *ringShards,
|
||||
) (shards *ringShards, created, unused map[string]*ringShard) {
|
||||
shards = &ringShards{m: make(map[string]*ringShard, len(addrs))}
|
||||
created = make(map[string]*ringShard) // indexed by addr
|
||||
unused = make(map[string]*ringShard) // indexed by addr
|
||||
|
||||
if existing != nil {
|
||||
for _, shard := range existing.list {
|
||||
unused[shard.addr] = shard
|
||||
}
|
||||
}
|
||||
|
||||
for name, addr := range addrs {
|
||||
if shard, ok := unused[addr]; ok {
|
||||
shards.m[name] = shard
|
||||
delete(unused, addr)
|
||||
} else {
|
||||
shard := newRingShard(c.opt, addr)
|
||||
shards.m[name] = shard
|
||||
created[addr] = shard
|
||||
|
||||
for _, fn := range c.onNewNode {
|
||||
fn(shard.Client)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, shard := range shards.m {
|
||||
shards.list = append(shards.list, shard)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Warning: External exposure of `c.shards.list` may cause data races.
|
||||
// So keep internal or implement deep copy if exposed.
|
||||
func (c *ringSharding) List() []*ringShard {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
|
||||
if c.closed {
|
||||
return nil
|
||||
}
|
||||
return c.shards.list
|
||||
}
|
||||
|
||||
func (c *ringSharding) Hash(key string) string {
|
||||
key = hashtag.Key(key)
|
||||
|
||||
var hash string
|
||||
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
|
||||
if c.numShard > 0 {
|
||||
hash = c.hash.Get(key)
|
||||
}
|
||||
|
||||
return hash
|
||||
}
|
||||
|
||||
func (c *ringSharding) GetByKey(key string) (*ringShard, error) {
|
||||
key = hashtag.Key(key)
|
||||
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
|
||||
if c.closed {
|
||||
return nil, pool.ErrClosed
|
||||
}
|
||||
|
||||
if c.numShard == 0 {
|
||||
return nil, errRingShardsDown
|
||||
}
|
||||
|
||||
shardName := c.hash.Get(key)
|
||||
if shardName == "" {
|
||||
return nil, errRingShardsDown
|
||||
}
|
||||
return c.shards.m[shardName], nil
|
||||
}
|
||||
|
||||
func (c *ringSharding) GetByName(shardName string) (*ringShard, error) {
|
||||
if shardName == "" {
|
||||
return c.Random()
|
||||
}
|
||||
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
|
||||
shard, ok := c.shards.m[shardName]
|
||||
if !ok {
|
||||
return nil, errors.New("redis: the shard is not in the ring")
|
||||
}
|
||||
|
||||
return shard, nil
|
||||
}
|
||||
|
||||
func (c *ringSharding) Random() (*ringShard, error) {
|
||||
return c.GetByKey(strconv.Itoa(rand.Int()))
|
||||
}
|
||||
|
||||
// Heartbeat monitors state of each shard in the ring.
|
||||
func (c *ringSharding) Heartbeat(ctx context.Context, frequency time.Duration) {
|
||||
ticker := time.NewTicker(frequency)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
var rebalance bool
|
||||
|
||||
// note: `c.List()` return a shadow copy of `[]*ringShard`.
|
||||
for _, shard := range c.List() {
|
||||
err := shard.Client.Ping(ctx).Err()
|
||||
isUp := err == nil || err == pool.ErrPoolTimeout
|
||||
if shard.Vote(isUp) {
|
||||
internal.Logger.Printf(ctx, "ring shard state changed: %s", shard)
|
||||
rebalance = true
|
||||
}
|
||||
}
|
||||
|
||||
if rebalance {
|
||||
c.mu.Lock()
|
||||
c.rebalanceLocked()
|
||||
c.mu.Unlock()
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// rebalanceLocked removes dead shards from the Ring.
|
||||
// Requires c.mu locked.
|
||||
func (c *ringSharding) rebalanceLocked() {
|
||||
if c.closed {
|
||||
return
|
||||
}
|
||||
if c.shards == nil {
|
||||
return
|
||||
}
|
||||
|
||||
liveShards := make([]string, 0, len(c.shards.m))
|
||||
|
||||
for name, shard := range c.shards.m {
|
||||
if shard.IsUp() {
|
||||
liveShards = append(liveShards, name)
|
||||
}
|
||||
}
|
||||
|
||||
c.hash = c.opt.NewConsistentHash(liveShards)
|
||||
c.numShard = len(liveShards)
|
||||
}
|
||||
|
||||
func (c *ringSharding) Len() int {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
|
||||
return c.numShard
|
||||
}
|
||||
|
||||
func (c *ringSharding) Close() error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if c.closed {
|
||||
return nil
|
||||
}
|
||||
c.closed = true
|
||||
|
||||
var firstErr error
|
||||
|
||||
for _, shard := range c.shards.list {
|
||||
if err := shard.Client.Close(); err != nil && firstErr == nil {
|
||||
firstErr = err
|
||||
}
|
||||
}
|
||||
|
||||
c.hash = nil
|
||||
c.shards = nil
|
||||
c.numShard = 0
|
||||
|
||||
return firstErr
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// Ring is a Redis client that uses consistent hashing to distribute
|
||||
// keys across multiple Redis servers (shards). It's safe for
|
||||
// concurrent use by multiple goroutines.
|
||||
//
|
||||
// Ring monitors the state of each shard and removes dead shards from
|
||||
// the ring. When a shard comes online it is added back to the ring. This
|
||||
// gives you maximum availability and partition tolerance, but no
|
||||
// consistency between different shards or even clients. Each client
|
||||
// uses shards that are available to the client and does not do any
|
||||
// coordination when shard state is changed.
|
||||
//
|
||||
// Ring should be used when you need multiple Redis servers for caching
|
||||
// and can tolerate losing data when one of the servers dies.
|
||||
// Otherwise you should use Redis Cluster.
|
||||
type Ring struct {
|
||||
cmdable
|
||||
hooksMixin
|
||||
|
||||
opt *RingOptions
|
||||
sharding *ringSharding
|
||||
cmdsInfoCache *cmdsInfoCache
|
||||
heartbeatCancelFn context.CancelFunc
|
||||
}
|
||||
|
||||
func NewRing(opt *RingOptions) *Ring {
|
||||
if opt == nil {
|
||||
panic("redis: NewRing nil options")
|
||||
}
|
||||
opt.init()
|
||||
|
||||
hbCtx, hbCancel := context.WithCancel(context.Background())
|
||||
|
||||
ring := Ring{
|
||||
opt: opt,
|
||||
sharding: newRingSharding(opt),
|
||||
heartbeatCancelFn: hbCancel,
|
||||
}
|
||||
|
||||
ring.cmdsInfoCache = newCmdsInfoCache(ring.cmdsInfo)
|
||||
ring.cmdable = ring.Process
|
||||
|
||||
ring.initHooks(hooks{
|
||||
process: ring.process,
|
||||
pipeline: func(ctx context.Context, cmds []Cmder) error {
|
||||
return ring.generalProcessPipeline(ctx, cmds, false)
|
||||
},
|
||||
txPipeline: func(ctx context.Context, cmds []Cmder) error {
|
||||
return ring.generalProcessPipeline(ctx, cmds, true)
|
||||
},
|
||||
})
|
||||
|
||||
go ring.sharding.Heartbeat(hbCtx, opt.HeartbeatFrequency)
|
||||
|
||||
return &ring
|
||||
}
|
||||
|
||||
func (c *Ring) SetAddrs(addrs map[string]string) {
|
||||
c.sharding.SetAddrs(addrs)
|
||||
}
|
||||
|
||||
func (c *Ring) Process(ctx context.Context, cmd Cmder) error {
|
||||
err := c.processHook(ctx, cmd)
|
||||
cmd.SetErr(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Options returns read-only Options that were used to create the client.
|
||||
func (c *Ring) Options() *RingOptions {
|
||||
return c.opt
|
||||
}
|
||||
|
||||
func (c *Ring) retryBackoff(attempt int) time.Duration {
|
||||
return internal.RetryBackoff(attempt, c.opt.MinRetryBackoff, c.opt.MaxRetryBackoff)
|
||||
}
|
||||
|
||||
// PoolStats returns accumulated connection pool stats.
|
||||
func (c *Ring) PoolStats() *PoolStats {
|
||||
// note: `c.List()` return a shadow copy of `[]*ringShard`.
|
||||
shards := c.sharding.List()
|
||||
var acc PoolStats
|
||||
for _, shard := range shards {
|
||||
s := shard.Client.connPool.Stats()
|
||||
acc.Hits += s.Hits
|
||||
acc.Misses += s.Misses
|
||||
acc.Timeouts += s.Timeouts
|
||||
acc.TotalConns += s.TotalConns
|
||||
acc.IdleConns += s.IdleConns
|
||||
}
|
||||
return &acc
|
||||
}
|
||||
|
||||
// Len returns the current number of shards in the ring.
|
||||
func (c *Ring) Len() int {
|
||||
return c.sharding.Len()
|
||||
}
|
||||
|
||||
// Subscribe subscribes the client to the specified channels.
|
||||
func (c *Ring) Subscribe(ctx context.Context, channels ...string) *PubSub {
|
||||
if len(channels) == 0 {
|
||||
panic("at least one channel is required")
|
||||
}
|
||||
|
||||
shard, err := c.sharding.GetByKey(channels[0])
|
||||
if err != nil {
|
||||
// TODO: return PubSub with sticky error
|
||||
panic(err)
|
||||
}
|
||||
return shard.Client.Subscribe(ctx, channels...)
|
||||
}
|
||||
|
||||
// PSubscribe subscribes the client to the given patterns.
|
||||
func (c *Ring) PSubscribe(ctx context.Context, channels ...string) *PubSub {
|
||||
if len(channels) == 0 {
|
||||
panic("at least one channel is required")
|
||||
}
|
||||
|
||||
shard, err := c.sharding.GetByKey(channels[0])
|
||||
if err != nil {
|
||||
// TODO: return PubSub with sticky error
|
||||
panic(err)
|
||||
}
|
||||
return shard.Client.PSubscribe(ctx, channels...)
|
||||
}
|
||||
|
||||
// SSubscribe Subscribes the client to the specified shard channels.
|
||||
func (c *Ring) SSubscribe(ctx context.Context, channels ...string) *PubSub {
|
||||
if len(channels) == 0 {
|
||||
panic("at least one channel is required")
|
||||
}
|
||||
shard, err := c.sharding.GetByKey(channels[0])
|
||||
if err != nil {
|
||||
// TODO: return PubSub with sticky error
|
||||
panic(err)
|
||||
}
|
||||
return shard.Client.SSubscribe(ctx, channels...)
|
||||
}
|
||||
|
||||
func (c *Ring) OnNewNode(fn func(rdb *Client)) {
|
||||
c.sharding.OnNewNode(fn)
|
||||
}
|
||||
|
||||
// ForEachShard concurrently calls the fn on each live shard in the ring.
|
||||
// It returns the first error if any.
|
||||
func (c *Ring) ForEachShard(
|
||||
ctx context.Context,
|
||||
fn func(ctx context.Context, client *Client) error,
|
||||
) error {
|
||||
// note: `c.List()` return a shadow copy of `[]*ringShard`.
|
||||
shards := c.sharding.List()
|
||||
var wg sync.WaitGroup
|
||||
errCh := make(chan error, 1)
|
||||
for _, shard := range shards {
|
||||
if shard.IsDown() {
|
||||
continue
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
go func(shard *ringShard) {
|
||||
defer wg.Done()
|
||||
err := fn(ctx, shard.Client)
|
||||
if err != nil {
|
||||
select {
|
||||
case errCh <- err:
|
||||
default:
|
||||
}
|
||||
}
|
||||
}(shard)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
select {
|
||||
case err := <-errCh:
|
||||
return err
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Ring) cmdsInfo(ctx context.Context) (map[string]*CommandInfo, error) {
|
||||
// note: `c.List()` return a shadow copy of `[]*ringShard`.
|
||||
shards := c.sharding.List()
|
||||
var firstErr error
|
||||
for _, shard := range shards {
|
||||
cmdsInfo, err := shard.Client.Command(ctx).Result()
|
||||
if err == nil {
|
||||
return cmdsInfo, nil
|
||||
}
|
||||
if firstErr == nil {
|
||||
firstErr = err
|
||||
}
|
||||
}
|
||||
if firstErr == nil {
|
||||
return nil, errRingShardsDown
|
||||
}
|
||||
return nil, firstErr
|
||||
}
|
||||
|
||||
func (c *Ring) cmdShard(cmd Cmder) (*ringShard, error) {
|
||||
pos := cmdFirstKeyPos(cmd)
|
||||
if pos == 0 {
|
||||
return c.sharding.Random()
|
||||
}
|
||||
firstKey := cmd.stringArg(pos)
|
||||
return c.sharding.GetByKey(firstKey)
|
||||
}
|
||||
|
||||
func (c *Ring) process(ctx context.Context, cmd Cmder) error {
|
||||
var lastErr error
|
||||
for attempt := 0; attempt <= c.opt.MaxRetries; attempt++ {
|
||||
if attempt > 0 {
|
||||
if err := internal.Sleep(ctx, c.retryBackoff(attempt)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
shard, err := c.cmdShard(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
lastErr = shard.Client.Process(ctx, cmd)
|
||||
if lastErr == nil || !shouldRetry(lastErr, cmd.readTimeout() == nil) {
|
||||
return lastErr
|
||||
}
|
||||
}
|
||||
return lastErr
|
||||
}
|
||||
|
||||
func (c *Ring) Pipelined(ctx context.Context, fn func(Pipeliner) error) ([]Cmder, error) {
|
||||
return c.Pipeline().Pipelined(ctx, fn)
|
||||
}
|
||||
|
||||
func (c *Ring) Pipeline() Pipeliner {
|
||||
pipe := Pipeline{
|
||||
exec: pipelineExecer(c.processPipelineHook),
|
||||
}
|
||||
pipe.init()
|
||||
return &pipe
|
||||
}
|
||||
|
||||
func (c *Ring) TxPipelined(ctx context.Context, fn func(Pipeliner) error) ([]Cmder, error) {
|
||||
return c.TxPipeline().Pipelined(ctx, fn)
|
||||
}
|
||||
|
||||
func (c *Ring) TxPipeline() Pipeliner {
|
||||
pipe := Pipeline{
|
||||
exec: func(ctx context.Context, cmds []Cmder) error {
|
||||
cmds = wrapMultiExec(ctx, cmds)
|
||||
return c.processTxPipelineHook(ctx, cmds)
|
||||
},
|
||||
}
|
||||
pipe.init()
|
||||
return &pipe
|
||||
}
|
||||
|
||||
func (c *Ring) generalProcessPipeline(
|
||||
ctx context.Context, cmds []Cmder, tx bool,
|
||||
) error {
|
||||
if tx {
|
||||
// Trim multi .. exec.
|
||||
cmds = cmds[1 : len(cmds)-1]
|
||||
}
|
||||
|
||||
cmdsMap := make(map[string][]Cmder)
|
||||
|
||||
for _, cmd := range cmds {
|
||||
hash := cmd.stringArg(cmdFirstKeyPos(cmd))
|
||||
if hash != "" {
|
||||
hash = c.sharding.Hash(hash)
|
||||
}
|
||||
cmdsMap[hash] = append(cmdsMap[hash], cmd)
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for hash, cmds := range cmdsMap {
|
||||
wg.Add(1)
|
||||
go func(hash string, cmds []Cmder) {
|
||||
defer wg.Done()
|
||||
|
||||
// TODO: retry?
|
||||
shard, err := c.sharding.GetByName(hash)
|
||||
if err != nil {
|
||||
setCmdsErr(cmds, err)
|
||||
return
|
||||
}
|
||||
|
||||
if tx {
|
||||
cmds = wrapMultiExec(ctx, cmds)
|
||||
_ = shard.Client.processTxPipelineHook(ctx, cmds)
|
||||
} else {
|
||||
_ = shard.Client.processPipelineHook(ctx, cmds)
|
||||
}
|
||||
}(hash, cmds)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
return cmdsFirstErr(cmds)
|
||||
}
|
||||
|
||||
func (c *Ring) Watch(ctx context.Context, fn func(*Tx) error, keys ...string) error {
|
||||
if len(keys) == 0 {
|
||||
return fmt.Errorf("redis: Watch requires at least one key")
|
||||
}
|
||||
|
||||
var shards []*ringShard
|
||||
|
||||
for _, key := range keys {
|
||||
if key != "" {
|
||||
shard, err := c.sharding.GetByKey(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
shards = append(shards, shard)
|
||||
}
|
||||
}
|
||||
|
||||
if len(shards) == 0 {
|
||||
return fmt.Errorf("redis: Watch requires at least one shard")
|
||||
}
|
||||
|
||||
if len(shards) > 1 {
|
||||
for _, shard := range shards[1:] {
|
||||
if shard.Client != shards[0].Client {
|
||||
err := fmt.Errorf("redis: Watch requires all keys to be in the same shard")
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return shards[0].Client.Watch(ctx, fn, keys...)
|
||||
}
|
||||
|
||||
// Close closes the ring client, releasing any open resources.
|
||||
//
|
||||
// It is rare to Close a Ring, as the Ring is meant to be long-lived
|
||||
// and shared between many goroutines.
|
||||
func (c *Ring) Close() error {
|
||||
c.heartbeatCancelFn()
|
||||
|
||||
return c.sharding.Close()
|
||||
}
|
||||
|
||||
// GetShardClients returns a list of all shard clients in the ring.
|
||||
// This can be used to create dedicated connections (e.g., PubSub) for each shard.
|
||||
func (c *Ring) GetShardClients() []*Client {
|
||||
shards := c.sharding.List()
|
||||
clients := make([]*Client, 0, len(shards))
|
||||
for _, shard := range shards {
|
||||
if shard.IsUp() {
|
||||
clients = append(clients, shard.Client)
|
||||
}
|
||||
}
|
||||
return clients
|
||||
}
|
||||
|
||||
// GetShardClientForKey returns the shard client that would handle the given key.
|
||||
// This can be used to determine which shard a particular key/channel would be routed to.
|
||||
func (c *Ring) GetShardClientForKey(key string) (*Client, error) {
|
||||
shard, err := c.sharding.GetByKey(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return shard.Client, nil
|
||||
}
|
||||
84
vendor/github.com/redis/go-redis/v9/script.go
generated
vendored
Normal file
84
vendor/github.com/redis/go-redis/v9/script.go
generated
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
)
|
||||
|
||||
type Scripter interface {
|
||||
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
|
||||
ScriptLoad(ctx context.Context, script string) *StringCmd
|
||||
}
|
||||
|
||||
var (
|
||||
_ Scripter = (*Client)(nil)
|
||||
_ Scripter = (*Ring)(nil)
|
||||
_ Scripter = (*ClusterClient)(nil)
|
||||
)
|
||||
|
||||
type Script struct {
|
||||
src, hash string
|
||||
}
|
||||
|
||||
func NewScript(src string) *Script {
|
||||
h := sha1.New()
|
||||
_, _ = io.WriteString(h, src)
|
||||
return &Script{
|
||||
src: src,
|
||||
hash: hex.EncodeToString(h.Sum(nil)),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Script) Hash() string {
|
||||
return s.hash
|
||||
}
|
||||
|
||||
func (s *Script) Load(ctx context.Context, c Scripter) *StringCmd {
|
||||
return c.ScriptLoad(ctx, s.src)
|
||||
}
|
||||
|
||||
func (s *Script) Exists(ctx context.Context, c Scripter) *BoolSliceCmd {
|
||||
return c.ScriptExists(ctx, s.hash)
|
||||
}
|
||||
|
||||
func (s *Script) Eval(ctx context.Context, c Scripter, keys []string, args ...interface{}) *Cmd {
|
||||
return c.Eval(ctx, s.src, keys, args...)
|
||||
}
|
||||
|
||||
func (s *Script) EvalRO(ctx context.Context, c Scripter, keys []string, args ...interface{}) *Cmd {
|
||||
return c.EvalRO(ctx, s.src, keys, args...)
|
||||
}
|
||||
|
||||
func (s *Script) EvalSha(ctx context.Context, c Scripter, keys []string, args ...interface{}) *Cmd {
|
||||
return c.EvalSha(ctx, s.hash, keys, args...)
|
||||
}
|
||||
|
||||
func (s *Script) EvalShaRO(ctx context.Context, c Scripter, keys []string, args ...interface{}) *Cmd {
|
||||
return c.EvalShaRO(ctx, s.hash, keys, args...)
|
||||
}
|
||||
|
||||
// Run optimistically uses EVALSHA to run the script. If script does not exist
|
||||
// it is retried using EVAL.
|
||||
func (s *Script) Run(ctx context.Context, c Scripter, keys []string, args ...interface{}) *Cmd {
|
||||
r := s.EvalSha(ctx, c, keys, args...)
|
||||
if HasErrorPrefix(r.Err(), "NOSCRIPT") {
|
||||
return s.Eval(ctx, c, keys, args...)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// RunRO optimistically uses EVALSHA_RO to run the script. If script does not exist
|
||||
// it is retried using EVAL_RO.
|
||||
func (s *Script) RunRO(ctx context.Context, c Scripter, keys []string, args ...interface{}) *Cmd {
|
||||
r := s.EvalShaRO(ctx, c, keys, args...)
|
||||
if HasErrorPrefix(r.Err(), "NOSCRIPT") {
|
||||
return s.EvalRO(ctx, c, keys, args...)
|
||||
}
|
||||
return r
|
||||
}
|
||||
215
vendor/github.com/redis/go-redis/v9/scripting_commands.go
generated
vendored
Normal file
215
vendor/github.com/redis/go-redis/v9/scripting_commands.go
generated
vendored
Normal file
@@ -0,0 +1,215 @@
|
||||
package redis
|
||||
|
||||
import "context"
|
||||
|
||||
type ScriptingFunctionsCmdable interface {
|
||||
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
|
||||
}
|
||||
|
||||
func (c cmdable) Eval(ctx context.Context, script string, keys []string, args ...interface{}) *Cmd {
|
||||
return c.eval(ctx, "eval", script, keys, args...)
|
||||
}
|
||||
|
||||
func (c cmdable) EvalRO(ctx context.Context, script string, keys []string, args ...interface{}) *Cmd {
|
||||
return c.eval(ctx, "eval_ro", script, keys, args...)
|
||||
}
|
||||
|
||||
func (c cmdable) EvalSha(ctx context.Context, sha1 string, keys []string, args ...interface{}) *Cmd {
|
||||
return c.eval(ctx, "evalsha", sha1, keys, args...)
|
||||
}
|
||||
|
||||
func (c cmdable) EvalShaRO(ctx context.Context, sha1 string, keys []string, args ...interface{}) *Cmd {
|
||||
return c.eval(ctx, "evalsha_ro", sha1, keys, args...)
|
||||
}
|
||||
|
||||
func (c cmdable) eval(ctx context.Context, name, payload string, keys []string, args ...interface{}) *Cmd {
|
||||
cmdArgs := make([]interface{}, 3+len(keys), 3+len(keys)+len(args))
|
||||
cmdArgs[0] = name
|
||||
cmdArgs[1] = payload
|
||||
cmdArgs[2] = len(keys)
|
||||
for i, key := range keys {
|
||||
cmdArgs[3+i] = key
|
||||
}
|
||||
cmdArgs = appendArgs(cmdArgs, args)
|
||||
cmd := NewCmd(ctx, cmdArgs...)
|
||||
|
||||
// it is possible that only args exist without a key.
|
||||
// rdb.eval(ctx, eval, script, nil, arg1, arg2)
|
||||
if len(keys) > 0 {
|
||||
cmd.SetFirstKeyPos(3)
|
||||
}
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) 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
|
||||
}
|
||||
cmd := NewBoolSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ScriptFlush(ctx context.Context) *StatusCmd {
|
||||
cmd := NewStatusCmd(ctx, "script", "flush")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ScriptKill(ctx context.Context) *StatusCmd {
|
||||
cmd := NewStatusCmd(ctx, "script", "kill")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ScriptLoad(ctx context.Context, script string) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "script", "load", script)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------
|
||||
|
||||
// FunctionListQuery is used with FunctionList to query for Redis libraries
|
||||
//
|
||||
// LibraryNamePattern - Use an empty string to get all libraries.
|
||||
// - Use a glob-style pattern to match multiple libraries with a matching name
|
||||
// - Use a library's full name to match a single library
|
||||
// WithCode - If true, it will return the code of the library
|
||||
type FunctionListQuery struct {
|
||||
LibraryNamePattern string
|
||||
WithCode bool
|
||||
}
|
||||
|
||||
func (c cmdable) FunctionLoad(ctx context.Context, code string) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "function", "load", code)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) FunctionLoadReplace(ctx context.Context, code string) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "function", "load", "replace", code)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) FunctionDelete(ctx context.Context, libName string) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "function", "delete", libName)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) FunctionFlush(ctx context.Context) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "function", "flush")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) FunctionKill(ctx context.Context) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "function", "kill")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) FunctionFlushAsync(ctx context.Context) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "function", "flush", "async")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) FunctionList(ctx context.Context, q FunctionListQuery) *FunctionListCmd {
|
||||
args := make([]interface{}, 2, 5)
|
||||
args[0] = "function"
|
||||
args[1] = "list"
|
||||
if q.LibraryNamePattern != "" {
|
||||
args = append(args, "libraryname", q.LibraryNamePattern)
|
||||
}
|
||||
if q.WithCode {
|
||||
args = append(args, "withcode")
|
||||
}
|
||||
cmd := NewFunctionListCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) FunctionDump(ctx context.Context) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "function", "dump")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) FunctionRestore(ctx context.Context, libDump string) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "function", "restore", libDump)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) FunctionStats(ctx context.Context) *FunctionStatsCmd {
|
||||
cmd := NewFunctionStatsCmd(ctx, "function", "stats")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) FCall(ctx context.Context, function string, keys []string, args ...interface{}) *Cmd {
|
||||
cmdArgs := fcallArgs("fcall", function, keys, args...)
|
||||
cmd := NewCmd(ctx, cmdArgs...)
|
||||
if len(keys) > 0 {
|
||||
cmd.SetFirstKeyPos(3)
|
||||
}
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// FCallRo this function simply calls FCallRO,
|
||||
// Deprecated: to maintain convention FCallRO.
|
||||
func (c cmdable) FCallRo(ctx context.Context, function string, keys []string, args ...interface{}) *Cmd {
|
||||
return c.FCallRO(ctx, function, keys, args...)
|
||||
}
|
||||
|
||||
func (c cmdable) FCallRO(ctx context.Context, function string, keys []string, args ...interface{}) *Cmd {
|
||||
cmdArgs := fcallArgs("fcall_ro", function, keys, args...)
|
||||
cmd := NewCmd(ctx, cmdArgs...)
|
||||
if len(keys) > 0 {
|
||||
cmd.SetFirstKeyPos(3)
|
||||
}
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func fcallArgs(command string, function string, keys []string, args ...interface{}) []interface{} {
|
||||
cmdArgs := make([]interface{}, 3+len(keys), 3+len(keys)+len(args))
|
||||
cmdArgs[0] = command
|
||||
cmdArgs[1] = function
|
||||
cmdArgs[2] = len(keys)
|
||||
for i, key := range keys {
|
||||
cmdArgs[3+i] = key
|
||||
}
|
||||
|
||||
cmdArgs = append(cmdArgs, args...)
|
||||
return cmdArgs
|
||||
}
|
||||
2103
vendor/github.com/redis/go-redis/v9/search_commands.go
generated
vendored
Normal file
2103
vendor/github.com/redis/go-redis/v9/search_commands.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1072
vendor/github.com/redis/go-redis/v9/sentinel.go
generated
vendored
Normal file
1072
vendor/github.com/redis/go-redis/v9/sentinel.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
223
vendor/github.com/redis/go-redis/v9/set_commands.go
generated
vendored
Normal file
223
vendor/github.com/redis/go-redis/v9/set_commands.go
generated
vendored
Normal file
@@ -0,0 +1,223 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/redis/go-redis/v9/internal/hashtag"
|
||||
)
|
||||
|
||||
type SetCmdable interface {
|
||||
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
|
||||
SScan(ctx context.Context, key string, cursor uint64, match string, count int64) *ScanCmd
|
||||
SUnion(ctx context.Context, keys ...string) *StringSliceCmd
|
||||
SUnionStore(ctx context.Context, destination string, keys ...string) *IntCmd
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
func (c cmdable) SAdd(ctx context.Context, key string, members ...interface{}) *IntCmd {
|
||||
args := make([]interface{}, 2, 2+len(members))
|
||||
args[0] = "sadd"
|
||||
args[1] = key
|
||||
args = appendArgs(args, members)
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) SCard(ctx context.Context, key string) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "scard", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) SDiff(ctx context.Context, keys ...string) *StringSliceCmd {
|
||||
args := make([]interface{}, 1+len(keys))
|
||||
args[0] = "sdiff"
|
||||
for i, key := range keys {
|
||||
args[1+i] = key
|
||||
}
|
||||
cmd := NewStringSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) SDiffStore(ctx context.Context, destination string, keys ...string) *IntCmd {
|
||||
args := make([]interface{}, 2+len(keys))
|
||||
args[0] = "sdiffstore"
|
||||
args[1] = destination
|
||||
for i, key := range keys {
|
||||
args[2+i] = key
|
||||
}
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) SInter(ctx context.Context, keys ...string) *StringSliceCmd {
|
||||
args := make([]interface{}, 1+len(keys))
|
||||
args[0] = "sinter"
|
||||
for i, key := range keys {
|
||||
args[1+i] = key
|
||||
}
|
||||
cmd := NewStringSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) SInterCard(ctx context.Context, limit int64, keys ...string) *IntCmd {
|
||||
numKeys := len(keys)
|
||||
args := make([]interface{}, 4+numKeys)
|
||||
args[0] = "sintercard"
|
||||
args[1] = numKeys
|
||||
for i, key := range keys {
|
||||
args[2+i] = key
|
||||
}
|
||||
args[2+numKeys] = "limit"
|
||||
args[3+numKeys] = limit
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) SInterStore(ctx context.Context, destination string, keys ...string) *IntCmd {
|
||||
args := make([]interface{}, 2+len(keys))
|
||||
args[0] = "sinterstore"
|
||||
args[1] = destination
|
||||
for i, key := range keys {
|
||||
args[2+i] = key
|
||||
}
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) SIsMember(ctx context.Context, key string, member interface{}) *BoolCmd {
|
||||
cmd := NewBoolCmd(ctx, "sismember", key, member)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// SMIsMember Redis `SMISMEMBER key member [member ...]` command.
|
||||
func (c cmdable) SMIsMember(ctx context.Context, key string, members ...interface{}) *BoolSliceCmd {
|
||||
args := make([]interface{}, 2, 2+len(members))
|
||||
args[0] = "smismember"
|
||||
args[1] = key
|
||||
args = appendArgs(args, members)
|
||||
cmd := NewBoolSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// SMembers Redis `SMEMBERS key` command output as a slice.
|
||||
func (c cmdable) SMembers(ctx context.Context, key string) *StringSliceCmd {
|
||||
cmd := NewStringSliceCmd(ctx, "smembers", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// SMembersMap Redis `SMEMBERS key` command output as a map.
|
||||
func (c cmdable) SMembersMap(ctx context.Context, key string) *StringStructMapCmd {
|
||||
cmd := NewStringStructMapCmd(ctx, "smembers", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) SMove(ctx context.Context, source, destination string, member interface{}) *BoolCmd {
|
||||
cmd := NewBoolCmd(ctx, "smove", source, destination, member)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// SPop Redis `SPOP key` command.
|
||||
func (c cmdable) SPop(ctx context.Context, key string) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "spop", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// SPopN Redis `SPOP key count` command.
|
||||
func (c cmdable) SPopN(ctx context.Context, key string, count int64) *StringSliceCmd {
|
||||
cmd := NewStringSliceCmd(ctx, "spop", key, count)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// SRandMember Redis `SRANDMEMBER key` command.
|
||||
func (c cmdable) SRandMember(ctx context.Context, key string) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "srandmember", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// SRandMemberN Redis `SRANDMEMBER key count` command.
|
||||
func (c cmdable) SRandMemberN(ctx context.Context, key string, count int64) *StringSliceCmd {
|
||||
cmd := NewStringSliceCmd(ctx, "srandmember", key, count)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) SRem(ctx context.Context, key string, members ...interface{}) *IntCmd {
|
||||
args := make([]interface{}, 2, 2+len(members))
|
||||
args[0] = "srem"
|
||||
args[1] = key
|
||||
args = appendArgs(args, members)
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) SUnion(ctx context.Context, keys ...string) *StringSliceCmd {
|
||||
args := make([]interface{}, 1+len(keys))
|
||||
args[0] = "sunion"
|
||||
for i, key := range keys {
|
||||
args[1+i] = key
|
||||
}
|
||||
cmd := NewStringSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) SUnionStore(ctx context.Context, destination string, keys ...string) *IntCmd {
|
||||
args := make([]interface{}, 2+len(keys))
|
||||
args[0] = "sunionstore"
|
||||
args[1] = destination
|
||||
for i, key := range keys {
|
||||
args[2+i] = key
|
||||
}
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) SScan(ctx context.Context, key string, cursor uint64, match string, count int64) *ScanCmd {
|
||||
args := []interface{}{"sscan", key, cursor}
|
||||
if match != "" {
|
||||
args = append(args, "match", match)
|
||||
}
|
||||
if count > 0 {
|
||||
args = append(args, "count", count)
|
||||
}
|
||||
cmd := NewScanCmd(ctx, c, args...)
|
||||
if hashtag.Present(match) {
|
||||
cmd.SetFirstKeyPos(4)
|
||||
}
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
776
vendor/github.com/redis/go-redis/v9/sortedset_commands.go
generated
vendored
Normal file
776
vendor/github.com/redis/go-redis/v9/sortedset_commands.go
generated
vendored
Normal file
@@ -0,0 +1,776 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/redis/go-redis/v9/internal/hashtag"
|
||||
)
|
||||
|
||||
type SortedSetCmdable interface {
|
||||
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
|
||||
ZScan(ctx context.Context, key string, cursor uint64, match string, count int64) *ScanCmd
|
||||
}
|
||||
|
||||
// BZPopMax Redis `BZPOPMAX key [key ...] timeout` command.
|
||||
func (c cmdable) BZPopMax(ctx context.Context, timeout time.Duration, keys ...string) *ZWithKeyCmd {
|
||||
args := make([]interface{}, 1+len(keys)+1)
|
||||
args[0] = "bzpopmax"
|
||||
for i, key := range keys {
|
||||
args[1+i] = key
|
||||
}
|
||||
args[len(args)-1] = formatSec(ctx, timeout)
|
||||
cmd := NewZWithKeyCmd(ctx, args...)
|
||||
cmd.setReadTimeout(timeout)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// BZPopMin Redis `BZPOPMIN key [key ...] timeout` command.
|
||||
func (c cmdable) BZPopMin(ctx context.Context, timeout time.Duration, keys ...string) *ZWithKeyCmd {
|
||||
args := make([]interface{}, 1+len(keys)+1)
|
||||
args[0] = "bzpopmin"
|
||||
for i, key := range keys {
|
||||
args[1+i] = key
|
||||
}
|
||||
args[len(args)-1] = formatSec(ctx, timeout)
|
||||
cmd := NewZWithKeyCmd(ctx, args...)
|
||||
cmd.setReadTimeout(timeout)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// BZMPop is the blocking variant of ZMPOP.
|
||||
// When any of the sorted sets contains elements, this command behaves exactly like ZMPOP.
|
||||
// When all sorted sets are empty, Redis will block the connection until another client adds members to one of the keys or until the timeout elapses.
|
||||
// A timeout of zero can be used to block indefinitely.
|
||||
// example: client.BZMPop(ctx, 0,"max", 1, "set")
|
||||
func (c cmdable) BZMPop(ctx context.Context, timeout time.Duration, order string, count int64, keys ...string) *ZSliceWithKeyCmd {
|
||||
args := make([]interface{}, 3+len(keys), 6+len(keys))
|
||||
args[0] = "bzmpop"
|
||||
args[1] = formatSec(ctx, timeout)
|
||||
args[2] = len(keys)
|
||||
for i, key := range keys {
|
||||
args[3+i] = key
|
||||
}
|
||||
args = append(args, strings.ToLower(order), "count", count)
|
||||
cmd := NewZSliceWithKeyCmd(ctx, args...)
|
||||
cmd.setReadTimeout(timeout)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// ZAddArgs WARN: The GT, LT and NX options are mutually exclusive.
|
||||
type ZAddArgs struct {
|
||||
NX bool
|
||||
XX bool
|
||||
LT bool
|
||||
GT bool
|
||||
Ch bool
|
||||
Members []Z
|
||||
}
|
||||
|
||||
func (c cmdable) zAddArgs(key string, args ZAddArgs, incr bool) []interface{} {
|
||||
a := make([]interface{}, 0, 6+2*len(args.Members))
|
||||
a = append(a, "zadd", key)
|
||||
|
||||
// The GT, LT and NX options are mutually exclusive.
|
||||
if args.NX {
|
||||
a = append(a, "nx")
|
||||
} else {
|
||||
if args.XX {
|
||||
a = append(a, "xx")
|
||||
}
|
||||
if args.GT {
|
||||
a = append(a, "gt")
|
||||
} else if args.LT {
|
||||
a = append(a, "lt")
|
||||
}
|
||||
}
|
||||
if args.Ch {
|
||||
a = append(a, "ch")
|
||||
}
|
||||
if incr {
|
||||
a = append(a, "incr")
|
||||
}
|
||||
for _, m := range args.Members {
|
||||
a = append(a, m.Score)
|
||||
a = append(a, m.Member)
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func (c cmdable) ZAddArgs(ctx context.Context, key string, args ZAddArgs) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, c.zAddArgs(key, args, false)...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ZAddArgsIncr(ctx context.Context, key string, args ZAddArgs) *FloatCmd {
|
||||
cmd := NewFloatCmd(ctx, c.zAddArgs(key, args, true)...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// ZAdd Redis `ZADD key score member [score member ...]` command.
|
||||
func (c cmdable) ZAdd(ctx context.Context, key string, members ...Z) *IntCmd {
|
||||
return c.ZAddArgs(ctx, key, ZAddArgs{
|
||||
Members: members,
|
||||
})
|
||||
}
|
||||
|
||||
// ZAddLT Redis `ZADD key LT score member [score member ...]` command.
|
||||
func (c cmdable) ZAddLT(ctx context.Context, key string, members ...Z) *IntCmd {
|
||||
return c.ZAddArgs(ctx, key, ZAddArgs{
|
||||
LT: true,
|
||||
Members: members,
|
||||
})
|
||||
}
|
||||
|
||||
// ZAddGT Redis `ZADD key GT score member [score member ...]` command.
|
||||
func (c cmdable) ZAddGT(ctx context.Context, key string, members ...Z) *IntCmd {
|
||||
return c.ZAddArgs(ctx, key, ZAddArgs{
|
||||
GT: true,
|
||||
Members: members,
|
||||
})
|
||||
}
|
||||
|
||||
// ZAddNX Redis `ZADD key NX score member [score member ...]` command.
|
||||
func (c cmdable) ZAddNX(ctx context.Context, key string, members ...Z) *IntCmd {
|
||||
return c.ZAddArgs(ctx, key, ZAddArgs{
|
||||
NX: true,
|
||||
Members: members,
|
||||
})
|
||||
}
|
||||
|
||||
// ZAddXX Redis `ZADD key XX score member [score member ...]` command.
|
||||
func (c cmdable) ZAddXX(ctx context.Context, key string, members ...Z) *IntCmd {
|
||||
return c.ZAddArgs(ctx, key, ZAddArgs{
|
||||
XX: true,
|
||||
Members: members,
|
||||
})
|
||||
}
|
||||
|
||||
func (c cmdable) ZCard(ctx context.Context, key string) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "zcard", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ZCount(ctx context.Context, key, min, max string) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "zcount", key, min, max)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ZLexCount(ctx context.Context, key, min, max string) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "zlexcount", key, min, max)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ZIncrBy(ctx context.Context, key string, increment float64, member string) *FloatCmd {
|
||||
cmd := NewFloatCmd(ctx, "zincrby", key, increment, member)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ZInterStore(ctx context.Context, destination string, store *ZStore) *IntCmd {
|
||||
args := make([]interface{}, 0, 3+store.len())
|
||||
args = append(args, "zinterstore", destination, len(store.Keys))
|
||||
args = store.appendArgs(args)
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
cmd.SetFirstKeyPos(3)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ZInter(ctx context.Context, store *ZStore) *StringSliceCmd {
|
||||
args := make([]interface{}, 0, 2+store.len())
|
||||
args = append(args, "zinter", len(store.Keys))
|
||||
args = store.appendArgs(args)
|
||||
cmd := NewStringSliceCmd(ctx, args...)
|
||||
cmd.SetFirstKeyPos(2)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ZInterWithScores(ctx context.Context, store *ZStore) *ZSliceCmd {
|
||||
args := make([]interface{}, 0, 3+store.len())
|
||||
args = append(args, "zinter", len(store.Keys))
|
||||
args = store.appendArgs(args)
|
||||
args = append(args, "withscores")
|
||||
cmd := NewZSliceCmd(ctx, args...)
|
||||
cmd.SetFirstKeyPos(2)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ZInterCard(ctx context.Context, limit int64, keys ...string) *IntCmd {
|
||||
numKeys := len(keys)
|
||||
args := make([]interface{}, 4+numKeys)
|
||||
args[0] = "zintercard"
|
||||
args[1] = numKeys
|
||||
for i, key := range keys {
|
||||
args[2+i] = key
|
||||
}
|
||||
args[2+numKeys] = "limit"
|
||||
args[3+numKeys] = limit
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// ZMPop Pops one or more elements with the highest or lowest score from the first non-empty sorted set key from the list of provided key names.
|
||||
// direction: "max" (highest score) or "min" (lowest score), count: > 0
|
||||
// example: client.ZMPop(ctx, "max", 5, "set1", "set2")
|
||||
func (c cmdable) ZMPop(ctx context.Context, order string, count int64, keys ...string) *ZSliceWithKeyCmd {
|
||||
args := make([]interface{}, 2+len(keys), 5+len(keys))
|
||||
args[0] = "zmpop"
|
||||
args[1] = len(keys)
|
||||
for i, key := range keys {
|
||||
args[2+i] = key
|
||||
}
|
||||
args = append(args, strings.ToLower(order), "count", count)
|
||||
cmd := NewZSliceWithKeyCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ZMScore(ctx context.Context, key string, members ...string) *FloatSliceCmd {
|
||||
args := make([]interface{}, 2+len(members))
|
||||
args[0] = "zmscore"
|
||||
args[1] = key
|
||||
for i, member := range members {
|
||||
args[2+i] = member
|
||||
}
|
||||
cmd := NewFloatSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ZPopMax(ctx context.Context, key string, count ...int64) *ZSliceCmd {
|
||||
args := []interface{}{
|
||||
"zpopmax",
|
||||
key,
|
||||
}
|
||||
|
||||
switch len(count) {
|
||||
case 0:
|
||||
break
|
||||
case 1:
|
||||
args = append(args, count[0])
|
||||
default:
|
||||
panic("too many arguments")
|
||||
}
|
||||
|
||||
cmd := NewZSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ZPopMin(ctx context.Context, key string, count ...int64) *ZSliceCmd {
|
||||
args := []interface{}{
|
||||
"zpopmin",
|
||||
key,
|
||||
}
|
||||
|
||||
switch len(count) {
|
||||
case 0:
|
||||
break
|
||||
case 1:
|
||||
args = append(args, count[0])
|
||||
default:
|
||||
panic("too many arguments")
|
||||
}
|
||||
|
||||
cmd := NewZSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// ZRangeArgs is all the options of the ZRange command.
|
||||
// In version> 6.2.0, you can replace the(cmd):
|
||||
//
|
||||
// ZREVRANGE,
|
||||
// ZRANGEBYSCORE,
|
||||
// ZREVRANGEBYSCORE,
|
||||
// ZRANGEBYLEX,
|
||||
// ZREVRANGEBYLEX.
|
||||
//
|
||||
// Please pay attention to your redis-server version.
|
||||
//
|
||||
// Rev, ByScore, ByLex and Offset+Count options require redis-server 6.2.0 and higher.
|
||||
type ZRangeArgs struct {
|
||||
Key string
|
||||
|
||||
// When the ByScore option is provided, the open interval(exclusive) can be set.
|
||||
// By default, the score intervals specified by <Start> and <Stop> are closed (inclusive).
|
||||
// It is similar to the deprecated(6.2.0+) ZRangeByScore command.
|
||||
// For example:
|
||||
// ZRangeArgs{
|
||||
// Key: "example-key",
|
||||
// Start: "(3",
|
||||
// Stop: 8,
|
||||
// ByScore: true,
|
||||
// }
|
||||
// cmd: "ZRange example-key (3 8 ByScore" (3 < score <= 8).
|
||||
//
|
||||
// For the ByLex option, it is similar to the deprecated(6.2.0+) ZRangeByLex command.
|
||||
// You can set the <Start> and <Stop> options as follows:
|
||||
// ZRangeArgs{
|
||||
// Key: "example-key",
|
||||
// Start: "[abc",
|
||||
// Stop: "(def",
|
||||
// ByLex: true,
|
||||
// }
|
||||
// cmd: "ZRange example-key [abc (def ByLex"
|
||||
//
|
||||
// For normal cases (ByScore==false && ByLex==false), <Start> and <Stop> should be set to the index range (int).
|
||||
// You can read the documentation for more information: https://redis.io/commands/zrange
|
||||
Start interface{}
|
||||
Stop interface{}
|
||||
|
||||
// The ByScore and ByLex options are mutually exclusive.
|
||||
ByScore bool
|
||||
ByLex bool
|
||||
|
||||
Rev bool
|
||||
|
||||
// limit offset count.
|
||||
Offset int64
|
||||
Count int64
|
||||
}
|
||||
|
||||
func (z ZRangeArgs) appendArgs(args []interface{}) []interface{} {
|
||||
// For Rev+ByScore/ByLex, we need to adjust the position of <Start> and <Stop>.
|
||||
if z.Rev && (z.ByScore || z.ByLex) {
|
||||
args = append(args, z.Key, z.Stop, z.Start)
|
||||
} else {
|
||||
args = append(args, z.Key, z.Start, z.Stop)
|
||||
}
|
||||
|
||||
if z.ByScore {
|
||||
args = append(args, "byscore")
|
||||
} else if z.ByLex {
|
||||
args = append(args, "bylex")
|
||||
}
|
||||
if z.Rev {
|
||||
args = append(args, "rev")
|
||||
}
|
||||
if z.Offset != 0 || z.Count != 0 {
|
||||
args = append(args, "limit", z.Offset, z.Count)
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
func (c cmdable) ZRangeArgs(ctx context.Context, z ZRangeArgs) *StringSliceCmd {
|
||||
args := make([]interface{}, 0, 9)
|
||||
args = append(args, "zrange")
|
||||
args = z.appendArgs(args)
|
||||
cmd := NewStringSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ZRangeArgsWithScores(ctx context.Context, z ZRangeArgs) *ZSliceCmd {
|
||||
args := make([]interface{}, 0, 10)
|
||||
args = append(args, "zrange")
|
||||
args = z.appendArgs(args)
|
||||
args = append(args, "withscores")
|
||||
cmd := NewZSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ZRange(ctx context.Context, key string, start, stop int64) *StringSliceCmd {
|
||||
return c.ZRangeArgs(ctx, ZRangeArgs{
|
||||
Key: key,
|
||||
Start: start,
|
||||
Stop: stop,
|
||||
})
|
||||
}
|
||||
|
||||
func (c cmdable) ZRangeWithScores(ctx context.Context, key string, start, stop int64) *ZSliceCmd {
|
||||
return c.ZRangeArgsWithScores(ctx, ZRangeArgs{
|
||||
Key: key,
|
||||
Start: start,
|
||||
Stop: stop,
|
||||
})
|
||||
}
|
||||
|
||||
type ZRangeBy struct {
|
||||
Min, Max string
|
||||
Offset, Count int64
|
||||
}
|
||||
|
||||
func (c cmdable) zRangeBy(ctx context.Context, zcmd, key string, opt *ZRangeBy, withScores bool) *StringSliceCmd {
|
||||
args := []interface{}{zcmd, key, opt.Min, opt.Max}
|
||||
if withScores {
|
||||
args = append(args, "withscores")
|
||||
}
|
||||
if opt.Offset != 0 || opt.Count != 0 {
|
||||
args = append(
|
||||
args,
|
||||
"limit",
|
||||
opt.Offset,
|
||||
opt.Count,
|
||||
)
|
||||
}
|
||||
cmd := NewStringSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ZRangeByScore(ctx context.Context, key string, opt *ZRangeBy) *StringSliceCmd {
|
||||
return c.zRangeBy(ctx, "zrangebyscore", key, opt, false)
|
||||
}
|
||||
|
||||
func (c cmdable) ZRangeByLex(ctx context.Context, key string, opt *ZRangeBy) *StringSliceCmd {
|
||||
return c.zRangeBy(ctx, "zrangebylex", key, opt, false)
|
||||
}
|
||||
|
||||
func (c cmdable) ZRangeByScoreWithScores(ctx context.Context, key string, opt *ZRangeBy) *ZSliceCmd {
|
||||
args := []interface{}{"zrangebyscore", key, opt.Min, opt.Max, "withscores"}
|
||||
if opt.Offset != 0 || opt.Count != 0 {
|
||||
args = append(
|
||||
args,
|
||||
"limit",
|
||||
opt.Offset,
|
||||
opt.Count,
|
||||
)
|
||||
}
|
||||
cmd := NewZSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ZRangeStore(ctx context.Context, dst string, z ZRangeArgs) *IntCmd {
|
||||
args := make([]interface{}, 0, 10)
|
||||
args = append(args, "zrangestore", dst)
|
||||
args = z.appendArgs(args)
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ZRank(ctx context.Context, key, member string) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "zrank", key, member)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// ZRankWithScore according to the Redis documentation, if member does not exist
|
||||
// in the sorted set or key does not exist, it will return a redis.Nil error.
|
||||
func (c cmdable) ZRankWithScore(ctx context.Context, key, member string) *RankWithScoreCmd {
|
||||
cmd := NewRankWithScoreCmd(ctx, "zrank", key, member, "withscore")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ZRem(ctx context.Context, key string, members ...interface{}) *IntCmd {
|
||||
args := make([]interface{}, 2, 2+len(members))
|
||||
args[0] = "zrem"
|
||||
args[1] = key
|
||||
args = appendArgs(args, members)
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ZRemRangeByRank(ctx context.Context, key string, start, stop int64) *IntCmd {
|
||||
cmd := NewIntCmd(
|
||||
ctx,
|
||||
"zremrangebyrank",
|
||||
key,
|
||||
start,
|
||||
stop,
|
||||
)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ZRemRangeByScore(ctx context.Context, key, min, max string) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "zremrangebyscore", key, min, max)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ZRemRangeByLex(ctx context.Context, key, min, max string) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "zremrangebylex", key, min, max)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ZRevRange(ctx context.Context, key string, start, stop int64) *StringSliceCmd {
|
||||
cmd := NewStringSliceCmd(ctx, "zrevrange", key, start, stop)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// ZRevRangeWithScores according to the Redis documentation, if member does not exist
|
||||
// in the sorted set or key does not exist, it will return a redis.Nil error.
|
||||
func (c cmdable) ZRevRangeWithScores(ctx context.Context, key string, start, stop int64) *ZSliceCmd {
|
||||
cmd := NewZSliceCmd(ctx, "zrevrange", key, start, stop, "withscores")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) zRevRangeBy(ctx context.Context, zcmd, key string, opt *ZRangeBy) *StringSliceCmd {
|
||||
args := []interface{}{zcmd, key, opt.Max, opt.Min}
|
||||
if opt.Offset != 0 || opt.Count != 0 {
|
||||
args = append(
|
||||
args,
|
||||
"limit",
|
||||
opt.Offset,
|
||||
opt.Count,
|
||||
)
|
||||
}
|
||||
cmd := NewStringSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ZRevRangeByScore(ctx context.Context, key string, opt *ZRangeBy) *StringSliceCmd {
|
||||
return c.zRevRangeBy(ctx, "zrevrangebyscore", key, opt)
|
||||
}
|
||||
|
||||
func (c cmdable) ZRevRangeByLex(ctx context.Context, key string, opt *ZRangeBy) *StringSliceCmd {
|
||||
return c.zRevRangeBy(ctx, "zrevrangebylex", key, opt)
|
||||
}
|
||||
|
||||
func (c cmdable) ZRevRangeByScoreWithScores(ctx context.Context, key string, opt *ZRangeBy) *ZSliceCmd {
|
||||
args := []interface{}{"zrevrangebyscore", key, opt.Max, opt.Min, "withscores"}
|
||||
if opt.Offset != 0 || opt.Count != 0 {
|
||||
args = append(
|
||||
args,
|
||||
"limit",
|
||||
opt.Offset,
|
||||
opt.Count,
|
||||
)
|
||||
}
|
||||
cmd := NewZSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ZRevRank(ctx context.Context, key, member string) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "zrevrank", key, member)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ZRevRankWithScore(ctx context.Context, key, member string) *RankWithScoreCmd {
|
||||
cmd := NewRankWithScoreCmd(ctx, "zrevrank", key, member, "withscore")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ZScore(ctx context.Context, key, member string) *FloatCmd {
|
||||
cmd := NewFloatCmd(ctx, "zscore", key, member)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ZUnion(ctx context.Context, store ZStore) *StringSliceCmd {
|
||||
args := make([]interface{}, 0, 2+store.len())
|
||||
args = append(args, "zunion", len(store.Keys))
|
||||
args = store.appendArgs(args)
|
||||
cmd := NewStringSliceCmd(ctx, args...)
|
||||
cmd.SetFirstKeyPos(2)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ZUnionWithScores(ctx context.Context, store ZStore) *ZSliceCmd {
|
||||
args := make([]interface{}, 0, 3+store.len())
|
||||
args = append(args, "zunion", len(store.Keys))
|
||||
args = store.appendArgs(args)
|
||||
args = append(args, "withscores")
|
||||
cmd := NewZSliceCmd(ctx, args...)
|
||||
cmd.SetFirstKeyPos(2)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ZUnionStore(ctx context.Context, dest string, store *ZStore) *IntCmd {
|
||||
args := make([]interface{}, 0, 3+store.len())
|
||||
args = append(args, "zunionstore", dest, len(store.Keys))
|
||||
args = store.appendArgs(args)
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
cmd.SetFirstKeyPos(3)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// ZRandMember redis-server version >= 6.2.0.
|
||||
func (c cmdable) ZRandMember(ctx context.Context, key string, count int) *StringSliceCmd {
|
||||
cmd := NewStringSliceCmd(ctx, "zrandmember", key, count)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// ZRandMemberWithScores redis-server version >= 6.2.0.
|
||||
func (c cmdable) ZRandMemberWithScores(ctx context.Context, key string, count int) *ZSliceCmd {
|
||||
cmd := NewZSliceCmd(ctx, "zrandmember", key, count, "withscores")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// ZDiff redis-server version >= 6.2.0.
|
||||
func (c cmdable) ZDiff(ctx context.Context, keys ...string) *StringSliceCmd {
|
||||
args := make([]interface{}, 2+len(keys))
|
||||
args[0] = "zdiff"
|
||||
args[1] = len(keys)
|
||||
for i, key := range keys {
|
||||
args[i+2] = key
|
||||
}
|
||||
|
||||
cmd := NewStringSliceCmd(ctx, args...)
|
||||
cmd.SetFirstKeyPos(2)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// ZDiffWithScores redis-server version >= 6.2.0.
|
||||
func (c cmdable) ZDiffWithScores(ctx context.Context, keys ...string) *ZSliceCmd {
|
||||
args := make([]interface{}, 3+len(keys))
|
||||
args[0] = "zdiff"
|
||||
args[1] = len(keys)
|
||||
for i, key := range keys {
|
||||
args[i+2] = key
|
||||
}
|
||||
args[len(keys)+2] = "withscores"
|
||||
|
||||
cmd := NewZSliceCmd(ctx, args...)
|
||||
cmd.SetFirstKeyPos(2)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// ZDiffStore redis-server version >=6.2.0.
|
||||
func (c cmdable) ZDiffStore(ctx context.Context, destination string, keys ...string) *IntCmd {
|
||||
args := make([]interface{}, 0, 3+len(keys))
|
||||
args = append(args, "zdiffstore", destination, len(keys))
|
||||
for _, key := range keys {
|
||||
args = append(args, key)
|
||||
}
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ZScan(ctx context.Context, key string, cursor uint64, match string, count int64) *ScanCmd {
|
||||
args := []interface{}{"zscan", key, cursor}
|
||||
if match != "" {
|
||||
args = append(args, "match", match)
|
||||
}
|
||||
if count > 0 {
|
||||
args = append(args, "count", count)
|
||||
}
|
||||
cmd := NewScanCmd(ctx, c, args...)
|
||||
if hashtag.Present(match) {
|
||||
cmd.SetFirstKeyPos(4)
|
||||
}
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Z represents sorted set member.
|
||||
type Z struct {
|
||||
Score float64
|
||||
Member interface{}
|
||||
}
|
||||
|
||||
// ZWithKey represents sorted set member including the name of the key where it was popped.
|
||||
type ZWithKey struct {
|
||||
Z
|
||||
Key string
|
||||
}
|
||||
|
||||
// ZStore is used as an arg to ZInter/ZInterStore and ZUnion/ZUnionStore.
|
||||
type ZStore struct {
|
||||
Keys []string
|
||||
Weights []float64
|
||||
// Can be SUM, MIN or MAX.
|
||||
Aggregate string
|
||||
}
|
||||
|
||||
func (z ZStore) len() (n int) {
|
||||
n = len(z.Keys)
|
||||
if len(z.Weights) > 0 {
|
||||
n += 1 + len(z.Weights)
|
||||
}
|
||||
if z.Aggregate != "" {
|
||||
n += 2
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (z ZStore) appendArgs(args []interface{}) []interface{} {
|
||||
for _, key := range z.Keys {
|
||||
args = append(args, key)
|
||||
}
|
||||
if len(z.Weights) > 0 {
|
||||
args = append(args, "weights")
|
||||
for _, weights := range z.Weights {
|
||||
args = append(args, weights)
|
||||
}
|
||||
}
|
||||
if z.Aggregate != "" {
|
||||
args = append(args, "aggregate", z.Aggregate)
|
||||
}
|
||||
return args
|
||||
}
|
||||
450
vendor/github.com/redis/go-redis/v9/stream_commands.go
generated
vendored
Normal file
450
vendor/github.com/redis/go-redis/v9/stream_commands.go
generated
vendored
Normal file
@@ -0,0 +1,450 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
type StreamCmdable interface {
|
||||
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
|
||||
}
|
||||
|
||||
// XAddArgs accepts values in the following formats:
|
||||
// - XAddArgs.Values = []interface{}{"key1", "value1", "key2", "value2"}
|
||||
// - XAddArgs.Values = []string("key1", "value1", "key2", "value2")
|
||||
// - XAddArgs.Values = map[string]interface{}{"key1": "value1", "key2": "value2"}
|
||||
//
|
||||
// Note that map will not preserve the order of key-value pairs.
|
||||
// MaxLen/MaxLenApprox and MinID are in conflict, only one of them can be used.
|
||||
type XAddArgs struct {
|
||||
Stream string
|
||||
NoMkStream bool
|
||||
MaxLen int64 // MAXLEN N
|
||||
MinID string
|
||||
// Approx causes MaxLen and MinID to use "~" matcher (instead of "=").
|
||||
Approx bool
|
||||
Limit int64
|
||||
ID string
|
||||
Values interface{}
|
||||
}
|
||||
|
||||
func (c cmdable) XAdd(ctx context.Context, a *XAddArgs) *StringCmd {
|
||||
args := make([]interface{}, 0, 11)
|
||||
args = append(args, "xadd", a.Stream)
|
||||
if a.NoMkStream {
|
||||
args = append(args, "nomkstream")
|
||||
}
|
||||
switch {
|
||||
case a.MaxLen > 0:
|
||||
if a.Approx {
|
||||
args = append(args, "maxlen", "~", a.MaxLen)
|
||||
} else {
|
||||
args = append(args, "maxlen", a.MaxLen)
|
||||
}
|
||||
case a.MinID != "":
|
||||
if a.Approx {
|
||||
args = append(args, "minid", "~", a.MinID)
|
||||
} else {
|
||||
args = append(args, "minid", a.MinID)
|
||||
}
|
||||
}
|
||||
if a.Limit > 0 {
|
||||
args = append(args, "limit", a.Limit)
|
||||
}
|
||||
if a.ID != "" {
|
||||
args = append(args, a.ID)
|
||||
} else {
|
||||
args = append(args, "*")
|
||||
}
|
||||
args = appendArg(args, a.Values)
|
||||
|
||||
cmd := NewStringCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) XDel(ctx context.Context, stream string, ids ...string) *IntCmd {
|
||||
args := []interface{}{"xdel", stream}
|
||||
for _, id := range ids {
|
||||
args = append(args, id)
|
||||
}
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) XLen(ctx context.Context, stream string) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "xlen", stream)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) XRange(ctx context.Context, stream, start, stop string) *XMessageSliceCmd {
|
||||
cmd := NewXMessageSliceCmd(ctx, "xrange", stream, start, stop)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) XRangeN(ctx context.Context, stream, start, stop string, count int64) *XMessageSliceCmd {
|
||||
cmd := NewXMessageSliceCmd(ctx, "xrange", stream, start, stop, "count", count)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) XRevRange(ctx context.Context, stream, start, stop string) *XMessageSliceCmd {
|
||||
cmd := NewXMessageSliceCmd(ctx, "xrevrange", stream, start, stop)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) XRevRangeN(ctx context.Context, stream, start, stop string, count int64) *XMessageSliceCmd {
|
||||
cmd := NewXMessageSliceCmd(ctx, "xrevrange", stream, start, stop, "count", count)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
type XReadArgs struct {
|
||||
Streams []string // list of streams and ids, e.g. stream1 stream2 id1 id2
|
||||
Count int64
|
||||
Block time.Duration
|
||||
ID string
|
||||
}
|
||||
|
||||
func (c cmdable) XRead(ctx context.Context, a *XReadArgs) *XStreamSliceCmd {
|
||||
args := make([]interface{}, 0, 2*len(a.Streams)+6)
|
||||
args = append(args, "xread")
|
||||
|
||||
keyPos := int8(1)
|
||||
if a.Count > 0 {
|
||||
args = append(args, "count")
|
||||
args = append(args, a.Count)
|
||||
keyPos += 2
|
||||
}
|
||||
if a.Block >= 0 {
|
||||
args = append(args, "block")
|
||||
args = append(args, int64(a.Block/time.Millisecond))
|
||||
keyPos += 2
|
||||
}
|
||||
args = append(args, "streams")
|
||||
keyPos++
|
||||
for _, s := range a.Streams {
|
||||
args = append(args, s)
|
||||
}
|
||||
if a.ID != "" {
|
||||
for range a.Streams {
|
||||
args = append(args, a.ID)
|
||||
}
|
||||
}
|
||||
|
||||
cmd := NewXStreamSliceCmd(ctx, args...)
|
||||
if a.Block >= 0 {
|
||||
cmd.setReadTimeout(a.Block)
|
||||
}
|
||||
cmd.SetFirstKeyPos(keyPos)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) XReadStreams(ctx context.Context, streams ...string) *XStreamSliceCmd {
|
||||
return c.XRead(ctx, &XReadArgs{
|
||||
Streams: streams,
|
||||
Block: -1,
|
||||
})
|
||||
}
|
||||
|
||||
func (c cmdable) XGroupCreate(ctx context.Context, stream, group, start string) *StatusCmd {
|
||||
cmd := NewStatusCmd(ctx, "xgroup", "create", stream, group, start)
|
||||
cmd.SetFirstKeyPos(2)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) XGroupCreateMkStream(ctx context.Context, stream, group, start string) *StatusCmd {
|
||||
cmd := NewStatusCmd(ctx, "xgroup", "create", stream, group, start, "mkstream")
|
||||
cmd.SetFirstKeyPos(2)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) XGroupSetID(ctx context.Context, stream, group, start string) *StatusCmd {
|
||||
cmd := NewStatusCmd(ctx, "xgroup", "setid", stream, group, start)
|
||||
cmd.SetFirstKeyPos(2)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) XGroupDestroy(ctx context.Context, stream, group string) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "xgroup", "destroy", stream, group)
|
||||
cmd.SetFirstKeyPos(2)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) XGroupCreateConsumer(ctx context.Context, stream, group, consumer string) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "xgroup", "createconsumer", stream, group, consumer)
|
||||
cmd.SetFirstKeyPos(2)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) XGroupDelConsumer(ctx context.Context, stream, group, consumer string) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "xgroup", "delconsumer", stream, group, consumer)
|
||||
cmd.SetFirstKeyPos(2)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
type XReadGroupArgs struct {
|
||||
Group string
|
||||
Consumer string
|
||||
Streams []string // list of streams and ids, e.g. stream1 stream2 id1 id2
|
||||
Count int64
|
||||
Block time.Duration
|
||||
NoAck bool
|
||||
}
|
||||
|
||||
func (c cmdable) XReadGroup(ctx context.Context, a *XReadGroupArgs) *XStreamSliceCmd {
|
||||
args := make([]interface{}, 0, 10+len(a.Streams))
|
||||
args = append(args, "xreadgroup", "group", a.Group, a.Consumer)
|
||||
|
||||
keyPos := int8(4)
|
||||
if a.Count > 0 {
|
||||
args = append(args, "count", a.Count)
|
||||
keyPos += 2
|
||||
}
|
||||
if a.Block >= 0 {
|
||||
args = append(args, "block", int64(a.Block/time.Millisecond))
|
||||
keyPos += 2
|
||||
}
|
||||
if a.NoAck {
|
||||
args = append(args, "noack")
|
||||
keyPos++
|
||||
}
|
||||
args = append(args, "streams")
|
||||
keyPos++
|
||||
for _, s := range a.Streams {
|
||||
args = append(args, s)
|
||||
}
|
||||
|
||||
cmd := NewXStreamSliceCmd(ctx, args...)
|
||||
if a.Block >= 0 {
|
||||
cmd.setReadTimeout(a.Block)
|
||||
}
|
||||
cmd.SetFirstKeyPos(keyPos)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) XAck(ctx context.Context, stream, group string, ids ...string) *IntCmd {
|
||||
args := []interface{}{"xack", stream, group}
|
||||
for _, id := range ids {
|
||||
args = append(args, id)
|
||||
}
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) XPending(ctx context.Context, stream, group string) *XPendingCmd {
|
||||
cmd := NewXPendingCmd(ctx, "xpending", stream, group)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
type XPendingExtArgs struct {
|
||||
Stream string
|
||||
Group string
|
||||
Idle time.Duration
|
||||
Start string
|
||||
End string
|
||||
Count int64
|
||||
Consumer string
|
||||
}
|
||||
|
||||
func (c cmdable) XPendingExt(ctx context.Context, a *XPendingExtArgs) *XPendingExtCmd {
|
||||
args := make([]interface{}, 0, 9)
|
||||
args = append(args, "xpending", a.Stream, a.Group)
|
||||
if a.Idle != 0 {
|
||||
args = append(args, "idle", formatMs(ctx, a.Idle))
|
||||
}
|
||||
args = append(args, a.Start, a.End, a.Count)
|
||||
if a.Consumer != "" {
|
||||
args = append(args, a.Consumer)
|
||||
}
|
||||
cmd := NewXPendingExtCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
type XAutoClaimArgs struct {
|
||||
Stream string
|
||||
Group string
|
||||
MinIdle time.Duration
|
||||
Start string
|
||||
Count int64
|
||||
Consumer string
|
||||
}
|
||||
|
||||
func (c cmdable) XAutoClaim(ctx context.Context, a *XAutoClaimArgs) *XAutoClaimCmd {
|
||||
args := xAutoClaimArgs(ctx, a)
|
||||
cmd := NewXAutoClaimCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) XAutoClaimJustID(ctx context.Context, a *XAutoClaimArgs) *XAutoClaimJustIDCmd {
|
||||
args := xAutoClaimArgs(ctx, a)
|
||||
args = append(args, "justid")
|
||||
cmd := NewXAutoClaimJustIDCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func xAutoClaimArgs(ctx context.Context, a *XAutoClaimArgs) []interface{} {
|
||||
args := make([]interface{}, 0, 8)
|
||||
args = append(args, "xautoclaim", a.Stream, a.Group, a.Consumer, formatMs(ctx, a.MinIdle), a.Start)
|
||||
if a.Count > 0 {
|
||||
args = append(args, "count", a.Count)
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
type XClaimArgs struct {
|
||||
Stream string
|
||||
Group string
|
||||
Consumer string
|
||||
MinIdle time.Duration
|
||||
Messages []string
|
||||
}
|
||||
|
||||
func (c cmdable) XClaim(ctx context.Context, a *XClaimArgs) *XMessageSliceCmd {
|
||||
args := xClaimArgs(a)
|
||||
cmd := NewXMessageSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) XClaimJustID(ctx context.Context, a *XClaimArgs) *StringSliceCmd {
|
||||
args := xClaimArgs(a)
|
||||
args = append(args, "justid")
|
||||
cmd := NewStringSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func xClaimArgs(a *XClaimArgs) []interface{} {
|
||||
args := make([]interface{}, 0, 5+len(a.Messages))
|
||||
args = append(args,
|
||||
"xclaim",
|
||||
a.Stream,
|
||||
a.Group, a.Consumer,
|
||||
int64(a.MinIdle/time.Millisecond))
|
||||
for _, id := range a.Messages {
|
||||
args = append(args, id)
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
// xTrim If approx is true, add the "~" parameter, otherwise it is the default "=" (redis default).
|
||||
// example:
|
||||
//
|
||||
// XTRIM key MAXLEN/MINID threshold LIMIT limit.
|
||||
// XTRIM key MAXLEN/MINID ~ threshold LIMIT limit.
|
||||
//
|
||||
// The redis-server version is lower than 6.2, please set limit to 0.
|
||||
func (c cmdable) xTrim(
|
||||
ctx context.Context, key, strategy string,
|
||||
approx bool, threshold interface{}, limit int64,
|
||||
) *IntCmd {
|
||||
args := make([]interface{}, 0, 7)
|
||||
args = append(args, "xtrim", key, strategy)
|
||||
if approx {
|
||||
args = append(args, "~")
|
||||
}
|
||||
args = append(args, threshold)
|
||||
if limit > 0 {
|
||||
args = append(args, "limit", limit)
|
||||
}
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// XTrimMaxLen No `~` rules are used, `limit` cannot be used.
|
||||
// cmd: XTRIM key MAXLEN maxLen
|
||||
func (c cmdable) XTrimMaxLen(ctx context.Context, key string, maxLen int64) *IntCmd {
|
||||
return c.xTrim(ctx, key, "maxlen", false, maxLen, 0)
|
||||
}
|
||||
|
||||
func (c cmdable) XTrimMaxLenApprox(ctx context.Context, key string, maxLen, limit int64) *IntCmd {
|
||||
return c.xTrim(ctx, key, "maxlen", true, maxLen, limit)
|
||||
}
|
||||
|
||||
func (c cmdable) XTrimMinID(ctx context.Context, key string, minID string) *IntCmd {
|
||||
return c.xTrim(ctx, key, "minid", false, minID, 0)
|
||||
}
|
||||
|
||||
func (c cmdable) XTrimMinIDApprox(ctx context.Context, key string, minID string, limit int64) *IntCmd {
|
||||
return c.xTrim(ctx, key, "minid", true, minID, limit)
|
||||
}
|
||||
|
||||
func (c cmdable) XInfoConsumers(ctx context.Context, key string, group string) *XInfoConsumersCmd {
|
||||
cmd := NewXInfoConsumersCmd(ctx, key, group)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) XInfoGroups(ctx context.Context, key string) *XInfoGroupsCmd {
|
||||
cmd := NewXInfoGroupsCmd(ctx, key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) XInfoStream(ctx context.Context, key string) *XInfoStreamCmd {
|
||||
cmd := NewXInfoStreamCmd(ctx, key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// XInfoStreamFull XINFO STREAM FULL [COUNT count]
|
||||
// redis-server >= 6.0.
|
||||
func (c cmdable) XInfoStreamFull(ctx context.Context, key string, count int) *XInfoStreamFullCmd {
|
||||
args := make([]interface{}, 0, 6)
|
||||
args = append(args, "xinfo", "stream", key, "full")
|
||||
if count > 0 {
|
||||
args = append(args, "count", count)
|
||||
}
|
||||
cmd := NewXInfoStreamFullCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
303
vendor/github.com/redis/go-redis/v9/string_commands.go
generated
vendored
Normal file
303
vendor/github.com/redis/go-redis/v9/string_commands.go
generated
vendored
Normal file
@@ -0,0 +1,303 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
type StringCmdable interface {
|
||||
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
|
||||
LCS(ctx context.Context, q *LCSQuery) *LCSCmd
|
||||
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
|
||||
}
|
||||
|
||||
func (c cmdable) Append(ctx context.Context, key, value string) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "append", key, value)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) Decr(ctx context.Context, key string) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "decr", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) DecrBy(ctx context.Context, key string, decrement int64) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "decrby", key, decrement)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Get Redis `GET key` command. It returns redis.Nil error when key does not exist.
|
||||
func (c cmdable) Get(ctx context.Context, key string) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "get", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) GetRange(ctx context.Context, key string, start, end int64) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "getrange", key, start, end)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) GetSet(ctx context.Context, key string, value interface{}) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "getset", key, value)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// GetEx An expiration of zero removes the TTL associated with the key (i.e. GETEX key persist).
|
||||
// Requires Redis >= 6.2.0.
|
||||
func (c cmdable) GetEx(ctx context.Context, key string, expiration time.Duration) *StringCmd {
|
||||
args := make([]interface{}, 0, 4)
|
||||
args = append(args, "getex", key)
|
||||
if expiration > 0 {
|
||||
if usePrecise(expiration) {
|
||||
args = append(args, "px", formatMs(ctx, expiration))
|
||||
} else {
|
||||
args = append(args, "ex", formatSec(ctx, expiration))
|
||||
}
|
||||
} else if expiration == 0 {
|
||||
args = append(args, "persist")
|
||||
}
|
||||
|
||||
cmd := NewStringCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// GetDel redis-server version >= 6.2.0.
|
||||
func (c cmdable) GetDel(ctx context.Context, key string) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "getdel", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) Incr(ctx context.Context, key string) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "incr", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) IncrBy(ctx context.Context, key string, value int64) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "incrby", key, value)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) IncrByFloat(ctx context.Context, key string, value float64) *FloatCmd {
|
||||
cmd := NewFloatCmd(ctx, "incrbyfloat", key, value)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) LCS(ctx context.Context, q *LCSQuery) *LCSCmd {
|
||||
cmd := NewLCSCmd(ctx, q)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) MGet(ctx context.Context, keys ...string) *SliceCmd {
|
||||
args := make([]interface{}, 1+len(keys))
|
||||
args[0] = "mget"
|
||||
for i, key := range keys {
|
||||
args[1+i] = key
|
||||
}
|
||||
cmd := NewSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// MSet is like Set but accepts multiple values:
|
||||
// - MSet("key1", "value1", "key2", "value2")
|
||||
// - MSet([]string{"key1", "value1", "key2", "value2"})
|
||||
// - MSet(map[string]interface{}{"key1": "value1", "key2": "value2"})
|
||||
// - MSet(struct), For struct types, see HSet description.
|
||||
func (c cmdable) MSet(ctx context.Context, values ...interface{}) *StatusCmd {
|
||||
args := make([]interface{}, 1, 1+len(values))
|
||||
args[0] = "mset"
|
||||
args = appendArgs(args, values)
|
||||
cmd := NewStatusCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// MSetNX is like SetNX but accepts multiple values:
|
||||
// - MSetNX("key1", "value1", "key2", "value2")
|
||||
// - MSetNX([]string{"key1", "value1", "key2", "value2"})
|
||||
// - MSetNX(map[string]interface{}{"key1": "value1", "key2": "value2"})
|
||||
// - MSetNX(struct), For struct types, see HSet description.
|
||||
func (c cmdable) MSetNX(ctx context.Context, values ...interface{}) *BoolCmd {
|
||||
args := make([]interface{}, 1, 1+len(values))
|
||||
args[0] = "msetnx"
|
||||
args = appendArgs(args, values)
|
||||
cmd := NewBoolCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Set Redis `SET key value [expiration]` command.
|
||||
// Use expiration for `SETEx`-like behavior.
|
||||
//
|
||||
// Zero expiration means the key has no expiration time.
|
||||
// KeepTTL is a Redis KEEPTTL option to keep existing TTL, it requires your redis-server version >= 6.0,
|
||||
// otherwise you will receive an error: (error) ERR syntax error.
|
||||
func (c cmdable) Set(ctx context.Context, key string, value interface{}, expiration time.Duration) *StatusCmd {
|
||||
args := make([]interface{}, 3, 5)
|
||||
args[0] = "set"
|
||||
args[1] = key
|
||||
args[2] = value
|
||||
if expiration > 0 {
|
||||
if usePrecise(expiration) {
|
||||
args = append(args, "px", formatMs(ctx, expiration))
|
||||
} else {
|
||||
args = append(args, "ex", formatSec(ctx, expiration))
|
||||
}
|
||||
} else if expiration == KeepTTL {
|
||||
args = append(args, "keepttl")
|
||||
}
|
||||
|
||||
cmd := NewStatusCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// SetArgs provides arguments for the SetArgs function.
|
||||
type SetArgs struct {
|
||||
// Mode can be `NX` or `XX` or empty.
|
||||
Mode string
|
||||
|
||||
// Zero `TTL` or `Expiration` means that the key has no expiration time.
|
||||
TTL time.Duration
|
||||
ExpireAt time.Time
|
||||
|
||||
// When Get is true, the command returns the old value stored at key, or nil when key did not exist.
|
||||
Get bool
|
||||
|
||||
// KeepTTL is a Redis KEEPTTL option to keep existing TTL, it requires your redis-server version >= 6.0,
|
||||
// otherwise you will receive an error: (error) ERR syntax error.
|
||||
KeepTTL bool
|
||||
}
|
||||
|
||||
// SetArgs supports all the options that the SET command supports.
|
||||
// It is the alternative to the Set function when you want
|
||||
// to have more control over the options.
|
||||
func (c cmdable) SetArgs(ctx context.Context, key string, value interface{}, a SetArgs) *StatusCmd {
|
||||
args := []interface{}{"set", key, value}
|
||||
|
||||
if a.KeepTTL {
|
||||
args = append(args, "keepttl")
|
||||
}
|
||||
|
||||
if !a.ExpireAt.IsZero() {
|
||||
args = append(args, "exat", a.ExpireAt.Unix())
|
||||
}
|
||||
if a.TTL > 0 {
|
||||
if usePrecise(a.TTL) {
|
||||
args = append(args, "px", formatMs(ctx, a.TTL))
|
||||
} else {
|
||||
args = append(args, "ex", formatSec(ctx, a.TTL))
|
||||
}
|
||||
}
|
||||
|
||||
if a.Mode != "" {
|
||||
args = append(args, a.Mode)
|
||||
}
|
||||
|
||||
if a.Get {
|
||||
args = append(args, "get")
|
||||
}
|
||||
|
||||
cmd := NewStatusCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// SetEx Redis `SETEx key expiration value` command.
|
||||
func (c cmdable) SetEx(ctx context.Context, key string, value interface{}, expiration time.Duration) *StatusCmd {
|
||||
cmd := NewStatusCmd(ctx, "setex", key, formatSec(ctx, expiration), value)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// SetNX Redis `SET key value [expiration] NX` command.
|
||||
//
|
||||
// Zero expiration means the key has no expiration time.
|
||||
// KeepTTL is a Redis KEEPTTL option to keep existing TTL, it requires your redis-server version >= 6.0,
|
||||
// otherwise you will receive an error: (error) ERR syntax error.
|
||||
func (c cmdable) SetNX(ctx context.Context, key string, value interface{}, expiration time.Duration) *BoolCmd {
|
||||
var cmd *BoolCmd
|
||||
switch expiration {
|
||||
case 0:
|
||||
// Use old `SETNX` to support old Redis versions.
|
||||
cmd = NewBoolCmd(ctx, "setnx", key, value)
|
||||
case KeepTTL:
|
||||
cmd = NewBoolCmd(ctx, "set", key, value, "keepttl", "nx")
|
||||
default:
|
||||
if usePrecise(expiration) {
|
||||
cmd = NewBoolCmd(ctx, "set", key, value, "px", formatMs(ctx, expiration), "nx")
|
||||
} else {
|
||||
cmd = NewBoolCmd(ctx, "set", key, value, "ex", formatSec(ctx, expiration), "nx")
|
||||
}
|
||||
}
|
||||
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// SetXX Redis `SET key value [expiration] XX` command.
|
||||
//
|
||||
// Zero expiration means the key has no expiration time.
|
||||
// KeepTTL is a Redis KEEPTTL option to keep existing TTL, it requires your redis-server version >= 6.0,
|
||||
// otherwise you will receive an error: (error) ERR syntax error.
|
||||
func (c cmdable) SetXX(ctx context.Context, key string, value interface{}, expiration time.Duration) *BoolCmd {
|
||||
var cmd *BoolCmd
|
||||
switch expiration {
|
||||
case 0:
|
||||
cmd = NewBoolCmd(ctx, "set", key, value, "xx")
|
||||
case KeepTTL:
|
||||
cmd = NewBoolCmd(ctx, "set", key, value, "keepttl", "xx")
|
||||
default:
|
||||
if usePrecise(expiration) {
|
||||
cmd = NewBoolCmd(ctx, "set", key, value, "px", formatMs(ctx, expiration), "xx")
|
||||
} else {
|
||||
cmd = NewBoolCmd(ctx, "set", key, value, "ex", formatSec(ctx, expiration), "xx")
|
||||
}
|
||||
}
|
||||
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) SetRange(ctx context.Context, key string, offset int64, value string) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "setrange", key, offset, value)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) StrLen(ctx context.Context, key string) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "strlen", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
950
vendor/github.com/redis/go-redis/v9/timeseries_commands.go
generated
vendored
Normal file
950
vendor/github.com/redis/go-redis/v9/timeseries_commands.go
generated
vendored
Normal file
@@ -0,0 +1,950 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
"github.com/redis/go-redis/v9/internal/proto"
|
||||
)
|
||||
|
||||
type TimeseriesCmdable interface {
|
||||
TSAdd(ctx context.Context, key string, timestamp interface{}, value float64) *IntCmd
|
||||
TSAddWithArgs(ctx context.Context, key string, timestamp interface{}, value float64, options *TSOptions) *IntCmd
|
||||
TSCreate(ctx context.Context, key string) *StatusCmd
|
||||
TSCreateWithArgs(ctx context.Context, key string, options *TSOptions) *StatusCmd
|
||||
TSAlter(ctx context.Context, key string, options *TSAlterOptions) *StatusCmd
|
||||
TSCreateRule(ctx context.Context, sourceKey string, destKey string, aggregator Aggregator, bucketDuration int) *StatusCmd
|
||||
TSCreateRuleWithArgs(ctx context.Context, sourceKey string, destKey string, aggregator Aggregator, bucketDuration int, options *TSCreateRuleOptions) *StatusCmd
|
||||
TSIncrBy(ctx context.Context, Key string, timestamp float64) *IntCmd
|
||||
TSIncrByWithArgs(ctx context.Context, key string, timestamp float64, options *TSIncrDecrOptions) *IntCmd
|
||||
TSDecrBy(ctx context.Context, Key string, timestamp float64) *IntCmd
|
||||
TSDecrByWithArgs(ctx context.Context, key string, timestamp float64, options *TSIncrDecrOptions) *IntCmd
|
||||
TSDel(ctx context.Context, Key string, fromTimestamp int, toTimestamp int) *IntCmd
|
||||
TSDeleteRule(ctx context.Context, sourceKey string, destKey string) *StatusCmd
|
||||
TSGet(ctx context.Context, key string) *TSTimestampValueCmd
|
||||
TSGetWithArgs(ctx context.Context, key string, options *TSGetOptions) *TSTimestampValueCmd
|
||||
TSInfo(ctx context.Context, key string) *MapStringInterfaceCmd
|
||||
TSInfoWithArgs(ctx context.Context, key string, options *TSInfoOptions) *MapStringInterfaceCmd
|
||||
TSMAdd(ctx context.Context, ktvSlices [][]interface{}) *IntSliceCmd
|
||||
TSQueryIndex(ctx context.Context, filterExpr []string) *StringSliceCmd
|
||||
TSRevRange(ctx context.Context, key string, fromTimestamp int, toTimestamp int) *TSTimestampValueSliceCmd
|
||||
TSRevRangeWithArgs(ctx context.Context, key string, fromTimestamp int, toTimestamp int, options *TSRevRangeOptions) *TSTimestampValueSliceCmd
|
||||
TSRange(ctx context.Context, key string, fromTimestamp int, toTimestamp int) *TSTimestampValueSliceCmd
|
||||
TSRangeWithArgs(ctx context.Context, key string, fromTimestamp int, toTimestamp int, options *TSRangeOptions) *TSTimestampValueSliceCmd
|
||||
TSMRange(ctx context.Context, fromTimestamp int, toTimestamp int, filterExpr []string) *MapStringSliceInterfaceCmd
|
||||
TSMRangeWithArgs(ctx context.Context, fromTimestamp int, toTimestamp int, filterExpr []string, options *TSMRangeOptions) *MapStringSliceInterfaceCmd
|
||||
TSMRevRange(ctx context.Context, fromTimestamp int, toTimestamp int, filterExpr []string) *MapStringSliceInterfaceCmd
|
||||
TSMRevRangeWithArgs(ctx context.Context, fromTimestamp int, toTimestamp int, filterExpr []string, options *TSMRevRangeOptions) *MapStringSliceInterfaceCmd
|
||||
TSMGet(ctx context.Context, filters []string) *MapStringSliceInterfaceCmd
|
||||
TSMGetWithArgs(ctx context.Context, filters []string, options *TSMGetOptions) *MapStringSliceInterfaceCmd
|
||||
}
|
||||
|
||||
type TSOptions struct {
|
||||
Retention int
|
||||
ChunkSize int
|
||||
Encoding string
|
||||
DuplicatePolicy string
|
||||
Labels map[string]string
|
||||
IgnoreMaxTimeDiff int64
|
||||
IgnoreMaxValDiff float64
|
||||
}
|
||||
type TSIncrDecrOptions struct {
|
||||
Timestamp int64
|
||||
Retention int
|
||||
ChunkSize int
|
||||
Uncompressed bool
|
||||
DuplicatePolicy string
|
||||
Labels map[string]string
|
||||
IgnoreMaxTimeDiff int64
|
||||
IgnoreMaxValDiff float64
|
||||
}
|
||||
|
||||
type TSAlterOptions struct {
|
||||
Retention int
|
||||
ChunkSize int
|
||||
DuplicatePolicy string
|
||||
Labels map[string]string
|
||||
IgnoreMaxTimeDiff int64
|
||||
IgnoreMaxValDiff float64
|
||||
}
|
||||
|
||||
type TSCreateRuleOptions struct {
|
||||
alignTimestamp int64
|
||||
}
|
||||
|
||||
type TSGetOptions struct {
|
||||
Latest bool
|
||||
}
|
||||
|
||||
type TSInfoOptions struct {
|
||||
Debug bool
|
||||
}
|
||||
type Aggregator int
|
||||
|
||||
const (
|
||||
Invalid = Aggregator(iota)
|
||||
Avg
|
||||
Sum
|
||||
Min
|
||||
Max
|
||||
Range
|
||||
Count
|
||||
First
|
||||
Last
|
||||
StdP
|
||||
StdS
|
||||
VarP
|
||||
VarS
|
||||
Twa
|
||||
)
|
||||
|
||||
func (a Aggregator) String() string {
|
||||
switch a {
|
||||
case Invalid:
|
||||
return ""
|
||||
case Avg:
|
||||
return "AVG"
|
||||
case Sum:
|
||||
return "SUM"
|
||||
case Min:
|
||||
return "MIN"
|
||||
case Max:
|
||||
return "MAX"
|
||||
case Range:
|
||||
return "RANGE"
|
||||
case Count:
|
||||
return "COUNT"
|
||||
case First:
|
||||
return "FIRST"
|
||||
case Last:
|
||||
return "LAST"
|
||||
case StdP:
|
||||
return "STD.P"
|
||||
case StdS:
|
||||
return "STD.S"
|
||||
case VarP:
|
||||
return "VAR.P"
|
||||
case VarS:
|
||||
return "VAR.S"
|
||||
case Twa:
|
||||
return "TWA"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
type TSRangeOptions struct {
|
||||
Latest bool
|
||||
FilterByTS []int
|
||||
FilterByValue []int
|
||||
Count int
|
||||
Align interface{}
|
||||
Aggregator Aggregator
|
||||
BucketDuration int
|
||||
BucketTimestamp interface{}
|
||||
Empty bool
|
||||
}
|
||||
|
||||
type TSRevRangeOptions struct {
|
||||
Latest bool
|
||||
FilterByTS []int
|
||||
FilterByValue []int
|
||||
Count int
|
||||
Align interface{}
|
||||
Aggregator Aggregator
|
||||
BucketDuration int
|
||||
BucketTimestamp interface{}
|
||||
Empty bool
|
||||
}
|
||||
|
||||
type TSMRangeOptions struct {
|
||||
Latest bool
|
||||
FilterByTS []int
|
||||
FilterByValue []int
|
||||
WithLabels bool
|
||||
SelectedLabels []interface{}
|
||||
Count int
|
||||
Align interface{}
|
||||
Aggregator Aggregator
|
||||
BucketDuration int
|
||||
BucketTimestamp interface{}
|
||||
Empty bool
|
||||
GroupByLabel interface{}
|
||||
Reducer interface{}
|
||||
}
|
||||
|
||||
type TSMRevRangeOptions struct {
|
||||
Latest bool
|
||||
FilterByTS []int
|
||||
FilterByValue []int
|
||||
WithLabels bool
|
||||
SelectedLabels []interface{}
|
||||
Count int
|
||||
Align interface{}
|
||||
Aggregator Aggregator
|
||||
BucketDuration int
|
||||
BucketTimestamp interface{}
|
||||
Empty bool
|
||||
GroupByLabel interface{}
|
||||
Reducer interface{}
|
||||
}
|
||||
|
||||
type TSMGetOptions struct {
|
||||
Latest bool
|
||||
WithLabels bool
|
||||
SelectedLabels []interface{}
|
||||
}
|
||||
|
||||
// TSAdd - Adds one or more observations to a t-digest sketch.
|
||||
// For more information - https://redis.io/commands/ts.add/
|
||||
func (c cmdable) TSAdd(ctx context.Context, key string, timestamp interface{}, value float64) *IntCmd {
|
||||
args := []interface{}{"TS.ADD", key, timestamp, value}
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// TSAddWithArgs - Adds one or more observations to a t-digest sketch.
|
||||
// This function also allows for specifying additional options such as:
|
||||
// Retention, ChunkSize, Encoding, DuplicatePolicy and Labels.
|
||||
// For more information - https://redis.io/commands/ts.add/
|
||||
func (c cmdable) TSAddWithArgs(ctx context.Context, key string, timestamp interface{}, value float64, options *TSOptions) *IntCmd {
|
||||
args := []interface{}{"TS.ADD", key, timestamp, value}
|
||||
if options != nil {
|
||||
if options.Retention != 0 {
|
||||
args = append(args, "RETENTION", options.Retention)
|
||||
}
|
||||
if options.ChunkSize != 0 {
|
||||
args = append(args, "CHUNK_SIZE", options.ChunkSize)
|
||||
}
|
||||
if options.Encoding != "" {
|
||||
args = append(args, "ENCODING", options.Encoding)
|
||||
}
|
||||
|
||||
if options.DuplicatePolicy != "" {
|
||||
args = append(args, "DUPLICATE_POLICY", options.DuplicatePolicy)
|
||||
}
|
||||
if options.Labels != nil {
|
||||
args = append(args, "LABELS")
|
||||
for label, value := range options.Labels {
|
||||
args = append(args, label, value)
|
||||
}
|
||||
}
|
||||
if options.IgnoreMaxTimeDiff != 0 || options.IgnoreMaxValDiff != 0 {
|
||||
args = append(args, "IGNORE", options.IgnoreMaxTimeDiff, options.IgnoreMaxValDiff)
|
||||
}
|
||||
}
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// TSCreate - Creates a new time-series key.
|
||||
// For more information - https://redis.io/commands/ts.create/
|
||||
func (c cmdable) TSCreate(ctx context.Context, key string) *StatusCmd {
|
||||
args := []interface{}{"TS.CREATE", key}
|
||||
cmd := NewStatusCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// TSCreateWithArgs - Creates a new time-series key with additional options.
|
||||
// This function allows for specifying additional options such as:
|
||||
// Retention, ChunkSize, Encoding, DuplicatePolicy and Labels.
|
||||
// For more information - https://redis.io/commands/ts.create/
|
||||
func (c cmdable) TSCreateWithArgs(ctx context.Context, key string, options *TSOptions) *StatusCmd {
|
||||
args := []interface{}{"TS.CREATE", key}
|
||||
if options != nil {
|
||||
if options.Retention != 0 {
|
||||
args = append(args, "RETENTION", options.Retention)
|
||||
}
|
||||
if options.ChunkSize != 0 {
|
||||
args = append(args, "CHUNK_SIZE", options.ChunkSize)
|
||||
}
|
||||
if options.Encoding != "" {
|
||||
args = append(args, "ENCODING", options.Encoding)
|
||||
}
|
||||
|
||||
if options.DuplicatePolicy != "" {
|
||||
args = append(args, "DUPLICATE_POLICY", options.DuplicatePolicy)
|
||||
}
|
||||
if options.Labels != nil {
|
||||
args = append(args, "LABELS")
|
||||
for label, value := range options.Labels {
|
||||
args = append(args, label, value)
|
||||
}
|
||||
}
|
||||
if options.IgnoreMaxTimeDiff != 0 || options.IgnoreMaxValDiff != 0 {
|
||||
args = append(args, "IGNORE", options.IgnoreMaxTimeDiff, options.IgnoreMaxValDiff)
|
||||
}
|
||||
}
|
||||
cmd := NewStatusCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// TSAlter - Alters an existing time-series key with additional options.
|
||||
// This function allows for specifying additional options such as:
|
||||
// Retention, ChunkSize and DuplicatePolicy.
|
||||
// For more information - https://redis.io/commands/ts.alter/
|
||||
func (c cmdable) TSAlter(ctx context.Context, key string, options *TSAlterOptions) *StatusCmd {
|
||||
args := []interface{}{"TS.ALTER", key}
|
||||
if options != nil {
|
||||
if options.Retention != 0 {
|
||||
args = append(args, "RETENTION", options.Retention)
|
||||
}
|
||||
if options.ChunkSize != 0 {
|
||||
args = append(args, "CHUNK_SIZE", options.ChunkSize)
|
||||
}
|
||||
if options.DuplicatePolicy != "" {
|
||||
args = append(args, "DUPLICATE_POLICY", options.DuplicatePolicy)
|
||||
}
|
||||
if options.Labels != nil {
|
||||
args = append(args, "LABELS")
|
||||
for label, value := range options.Labels {
|
||||
args = append(args, label, value)
|
||||
}
|
||||
}
|
||||
if options.IgnoreMaxTimeDiff != 0 || options.IgnoreMaxValDiff != 0 {
|
||||
args = append(args, "IGNORE", options.IgnoreMaxTimeDiff, options.IgnoreMaxValDiff)
|
||||
}
|
||||
}
|
||||
cmd := NewStatusCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// TSCreateRule - Creates a compaction rule from sourceKey to destKey.
|
||||
// For more information - https://redis.io/commands/ts.createrule/
|
||||
func (c cmdable) TSCreateRule(ctx context.Context, sourceKey string, destKey string, aggregator Aggregator, bucketDuration int) *StatusCmd {
|
||||
args := []interface{}{"TS.CREATERULE", sourceKey, destKey, "AGGREGATION", aggregator.String(), bucketDuration}
|
||||
cmd := NewStatusCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// TSCreateRuleWithArgs - Creates a compaction rule from sourceKey to destKey with additional option.
|
||||
// This function allows for specifying additional option such as:
|
||||
// alignTimestamp.
|
||||
// For more information - https://redis.io/commands/ts.createrule/
|
||||
func (c cmdable) TSCreateRuleWithArgs(ctx context.Context, sourceKey string, destKey string, aggregator Aggregator, bucketDuration int, options *TSCreateRuleOptions) *StatusCmd {
|
||||
args := []interface{}{"TS.CREATERULE", sourceKey, destKey, "AGGREGATION", aggregator.String(), bucketDuration}
|
||||
if options != nil {
|
||||
if options.alignTimestamp != 0 {
|
||||
args = append(args, options.alignTimestamp)
|
||||
}
|
||||
}
|
||||
cmd := NewStatusCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// TSIncrBy - Increments the value of a time-series key by the specified timestamp.
|
||||
// For more information - https://redis.io/commands/ts.incrby/
|
||||
func (c cmdable) TSIncrBy(ctx context.Context, Key string, timestamp float64) *IntCmd {
|
||||
args := []interface{}{"TS.INCRBY", Key, timestamp}
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// TSIncrByWithArgs - Increments the value of a time-series key by the specified timestamp with additional options.
|
||||
// This function allows for specifying additional options such as:
|
||||
// Timestamp, Retention, ChunkSize, Uncompressed and Labels.
|
||||
// For more information - https://redis.io/commands/ts.incrby/
|
||||
func (c cmdable) TSIncrByWithArgs(ctx context.Context, key string, timestamp float64, options *TSIncrDecrOptions) *IntCmd {
|
||||
args := []interface{}{"TS.INCRBY", key, timestamp}
|
||||
if options != nil {
|
||||
if options.Timestamp != 0 {
|
||||
args = append(args, "TIMESTAMP", options.Timestamp)
|
||||
}
|
||||
if options.Retention != 0 {
|
||||
args = append(args, "RETENTION", options.Retention)
|
||||
}
|
||||
if options.ChunkSize != 0 {
|
||||
args = append(args, "CHUNK_SIZE", options.ChunkSize)
|
||||
}
|
||||
if options.Uncompressed {
|
||||
args = append(args, "UNCOMPRESSED")
|
||||
}
|
||||
if options.DuplicatePolicy != "" {
|
||||
args = append(args, "DUPLICATE_POLICY", options.DuplicatePolicy)
|
||||
}
|
||||
if options.Labels != nil {
|
||||
args = append(args, "LABELS")
|
||||
for label, value := range options.Labels {
|
||||
args = append(args, label, value)
|
||||
}
|
||||
}
|
||||
if options.IgnoreMaxTimeDiff != 0 || options.IgnoreMaxValDiff != 0 {
|
||||
args = append(args, "IGNORE", options.IgnoreMaxTimeDiff, options.IgnoreMaxValDiff)
|
||||
}
|
||||
}
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// TSDecrBy - Decrements the value of a time-series key by the specified timestamp.
|
||||
// For more information - https://redis.io/commands/ts.decrby/
|
||||
func (c cmdable) TSDecrBy(ctx context.Context, Key string, timestamp float64) *IntCmd {
|
||||
args := []interface{}{"TS.DECRBY", Key, timestamp}
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// TSDecrByWithArgs - Decrements the value of a time-series key by the specified timestamp with additional options.
|
||||
// This function allows for specifying additional options such as:
|
||||
// Timestamp, Retention, ChunkSize, Uncompressed and Labels.
|
||||
// For more information - https://redis.io/commands/ts.decrby/
|
||||
func (c cmdable) TSDecrByWithArgs(ctx context.Context, key string, timestamp float64, options *TSIncrDecrOptions) *IntCmd {
|
||||
args := []interface{}{"TS.DECRBY", key, timestamp}
|
||||
if options != nil {
|
||||
if options.Timestamp != 0 {
|
||||
args = append(args, "TIMESTAMP", options.Timestamp)
|
||||
}
|
||||
if options.Retention != 0 {
|
||||
args = append(args, "RETENTION", options.Retention)
|
||||
}
|
||||
if options.ChunkSize != 0 {
|
||||
args = append(args, "CHUNK_SIZE", options.ChunkSize)
|
||||
}
|
||||
if options.Uncompressed {
|
||||
args = append(args, "UNCOMPRESSED")
|
||||
}
|
||||
if options.DuplicatePolicy != "" {
|
||||
args = append(args, "DUPLICATE_POLICY", options.DuplicatePolicy)
|
||||
}
|
||||
if options.Labels != nil {
|
||||
args = append(args, "LABELS")
|
||||
for label, value := range options.Labels {
|
||||
args = append(args, label, value)
|
||||
}
|
||||
}
|
||||
if options.IgnoreMaxTimeDiff != 0 || options.IgnoreMaxValDiff != 0 {
|
||||
args = append(args, "IGNORE", options.IgnoreMaxTimeDiff, options.IgnoreMaxValDiff)
|
||||
}
|
||||
}
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// TSDel - Deletes a range of samples from a time-series key.
|
||||
// For more information - https://redis.io/commands/ts.del/
|
||||
func (c cmdable) TSDel(ctx context.Context, Key string, fromTimestamp int, toTimestamp int) *IntCmd {
|
||||
args := []interface{}{"TS.DEL", Key, fromTimestamp, toTimestamp}
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// TSDeleteRule - Deletes a compaction rule from sourceKey to destKey.
|
||||
// For more information - https://redis.io/commands/ts.deleterule/
|
||||
func (c cmdable) TSDeleteRule(ctx context.Context, sourceKey string, destKey string) *StatusCmd {
|
||||
args := []interface{}{"TS.DELETERULE", sourceKey, destKey}
|
||||
cmd := NewStatusCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// TSGetWithArgs - Gets the last sample of a time-series key with additional option.
|
||||
// This function allows for specifying additional option such as:
|
||||
// Latest.
|
||||
// For more information - https://redis.io/commands/ts.get/
|
||||
func (c cmdable) TSGetWithArgs(ctx context.Context, key string, options *TSGetOptions) *TSTimestampValueCmd {
|
||||
args := []interface{}{"TS.GET", key}
|
||||
if options != nil {
|
||||
if options.Latest {
|
||||
args = append(args, "LATEST")
|
||||
}
|
||||
}
|
||||
cmd := newTSTimestampValueCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// TSGet - Gets the last sample of a time-series key.
|
||||
// For more information - https://redis.io/commands/ts.get/
|
||||
func (c cmdable) TSGet(ctx context.Context, key string) *TSTimestampValueCmd {
|
||||
args := []interface{}{"TS.GET", key}
|
||||
cmd := newTSTimestampValueCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
type TSTimestampValue struct {
|
||||
Timestamp int64
|
||||
Value float64
|
||||
}
|
||||
type TSTimestampValueCmd struct {
|
||||
baseCmd
|
||||
val TSTimestampValue
|
||||
}
|
||||
|
||||
func newTSTimestampValueCmd(ctx context.Context, args ...interface{}) *TSTimestampValueCmd {
|
||||
return &TSTimestampValueCmd{
|
||||
baseCmd: baseCmd{
|
||||
ctx: ctx,
|
||||
args: args,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *TSTimestampValueCmd) String() string {
|
||||
return cmdString(cmd, cmd.val)
|
||||
}
|
||||
|
||||
func (cmd *TSTimestampValueCmd) SetVal(val TSTimestampValue) {
|
||||
cmd.val = val
|
||||
}
|
||||
|
||||
func (cmd *TSTimestampValueCmd) Result() (TSTimestampValue, error) {
|
||||
return cmd.val, cmd.err
|
||||
}
|
||||
|
||||
func (cmd *TSTimestampValueCmd) Val() TSTimestampValue {
|
||||
return cmd.val
|
||||
}
|
||||
|
||||
func (cmd *TSTimestampValueCmd) readReply(rd *proto.Reader) (err error) {
|
||||
n, err := rd.ReadMapLen()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd.val = TSTimestampValue{}
|
||||
for i := 0; i < n; i++ {
|
||||
timestamp, err := rd.ReadInt()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
value, err := rd.ReadString()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd.val.Timestamp = timestamp
|
||||
cmd.val.Value, err = strconv.ParseFloat(value, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TSInfo - Returns information about a time-series key.
|
||||
// For more information - https://redis.io/commands/ts.info/
|
||||
func (c cmdable) TSInfo(ctx context.Context, key string) *MapStringInterfaceCmd {
|
||||
args := []interface{}{"TS.INFO", key}
|
||||
cmd := NewMapStringInterfaceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// TSInfoWithArgs - Returns information about a time-series key with additional option.
|
||||
// This function allows for specifying additional option such as:
|
||||
// Debug.
|
||||
// For more information - https://redis.io/commands/ts.info/
|
||||
func (c cmdable) TSInfoWithArgs(ctx context.Context, key string, options *TSInfoOptions) *MapStringInterfaceCmd {
|
||||
args := []interface{}{"TS.INFO", key}
|
||||
if options != nil {
|
||||
if options.Debug {
|
||||
args = append(args, "DEBUG")
|
||||
}
|
||||
}
|
||||
cmd := NewMapStringInterfaceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// TSMAdd - Adds multiple samples to multiple time-series keys.
|
||||
// It accepts a slice of 'ktv' slices, each containing exactly three elements: key, timestamp, and value.
|
||||
// This struct must be provided for this command to work.
|
||||
// For more information - https://redis.io/commands/ts.madd/
|
||||
func (c cmdable) TSMAdd(ctx context.Context, ktvSlices [][]interface{}) *IntSliceCmd {
|
||||
args := []interface{}{"TS.MADD"}
|
||||
for _, ktv := range ktvSlices {
|
||||
args = append(args, ktv...)
|
||||
}
|
||||
cmd := NewIntSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// TSQueryIndex - Returns all the keys matching the filter expression.
|
||||
// For more information - https://redis.io/commands/ts.queryindex/
|
||||
func (c cmdable) TSQueryIndex(ctx context.Context, filterExpr []string) *StringSliceCmd {
|
||||
args := []interface{}{"TS.QUERYINDEX"}
|
||||
for _, f := range filterExpr {
|
||||
args = append(args, f)
|
||||
}
|
||||
cmd := NewStringSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// TSRevRange - Returns a range of samples from a time-series key in reverse order.
|
||||
// For more information - https://redis.io/commands/ts.revrange/
|
||||
func (c cmdable) TSRevRange(ctx context.Context, key string, fromTimestamp int, toTimestamp int) *TSTimestampValueSliceCmd {
|
||||
args := []interface{}{"TS.REVRANGE", key, fromTimestamp, toTimestamp}
|
||||
cmd := newTSTimestampValueSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// TSRevRangeWithArgs - Returns a range of samples from a time-series key in reverse order with additional options.
|
||||
// This function allows for specifying additional options such as:
|
||||
// Latest, FilterByTS, FilterByValue, Count, Align, Aggregator,
|
||||
// BucketDuration, BucketTimestamp and Empty.
|
||||
// For more information - https://redis.io/commands/ts.revrange/
|
||||
func (c cmdable) TSRevRangeWithArgs(ctx context.Context, key string, fromTimestamp int, toTimestamp int, options *TSRevRangeOptions) *TSTimestampValueSliceCmd {
|
||||
args := []interface{}{"TS.REVRANGE", key, fromTimestamp, toTimestamp}
|
||||
if options != nil {
|
||||
if options.Latest {
|
||||
args = append(args, "LATEST")
|
||||
}
|
||||
if options.FilterByTS != nil {
|
||||
args = append(args, "FILTER_BY_TS")
|
||||
for _, f := range options.FilterByTS {
|
||||
args = append(args, f)
|
||||
}
|
||||
}
|
||||
if options.FilterByValue != nil {
|
||||
args = append(args, "FILTER_BY_VALUE")
|
||||
for _, f := range options.FilterByValue {
|
||||
args = append(args, f)
|
||||
}
|
||||
}
|
||||
if options.Count != 0 {
|
||||
args = append(args, "COUNT", options.Count)
|
||||
}
|
||||
if options.Align != nil {
|
||||
args = append(args, "ALIGN", options.Align)
|
||||
}
|
||||
if options.Aggregator != 0 {
|
||||
args = append(args, "AGGREGATION", options.Aggregator.String())
|
||||
}
|
||||
if options.BucketDuration != 0 {
|
||||
args = append(args, options.BucketDuration)
|
||||
}
|
||||
if options.BucketTimestamp != nil {
|
||||
args = append(args, "BUCKETTIMESTAMP", options.BucketTimestamp)
|
||||
}
|
||||
if options.Empty {
|
||||
args = append(args, "EMPTY")
|
||||
}
|
||||
}
|
||||
cmd := newTSTimestampValueSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// TSRange - Returns a range of samples from a time-series key.
|
||||
// For more information - https://redis.io/commands/ts.range/
|
||||
func (c cmdable) TSRange(ctx context.Context, key string, fromTimestamp int, toTimestamp int) *TSTimestampValueSliceCmd {
|
||||
args := []interface{}{"TS.RANGE", key, fromTimestamp, toTimestamp}
|
||||
cmd := newTSTimestampValueSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// TSRangeWithArgs - Returns a range of samples from a time-series key with additional options.
|
||||
// This function allows for specifying additional options such as:
|
||||
// Latest, FilterByTS, FilterByValue, Count, Align, Aggregator,
|
||||
// BucketDuration, BucketTimestamp and Empty.
|
||||
// For more information - https://redis.io/commands/ts.range/
|
||||
func (c cmdable) TSRangeWithArgs(ctx context.Context, key string, fromTimestamp int, toTimestamp int, options *TSRangeOptions) *TSTimestampValueSliceCmd {
|
||||
args := []interface{}{"TS.RANGE", key, fromTimestamp, toTimestamp}
|
||||
if options != nil {
|
||||
if options.Latest {
|
||||
args = append(args, "LATEST")
|
||||
}
|
||||
if options.FilterByTS != nil {
|
||||
args = append(args, "FILTER_BY_TS")
|
||||
for _, f := range options.FilterByTS {
|
||||
args = append(args, f)
|
||||
}
|
||||
}
|
||||
if options.FilterByValue != nil {
|
||||
args = append(args, "FILTER_BY_VALUE")
|
||||
for _, f := range options.FilterByValue {
|
||||
args = append(args, f)
|
||||
}
|
||||
}
|
||||
if options.Count != 0 {
|
||||
args = append(args, "COUNT", options.Count)
|
||||
}
|
||||
if options.Align != nil {
|
||||
args = append(args, "ALIGN", options.Align)
|
||||
}
|
||||
if options.Aggregator != 0 {
|
||||
args = append(args, "AGGREGATION", options.Aggregator.String())
|
||||
}
|
||||
if options.BucketDuration != 0 {
|
||||
args = append(args, options.BucketDuration)
|
||||
}
|
||||
if options.BucketTimestamp != nil {
|
||||
args = append(args, "BUCKETTIMESTAMP", options.BucketTimestamp)
|
||||
}
|
||||
if options.Empty {
|
||||
args = append(args, "EMPTY")
|
||||
}
|
||||
}
|
||||
cmd := newTSTimestampValueSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
type TSTimestampValueSliceCmd struct {
|
||||
baseCmd
|
||||
val []TSTimestampValue
|
||||
}
|
||||
|
||||
func newTSTimestampValueSliceCmd(ctx context.Context, args ...interface{}) *TSTimestampValueSliceCmd {
|
||||
return &TSTimestampValueSliceCmd{
|
||||
baseCmd: baseCmd{
|
||||
ctx: ctx,
|
||||
args: args,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *TSTimestampValueSliceCmd) String() string {
|
||||
return cmdString(cmd, cmd.val)
|
||||
}
|
||||
|
||||
func (cmd *TSTimestampValueSliceCmd) SetVal(val []TSTimestampValue) {
|
||||
cmd.val = val
|
||||
}
|
||||
|
||||
func (cmd *TSTimestampValueSliceCmd) Result() ([]TSTimestampValue, error) {
|
||||
return cmd.val, cmd.err
|
||||
}
|
||||
|
||||
func (cmd *TSTimestampValueSliceCmd) Val() []TSTimestampValue {
|
||||
return cmd.val
|
||||
}
|
||||
|
||||
func (cmd *TSTimestampValueSliceCmd) readReply(rd *proto.Reader) (err error) {
|
||||
n, err := rd.ReadArrayLen()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd.val = make([]TSTimestampValue, n)
|
||||
for i := 0; i < n; i++ {
|
||||
_, _ = rd.ReadArrayLen()
|
||||
timestamp, err := rd.ReadInt()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
value, err := rd.ReadString()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd.val[i].Timestamp = timestamp
|
||||
cmd.val[i].Value, err = strconv.ParseFloat(value, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TSMRange - Returns a range of samples from multiple time-series keys.
|
||||
// For more information - https://redis.io/commands/ts.mrange/
|
||||
func (c cmdable) TSMRange(ctx context.Context, fromTimestamp int, toTimestamp int, filterExpr []string) *MapStringSliceInterfaceCmd {
|
||||
args := []interface{}{"TS.MRANGE", fromTimestamp, toTimestamp, "FILTER"}
|
||||
for _, f := range filterExpr {
|
||||
args = append(args, f)
|
||||
}
|
||||
cmd := NewMapStringSliceInterfaceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// TSMRangeWithArgs - Returns a range of samples from multiple time-series keys with additional options.
|
||||
// This function allows for specifying additional options such as:
|
||||
// Latest, FilterByTS, FilterByValue, WithLabels, SelectedLabels,
|
||||
// Count, Align, Aggregator, BucketDuration, BucketTimestamp,
|
||||
// Empty, GroupByLabel and Reducer.
|
||||
// For more information - https://redis.io/commands/ts.mrange/
|
||||
func (c cmdable) TSMRangeWithArgs(ctx context.Context, fromTimestamp int, toTimestamp int, filterExpr []string, options *TSMRangeOptions) *MapStringSliceInterfaceCmd {
|
||||
args := []interface{}{"TS.MRANGE", fromTimestamp, toTimestamp}
|
||||
if options != nil {
|
||||
if options.Latest {
|
||||
args = append(args, "LATEST")
|
||||
}
|
||||
if options.FilterByTS != nil {
|
||||
args = append(args, "FILTER_BY_TS")
|
||||
for _, f := range options.FilterByTS {
|
||||
args = append(args, f)
|
||||
}
|
||||
}
|
||||
if options.FilterByValue != nil {
|
||||
args = append(args, "FILTER_BY_VALUE")
|
||||
for _, f := range options.FilterByValue {
|
||||
args = append(args, f)
|
||||
}
|
||||
}
|
||||
if options.WithLabels {
|
||||
args = append(args, "WITHLABELS")
|
||||
}
|
||||
if options.SelectedLabels != nil {
|
||||
args = append(args, "SELECTED_LABELS")
|
||||
args = append(args, options.SelectedLabels...)
|
||||
}
|
||||
if options.Count != 0 {
|
||||
args = append(args, "COUNT", options.Count)
|
||||
}
|
||||
if options.Align != nil {
|
||||
args = append(args, "ALIGN", options.Align)
|
||||
}
|
||||
if options.Aggregator != 0 {
|
||||
args = append(args, "AGGREGATION", options.Aggregator.String())
|
||||
}
|
||||
if options.BucketDuration != 0 {
|
||||
args = append(args, options.BucketDuration)
|
||||
}
|
||||
if options.BucketTimestamp != nil {
|
||||
args = append(args, "BUCKETTIMESTAMP", options.BucketTimestamp)
|
||||
}
|
||||
if options.Empty {
|
||||
args = append(args, "EMPTY")
|
||||
}
|
||||
}
|
||||
args = append(args, "FILTER")
|
||||
for _, f := range filterExpr {
|
||||
args = append(args, f)
|
||||
}
|
||||
if options != nil {
|
||||
if options.GroupByLabel != nil {
|
||||
args = append(args, "GROUPBY", options.GroupByLabel)
|
||||
}
|
||||
if options.Reducer != nil {
|
||||
args = append(args, "REDUCE", options.Reducer)
|
||||
}
|
||||
}
|
||||
cmd := NewMapStringSliceInterfaceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// TSMRevRange - Returns a range of samples from multiple time-series keys in reverse order.
|
||||
// For more information - https://redis.io/commands/ts.mrevrange/
|
||||
func (c cmdable) TSMRevRange(ctx context.Context, fromTimestamp int, toTimestamp int, filterExpr []string) *MapStringSliceInterfaceCmd {
|
||||
args := []interface{}{"TS.MREVRANGE", fromTimestamp, toTimestamp, "FILTER"}
|
||||
for _, f := range filterExpr {
|
||||
args = append(args, f)
|
||||
}
|
||||
cmd := NewMapStringSliceInterfaceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// TSMRevRangeWithArgs - Returns a range of samples from multiple time-series keys in reverse order with additional options.
|
||||
// This function allows for specifying additional options such as:
|
||||
// Latest, FilterByTS, FilterByValue, WithLabels, SelectedLabels,
|
||||
// Count, Align, Aggregator, BucketDuration, BucketTimestamp,
|
||||
// Empty, GroupByLabel and Reducer.
|
||||
// For more information - https://redis.io/commands/ts.mrevrange/
|
||||
func (c cmdable) TSMRevRangeWithArgs(ctx context.Context, fromTimestamp int, toTimestamp int, filterExpr []string, options *TSMRevRangeOptions) *MapStringSliceInterfaceCmd {
|
||||
args := []interface{}{"TS.MREVRANGE", fromTimestamp, toTimestamp}
|
||||
if options != nil {
|
||||
if options.Latest {
|
||||
args = append(args, "LATEST")
|
||||
}
|
||||
if options.FilterByTS != nil {
|
||||
args = append(args, "FILTER_BY_TS")
|
||||
for _, f := range options.FilterByTS {
|
||||
args = append(args, f)
|
||||
}
|
||||
}
|
||||
if options.FilterByValue != nil {
|
||||
args = append(args, "FILTER_BY_VALUE")
|
||||
for _, f := range options.FilterByValue {
|
||||
args = append(args, f)
|
||||
}
|
||||
}
|
||||
if options.WithLabels {
|
||||
args = append(args, "WITHLABELS")
|
||||
}
|
||||
if options.SelectedLabels != nil {
|
||||
args = append(args, "SELECTED_LABELS")
|
||||
args = append(args, options.SelectedLabels...)
|
||||
}
|
||||
if options.Count != 0 {
|
||||
args = append(args, "COUNT", options.Count)
|
||||
}
|
||||
if options.Align != nil {
|
||||
args = append(args, "ALIGN", options.Align)
|
||||
}
|
||||
if options.Aggregator != 0 {
|
||||
args = append(args, "AGGREGATION", options.Aggregator.String())
|
||||
}
|
||||
if options.BucketDuration != 0 {
|
||||
args = append(args, options.BucketDuration)
|
||||
}
|
||||
if options.BucketTimestamp != nil {
|
||||
args = append(args, "BUCKETTIMESTAMP", options.BucketTimestamp)
|
||||
}
|
||||
if options.Empty {
|
||||
args = append(args, "EMPTY")
|
||||
}
|
||||
}
|
||||
args = append(args, "FILTER")
|
||||
for _, f := range filterExpr {
|
||||
args = append(args, f)
|
||||
}
|
||||
if options != nil {
|
||||
if options.GroupByLabel != nil {
|
||||
args = append(args, "GROUPBY", options.GroupByLabel)
|
||||
}
|
||||
if options.Reducer != nil {
|
||||
args = append(args, "REDUCE", options.Reducer)
|
||||
}
|
||||
}
|
||||
cmd := NewMapStringSliceInterfaceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// TSMGet - Returns the last sample of multiple time-series keys.
|
||||
// For more information - https://redis.io/commands/ts.mget/
|
||||
func (c cmdable) TSMGet(ctx context.Context, filters []string) *MapStringSliceInterfaceCmd {
|
||||
args := []interface{}{"TS.MGET", "FILTER"}
|
||||
for _, f := range filters {
|
||||
args = append(args, f)
|
||||
}
|
||||
cmd := NewMapStringSliceInterfaceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// TSMGetWithArgs - Returns the last sample of multiple time-series keys with additional options.
|
||||
// This function allows for specifying additional options such as:
|
||||
// Latest, WithLabels and SelectedLabels.
|
||||
// For more information - https://redis.io/commands/ts.mget/
|
||||
func (c cmdable) TSMGetWithArgs(ctx context.Context, filters []string, options *TSMGetOptions) *MapStringSliceInterfaceCmd {
|
||||
args := []interface{}{"TS.MGET"}
|
||||
if options != nil {
|
||||
if options.Latest {
|
||||
args = append(args, "LATEST")
|
||||
}
|
||||
if options.WithLabels {
|
||||
args = append(args, "WITHLABELS")
|
||||
}
|
||||
if options.SelectedLabels != nil {
|
||||
args = append(args, "SELECTED_LABELS")
|
||||
args = append(args, options.SelectedLabels...)
|
||||
}
|
||||
}
|
||||
args = append(args, "FILTER")
|
||||
for _, f := range filters {
|
||||
args = append(args, f)
|
||||
}
|
||||
cmd := NewMapStringSliceInterfaceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
150
vendor/github.com/redis/go-redis/v9/tx.go
generated
vendored
Normal file
150
vendor/github.com/redis/go-redis/v9/tx.go
generated
vendored
Normal file
@@ -0,0 +1,150 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/redis/go-redis/v9/internal/pool"
|
||||
"github.com/redis/go-redis/v9/internal/proto"
|
||||
)
|
||||
|
||||
// TxFailedErr transaction redis failed.
|
||||
const TxFailedErr = proto.RedisError("redis: transaction failed")
|
||||
|
||||
// Tx implements Redis transactions as described in
|
||||
// http://redis.io/topics/transactions. It's NOT safe for concurrent use
|
||||
// by multiple goroutines, because Exec resets list of watched keys.
|
||||
//
|
||||
// If you don't need WATCH, use Pipeline instead.
|
||||
type Tx struct {
|
||||
baseClient
|
||||
cmdable
|
||||
statefulCmdable
|
||||
}
|
||||
|
||||
func (c *Client) newTx() *Tx {
|
||||
tx := Tx{
|
||||
baseClient: baseClient{
|
||||
opt: c.opt,
|
||||
connPool: pool.NewStickyConnPool(c.connPool),
|
||||
hooksMixin: c.hooksMixin.clone(),
|
||||
},
|
||||
}
|
||||
tx.init()
|
||||
return &tx
|
||||
}
|
||||
|
||||
func (c *Tx) init() {
|
||||
c.cmdable = c.Process
|
||||
c.statefulCmdable = c.Process
|
||||
|
||||
c.initHooks(hooks{
|
||||
dial: c.baseClient.dial,
|
||||
process: c.baseClient.process,
|
||||
pipeline: c.baseClient.processPipeline,
|
||||
txPipeline: c.baseClient.processTxPipeline,
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Tx) Process(ctx context.Context, cmd Cmder) error {
|
||||
err := c.processHook(ctx, cmd)
|
||||
cmd.SetErr(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Watch prepares a transaction and marks the keys to be watched
|
||||
// for conditional execution if there are any keys.
|
||||
//
|
||||
// The transaction is automatically closed when fn exits.
|
||||
func (c *Client) Watch(ctx context.Context, fn func(*Tx) error, keys ...string) error {
|
||||
tx := c.newTx()
|
||||
defer tx.Close(ctx)
|
||||
if len(keys) > 0 {
|
||||
if err := tx.Watch(ctx, keys...).Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return fn(tx)
|
||||
}
|
||||
|
||||
// Close closes the transaction, releasing any open resources.
|
||||
func (c *Tx) Close(ctx context.Context) error {
|
||||
_ = c.Unwatch(ctx).Err()
|
||||
return c.baseClient.Close()
|
||||
}
|
||||
|
||||
// Watch marks the keys to be watched for conditional execution
|
||||
// of a transaction.
|
||||
func (c *Tx) Watch(ctx context.Context, keys ...string) *StatusCmd {
|
||||
args := make([]interface{}, 1+len(keys))
|
||||
args[0] = "watch"
|
||||
for i, key := range keys {
|
||||
args[1+i] = key
|
||||
}
|
||||
cmd := NewStatusCmd(ctx, args...)
|
||||
_ = c.Process(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Unwatch flushes all the previously watched keys for a transaction.
|
||||
func (c *Tx) Unwatch(ctx context.Context, keys ...string) *StatusCmd {
|
||||
args := make([]interface{}, 1+len(keys))
|
||||
args[0] = "unwatch"
|
||||
for i, key := range keys {
|
||||
args[1+i] = key
|
||||
}
|
||||
cmd := NewStatusCmd(ctx, args...)
|
||||
_ = c.Process(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Pipeline creates a pipeline. Usually it is more convenient to use Pipelined.
|
||||
func (c *Tx) Pipeline() Pipeliner {
|
||||
pipe := Pipeline{
|
||||
exec: func(ctx context.Context, cmds []Cmder) error {
|
||||
return c.processPipelineHook(ctx, cmds)
|
||||
},
|
||||
}
|
||||
pipe.init()
|
||||
return &pipe
|
||||
}
|
||||
|
||||
// Pipelined executes commands queued in the fn outside of the transaction.
|
||||
// Use TxPipelined if you need transactional behavior.
|
||||
func (c *Tx) Pipelined(ctx context.Context, fn func(Pipeliner) error) ([]Cmder, error) {
|
||||
return c.Pipeline().Pipelined(ctx, fn)
|
||||
}
|
||||
|
||||
// TxPipelined executes commands queued in the fn in the transaction.
|
||||
//
|
||||
// When using WATCH, EXEC will execute commands only if the watched keys
|
||||
// were not modified, allowing for a check-and-set mechanism.
|
||||
//
|
||||
// Exec always returns list of commands. If transaction fails
|
||||
// TxFailedErr is returned. Otherwise Exec returns an error of the first
|
||||
// failed command or nil.
|
||||
func (c *Tx) TxPipelined(ctx context.Context, fn func(Pipeliner) error) ([]Cmder, error) {
|
||||
return c.TxPipeline().Pipelined(ctx, fn)
|
||||
}
|
||||
|
||||
// TxPipeline creates a pipeline. Usually it is more convenient to use TxPipelined.
|
||||
func (c *Tx) TxPipeline() Pipeliner {
|
||||
pipe := Pipeline{
|
||||
exec: func(ctx context.Context, cmds []Cmder) error {
|
||||
cmds = wrapMultiExec(ctx, cmds)
|
||||
return c.processTxPipelineHook(ctx, cmds)
|
||||
},
|
||||
}
|
||||
pipe.init()
|
||||
return &pipe
|
||||
}
|
||||
|
||||
func wrapMultiExec(ctx context.Context, cmds []Cmder) []Cmder {
|
||||
if len(cmds) == 0 {
|
||||
panic("not reached")
|
||||
}
|
||||
cmdsCopy := make([]Cmder, len(cmds)+2)
|
||||
cmdsCopy[0] = NewStatusCmd(ctx, "multi")
|
||||
copy(cmdsCopy[1:], cmds)
|
||||
cmdsCopy[len(cmdsCopy)-1] = NewSliceCmd(ctx, "exec")
|
||||
return cmdsCopy
|
||||
}
|
||||
314
vendor/github.com/redis/go-redis/v9/universal.go
generated
vendored
Normal file
314
vendor/github.com/redis/go-redis/v9/universal.go
generated
vendored
Normal file
@@ -0,0 +1,314 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/redis/go-redis/v9/auth"
|
||||
)
|
||||
|
||||
// UniversalOptions information is required by UniversalClient to establish
|
||||
// connections.
|
||||
type UniversalOptions struct {
|
||||
// Either a single address or a seed list of host:port addresses
|
||||
// of cluster/sentinel nodes.
|
||||
Addrs []string
|
||||
|
||||
// ClientName will execute the `CLIENT SETNAME ClientName` command for each conn.
|
||||
ClientName string
|
||||
|
||||
// Database to be selected after connecting to the server.
|
||||
// Only single-node and failover clients.
|
||||
DB int
|
||||
|
||||
// Common options.
|
||||
|
||||
Dialer func(ctx context.Context, network, addr string) (net.Conn, error)
|
||||
OnConnect func(ctx context.Context, cn *Conn) error
|
||||
|
||||
Protocol int
|
||||
Username string
|
||||
Password string
|
||||
// CredentialsProvider allows the username and password to be updated
|
||||
// before reconnecting. It should return the current username and password.
|
||||
CredentialsProvider func() (username string, password string)
|
||||
|
||||
// CredentialsProviderContext is an enhanced parameter of CredentialsProvider,
|
||||
// done to maintain API compatibility. In the future,
|
||||
// there might be a merge between CredentialsProviderContext and CredentialsProvider.
|
||||
// There will be a conflict between them; if CredentialsProviderContext exists, we will ignore CredentialsProvider.
|
||||
CredentialsProviderContext func(ctx context.Context) (username string, password string, err error)
|
||||
|
||||
// StreamingCredentialsProvider is used to retrieve the credentials
|
||||
// for the connection from an external source. Those credentials may change
|
||||
// during the connection lifetime. This is useful for managed identity
|
||||
// scenarios where the credentials are retrieved from an external source.
|
||||
//
|
||||
// Currently, this is a placeholder for the future implementation.
|
||||
StreamingCredentialsProvider auth.StreamingCredentialsProvider
|
||||
|
||||
SentinelUsername string
|
||||
SentinelPassword string
|
||||
|
||||
MaxRetries int
|
||||
MinRetryBackoff time.Duration
|
||||
MaxRetryBackoff time.Duration
|
||||
|
||||
DialTimeout time.Duration
|
||||
ReadTimeout time.Duration
|
||||
WriteTimeout time.Duration
|
||||
ContextTimeoutEnabled bool
|
||||
|
||||
// PoolFIFO uses FIFO mode for each node connection pool GET/PUT (default LIFO).
|
||||
PoolFIFO bool
|
||||
|
||||
PoolSize int
|
||||
PoolTimeout time.Duration
|
||||
MinIdleConns int
|
||||
MaxIdleConns int
|
||||
MaxActiveConns int
|
||||
ConnMaxIdleTime time.Duration
|
||||
ConnMaxLifetime time.Duration
|
||||
|
||||
TLSConfig *tls.Config
|
||||
|
||||
// Only cluster clients.
|
||||
|
||||
MaxRedirects int
|
||||
ReadOnly bool
|
||||
RouteByLatency bool
|
||||
RouteRandomly bool
|
||||
|
||||
// MasterName is the sentinel master name.
|
||||
// Only for failover clients.
|
||||
MasterName string
|
||||
|
||||
// DisableIndentity - Disable set-lib on connect.
|
||||
//
|
||||
// default: false
|
||||
//
|
||||
// Deprecated: Use DisableIdentity instead.
|
||||
DisableIndentity bool
|
||||
|
||||
// DisableIdentity is used to disable CLIENT SETINFO command on connect.
|
||||
//
|
||||
// default: false
|
||||
DisableIdentity bool
|
||||
|
||||
IdentitySuffix string
|
||||
UnstableResp3 bool
|
||||
|
||||
// IsClusterMode can be used when only one Addrs is provided (e.g. Elasticache supports setting up cluster mode with configuration endpoint).
|
||||
IsClusterMode bool
|
||||
}
|
||||
|
||||
// Cluster returns cluster options created from the universal options.
|
||||
func (o *UniversalOptions) Cluster() *ClusterOptions {
|
||||
if len(o.Addrs) == 0 {
|
||||
o.Addrs = []string{"127.0.0.1:6379"}
|
||||
}
|
||||
|
||||
return &ClusterOptions{
|
||||
Addrs: o.Addrs,
|
||||
ClientName: o.ClientName,
|
||||
Dialer: o.Dialer,
|
||||
OnConnect: o.OnConnect,
|
||||
|
||||
Protocol: o.Protocol,
|
||||
Username: o.Username,
|
||||
Password: o.Password,
|
||||
CredentialsProvider: o.CredentialsProvider,
|
||||
CredentialsProviderContext: o.CredentialsProviderContext,
|
||||
StreamingCredentialsProvider: o.StreamingCredentialsProvider,
|
||||
|
||||
MaxRedirects: o.MaxRedirects,
|
||||
ReadOnly: o.ReadOnly,
|
||||
RouteByLatency: o.RouteByLatency,
|
||||
RouteRandomly: o.RouteRandomly,
|
||||
|
||||
MaxRetries: o.MaxRetries,
|
||||
MinRetryBackoff: o.MinRetryBackoff,
|
||||
MaxRetryBackoff: o.MaxRetryBackoff,
|
||||
|
||||
DialTimeout: o.DialTimeout,
|
||||
ReadTimeout: o.ReadTimeout,
|
||||
WriteTimeout: o.WriteTimeout,
|
||||
ContextTimeoutEnabled: o.ContextTimeoutEnabled,
|
||||
|
||||
PoolFIFO: o.PoolFIFO,
|
||||
|
||||
PoolSize: o.PoolSize,
|
||||
PoolTimeout: o.PoolTimeout,
|
||||
MinIdleConns: o.MinIdleConns,
|
||||
MaxIdleConns: o.MaxIdleConns,
|
||||
MaxActiveConns: o.MaxActiveConns,
|
||||
ConnMaxIdleTime: o.ConnMaxIdleTime,
|
||||
ConnMaxLifetime: o.ConnMaxLifetime,
|
||||
|
||||
TLSConfig: o.TLSConfig,
|
||||
|
||||
DisableIdentity: o.DisableIdentity,
|
||||
DisableIndentity: o.DisableIndentity,
|
||||
IdentitySuffix: o.IdentitySuffix,
|
||||
UnstableResp3: o.UnstableResp3,
|
||||
}
|
||||
}
|
||||
|
||||
// Failover returns failover options created from the universal options.
|
||||
func (o *UniversalOptions) Failover() *FailoverOptions {
|
||||
if len(o.Addrs) == 0 {
|
||||
o.Addrs = []string{"127.0.0.1:26379"}
|
||||
}
|
||||
|
||||
return &FailoverOptions{
|
||||
SentinelAddrs: o.Addrs,
|
||||
MasterName: o.MasterName,
|
||||
ClientName: o.ClientName,
|
||||
|
||||
Dialer: o.Dialer,
|
||||
OnConnect: o.OnConnect,
|
||||
|
||||
DB: o.DB,
|
||||
Protocol: o.Protocol,
|
||||
Username: o.Username,
|
||||
Password: o.Password,
|
||||
CredentialsProvider: o.CredentialsProvider,
|
||||
CredentialsProviderContext: o.CredentialsProviderContext,
|
||||
StreamingCredentialsProvider: o.StreamingCredentialsProvider,
|
||||
|
||||
SentinelUsername: o.SentinelUsername,
|
||||
SentinelPassword: o.SentinelPassword,
|
||||
|
||||
RouteByLatency: o.RouteByLatency,
|
||||
RouteRandomly: o.RouteRandomly,
|
||||
|
||||
MaxRetries: o.MaxRetries,
|
||||
MinRetryBackoff: o.MinRetryBackoff,
|
||||
MaxRetryBackoff: o.MaxRetryBackoff,
|
||||
|
||||
DialTimeout: o.DialTimeout,
|
||||
ReadTimeout: o.ReadTimeout,
|
||||
WriteTimeout: o.WriteTimeout,
|
||||
ContextTimeoutEnabled: o.ContextTimeoutEnabled,
|
||||
|
||||
PoolFIFO: o.PoolFIFO,
|
||||
PoolSize: o.PoolSize,
|
||||
PoolTimeout: o.PoolTimeout,
|
||||
MinIdleConns: o.MinIdleConns,
|
||||
MaxIdleConns: o.MaxIdleConns,
|
||||
MaxActiveConns: o.MaxActiveConns,
|
||||
ConnMaxIdleTime: o.ConnMaxIdleTime,
|
||||
ConnMaxLifetime: o.ConnMaxLifetime,
|
||||
|
||||
TLSConfig: o.TLSConfig,
|
||||
|
||||
ReplicaOnly: o.ReadOnly,
|
||||
|
||||
DisableIdentity: o.DisableIdentity,
|
||||
DisableIndentity: o.DisableIndentity,
|
||||
IdentitySuffix: o.IdentitySuffix,
|
||||
UnstableResp3: o.UnstableResp3,
|
||||
}
|
||||
}
|
||||
|
||||
// Simple returns basic options created from the universal options.
|
||||
func (o *UniversalOptions) Simple() *Options {
|
||||
addr := "127.0.0.1:6379"
|
||||
if len(o.Addrs) > 0 {
|
||||
addr = o.Addrs[0]
|
||||
}
|
||||
|
||||
return &Options{
|
||||
Addr: addr,
|
||||
ClientName: o.ClientName,
|
||||
Dialer: o.Dialer,
|
||||
OnConnect: o.OnConnect,
|
||||
|
||||
DB: o.DB,
|
||||
Protocol: o.Protocol,
|
||||
Username: o.Username,
|
||||
Password: o.Password,
|
||||
CredentialsProvider: o.CredentialsProvider,
|
||||
CredentialsProviderContext: o.CredentialsProviderContext,
|
||||
StreamingCredentialsProvider: o.StreamingCredentialsProvider,
|
||||
|
||||
MaxRetries: o.MaxRetries,
|
||||
MinRetryBackoff: o.MinRetryBackoff,
|
||||
MaxRetryBackoff: o.MaxRetryBackoff,
|
||||
|
||||
DialTimeout: o.DialTimeout,
|
||||
ReadTimeout: o.ReadTimeout,
|
||||
WriteTimeout: o.WriteTimeout,
|
||||
ContextTimeoutEnabled: o.ContextTimeoutEnabled,
|
||||
|
||||
PoolFIFO: o.PoolFIFO,
|
||||
PoolSize: o.PoolSize,
|
||||
PoolTimeout: o.PoolTimeout,
|
||||
MinIdleConns: o.MinIdleConns,
|
||||
MaxIdleConns: o.MaxIdleConns,
|
||||
MaxActiveConns: o.MaxActiveConns,
|
||||
ConnMaxIdleTime: o.ConnMaxIdleTime,
|
||||
ConnMaxLifetime: o.ConnMaxLifetime,
|
||||
|
||||
TLSConfig: o.TLSConfig,
|
||||
|
||||
DisableIdentity: o.DisableIdentity,
|
||||
DisableIndentity: o.DisableIndentity,
|
||||
IdentitySuffix: o.IdentitySuffix,
|
||||
UnstableResp3: o.UnstableResp3,
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
// UniversalClient is an abstract client which - based on the provided options -
|
||||
// represents either a ClusterClient, a FailoverClient, or a single-node Client.
|
||||
// This can be useful for testing cluster-specific applications locally or having different
|
||||
// clients in different environments.
|
||||
type UniversalClient interface {
|
||||
Cmdable
|
||||
AddHook(Hook)
|
||||
Watch(ctx context.Context, fn func(*Tx) error, keys ...string) error
|
||||
Do(ctx context.Context, args ...interface{}) *Cmd
|
||||
Process(ctx context.Context, cmd Cmder) error
|
||||
Subscribe(ctx context.Context, channels ...string) *PubSub
|
||||
PSubscribe(ctx context.Context, channels ...string) *PubSub
|
||||
SSubscribe(ctx context.Context, channels ...string) *PubSub
|
||||
Close() error
|
||||
PoolStats() *PoolStats
|
||||
}
|
||||
|
||||
var (
|
||||
_ UniversalClient = (*Client)(nil)
|
||||
_ UniversalClient = (*ClusterClient)(nil)
|
||||
_ UniversalClient = (*Ring)(nil)
|
||||
)
|
||||
|
||||
// NewUniversalClient returns a new multi client. The type of the returned client depends
|
||||
// on the following conditions:
|
||||
//
|
||||
// 1. If the MasterName option is specified with RouteByLatency, RouteRandomly or IsClusterMode,
|
||||
// a FailoverClusterClient is returned.
|
||||
// 2. If the MasterName option is specified without RouteByLatency, RouteRandomly or IsClusterMode,
|
||||
// a sentinel-backed FailoverClient is returned.
|
||||
// 3. If the number of Addrs is two or more, or IsClusterMode option is specified,
|
||||
// a ClusterClient is returned.
|
||||
// 4. Otherwise, a single-node Client is returned.
|
||||
func NewUniversalClient(opts *UniversalOptions) UniversalClient {
|
||||
if opts == nil {
|
||||
panic("redis: NewUniversalClient nil options")
|
||||
}
|
||||
|
||||
switch {
|
||||
case opts.MasterName != "" && (opts.RouteByLatency || opts.RouteRandomly || opts.IsClusterMode):
|
||||
return NewFailoverClusterClient(opts.Failover())
|
||||
case opts.MasterName != "":
|
||||
return NewFailoverClient(opts.Failover())
|
||||
case len(opts.Addrs) > 1 || opts.IsClusterMode:
|
||||
return NewClusterClient(opts.Cluster())
|
||||
default:
|
||||
return NewClient(opts.Simple())
|
||||
}
|
||||
}
|
||||
348
vendor/github.com/redis/go-redis/v9/vectorset_commands.go
generated
vendored
Normal file
348
vendor/github.com/redis/go-redis/v9/vectorset_commands.go
generated
vendored
Normal file
@@ -0,0 +1,348 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// note: the APIs is experimental and may be subject to change.
|
||||
type VectorSetCmdable interface {
|
||||
VAdd(ctx context.Context, key, element string, val Vector) *BoolCmd
|
||||
VAddWithArgs(ctx context.Context, key, element string, val Vector, addArgs *VAddArgs) *BoolCmd
|
||||
VCard(ctx context.Context, key string) *IntCmd
|
||||
VDim(ctx context.Context, key string) *IntCmd
|
||||
VEmb(ctx context.Context, key, element string, raw bool) *SliceCmd
|
||||
VGetAttr(ctx context.Context, key, element string) *StringCmd
|
||||
VInfo(ctx context.Context, key string) *MapStringInterfaceCmd
|
||||
VLinks(ctx context.Context, key, element string) *StringSliceCmd
|
||||
VLinksWithScores(ctx context.Context, key, element string) *VectorScoreSliceCmd
|
||||
VRandMember(ctx context.Context, key string) *StringCmd
|
||||
VRandMemberCount(ctx context.Context, key string, count int) *StringSliceCmd
|
||||
VRem(ctx context.Context, key, element string) *BoolCmd
|
||||
VSetAttr(ctx context.Context, key, element string, attr interface{}) *BoolCmd
|
||||
VClearAttributes(ctx context.Context, key, element string) *BoolCmd
|
||||
VSim(ctx context.Context, key string, val Vector) *StringSliceCmd
|
||||
VSimWithScores(ctx context.Context, key string, val Vector) *VectorScoreSliceCmd
|
||||
VSimWithArgs(ctx context.Context, key string, val Vector, args *VSimArgs) *StringSliceCmd
|
||||
VSimWithArgsWithScores(ctx context.Context, key string, val Vector, args *VSimArgs) *VectorScoreSliceCmd
|
||||
}
|
||||
|
||||
type Vector interface {
|
||||
Value() []any
|
||||
}
|
||||
|
||||
const (
|
||||
vectorFormatFP32 string = "FP32"
|
||||
vectorFormatValues string = "Values"
|
||||
)
|
||||
|
||||
type VectorFP32 struct {
|
||||
Val []byte
|
||||
}
|
||||
|
||||
func (v *VectorFP32) Value() []any {
|
||||
return []any{vectorFormatFP32, v.Val}
|
||||
}
|
||||
|
||||
var _ Vector = (*VectorFP32)(nil)
|
||||
|
||||
type VectorValues struct {
|
||||
Val []float64
|
||||
}
|
||||
|
||||
func (v *VectorValues) Value() []any {
|
||||
res := make([]any, 2+len(v.Val))
|
||||
res[0] = vectorFormatValues
|
||||
res[1] = len(v.Val)
|
||||
for i, v := range v.Val {
|
||||
res[2+i] = v
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
var _ Vector = (*VectorValues)(nil)
|
||||
|
||||
type VectorRef struct {
|
||||
Name string // the name of the referent vector
|
||||
}
|
||||
|
||||
func (v *VectorRef) Value() []any {
|
||||
return []any{"ele", v.Name}
|
||||
}
|
||||
|
||||
var _ Vector = (*VectorRef)(nil)
|
||||
|
||||
type VectorScore struct {
|
||||
Name string
|
||||
Score float64
|
||||
}
|
||||
|
||||
// `VADD key (FP32 | VALUES num) vector element`
|
||||
// note: the API is experimental and may be subject to change.
|
||||
func (c cmdable) VAdd(ctx context.Context, key, element string, val Vector) *BoolCmd {
|
||||
return c.VAddWithArgs(ctx, key, element, val, &VAddArgs{})
|
||||
}
|
||||
|
||||
type VAddArgs struct {
|
||||
// the REDUCE option must be passed immediately after the key
|
||||
Reduce int64
|
||||
Cas bool
|
||||
|
||||
// The NoQuant, Q8 and Bin options are mutually exclusive.
|
||||
NoQuant bool
|
||||
Q8 bool
|
||||
Bin bool
|
||||
|
||||
EF int64
|
||||
SetAttr string
|
||||
M int64
|
||||
}
|
||||
|
||||
func (v VAddArgs) reduce() int64 {
|
||||
return v.Reduce
|
||||
}
|
||||
|
||||
func (v VAddArgs) appendArgs(args []any) []any {
|
||||
if v.Cas {
|
||||
args = append(args, "cas")
|
||||
}
|
||||
|
||||
if v.NoQuant {
|
||||
args = append(args, "noquant")
|
||||
} else if v.Q8 {
|
||||
args = append(args, "q8")
|
||||
} else if v.Bin {
|
||||
args = append(args, "bin")
|
||||
}
|
||||
|
||||
if v.EF > 0 {
|
||||
args = append(args, "ef", strconv.FormatInt(v.EF, 10))
|
||||
}
|
||||
if len(v.SetAttr) > 0 {
|
||||
args = append(args, "setattr", v.SetAttr)
|
||||
}
|
||||
if v.M > 0 {
|
||||
args = append(args, "m", strconv.FormatInt(v.M, 10))
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
// `VADD key [REDUCE dim] (FP32 | VALUES num) vector element [CAS] [NOQUANT | Q8 | BIN] [EF build-exploration-factor] [SETATTR attributes] [M numlinks]`
|
||||
// note: the API is experimental and may be subject to change.
|
||||
func (c cmdable) VAddWithArgs(ctx context.Context, key, element string, val Vector, addArgs *VAddArgs) *BoolCmd {
|
||||
if addArgs == nil {
|
||||
addArgs = &VAddArgs{}
|
||||
}
|
||||
args := []any{"vadd", key}
|
||||
if addArgs.reduce() > 0 {
|
||||
args = append(args, "reduce", addArgs.reduce())
|
||||
}
|
||||
args = append(args, val.Value()...)
|
||||
args = append(args, element)
|
||||
args = addArgs.appendArgs(args)
|
||||
cmd := NewBoolCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// `VCARD key`
|
||||
// note: the API is experimental and may be subject to change.
|
||||
func (c cmdable) VCard(ctx context.Context, key string) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "vcard", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// `VDIM key`
|
||||
// note: the API is experimental and may be subject to change.
|
||||
func (c cmdable) VDim(ctx context.Context, key string) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "vdim", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// `VEMB key element [RAW]`
|
||||
// note: the API is experimental and may be subject to change.
|
||||
func (c cmdable) VEmb(ctx context.Context, key, element string, raw bool) *SliceCmd {
|
||||
args := []any{"vemb", key, element}
|
||||
if raw {
|
||||
args = append(args, "raw")
|
||||
}
|
||||
cmd := NewSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// `VGETATTR key element`
|
||||
// note: the API is experimental and may be subject to change.
|
||||
func (c cmdable) VGetAttr(ctx context.Context, key, element string) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "vgetattr", key, element)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// `VINFO key`
|
||||
// note: the API is experimental and may be subject to change.
|
||||
func (c cmdable) VInfo(ctx context.Context, key string) *MapStringInterfaceCmd {
|
||||
cmd := NewMapStringInterfaceCmd(ctx, "vinfo", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// `VLINKS key element`
|
||||
// note: the API is experimental and may be subject to change.
|
||||
func (c cmdable) VLinks(ctx context.Context, key, element string) *StringSliceCmd {
|
||||
cmd := NewStringSliceCmd(ctx, "vlinks", key, element)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// `VLINKS key element WITHSCORES`
|
||||
// note: the API is experimental and may be subject to change.
|
||||
func (c cmdable) VLinksWithScores(ctx context.Context, key, element string) *VectorScoreSliceCmd {
|
||||
cmd := NewVectorInfoSliceCmd(ctx, "vlinks", key, element, "withscores")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// `VRANDMEMBER key`
|
||||
// note: the API is experimental and may be subject to change.
|
||||
func (c cmdable) VRandMember(ctx context.Context, key string) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "vrandmember", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// `VRANDMEMBER key [count]`
|
||||
// note: the API is experimental and may be subject to change.
|
||||
func (c cmdable) VRandMemberCount(ctx context.Context, key string, count int) *StringSliceCmd {
|
||||
cmd := NewStringSliceCmd(ctx, "vrandmember", key, count)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// `VREM key element`
|
||||
// note: the API is experimental and may be subject to change.
|
||||
func (c cmdable) VRem(ctx context.Context, key, element string) *BoolCmd {
|
||||
cmd := NewBoolCmd(ctx, "vrem", key, element)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// `VSETATTR key element "{ JSON obj }"`
|
||||
// The `attr` must be something that can be marshaled to JSON (using encoding/JSON) unless
|
||||
// the argument is a string or []byte when we assume that it can be passed directly as JSON.
|
||||
//
|
||||
// note: the API is experimental and may be subject to change.
|
||||
func (c cmdable) VSetAttr(ctx context.Context, key, element string, attr interface{}) *BoolCmd {
|
||||
var attrStr string
|
||||
var err error
|
||||
switch v := attr.(type) {
|
||||
case string:
|
||||
attrStr = v
|
||||
case []byte:
|
||||
attrStr = string(v)
|
||||
default:
|
||||
var bytes []byte
|
||||
bytes, err = json.Marshal(v)
|
||||
if err != nil {
|
||||
// If marshalling fails, create the command and set the error; this command won't be executed.
|
||||
cmd := NewBoolCmd(ctx, "vsetattr", key, element, "")
|
||||
cmd.SetErr(err)
|
||||
return cmd
|
||||
}
|
||||
attrStr = string(bytes)
|
||||
}
|
||||
cmd := NewBoolCmd(ctx, "vsetattr", key, element, attrStr)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// `VClearAttributes` clear attributes on a vector set element.
|
||||
// The implementation of `VClearAttributes` is execute command `VSETATTR key element ""`.
|
||||
// note: the API is experimental and may be subject to change.
|
||||
func (c cmdable) VClearAttributes(ctx context.Context, key, element string) *BoolCmd {
|
||||
cmd := NewBoolCmd(ctx, "vsetattr", key, element, "")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// `VSIM key (ELE | FP32 | VALUES num) (vector | element)`
|
||||
// note: the API is experimental and may be subject to change.
|
||||
func (c cmdable) VSim(ctx context.Context, key string, val Vector) *StringSliceCmd {
|
||||
return c.VSimWithArgs(ctx, key, val, &VSimArgs{})
|
||||
}
|
||||
|
||||
// `VSIM key (ELE | FP32 | VALUES num) (vector | element) WITHSCORES`
|
||||
// note: the API is experimental and may be subject to change.
|
||||
func (c cmdable) VSimWithScores(ctx context.Context, key string, val Vector) *VectorScoreSliceCmd {
|
||||
return c.VSimWithArgsWithScores(ctx, key, val, &VSimArgs{})
|
||||
}
|
||||
|
||||
type VSimArgs struct {
|
||||
Count int64
|
||||
EF int64
|
||||
Filter string
|
||||
FilterEF int64
|
||||
Truth bool
|
||||
NoThread bool
|
||||
// The `VSim` command in Redis has the option, by the doc in Redis.io don't have.
|
||||
// Epsilon float64
|
||||
}
|
||||
|
||||
func (v VSimArgs) appendArgs(args []any) []any {
|
||||
if v.Count > 0 {
|
||||
args = append(args, "count", v.Count)
|
||||
}
|
||||
if v.EF > 0 {
|
||||
args = append(args, "ef", v.EF)
|
||||
}
|
||||
if len(v.Filter) > 0 {
|
||||
args = append(args, "filter", v.Filter)
|
||||
}
|
||||
if v.FilterEF > 0 {
|
||||
args = append(args, "filter-ef", v.FilterEF)
|
||||
}
|
||||
if v.Truth {
|
||||
args = append(args, "truth")
|
||||
}
|
||||
if v.NoThread {
|
||||
args = append(args, "nothread")
|
||||
}
|
||||
// if v.Epsilon > 0 {
|
||||
// args = append(args, "Epsilon", v.Epsilon)
|
||||
// }
|
||||
return args
|
||||
}
|
||||
|
||||
// `VSIM key (ELE | FP32 | VALUES num) (vector | element) [COUNT num]
|
||||
// [EF search-exploration-factor] [FILTER expression] [FILTER-EF max-filtering-effort] [TRUTH] [NOTHREAD]`
|
||||
// note: the API is experimental and may be subject to change.
|
||||
func (c cmdable) VSimWithArgs(ctx context.Context, key string, val Vector, simArgs *VSimArgs) *StringSliceCmd {
|
||||
if simArgs == nil {
|
||||
simArgs = &VSimArgs{}
|
||||
}
|
||||
args := []any{"vsim", key}
|
||||
args = append(args, val.Value()...)
|
||||
args = simArgs.appendArgs(args)
|
||||
cmd := NewStringSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// `VSIM key (ELE | FP32 | VALUES num) (vector | element) [WITHSCORES] [COUNT num]
|
||||
// [EF search-exploration-factor] [FILTER expression] [FILTER-EF max-filtering-effort] [TRUTH] [NOTHREAD]`
|
||||
// note: the API is experimental and may be subject to change.
|
||||
func (c cmdable) VSimWithArgsWithScores(ctx context.Context, key string, val Vector, simArgs *VSimArgs) *VectorScoreSliceCmd {
|
||||
if simArgs == nil {
|
||||
simArgs = &VSimArgs{}
|
||||
}
|
||||
args := []any{"vsim", key}
|
||||
args = append(args, val.Value()...)
|
||||
args = append(args, "withscores")
|
||||
args = simArgs.appendArgs(args)
|
||||
cmd := NewVectorInfoSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
6
vendor/github.com/redis/go-redis/v9/version.go
generated
vendored
Normal file
6
vendor/github.com/redis/go-redis/v9/version.go
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
package redis
|
||||
|
||||
// Version is the current release version.
|
||||
func Version() string {
|
||||
return "9.11.0"
|
||||
}
|
||||
Reference in New Issue
Block a user