mirror of
https://github.com/redis/go-redis.git
synced 2025-04-26 21:08:50 +03:00
Merge branch 'master' into os-add-csc-redis-commands
This commit is contained in:
commit
da5a7e3def
.github
.gitignore.golangci.ymlCHANGELOG.mdCONTRIBUTING.mdMakefileREADME.mdacl_commands.goacl_commands_test.gobench_test.gobitmap_commands.gobitmap_commands_test.gocluster_commands.gocommand.gocommands.gocommands_test.godocker-compose.ymldockers
doctests
bf_tutorial_test.gobitfield_tutorial_test.gobitmap_tutorial_test.gocmds_generic_test.gocmds_hash_test.gocmds_servermgmt_test.gocmds_sorted_set_test.gocmds_string_test.gocms_tutorial_test.gocuckoo_tutorial_test.gogeo_index_test.gogeo_tutorial_test.gohash_tutorial_test.gohll_tutorial_test.gohome_json_example_test.gojson_tutorial_test.golist_tutorial_test.gopipe_trans_example_test.goquery_agg_test.goquery_em_test.goquery_ft_test.goquery_geo_test.goquery_range_test.goset_get_test.gosets_example_test.goss_tutorial_test.gostream_tutorial_test.gostring_example_test.gotdigest_tutorial_test.gotopk_tutorial_test.go
error.goexample
del-keys-without-ttl
hll
hset-struct
lua-scripting
otel
redis-bloom
scan-struct
extra
rediscensus
rediscmd
redisotel
redisprometheus
internal
iterator_test.go
53
.github/actions/run-tests/action.yml
vendored
Normal file
53
.github/actions/run-tests/action.yml
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
name: 'Run go-redis tests'
|
||||
description: 'Runs go-redis tests against different Redis versions and configurations'
|
||||
inputs:
|
||||
go-version:
|
||||
description: 'Go version to use for running tests'
|
||||
default: '1.23'
|
||||
redis-version:
|
||||
description: 'Redis version to test against'
|
||||
required: true
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Set up ${{ inputs.go-version }}
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ inputs.go-version }}
|
||||
|
||||
- name: Setup Test environment
|
||||
env:
|
||||
REDIS_VERSION: ${{ inputs.redis-version }}
|
||||
CLIENT_LIBS_TEST_IMAGE: "redislabs/client-libs-test:${{ inputs.redis-version }}"
|
||||
run: |
|
||||
set -e
|
||||
redis_version_np=$(echo "$REDIS_VERSION" | grep -oP '^\d+.\d+')
|
||||
|
||||
# Mapping of redis version to redis testing containers
|
||||
declare -A redis_version_mapping=(
|
||||
["8.0-M03"]="8.0-M04-pre"
|
||||
["7.4.2"]="rs-7.4.0-v2"
|
||||
["7.2.7"]="rs-7.2.0-v14"
|
||||
)
|
||||
|
||||
if [[ -v redis_version_mapping[$REDIS_VERSION] ]]; then
|
||||
echo "REDIS_VERSION=${redis_version_np}" >> $GITHUB_ENV
|
||||
echo "REDIS_IMAGE=redis:${{ inputs.redis-version }}" >> $GITHUB_ENV
|
||||
echo "CLIENT_LIBS_TEST_IMAGE=redislabs/client-libs-test:${redis_version_mapping[$REDIS_VERSION]}" >> $GITHUB_ENV
|
||||
else
|
||||
echo "Version not found in the mapping."
|
||||
exit 1
|
||||
fi
|
||||
sleep 10 # wait for redis to start
|
||||
shell: bash
|
||||
- name: Set up Docker Compose environment with redis ${{ inputs.redis-version }}
|
||||
run: |
|
||||
make docker.start
|
||||
shell: bash
|
||||
- name: Run tests
|
||||
env:
|
||||
RCE_DOCKER: "true"
|
||||
RE_CLUSTER: "false"
|
||||
run: |
|
||||
make test.ci
|
||||
shell: bash
|
12
.github/wordlist.txt
vendored
12
.github/wordlist.txt
vendored
@ -1,7 +1,9 @@
|
||||
ACLs
|
||||
APIs
|
||||
autoload
|
||||
autoloader
|
||||
autoloading
|
||||
analytics
|
||||
Autoloading
|
||||
backend
|
||||
backends
|
||||
@ -13,6 +15,7 @@ customizable
|
||||
Customizable
|
||||
dataset
|
||||
de
|
||||
DisableIdentity
|
||||
ElastiCache
|
||||
extensibility
|
||||
FPM
|
||||
@ -43,15 +46,22 @@ RocksDB
|
||||
runtime
|
||||
SHA
|
||||
sharding
|
||||
SETNAME
|
||||
SpellCheck
|
||||
SSL
|
||||
struct
|
||||
stunnel
|
||||
SynDump
|
||||
TCP
|
||||
TLS
|
||||
UnstableResp
|
||||
uri
|
||||
URI
|
||||
url
|
||||
variadic
|
||||
RedisStack
|
||||
RedisGears
|
||||
RedisTimeseries
|
||||
RedisTimeseries
|
||||
RediSearch
|
||||
RawResult
|
||||
RawVal
|
88
.github/workflows/build.yml
vendored
88
.github/workflows/build.yml
vendored
@ -2,29 +2,27 @@ name: Go
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master, v9]
|
||||
branches: [master, v9, v9.7]
|
||||
pull_request:
|
||||
branches: [master, v9]
|
||||
branches: [master, v9, v9.7]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: build
|
||||
|
||||
benchmark:
|
||||
name: benchmark
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
go-version: [1.20.x, 1.21.x]
|
||||
|
||||
services:
|
||||
redis:
|
||||
image: redis/redis-stack-server:edge
|
||||
options: >-
|
||||
--health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5
|
||||
ports:
|
||||
- 6379:6379
|
||||
redis-version:
|
||||
- "8.0-M03" # 8.0 milestone 4
|
||||
- "7.4.2" # should use redis stack 7.4
|
||||
go-version:
|
||||
- "1.23.x"
|
||||
- "1.24.x"
|
||||
|
||||
steps:
|
||||
- name: Set up ${{ matrix.go-version }}
|
||||
@ -35,5 +33,65 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Test
|
||||
run: make test
|
||||
- name: Setup Test environment
|
||||
env:
|
||||
REDIS_VERSION: ${{ matrix.redis-version }}
|
||||
CLIENT_LIBS_TEST_IMAGE: "redislabs/client-libs-test:${{ matrix.redis-version }}"
|
||||
run: |
|
||||
set -e
|
||||
redis_version_np=$(echo "$REDIS_VERSION" | grep -oP '^\d+.\d+')
|
||||
|
||||
# Mapping of redis version to redis testing containers
|
||||
declare -A redis_version_mapping=(
|
||||
["8.0-M03"]="8.0-M04-pre"
|
||||
["7.4.2"]="rs-7.4.0-v2"
|
||||
)
|
||||
if [[ -v redis_version_mapping[$REDIS_VERSION] ]]; then
|
||||
echo "REDIS_VERSION=${redis_version_np}" >> $GITHUB_ENV
|
||||
echo "REDIS_IMAGE=redis:${{ matrix.redis-version }}" >> $GITHUB_ENV
|
||||
echo "CLIENT_LIBS_TEST_IMAGE=redislabs/client-libs-test:${redis_version_mapping[$REDIS_VERSION]}" >> $GITHUB_ENV
|
||||
else
|
||||
echo "Version not found in the mapping."
|
||||
exit 1
|
||||
fi
|
||||
shell: bash
|
||||
- name: Set up Docker Compose environment with redis ${{ matrix.redis-version }}
|
||||
run: make docker.start
|
||||
shell: bash
|
||||
- name: Benchmark Tests
|
||||
env:
|
||||
RCE_DOCKER: "true"
|
||||
RE_CLUSTER: "false"
|
||||
run: make bench
|
||||
shell: bash
|
||||
|
||||
test-redis-ce:
|
||||
name: test-redis-ce
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
redis-version:
|
||||
- "8.0-M03" # 8.0 milestone 4
|
||||
- "7.4.2" # should use redis stack 7.4
|
||||
- "7.2.7" # should redis stack 7.2
|
||||
go-version:
|
||||
- "1.23.x"
|
||||
- "1.24.x"
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Run tests
|
||||
uses: ./.github/actions/run-tests
|
||||
with:
|
||||
go-version: ${{matrix.go-version}}
|
||||
redis-version: ${{ matrix.redis-version }}
|
||||
|
||||
- name: Upload to Codecov
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
files: coverage.txt
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
|
68
.github/workflows/codeql-analysis.yml
vendored
Normal file
68
.github/workflows/codeql-analysis.yml
vendored
Normal file
@ -0,0 +1,68 @@
|
||||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'go' ]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
||||
# Learn more about CodeQL language support at https://git.io/codeql-language-support
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
4
.github/workflows/doctests.yaml
vendored
4
.github/workflows/doctests.yaml
vendored
@ -25,7 +25,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
go-version: [ "1.18", "1.19", "1.20", "1.21" ]
|
||||
go-version: ["1.24"]
|
||||
|
||||
steps:
|
||||
- name: Set up ${{ matrix.go-version }}
|
||||
@ -38,4 +38,4 @@ jobs:
|
||||
|
||||
- name: Test doc examples
|
||||
working-directory: ./doctests
|
||||
run: go test
|
||||
run: go test -v
|
||||
|
6
.github/workflows/golangci-lint.yml
vendored
6
.github/workflows/golangci-lint.yml
vendored
@ -12,15 +12,13 @@ on:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read # for golangci/golangci-lint-action to fetch pull requests
|
||||
|
||||
jobs:
|
||||
golangci:
|
||||
permissions:
|
||||
contents: read # for actions/checkout to fetch code
|
||||
pull-requests: read # for golangci/golangci-lint-action to fetch pull requests
|
||||
name: lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v4
|
||||
uses: golangci/golangci-lint-action@v6.5.0
|
||||
|
2
.github/workflows/release-drafter.yml
vendored
2
.github/workflows/release-drafter.yml
vendored
@ -16,7 +16,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# Drafts your next Release notes as Pull Requests are merged into "master"
|
||||
- uses: release-drafter/release-drafter@v5
|
||||
- uses: release-drafter/release-drafter@v6
|
||||
with:
|
||||
# (Optional) specify config name to use, relative to .github/. Default: release-drafter.yml
|
||||
config-name: release-drafter-config.yml
|
||||
|
2
.github/workflows/spellcheck.yml
vendored
2
.github/workflows/spellcheck.yml
vendored
@ -8,7 +8,7 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Check Spelling
|
||||
uses: rojopolis/spellcheck-github-actions@0.36.0
|
||||
uses: rojopolis/spellcheck-github-actions@0.47.0
|
||||
with:
|
||||
config_path: .github/spellcheck-settings.yml
|
||||
task_name: Markdown
|
||||
|
19
.github/workflows/test-redis-enterprise.yml
vendored
19
.github/workflows/test-redis-enterprise.yml
vendored
@ -15,8 +15,8 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
go-version: [1.21.x]
|
||||
re-build: ["7.2.4-108"]
|
||||
go-version: [1.24.x]
|
||||
re-build: ["7.4.2-54"]
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
@ -36,19 +36,18 @@ jobs:
|
||||
- name: Build cluster
|
||||
working-directory: redis-ee
|
||||
env:
|
||||
IMAGE: "redislabs/redis-internal:${{ matrix.re-build }}"
|
||||
RE_USERNAME: ${{ secrets.RE_USERNAME }}
|
||||
RE_PASS: ${{ secrets.RE_PASS }}
|
||||
RE_CLUSTER_NAME: ${{ secrets.RE_CLUSTER_NAME }}
|
||||
IMAGE: "redislabs/redis:${{ matrix.re-build }}"
|
||||
RE_USERNAME: test@test.com
|
||||
RE_PASS: 12345
|
||||
RE_CLUSTER_NAME: re-test
|
||||
RE_USE_OSS_CLUSTER: false
|
||||
RE_DB_PORT: ${{ secrets.RE_DB_PORT }}
|
||||
DOCKER_ACCESS_TOKEN: ${{ secrets.DOCKER_ACCESS_TOKEN }}
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
RE_DB_PORT: 6379
|
||||
run: ./build.sh
|
||||
|
||||
- name: Test
|
||||
env:
|
||||
RE_CLUSTER: "1"
|
||||
RE_CLUSTER: true
|
||||
REDIS_VERSION: "7.4"
|
||||
run: |
|
||||
go test \
|
||||
--ginkgo.skip-file="ring_test.go" \
|
||||
|
6
.gitignore
vendored
6
.gitignore
vendored
@ -3,4 +3,8 @@ testdata/*
|
||||
.idea/
|
||||
.DS_Store
|
||||
*.tar.gz
|
||||
*.dic
|
||||
*.dic
|
||||
redis8tests.sh
|
||||
coverage.txt
|
||||
**/coverage.txt
|
||||
.vscode
|
@ -1,4 +1,3 @@
|
||||
run:
|
||||
concurrency: 8
|
||||
deadline: 5m
|
||||
timeout: 5m
|
||||
tests: false
|
||||
|
@ -1,3 +1,12 @@
|
||||
## 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)
|
||||
|
||||
|
||||
|
@ -32,20 +32,33 @@ 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 test`. Note: this clones and builds the latest release of [redis](https://redis.io). You also need a redis-stack-server docker, in order to run the capabilities tests. This can be started by running:
|
||||
```docker run -p 6379:6379 -it redis/redis-stack-server:edge```
|
||||
4. While developing, make sure the tests pass by running `make tests`
|
||||
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
|
||||
|
||||
Call `make test` to run all tests, including linters.
|
||||
### 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 python. Feel free to test your
|
||||
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.
|
||||
|
||||
|
33
Makefile
33
Makefile
@ -1,36 +1,35 @@
|
||||
GO_MOD_DIRS := $(shell find . -type f -name 'go.mod' -exec dirname {} \; | sort)
|
||||
|
||||
test: testdeps
|
||||
docker.start:
|
||||
docker compose --profile all up -d --quiet-pull
|
||||
|
||||
docker.stop:
|
||||
docker compose --profile all down
|
||||
|
||||
test:
|
||||
$(MAKE) docker.start
|
||||
$(MAKE) test.ci
|
||||
$(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 test && \
|
||||
go test ./... -short -race && \
|
||||
go test ./... -run=NONE -bench=. -benchmem && \
|
||||
env GOOS=linux GOARCH=386 go test && \
|
||||
go vet); \
|
||||
go vet && \
|
||||
go test -coverprofile=coverage.txt -covermode=atomic ./... -race); \
|
||||
done
|
||||
cd internal/customvet && go build .
|
||||
go vet -vettool ./internal/customvet/customvet
|
||||
|
||||
testdeps: testdata/redis/src/redis-server
|
||||
|
||||
bench: testdeps
|
||||
bench:
|
||||
go test ./... -test.run=NONE -test.bench=. -test.benchmem
|
||||
|
||||
.PHONY: all test testdeps bench fmt
|
||||
.PHONY: all test bench fmt
|
||||
|
||||
build:
|
||||
go build .
|
||||
|
||||
testdata/redis:
|
||||
mkdir -p $@
|
||||
wget -qO- https://download.redis.io/releases/redis-7.2.1.tar.gz | tar xvz --strip-components=1 -C $@
|
||||
|
||||
testdata/redis/src/redis-server: testdata/redis
|
||||
cd $< && make all
|
||||
|
||||
fmt:
|
||||
gofumpt -w ./
|
||||
goimports -w -local github.com/redis/go-redis ./
|
||||
|
80
README.md
80
README.md
@ -3,15 +3,24 @@
|
||||
[](https://github.com/redis/go-redis/actions)
|
||||
[](https://pkg.go.dev/github.com/redis/go-redis/v9?tab=doc)
|
||||
[](https://redis.uptrace.dev/)
|
||||
[](https://codecov.io/github/redis/go-redis)
|
||||
[](https://discord.gg/rWtp5Aj)
|
||||
|
||||
> go-redis is brought to you by :star: [**uptrace/uptrace**](https://github.com/uptrace/uptrace).
|
||||
> Uptrace is an open-source APM tool that supports distributed tracing, metrics, and logs. You can
|
||||
> use it to monitor applications and set up automatic alerts to receive notifications via email,
|
||||
> Slack, Telegram, and others.
|
||||
>
|
||||
> See [OpenTelemetry](https://github.com/redis/go-redis/tree/master/example/otel) example which
|
||||
> demonstrates how you can use Uptrace to monitor 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?
|
||||
|
||||
@ -51,8 +60,8 @@ key value NoSQL database that uses RocksDB as storage engine and is compatible w
|
||||
|
||||
## Features
|
||||
|
||||
- Redis 3 commands except QUIT, MONITOR, and SYNC.
|
||||
- Automatic connection pooling with
|
||||
- Redis commands except QUIT and SYNC.
|
||||
- Automatic connection pooling.
|
||||
- [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).
|
||||
@ -143,9 +152,6 @@ to this specification.
|
||||
|
||||
```go
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
@ -161,6 +167,48 @@ func ExampleClient() *redis.Client {
|
||||
|
||||
```
|
||||
|
||||
|
||||
### 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 `DisableIndentity` configuration option exists. In V10 of this library, `DisableIndentity` will become `DisableIdentity` in order to fix the associated typo.
|
||||
|
||||
To disable verification, set the `DisableIndentity` option to `true` in the Redis client options:
|
||||
|
||||
```go
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "",
|
||||
DB: 0,
|
||||
DisableIndentity: 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()
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
Please see [out contributing guidelines](CONTRIBUTING.md) to help us improve this library!
|
||||
@ -243,6 +291,14 @@ REDIS_PORT=9999 go test <your options>
|
||||
|
||||
## 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">
|
||||
|
@ -4,8 +4,20 @@ 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 {
|
||||
@ -33,3 +45,45 @@ func (c cmdable) ACLLogReset(ctx context.Context) *StatusCmd {
|
||||
_ = 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)
|
||||
}
|
||||
|
449
acl_commands_test.go
Normal file
449
acl_commands_test.go
Normal file
@ -0,0 +1,449 @@
|
||||
package redis_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
|
||||
. "github.com/bsm/ginkgo/v2"
|
||||
. "github.com/bsm/gomega"
|
||||
)
|
||||
|
||||
var TestUserName string = "goredis"
|
||||
var _ = Describe("ACL", func() {
|
||||
var client *redis.Client
|
||||
var ctx context.Context
|
||||
|
||||
BeforeEach(func() {
|
||||
ctx = context.Background()
|
||||
opt := redisOptions()
|
||||
client = redis.NewClient(opt)
|
||||
})
|
||||
|
||||
It("should ACL LOG", Label("NonRedisEnterprise"), func() {
|
||||
Expect(client.ACLLogReset(ctx).Err()).NotTo(HaveOccurred())
|
||||
err := client.Do(ctx, "acl", "setuser", "test", ">test", "on", "allkeys", "+get").Err()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
clientAcl := redis.NewClient(redisOptions())
|
||||
clientAcl.Options().Username = "test"
|
||||
clientAcl.Options().Password = "test"
|
||||
clientAcl.Options().DB = 0
|
||||
_ = clientAcl.Set(ctx, "mystring", "foo", 0).Err()
|
||||
_ = clientAcl.HSet(ctx, "myhash", "foo", "bar").Err()
|
||||
_ = clientAcl.SAdd(ctx, "myset", "foo", "bar").Err()
|
||||
|
||||
logEntries, err := client.ACLLog(ctx, 10).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(logEntries)).To(Equal(4))
|
||||
|
||||
for _, entry := range logEntries {
|
||||
Expect(entry.Reason).To(Equal("command"))
|
||||
Expect(entry.Context).To(Equal("toplevel"))
|
||||
Expect(entry.Object).NotTo(BeEmpty())
|
||||
Expect(entry.Username).To(Equal("test"))
|
||||
Expect(entry.AgeSeconds).To(BeNumerically(">=", 0))
|
||||
Expect(entry.ClientInfo).NotTo(BeNil())
|
||||
Expect(entry.EntryID).To(BeNumerically(">=", 0))
|
||||
Expect(entry.TimestampCreated).To(BeNumerically(">=", 0))
|
||||
Expect(entry.TimestampLastUpdated).To(BeNumerically(">=", 0))
|
||||
}
|
||||
|
||||
limitedLogEntries, err := client.ACLLog(ctx, 2).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(limitedLogEntries)).To(Equal(2))
|
||||
|
||||
// cleanup after creating the user
|
||||
err = client.Do(ctx, "acl", "deluser", "test").Err()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("should ACL LOG RESET", Label("NonRedisEnterprise"), func() {
|
||||
// Call ACL LOG RESET
|
||||
resetCmd := client.ACLLogReset(ctx)
|
||||
Expect(resetCmd.Err()).NotTo(HaveOccurred())
|
||||
Expect(resetCmd.Val()).To(Equal("OK"))
|
||||
|
||||
// Verify that the log is empty after the reset
|
||||
logEntries, err := client.ACLLog(ctx, 10).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(logEntries)).To(Equal(0))
|
||||
})
|
||||
|
||||
})
|
||||
var _ = Describe("ACL user commands", Label("NonRedisEnterprise"), func() {
|
||||
var client *redis.Client
|
||||
var ctx context.Context
|
||||
|
||||
BeforeEach(func() {
|
||||
ctx = context.Background()
|
||||
opt := redisOptions()
|
||||
client = redis.NewClient(opt)
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
_, err := client.ACLDelUser(context.Background(), TestUserName).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(client.Close()).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("list only default user", func() {
|
||||
res, err := client.ACLList(ctx).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(HaveLen(1))
|
||||
Expect(res[0]).To(ContainSubstring("default"))
|
||||
})
|
||||
|
||||
It("setuser and deluser", func() {
|
||||
res, err := client.ACLList(ctx).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(HaveLen(1))
|
||||
Expect(res[0]).To(ContainSubstring("default"))
|
||||
|
||||
add, err := client.ACLSetUser(ctx, TestUserName, "nopass", "on", "allkeys", "+set", "+get").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(add).To(Equal("OK"))
|
||||
|
||||
resAfter, err := client.ACLList(ctx).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(resAfter).To(HaveLen(2))
|
||||
Expect(resAfter[1]).To(ContainSubstring(TestUserName))
|
||||
|
||||
deletedN, err := client.ACLDelUser(ctx, TestUserName).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(deletedN).To(BeNumerically("==", 1))
|
||||
|
||||
resAfterDeletion, err := client.ACLList(ctx).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(resAfterDeletion).To(HaveLen(1))
|
||||
Expect(resAfterDeletion[0]).To(BeEquivalentTo(res[0]))
|
||||
})
|
||||
|
||||
It("should acl dryrun", func() {
|
||||
dryRun := client.ACLDryRun(ctx, "default", "get", "randomKey")
|
||||
Expect(dryRun.Err()).NotTo(HaveOccurred())
|
||||
Expect(dryRun.Val()).To(Equal("OK"))
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("ACL permissions", Label("NonRedisEnterprise"), func() {
|
||||
var client *redis.Client
|
||||
var ctx context.Context
|
||||
|
||||
BeforeEach(func() {
|
||||
ctx = context.Background()
|
||||
opt := redisOptions()
|
||||
opt.UnstableResp3 = true
|
||||
client = redis.NewClient(opt)
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
_, err := client.ACLDelUser(context.Background(), TestUserName).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(client.Close()).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("reset permissions", func() {
|
||||
add, err := client.ACLSetUser(ctx,
|
||||
TestUserName,
|
||||
"reset",
|
||||
"nopass",
|
||||
"on",
|
||||
).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(add).To(Equal("OK"))
|
||||
|
||||
connection := client.Conn()
|
||||
authed, err := connection.AuthACL(ctx, TestUserName, "").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(authed).To(Equal("OK"))
|
||||
|
||||
_, err = connection.Get(ctx, "anykey").Result()
|
||||
Expect(err).To(HaveOccurred())
|
||||
})
|
||||
|
||||
It("add write permissions", func() {
|
||||
add, err := client.ACLSetUser(ctx,
|
||||
TestUserName,
|
||||
"reset",
|
||||
"nopass",
|
||||
"on",
|
||||
"~*",
|
||||
"+SET",
|
||||
).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(add).To(Equal("OK"))
|
||||
|
||||
connection := client.Conn()
|
||||
authed, err := connection.AuthACL(ctx, TestUserName, "").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(authed).To(Equal("OK"))
|
||||
|
||||
// can write
|
||||
v, err := connection.Set(ctx, "anykey", "anyvalue", 0).Result()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(v).To(Equal("OK"))
|
||||
|
||||
// but can't read
|
||||
value, err := connection.Get(ctx, "anykey").Result()
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(value).To(BeEmpty())
|
||||
})
|
||||
|
||||
It("add read permissions", func() {
|
||||
add, err := client.ACLSetUser(ctx,
|
||||
TestUserName,
|
||||
"reset",
|
||||
"nopass",
|
||||
"on",
|
||||
"~*",
|
||||
"+GET",
|
||||
).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(add).To(Equal("OK"))
|
||||
|
||||
connection := client.Conn()
|
||||
authed, err := connection.AuthACL(ctx, TestUserName, "").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(authed).To(Equal("OK"))
|
||||
|
||||
// can read
|
||||
value, err := connection.Get(ctx, "anykey").Result()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(value).To(Equal("anyvalue"))
|
||||
|
||||
// but can't delete
|
||||
del, err := connection.Del(ctx, "anykey").Result()
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(del).ToNot(Equal(1))
|
||||
})
|
||||
|
||||
It("add del permissions", func() {
|
||||
add, err := client.ACLSetUser(ctx,
|
||||
TestUserName,
|
||||
"reset",
|
||||
"nopass",
|
||||
"on",
|
||||
"~*",
|
||||
"+DEL",
|
||||
).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(add).To(Equal("OK"))
|
||||
|
||||
connection := client.Conn()
|
||||
authed, err := connection.AuthACL(ctx, TestUserName, "").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(authed).To(Equal("OK"))
|
||||
|
||||
// can read
|
||||
del, err := connection.Del(ctx, "anykey").Result()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(del).To(BeEquivalentTo(1))
|
||||
})
|
||||
|
||||
It("set permissions for module commands", func() {
|
||||
SkipBeforeRedisVersion(8, "permissions for modules are supported for Redis Version >=8")
|
||||
Expect(client.FlushDB(ctx).Err()).NotTo(HaveOccurred())
|
||||
val, err := client.FTCreate(ctx, "txt", &redis.FTCreateOptions{}, &redis.FieldSchema{FieldName: "txt", FieldType: redis.SearchFieldTypeText}).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(val).To(BeEquivalentTo("OK"))
|
||||
WaitForIndexing(client, "txt")
|
||||
client.HSet(ctx, "doc1", "txt", "foo baz")
|
||||
client.HSet(ctx, "doc2", "txt", "foo bar")
|
||||
add, err := client.ACLSetUser(ctx,
|
||||
TestUserName,
|
||||
"reset",
|
||||
"nopass",
|
||||
"on",
|
||||
"~*",
|
||||
"+FT.SEARCH",
|
||||
"-FT.DROPINDEX",
|
||||
"+json.set",
|
||||
"+json.get",
|
||||
"-json.clear",
|
||||
"+bf.reserve",
|
||||
"-bf.info",
|
||||
"+cf.reserve",
|
||||
"+cms.initbydim",
|
||||
"+topk.reserve",
|
||||
"+tdigest.create",
|
||||
"+ts.create",
|
||||
"-ts.info",
|
||||
).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(add).To(Equal("OK"))
|
||||
|
||||
c := client.Conn()
|
||||
authed, err := c.AuthACL(ctx, TestUserName, "").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(authed).To(Equal("OK"))
|
||||
|
||||
// has perm for search
|
||||
Expect(c.FTSearch(ctx, "txt", "foo ~bar").Err()).NotTo(HaveOccurred())
|
||||
|
||||
// no perm for dropindex
|
||||
err = c.FTDropIndex(ctx, "txt").Err()
|
||||
Expect(err).ToNot(BeEmpty())
|
||||
Expect(err.Error()).To(ContainSubstring("NOPERM"))
|
||||
|
||||
// json set and get have perm
|
||||
Expect(c.JSONSet(ctx, "foo", "$", "\"bar\"").Err()).NotTo(HaveOccurred())
|
||||
Expect(c.JSONGet(ctx, "foo", "$").Val()).To(BeEquivalentTo("[\"bar\"]"))
|
||||
|
||||
// no perm for json clear
|
||||
err = c.JSONClear(ctx, "foo", "$").Err()
|
||||
Expect(err).ToNot(BeEmpty())
|
||||
Expect(err.Error()).To(ContainSubstring("NOPERM"))
|
||||
|
||||
// perm for reserve
|
||||
Expect(c.BFReserve(ctx, "bloom", 0.01, 100).Err()).NotTo(HaveOccurred())
|
||||
|
||||
// no perm for info
|
||||
err = c.BFInfo(ctx, "bloom").Err()
|
||||
Expect(err).ToNot(BeEmpty())
|
||||
Expect(err.Error()).To(ContainSubstring("NOPERM"))
|
||||
|
||||
// perm for cf.reserve
|
||||
Expect(c.CFReserve(ctx, "cfres", 100).Err()).NotTo(HaveOccurred())
|
||||
// perm for cms.initbydim
|
||||
Expect(c.CMSInitByDim(ctx, "cmsdim", 100, 5).Err()).NotTo(HaveOccurred())
|
||||
// perm for topk.reserve
|
||||
Expect(c.TopKReserve(ctx, "topk", 10).Err()).NotTo(HaveOccurred())
|
||||
// perm for tdigest.create
|
||||
Expect(c.TDigestCreate(ctx, "tdc").Err()).NotTo(HaveOccurred())
|
||||
// perm for ts.create
|
||||
Expect(c.TSCreate(ctx, "tsts").Err()).NotTo(HaveOccurred())
|
||||
// noperm for ts.info
|
||||
err = c.TSInfo(ctx, "tsts").Err()
|
||||
Expect(err).ToNot(BeEmpty())
|
||||
Expect(err.Error()).To(ContainSubstring("NOPERM"))
|
||||
|
||||
Expect(client.FlushDB(ctx).Err()).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("set permissions for module categories", func() {
|
||||
SkipBeforeRedisVersion(8, "permissions for modules are supported for Redis Version >=8")
|
||||
Expect(client.FlushDB(ctx).Err()).NotTo(HaveOccurred())
|
||||
val, err := client.FTCreate(ctx, "txt", &redis.FTCreateOptions{}, &redis.FieldSchema{FieldName: "txt", FieldType: redis.SearchFieldTypeText}).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(val).To(BeEquivalentTo("OK"))
|
||||
WaitForIndexing(client, "txt")
|
||||
client.HSet(ctx, "doc1", "txt", "foo baz")
|
||||
client.HSet(ctx, "doc2", "txt", "foo bar")
|
||||
add, err := client.ACLSetUser(ctx,
|
||||
TestUserName,
|
||||
"reset",
|
||||
"nopass",
|
||||
"on",
|
||||
"~*",
|
||||
"+@search",
|
||||
"+@json",
|
||||
"+@bloom",
|
||||
"+@cuckoo",
|
||||
"+@topk",
|
||||
"+@cms",
|
||||
"+@timeseries",
|
||||
"+@tdigest",
|
||||
).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(add).To(Equal("OK"))
|
||||
|
||||
c := client.Conn()
|
||||
authed, err := c.AuthACL(ctx, TestUserName, "").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(authed).To(Equal("OK"))
|
||||
|
||||
// has perm for search
|
||||
Expect(c.FTSearch(ctx, "txt", "foo ~bar").Err()).NotTo(HaveOccurred())
|
||||
// perm for dropindex
|
||||
Expect(c.FTDropIndex(ctx, "txt").Err()).NotTo(HaveOccurred())
|
||||
// json set and get have perm
|
||||
Expect(c.JSONSet(ctx, "foo", "$", "\"bar\"").Err()).NotTo(HaveOccurred())
|
||||
Expect(c.JSONGet(ctx, "foo", "$").Val()).To(BeEquivalentTo("[\"bar\"]"))
|
||||
// perm for json clear
|
||||
Expect(c.JSONClear(ctx, "foo", "$").Err()).NotTo(HaveOccurred())
|
||||
// perm for reserve
|
||||
Expect(c.BFReserve(ctx, "bloom", 0.01, 100).Err()).NotTo(HaveOccurred())
|
||||
// perm for info
|
||||
Expect(c.BFInfo(ctx, "bloom").Err()).NotTo(HaveOccurred())
|
||||
// perm for cf.reserve
|
||||
Expect(c.CFReserve(ctx, "cfres", 100).Err()).NotTo(HaveOccurred())
|
||||
// perm for cms.initbydim
|
||||
Expect(c.CMSInitByDim(ctx, "cmsdim", 100, 5).Err()).NotTo(HaveOccurred())
|
||||
// perm for topk.reserve
|
||||
Expect(c.TopKReserve(ctx, "topk", 10).Err()).NotTo(HaveOccurred())
|
||||
// perm for tdigest.create
|
||||
Expect(c.TDigestCreate(ctx, "tdc").Err()).NotTo(HaveOccurred())
|
||||
// perm for ts.create
|
||||
Expect(c.TSCreate(ctx, "tsts").Err()).NotTo(HaveOccurred())
|
||||
// perm for ts.info
|
||||
Expect(c.TSInfo(ctx, "tsts").Err()).NotTo(HaveOccurred())
|
||||
|
||||
Expect(client.FlushDB(ctx).Err()).NotTo(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("ACL Categories", func() {
|
||||
var client *redis.Client
|
||||
var ctx context.Context
|
||||
|
||||
BeforeEach(func() {
|
||||
ctx = context.Background()
|
||||
opt := redisOptions()
|
||||
client = redis.NewClient(opt)
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
Expect(client.Close()).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("lists acl categories and subcategories", func() {
|
||||
res, err := client.ACLCat(ctx).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(res)).To(BeNumerically(">", 20))
|
||||
Expect(res).To(ContainElements(
|
||||
"read",
|
||||
"write",
|
||||
"keyspace",
|
||||
"dangerous",
|
||||
"slow",
|
||||
"set",
|
||||
"sortedset",
|
||||
"list",
|
||||
"hash",
|
||||
))
|
||||
|
||||
res, err = client.ACLCatArgs(ctx, &redis.ACLCatArgs{Category: "read"}).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(ContainElement("get"))
|
||||
})
|
||||
|
||||
It("lists acl categories and subcategories with Modules", func() {
|
||||
SkipBeforeRedisVersion(8, "modules are included in acl for redis version >= 8")
|
||||
aclTestCase := map[string]string{
|
||||
"search": "FT.CREATE",
|
||||
"bloom": "bf.add",
|
||||
"json": "json.get",
|
||||
"cuckoo": "cf.insert",
|
||||
"cms": "cms.query",
|
||||
"topk": "topk.list",
|
||||
"tdigest": "tdigest.rank",
|
||||
"timeseries": "ts.range",
|
||||
}
|
||||
var cats []interface{}
|
||||
|
||||
for cat, subitem := range aclTestCase {
|
||||
cats = append(cats, cat)
|
||||
|
||||
res, err := client.ACLCatArgs(ctx, &redis.ACLCatArgs{
|
||||
Category: cat,
|
||||
}).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(ContainElement(subitem))
|
||||
}
|
||||
|
||||
res, err := client.ACLCat(ctx).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(ContainElements(cats...))
|
||||
})
|
||||
})
|
105
bench_test.go
105
bench_test.go
@ -277,37 +277,41 @@ func BenchmarkXRead(b *testing.B) {
|
||||
|
||||
func newClusterScenario() *clusterScenario {
|
||||
return &clusterScenario{
|
||||
ports: []string{"8220", "8221", "8222", "8223", "8224", "8225"},
|
||||
nodeIDs: make([]string, 6),
|
||||
processes: make(map[string]*redisProcess, 6),
|
||||
clients: make(map[string]*redis.Client, 6),
|
||||
ports: []string{"16600", "16601", "16602", "16603", "16604", "16605"},
|
||||
nodeIDs: make([]string, 6),
|
||||
clients: make(map[string]*redis.Client, 6),
|
||||
}
|
||||
}
|
||||
|
||||
var clusterBench *clusterScenario
|
||||
|
||||
func BenchmarkClusterPing(b *testing.B) {
|
||||
if testing.Short() {
|
||||
b.Skip("skipping in short mode")
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
cluster := newClusterScenario()
|
||||
if err := startCluster(ctx, cluster); err != nil {
|
||||
b.Fatal(err)
|
||||
if clusterBench == nil {
|
||||
clusterBench = newClusterScenario()
|
||||
if err := configureClusterTopology(ctx, clusterBench); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
defer cluster.Close()
|
||||
|
||||
client := cluster.newClusterClient(ctx, redisClusterOptions())
|
||||
client := clusterBench.newClusterClient(ctx, redisClusterOptions())
|
||||
defer client.Close()
|
||||
|
||||
b.ResetTimer()
|
||||
b.Run("cluster ping", func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
err := client.Ping(ctx).Err()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
err := client.Ping(ctx).Err()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@ -317,23 +321,26 @@ func BenchmarkClusterDoInt(b *testing.B) {
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
cluster := newClusterScenario()
|
||||
if err := startCluster(ctx, cluster); err != nil {
|
||||
b.Fatal(err)
|
||||
if clusterBench == nil {
|
||||
clusterBench = newClusterScenario()
|
||||
if err := configureClusterTopology(ctx, clusterBench); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
defer cluster.Close()
|
||||
|
||||
client := cluster.newClusterClient(ctx, redisClusterOptions())
|
||||
client := clusterBench.newClusterClient(ctx, redisClusterOptions())
|
||||
defer client.Close()
|
||||
|
||||
b.ResetTimer()
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
err := client.Do(ctx, "SET", 10, 10).Err()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
b.Run("cluster do set int", func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
err := client.Do(ctx, "SET", 10, 10).Err()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@ -343,26 +350,29 @@ func BenchmarkClusterSetString(b *testing.B) {
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
cluster := newClusterScenario()
|
||||
if err := startCluster(ctx, cluster); err != nil {
|
||||
b.Fatal(err)
|
||||
if clusterBench == nil {
|
||||
clusterBench = newClusterScenario()
|
||||
if err := configureClusterTopology(ctx, clusterBench); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
defer cluster.Close()
|
||||
|
||||
client := cluster.newClusterClient(ctx, redisClusterOptions())
|
||||
client := clusterBench.newClusterClient(ctx, redisClusterOptions())
|
||||
defer client.Close()
|
||||
|
||||
value := string(bytes.Repeat([]byte{'1'}, 10000))
|
||||
|
||||
b.ResetTimer()
|
||||
b.Run("cluster set string", func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
err := client.Set(ctx, "key", value, 0).Err()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
err := client.Set(ctx, "key", value, 0).Err()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@ -372,21 +382,6 @@ func BenchmarkExecRingSetAddrsCmd(b *testing.B) {
|
||||
ringShard2Name = "ringShardTwo"
|
||||
)
|
||||
|
||||
for _, port := range []string{ringShard1Port, ringShard2Port} {
|
||||
if _, err := startRedis(port); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
b.Cleanup(func() {
|
||||
for _, p := range processes {
|
||||
if err := p.Close(); err != nil {
|
||||
b.Errorf("Failed to stop redis process: %v", err)
|
||||
}
|
||||
}
|
||||
processes = nil
|
||||
})
|
||||
|
||||
ring := redis.NewRing(&redis.RingOptions{
|
||||
Addrs: map[string]string{
|
||||
"ringShardOne": ":" + ringShard1Port,
|
||||
|
@ -2,6 +2,7 @@ package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
)
|
||||
|
||||
type BitMapCmdable interface {
|
||||
@ -15,6 +16,7 @@ type BitMapCmdable interface {
|
||||
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 {
|
||||
@ -37,16 +39,26 @@ func (c cmdable) SetBit(ctx context.Context, key string, offset int64, value int
|
||||
|
||||
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 := []interface{}{"bitcount", key}
|
||||
args := make([]any, 2, 5)
|
||||
args[0] = "bitcount"
|
||||
args[1] = key
|
||||
if bitCount != nil {
|
||||
args = append(
|
||||
args,
|
||||
bitCount.Start,
|
||||
bitCount.End,
|
||||
)
|
||||
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)
|
||||
|
98
bitmap_commands_test.go
Normal file
98
bitmap_commands_test.go
Normal file
@ -0,0 +1,98 @@
|
||||
package redis_test
|
||||
|
||||
import (
|
||||
. "github.com/bsm/ginkgo/v2"
|
||||
. "github.com/bsm/gomega"
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
type bitCountExpected struct {
|
||||
Start int64
|
||||
End int64
|
||||
Expected int64
|
||||
}
|
||||
|
||||
var _ = Describe("BitCountBite", func() {
|
||||
var client *redis.Client
|
||||
key := "bit_count_test"
|
||||
|
||||
BeforeEach(func() {
|
||||
client = redis.NewClient(redisOptions())
|
||||
Expect(client.FlushDB(ctx).Err()).NotTo(HaveOccurred())
|
||||
values := []int{0, 1, 0, 0, 1, 0, 1, 0, 1, 1}
|
||||
for i, v := range values {
|
||||
cmd := client.SetBit(ctx, key, int64(i), v)
|
||||
Expect(cmd.Err()).NotTo(HaveOccurred())
|
||||
}
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
Expect(client.Close()).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("bit count bite", func() {
|
||||
var expected = []bitCountExpected{
|
||||
{0, 0, 0},
|
||||
{0, 1, 1},
|
||||
{0, 2, 1},
|
||||
{0, 3, 1},
|
||||
{0, 4, 2},
|
||||
{0, 5, 2},
|
||||
{0, 6, 3},
|
||||
{0, 7, 3},
|
||||
{0, 8, 4},
|
||||
{0, 9, 5},
|
||||
}
|
||||
|
||||
for _, e := range expected {
|
||||
cmd := client.BitCount(ctx, key, &redis.BitCount{Start: e.Start, End: e.End, Unit: redis.BitCountIndexBit})
|
||||
Expect(cmd.Err()).NotTo(HaveOccurred())
|
||||
Expect(cmd.Val()).To(Equal(e.Expected))
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("BitCountByte", func() {
|
||||
var client *redis.Client
|
||||
key := "bit_count_test"
|
||||
|
||||
BeforeEach(func() {
|
||||
client = redis.NewClient(redisOptions())
|
||||
Expect(client.FlushDB(ctx).Err()).NotTo(HaveOccurred())
|
||||
values := []int{0, 0, 0, 0, 0, 0, 0, 1, 1, 1}
|
||||
for i, v := range values {
|
||||
cmd := client.SetBit(ctx, key, int64(i), v)
|
||||
Expect(cmd.Err()).NotTo(HaveOccurred())
|
||||
}
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
Expect(client.Close()).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("bit count byte", func() {
|
||||
var expected = []bitCountExpected{
|
||||
{0, 0, 1},
|
||||
{0, 1, 3},
|
||||
}
|
||||
|
||||
for _, e := range expected {
|
||||
cmd := client.BitCount(ctx, key, &redis.BitCount{Start: e.Start, End: e.End, Unit: redis.BitCountIndexByte})
|
||||
Expect(cmd.Err()).NotTo(HaveOccurred())
|
||||
Expect(cmd.Val()).To(Equal(e.Expected))
|
||||
}
|
||||
})
|
||||
|
||||
It("bit count byte with no unit specified", func() {
|
||||
var expected = []bitCountExpected{
|
||||
{0, 0, 1},
|
||||
{0, 1, 3},
|
||||
}
|
||||
|
||||
for _, e := range expected {
|
||||
cmd := client.BitCount(ctx, key, &redis.BitCount{Start: e.Start, End: e.End})
|
||||
Expect(cmd.Err()).NotTo(HaveOccurred())
|
||||
Expect(cmd.Val()).To(Equal(e.Expected))
|
||||
}
|
||||
})
|
||||
})
|
@ -4,6 +4,7 @@ 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
|
||||
@ -35,6 +36,12 @@ func (c cmdable) ClusterMyShardID(ctx context.Context) *StringCmd {
|
||||
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)
|
||||
|
184
command.go
184
command.go
@ -40,7 +40,7 @@ type Cmder interface {
|
||||
|
||||
readTimeout() *time.Duration
|
||||
readReply(rd *proto.Reader) error
|
||||
|
||||
readRawReply(rd *proto.Reader) error
|
||||
SetErr(error)
|
||||
Err() error
|
||||
}
|
||||
@ -122,11 +122,11 @@ func cmdString(cmd Cmder, val interface{}) string {
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
type baseCmd struct {
|
||||
ctx context.Context
|
||||
args []interface{}
|
||||
err error
|
||||
keyPos int8
|
||||
|
||||
ctx context.Context
|
||||
args []interface{}
|
||||
err error
|
||||
keyPos int8
|
||||
rawVal interface{}
|
||||
_readTimeout *time.Duration
|
||||
}
|
||||
|
||||
@ -167,6 +167,8 @@ func (cmd *baseCmd) stringArg(pos int) string {
|
||||
switch v := arg.(type) {
|
||||
case string:
|
||||
return v
|
||||
case []byte:
|
||||
return string(v)
|
||||
default:
|
||||
// TODO: consider using appendArg
|
||||
return fmt.Sprint(v)
|
||||
@ -197,6 +199,11 @@ func (cmd *baseCmd) setReadTimeout(d time.Duration) {
|
||||
cmd._readTimeout = &d
|
||||
}
|
||||
|
||||
func (cmd *baseCmd) readRawReply(rd *proto.Reader) (err error) {
|
||||
cmd.rawVal, err = rd.ReadReply()
|
||||
return err
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
type Cmd struct {
|
||||
@ -573,6 +580,10 @@ func (cmd *StatusCmd) Result() (string, error) {
|
||||
return cmd.val, cmd.err
|
||||
}
|
||||
|
||||
func (cmd *StatusCmd) Bytes() ([]byte, error) {
|
||||
return util.StringToBytes(cmd.val), cmd.err
|
||||
}
|
||||
|
||||
func (cmd *StatusCmd) String() string {
|
||||
return cmdString(cmd, cmd.val)
|
||||
}
|
||||
@ -1394,27 +1405,63 @@ func (cmd *MapStringSliceInterfaceCmd) Val() map[string][]interface{} {
|
||||
}
|
||||
|
||||
func (cmd *MapStringSliceInterfaceCmd) readReply(rd *proto.Reader) (err error) {
|
||||
n, err := rd.ReadMapLen()
|
||||
readType, err := rd.PeekReplyType()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd.val = make(map[string][]interface{}, n)
|
||||
for i := 0; i < n; i++ {
|
||||
k, err := rd.ReadString()
|
||||
|
||||
cmd.val = make(map[string][]interface{})
|
||||
|
||||
if readType == proto.RespMap {
|
||||
n, err := rd.ReadMapLen()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nn, err := rd.ReadArrayLen()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd.val[k] = make([]interface{}, nn)
|
||||
for j := 0; j < nn; j++ {
|
||||
value, err := rd.ReadReply()
|
||||
for i := 0; i < n; i++ {
|
||||
k, err := rd.ReadString()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd.val[k][j] = value
|
||||
nn, err := rd.ReadArrayLen()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd.val[k] = make([]interface{}, nn)
|
||||
for j := 0; j < nn; j++ {
|
||||
value, err := rd.ReadReply()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd.val[k][j] = value
|
||||
}
|
||||
}
|
||||
} else if readType == proto.RespArray {
|
||||
// RESP2 response
|
||||
n, err := rd.ReadArrayLen()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
// Each entry in this array is itself an array with key details
|
||||
itemLen, err := rd.ReadArrayLen()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
key, err := rd.ReadString()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd.val[key] = make([]interface{}, 0, itemLen-1)
|
||||
for j := 1; j < itemLen; j++ {
|
||||
// Read the inner array for timestamp-value pairs
|
||||
data, err := rd.ReadReply()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd.val[key] = append(cmd.val[key], data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3783,6 +3830,83 @@ func (cmd *MapStringStringSliceCmd) readReply(rd *proto.Reader) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// MapStringInterfaceCmd represents a command that returns a map of strings to interface{}.
|
||||
type MapMapStringInterfaceCmd struct {
|
||||
baseCmd
|
||||
val map[string]interface{}
|
||||
}
|
||||
|
||||
func NewMapMapStringInterfaceCmd(ctx context.Context, args ...interface{}) *MapMapStringInterfaceCmd {
|
||||
return &MapMapStringInterfaceCmd{
|
||||
baseCmd: baseCmd{
|
||||
ctx: ctx,
|
||||
args: args,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *MapMapStringInterfaceCmd) String() string {
|
||||
return cmdString(cmd, cmd.val)
|
||||
}
|
||||
|
||||
func (cmd *MapMapStringInterfaceCmd) SetVal(val map[string]interface{}) {
|
||||
cmd.val = val
|
||||
}
|
||||
|
||||
func (cmd *MapMapStringInterfaceCmd) Result() (map[string]interface{}, error) {
|
||||
return cmd.val, cmd.err
|
||||
}
|
||||
|
||||
func (cmd *MapMapStringInterfaceCmd) Val() map[string]interface{} {
|
||||
return cmd.val
|
||||
}
|
||||
|
||||
// readReply will try to parse the reply from the proto.Reader for both resp2 and resp3
|
||||
func (cmd *MapMapStringInterfaceCmd) readReply(rd *proto.Reader) (err error) {
|
||||
data, err := rd.ReadReply()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resultMap := map[string]interface{}{}
|
||||
|
||||
switch midResponse := data.(type) {
|
||||
case map[interface{}]interface{}: // resp3 will return map
|
||||
for k, v := range midResponse {
|
||||
stringKey, ok := k.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("redis: invalid map key %#v", k)
|
||||
}
|
||||
resultMap[stringKey] = v
|
||||
}
|
||||
case []interface{}: // resp2 will return array of arrays
|
||||
n := len(midResponse)
|
||||
for i := 0; i < n; i++ {
|
||||
finalArr, ok := midResponse[i].([]interface{}) // final array that we need to transform to map
|
||||
if !ok {
|
||||
return fmt.Errorf("redis: unexpected response %#v", data)
|
||||
}
|
||||
m := len(finalArr)
|
||||
if m%2 != 0 { // since this should be map, keys should be even number
|
||||
return fmt.Errorf("redis: unexpected response %#v", data)
|
||||
}
|
||||
|
||||
for j := 0; j < m; j += 2 {
|
||||
stringKey, ok := finalArr[j].(string) // the first one
|
||||
if !ok {
|
||||
return fmt.Errorf("redis: invalid map key %#v", finalArr[i])
|
||||
}
|
||||
resultMap[stringKey] = finalArr[j+1] // second one is value
|
||||
}
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("redis: unexpected response %#v", data)
|
||||
}
|
||||
|
||||
cmd.val = resultMap
|
||||
return nil
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
type MapStringInterfaceSliceCmd struct {
|
||||
@ -4997,6 +5121,7 @@ type ClientInfo struct {
|
||||
PSub int // number of pattern matching subscriptions
|
||||
SSub int // redis version 7.0.3, number of shard channel subscriptions
|
||||
Multi int // number of commands in a MULTI/EXEC context
|
||||
Watch int // redis version 7.4 RC1, number of keys this client is currently watching.
|
||||
QueryBuf int // qbuf, query buffer length (0 means no query pending)
|
||||
QueryBufFree int // qbuf-free, free space of the query buffer (0 means the buffer is full)
|
||||
ArgvMem int // incomplete arguments for the next command (already extracted from query buffer)
|
||||
@ -5007,6 +5132,7 @@ type ClientInfo struct {
|
||||
OutputListLength int // oll, output list length (replies are queued in this list when the buffer is full)
|
||||
OutputMemory int // omem, output buffer memory usage
|
||||
TotalMemory int // tot-mem, total memory consumed by this client in its various buffers
|
||||
IoThread int // io-thread id
|
||||
Events string // file descriptor events (see below)
|
||||
LastCmd string // cmd, last command played
|
||||
User string // the authenticated username of the client
|
||||
@ -5149,6 +5275,8 @@ func parseClientInfo(txt string) (info *ClientInfo, err error) {
|
||||
info.SSub, err = strconv.Atoi(val)
|
||||
case "multi":
|
||||
info.Multi, err = strconv.Atoi(val)
|
||||
case "watch":
|
||||
info.Watch, err = strconv.Atoi(val)
|
||||
case "qbuf":
|
||||
info.QueryBuf, err = strconv.Atoi(val)
|
||||
case "qbuf-free":
|
||||
@ -5183,6 +5311,8 @@ func parseClientInfo(txt string) (info *ClientInfo, err error) {
|
||||
info.LibName = val
|
||||
case "lib-ver":
|
||||
info.LibVer = val
|
||||
case "io-thread":
|
||||
info.IoThread, err = strconv.Atoi(val)
|
||||
default:
|
||||
return nil, fmt.Errorf("redis: unexpected client info key(%s)", key)
|
||||
}
|
||||
@ -5310,6 +5440,16 @@ type LibraryInfo struct {
|
||||
LibVer *string
|
||||
}
|
||||
|
||||
// WithLibraryName returns a valid LibraryInfo with library name only.
|
||||
func WithLibraryName(libName string) LibraryInfo {
|
||||
return LibraryInfo{LibName: &libName}
|
||||
}
|
||||
|
||||
// WithLibraryVersion returns a valid LibraryInfo with library version only.
|
||||
func WithLibraryVersion(libVer string) LibraryInfo {
|
||||
return LibraryInfo{LibVer: &libVer}
|
||||
}
|
||||
|
||||
// -------------------------------------------
|
||||
|
||||
type InfoCmd struct {
|
||||
@ -5352,8 +5492,6 @@ func (cmd *InfoCmd) readReply(rd *proto.Reader) error {
|
||||
|
||||
section := ""
|
||||
scanner := bufio.NewScanner(strings.NewReader(val))
|
||||
moduleRe := regexp.MustCompile(`module:name=(.+?),(.+)$`)
|
||||
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if strings.HasPrefix(line, "#") {
|
||||
@ -5364,6 +5502,7 @@ func (cmd *InfoCmd) readReply(rd *proto.Reader) error {
|
||||
cmd.val[section] = make(map[string]string)
|
||||
} else if line != "" {
|
||||
if section == "Modules" {
|
||||
moduleRe := regexp.MustCompile(`module:name=(.+?),(.+)$`)
|
||||
kv := moduleRe.FindStringSubmatch(line)
|
||||
if len(kv) == 3 {
|
||||
cmd.val[section][kv[1]] = kv[2]
|
||||
@ -5444,9 +5583,12 @@ func (cmd *MonitorCmd) readMonitor(rd *proto.Reader, cancel context.CancelFunc)
|
||||
for {
|
||||
cmd.mu.Lock()
|
||||
st := cmd.status
|
||||
pk, _ := rd.Peek(1)
|
||||
cmd.mu.Unlock()
|
||||
if pk, _ := rd.Peek(1); len(pk) != 0 && st == monitorStatusStart {
|
||||
if len(pk) != 0 && st == monitorStatusStart {
|
||||
cmd.mu.Lock()
|
||||
line, err := rd.ReadString()
|
||||
cmd.mu.Unlock()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -220,6 +220,7 @@ type Cmdable interface {
|
||||
ProbabilisticCmdable
|
||||
PubSubCmdable
|
||||
ScriptingFunctionsCmdable
|
||||
SearchCmdable
|
||||
SetCmdable
|
||||
SortedSetCmdable
|
||||
StringCmdable
|
||||
@ -315,7 +316,7 @@ func (c statefulCmdable) ClientSetInfo(ctx context.Context, info LibraryInfo) *S
|
||||
|
||||
var cmd *StatusCmd
|
||||
if info.LibName != nil {
|
||||
libName := fmt.Sprintf("go-redis(%s,%s)", *info.LibName, runtime.Version())
|
||||
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)
|
||||
|
608
commands_test.go
608
commands_test.go
@ -193,6 +193,41 @@ var _ = Describe("Commands", func() {
|
||||
Expect(r.Val()).To(Equal(int64(0)))
|
||||
})
|
||||
|
||||
It("should ClientKillByFilter with MAXAGE", Label("NonRedisEnterprise"), func() {
|
||||
SkipBeforeRedisVersion(7.4, "doesn't work with older redis stack images")
|
||||
var s []string
|
||||
started := make(chan bool)
|
||||
done := make(chan bool)
|
||||
|
||||
go func() {
|
||||
defer GinkgoRecover()
|
||||
|
||||
started <- true
|
||||
blpop := client.BLPop(ctx, 0, "list")
|
||||
Expect(blpop.Val()).To(Equal(s))
|
||||
done <- true
|
||||
}()
|
||||
<-started
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
Fail("BLPOP is not blocked.")
|
||||
case <-time.After(1100 * time.Millisecond):
|
||||
// ok
|
||||
}
|
||||
|
||||
killed := client.ClientKillByFilter(ctx, "MAXAGE", "1")
|
||||
Expect(killed.Err()).NotTo(HaveOccurred())
|
||||
Expect(killed.Val()).To(BeNumerically(">=", 1))
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
// ok
|
||||
case <-time.After(200 * time.Millisecond):
|
||||
Fail("BLPOP is still blocked.")
|
||||
}
|
||||
})
|
||||
|
||||
It("should ClientID", func() {
|
||||
err := client.ClientID(ctx).Err()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
@ -254,7 +289,7 @@ var _ = Describe("Commands", func() {
|
||||
|
||||
// Test setting the libName
|
||||
libName := "go-redis"
|
||||
libInfo := redis.LibraryInfo{LibName: &libName}
|
||||
libInfo := redis.WithLibraryName(libName)
|
||||
setInfo := pipe.ClientSetInfo(ctx, libInfo)
|
||||
_, err := pipe.Exec(ctx)
|
||||
|
||||
@ -264,7 +299,7 @@ var _ = Describe("Commands", func() {
|
||||
|
||||
// Test setting the libVer
|
||||
libVer := "vX.x"
|
||||
libInfo = redis.LibraryInfo{LibVer: &libVer}
|
||||
libInfo = redis.WithLibraryVersion(libVer)
|
||||
setInfo = pipe.ClientSetInfo(ctx, libInfo)
|
||||
_, err = pipe.Exec(ctx)
|
||||
|
||||
@ -316,6 +351,23 @@ var _ = Describe("Commands", func() {
|
||||
Expect(val).NotTo(BeEmpty())
|
||||
})
|
||||
|
||||
It("should ConfigGet Modules", func() {
|
||||
SkipBeforeRedisVersion(8, "Config doesn't include modules before Redis 8")
|
||||
expected := map[string]string{
|
||||
"search-*": "search-timeout",
|
||||
"ts-*": "ts-retention-policy",
|
||||
"bf-*": "bf-error-rate",
|
||||
"cf-*": "cf-initial-size",
|
||||
}
|
||||
|
||||
for prefix, lookup := range expected {
|
||||
val, err := client.ConfigGet(ctx, prefix).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(val).NotTo(BeEmpty())
|
||||
Expect(val[lookup]).NotTo(BeEmpty())
|
||||
}
|
||||
})
|
||||
|
||||
It("should ConfigResetStat", Label("NonRedisEnterprise"), func() {
|
||||
r := client.ConfigResetStat(ctx)
|
||||
Expect(r.Err()).NotTo(HaveOccurred())
|
||||
@ -334,6 +386,127 @@ var _ = Describe("Commands", func() {
|
||||
Expect(configSet.Val()).To(Equal("OK"))
|
||||
})
|
||||
|
||||
It("should ConfigGet with Modules", Label("NonRedisEnterprise"), func() {
|
||||
SkipBeforeRedisVersion(8, "config get won't return modules configs before redis 8")
|
||||
configGet := client.ConfigGet(ctx, "*")
|
||||
Expect(configGet.Err()).NotTo(HaveOccurred())
|
||||
Expect(configGet.Val()).To(HaveKey("maxmemory"))
|
||||
Expect(configGet.Val()).To(HaveKey("search-timeout"))
|
||||
Expect(configGet.Val()).To(HaveKey("ts-retention-policy"))
|
||||
Expect(configGet.Val()).To(HaveKey("bf-error-rate"))
|
||||
Expect(configGet.Val()).To(HaveKey("cf-initial-size"))
|
||||
})
|
||||
|
||||
It("should ConfigSet FT DIALECT", func() {
|
||||
SkipBeforeRedisVersion(8, "config doesn't include modules before Redis 8")
|
||||
defaultState, err := client.ConfigGet(ctx, "search-default-dialect").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// set to 3
|
||||
res, err := client.ConfigSet(ctx, "search-default-dialect", "3").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(BeEquivalentTo("OK"))
|
||||
|
||||
defDialect, err := client.FTConfigGet(ctx, "DEFAULT_DIALECT").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(defDialect).To(BeEquivalentTo(map[string]interface{}{"DEFAULT_DIALECT": "3"}))
|
||||
|
||||
resGet, err := client.ConfigGet(ctx, "search-default-dialect").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(resGet).To(BeEquivalentTo(map[string]string{"search-default-dialect": "3"}))
|
||||
|
||||
// set to 2
|
||||
res, err = client.ConfigSet(ctx, "search-default-dialect", "2").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(BeEquivalentTo("OK"))
|
||||
|
||||
defDialect, err = client.FTConfigGet(ctx, "DEFAULT_DIALECT").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(defDialect).To(BeEquivalentTo(map[string]interface{}{"DEFAULT_DIALECT": "2"}))
|
||||
|
||||
// set to 1
|
||||
res, err = client.ConfigSet(ctx, "search-default-dialect", "1").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(BeEquivalentTo("OK"))
|
||||
|
||||
defDialect, err = client.FTConfigGet(ctx, "DEFAULT_DIALECT").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(defDialect).To(BeEquivalentTo(map[string]interface{}{"DEFAULT_DIALECT": "1"}))
|
||||
|
||||
resGet, err = client.ConfigGet(ctx, "search-default-dialect").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(resGet).To(BeEquivalentTo(map[string]string{"search-default-dialect": "1"}))
|
||||
|
||||
// set to default
|
||||
res, err = client.ConfigSet(ctx, "search-default-dialect", defaultState["search-default-dialect"]).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(BeEquivalentTo("OK"))
|
||||
})
|
||||
|
||||
It("should ConfigSet fail for ReadOnly", func() {
|
||||
SkipBeforeRedisVersion(8, "Config doesn't include modules before Redis 8")
|
||||
_, err := client.ConfigSet(ctx, "search-max-doctablesize", "100000").Result()
|
||||
Expect(err).To(HaveOccurred())
|
||||
})
|
||||
|
||||
It("should ConfigSet Modules", func() {
|
||||
SkipBeforeRedisVersion(8, "Config doesn't include modules before Redis 8")
|
||||
defaults := map[string]string{}
|
||||
expected := map[string]string{
|
||||
"search-timeout": "100",
|
||||
"ts-retention-policy": "2",
|
||||
"bf-error-rate": "0.13",
|
||||
"cf-initial-size": "64",
|
||||
}
|
||||
|
||||
// read the defaults to set them back later
|
||||
for setting, _ := range expected {
|
||||
val, err := client.ConfigGet(ctx, setting).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
defaults[setting] = val[setting]
|
||||
}
|
||||
|
||||
// check if new values can be set
|
||||
for setting, value := range expected {
|
||||
val, err := client.ConfigSet(ctx, setting, value).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(val).NotTo(BeEmpty())
|
||||
Expect(val).To(Equal("OK"))
|
||||
}
|
||||
|
||||
for setting, value := range expected {
|
||||
val, err := client.ConfigGet(ctx, setting).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(val).NotTo(BeEmpty())
|
||||
Expect(val[setting]).To(Equal(value))
|
||||
}
|
||||
|
||||
// set back to the defaults
|
||||
for setting, value := range defaults {
|
||||
val, err := client.ConfigSet(ctx, setting, value).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(val).NotTo(BeEmpty())
|
||||
Expect(val).To(Equal("OK"))
|
||||
}
|
||||
})
|
||||
|
||||
It("should Fail ConfigSet Modules", func() {
|
||||
SkipBeforeRedisVersion(8, "Config doesn't include modules before Redis 8")
|
||||
expected := map[string]string{
|
||||
"search-timeout": "-100",
|
||||
"ts-retention-policy": "-10",
|
||||
"bf-error-rate": "1.5",
|
||||
"cf-initial-size": "-10",
|
||||
}
|
||||
|
||||
for setting, value := range expected {
|
||||
val, err := client.ConfigSet(ctx, setting, value).Result()
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err).To(MatchError(ContainSubstring(setting)))
|
||||
Expect(val).To(BeEmpty())
|
||||
}
|
||||
})
|
||||
|
||||
It("should ConfigRewrite", Label("NonRedisEnterprise"), func() {
|
||||
configRewrite := client.ConfigRewrite(ctx)
|
||||
Expect(configRewrite.Err()).NotTo(HaveOccurred())
|
||||
@ -366,6 +539,59 @@ var _ = Describe("Commands", func() {
|
||||
Expect(info.Val()).To(HaveLen(1))
|
||||
})
|
||||
|
||||
It("should Info Modules", Label("redis.info"), func() {
|
||||
SkipBeforeRedisVersion(8, "modules are included in info for Redis Version >= 8")
|
||||
info := client.Info(ctx)
|
||||
Expect(info.Err()).NotTo(HaveOccurred())
|
||||
Expect(info.Val()).NotTo(BeNil())
|
||||
|
||||
info = client.Info(ctx, "search")
|
||||
Expect(info.Err()).NotTo(HaveOccurred())
|
||||
Expect(info.Val()).To(ContainSubstring("search"))
|
||||
|
||||
info = client.Info(ctx, "modules")
|
||||
Expect(info.Err()).NotTo(HaveOccurred())
|
||||
Expect(info.Val()).To(ContainSubstring("search"))
|
||||
Expect(info.Val()).To(ContainSubstring("ReJSON"))
|
||||
Expect(info.Val()).To(ContainSubstring("timeseries"))
|
||||
Expect(info.Val()).To(ContainSubstring("bf"))
|
||||
|
||||
info = client.Info(ctx, "everything")
|
||||
Expect(info.Err()).NotTo(HaveOccurred())
|
||||
Expect(info.Val()).To(ContainSubstring("search"))
|
||||
Expect(info.Val()).To(ContainSubstring("ReJSON"))
|
||||
Expect(info.Val()).To(ContainSubstring("timeseries"))
|
||||
Expect(info.Val()).To(ContainSubstring("bf"))
|
||||
})
|
||||
|
||||
It("should InfoMap Modules", Label("redis.info"), func() {
|
||||
SkipBeforeRedisVersion(8, "modules are included in info for Redis Version >= 8")
|
||||
info := client.InfoMap(ctx)
|
||||
Expect(info.Err()).NotTo(HaveOccurred())
|
||||
Expect(info.Val()).NotTo(BeNil())
|
||||
|
||||
info = client.InfoMap(ctx, "search")
|
||||
Expect(info.Err()).NotTo(HaveOccurred())
|
||||
Expect(len(info.Val())).To(BeNumerically(">=", 2))
|
||||
Expect(info.Val()["search_version"]).ToNot(BeNil())
|
||||
|
||||
info = client.InfoMap(ctx, "modules")
|
||||
Expect(info.Err()).NotTo(HaveOccurred())
|
||||
val := info.Val()
|
||||
modules, ok := val["Modules"]
|
||||
Expect(ok).To(BeTrue())
|
||||
Expect(len(val)).To(BeNumerically(">=", 2))
|
||||
Expect(val["search_version"]).ToNot(BeNil())
|
||||
Expect(modules["search"]).ToNot(BeNil())
|
||||
Expect(modules["ReJSON"]).ToNot(BeNil())
|
||||
Expect(modules["timeseries"]).ToNot(BeNil())
|
||||
Expect(modules["bf"]).ToNot(BeNil())
|
||||
|
||||
info = client.InfoMap(ctx, "everything")
|
||||
Expect(info.Err()).NotTo(HaveOccurred())
|
||||
Expect(len(info.Val())).To(BeNumerically(">=", 10))
|
||||
})
|
||||
|
||||
It("should Info cpu", func() {
|
||||
info := client.Info(ctx, "cpu")
|
||||
Expect(info.Err()).NotTo(HaveOccurred())
|
||||
@ -413,7 +639,6 @@ var _ = Describe("Commands", func() {
|
||||
It("should Command", Label("NonRedisEnterprise"), func() {
|
||||
cmds, err := client.Command(ctx).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(cmds)).To(BeNumerically("~", 240, 25))
|
||||
|
||||
cmd := cmds["mget"]
|
||||
Expect(cmd.Name).To(Equal("mget"))
|
||||
@ -483,8 +708,8 @@ var _ = Describe("Commands", func() {
|
||||
})
|
||||
})
|
||||
|
||||
Describe("debugging", func() {
|
||||
PIt("should DebugObject", func() {
|
||||
Describe("debugging", Label("NonRedisEnterprise"), func() {
|
||||
It("should DebugObject", func() {
|
||||
err := client.DebugObject(ctx, "foo").Err()
|
||||
Expect(err).To(MatchError("ERR no such key"))
|
||||
|
||||
@ -682,7 +907,7 @@ var _ = Describe("Commands", func() {
|
||||
Expect(get.Val()).To(Equal("hello"))
|
||||
})
|
||||
|
||||
It("should Object", func() {
|
||||
It("should Object", Label("NonRedisEnterprise"), func() {
|
||||
start := time.Now()
|
||||
set := client.Set(ctx, "key", "hello", 0)
|
||||
Expect(set.Err()).NotTo(HaveOccurred())
|
||||
@ -692,6 +917,11 @@ var _ = Describe("Commands", func() {
|
||||
Expect(refCount.Err()).NotTo(HaveOccurred())
|
||||
Expect(refCount.Val()).To(Equal(int64(1)))
|
||||
|
||||
client.ConfigSet(ctx, "maxmemory-policy", "volatile-lfu")
|
||||
freq := client.ObjectFreq(ctx, "key")
|
||||
Expect(freq.Err()).NotTo(HaveOccurred())
|
||||
client.ConfigSet(ctx, "maxmemory-policy", "noeviction") // default
|
||||
|
||||
err := client.ObjectEncoding(ctx, "key").Err()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
@ -1101,6 +1331,27 @@ var _ = Describe("Commands", func() {
|
||||
|
||||
keys, cursor, err := client.HScan(ctx, "myhash", 0, "", 0).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
// If we don't get at least two items back, it's really strange.
|
||||
Expect(cursor).To(BeNumerically(">=", 2))
|
||||
Expect(len(keys)).To(BeNumerically(">=", 2))
|
||||
Expect(keys[0]).To(HavePrefix("key"))
|
||||
Expect(keys[1]).To(Equal("hello"))
|
||||
})
|
||||
|
||||
It("should HScan without values", Label("NonRedisEnterprise"), func() {
|
||||
SkipBeforeRedisVersion(7.4, "doesn't work with older redis stack images")
|
||||
for i := 0; i < 1000; i++ {
|
||||
sadd := client.HSet(ctx, "myhash", fmt.Sprintf("key%d", i), "hello")
|
||||
Expect(sadd.Err()).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
keys, cursor, err := client.HScanNoValues(ctx, "myhash", 0, "", 0).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
// If we don't get at least two items back, it's really strange.
|
||||
Expect(cursor).To(BeNumerically(">=", 2))
|
||||
Expect(len(keys)).To(BeNumerically(">=", 2))
|
||||
Expect(keys[0]).To(HavePrefix("key"))
|
||||
Expect(keys[1]).To(HavePrefix("key"))
|
||||
Expect(keys).NotTo(BeEmpty())
|
||||
Expect(cursor).NotTo(BeZero())
|
||||
})
|
||||
@ -2038,12 +2289,6 @@ var _ = Describe("Commands", func() {
|
||||
Expect(replace.Val()).To(Equal(int64(1)))
|
||||
})
|
||||
|
||||
It("should acl dryrun", func() {
|
||||
dryRun := client.ACLDryRun(ctx, "default", "get", "randomKey")
|
||||
Expect(dryRun.Err()).NotTo(HaveOccurred())
|
||||
Expect(dryRun.Val()).To(Equal("OK"))
|
||||
})
|
||||
|
||||
It("should fail module loadex", Label("NonRedisEnterprise"), func() {
|
||||
dryRun := client.ModuleLoadex(ctx, &redis.ModuleLoadexConfig{
|
||||
Path: "/path/to/non-existent-library.so",
|
||||
@ -2091,51 +2336,6 @@ var _ = Describe("Commands", func() {
|
||||
|
||||
Expect(args).To(Equal(expectedArgs))
|
||||
})
|
||||
|
||||
It("should ACL LOG", Label("NonRedisEnterprise"), func() {
|
||||
err := client.Do(ctx, "acl", "setuser", "test", ">test", "on", "allkeys", "+get").Err()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
clientAcl := redis.NewClient(redisOptions())
|
||||
clientAcl.Options().Username = "test"
|
||||
clientAcl.Options().Password = "test"
|
||||
clientAcl.Options().DB = 0
|
||||
_ = clientAcl.Set(ctx, "mystring", "foo", 0).Err()
|
||||
_ = clientAcl.HSet(ctx, "myhash", "foo", "bar").Err()
|
||||
_ = clientAcl.SAdd(ctx, "myset", "foo", "bar").Err()
|
||||
|
||||
logEntries, err := client.ACLLog(ctx, 10).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(logEntries)).To(Equal(4))
|
||||
|
||||
for _, entry := range logEntries {
|
||||
Expect(entry.Reason).To(Equal("command"))
|
||||
Expect(entry.Context).To(Equal("toplevel"))
|
||||
Expect(entry.Object).NotTo(BeEmpty())
|
||||
Expect(entry.Username).To(Equal("test"))
|
||||
Expect(entry.AgeSeconds).To(BeNumerically(">=", 0))
|
||||
Expect(entry.ClientInfo).NotTo(BeNil())
|
||||
Expect(entry.EntryID).To(BeNumerically(">=", 0))
|
||||
Expect(entry.TimestampCreated).To(BeNumerically(">=", 0))
|
||||
Expect(entry.TimestampLastUpdated).To(BeNumerically(">=", 0))
|
||||
}
|
||||
|
||||
limitedLogEntries, err := client.ACLLog(ctx, 2).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(limitedLogEntries)).To(Equal(2))
|
||||
})
|
||||
|
||||
It("should ACL LOG RESET", Label("NonRedisEnterprise"), func() {
|
||||
// Call ACL LOG RESET
|
||||
resetCmd := client.ACLLogReset(ctx)
|
||||
Expect(resetCmd.Err()).NotTo(HaveOccurred())
|
||||
Expect(resetCmd.Val()).To(Equal("OK"))
|
||||
|
||||
// Verify that the log is empty after the reset
|
||||
logEntries, err := client.ACLLog(ctx, 10).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(logEntries)).To(Equal(0))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("hashes", func() {
|
||||
@ -2431,6 +2631,193 @@ var _ = Describe("Commands", func() {
|
||||
Equal([]redis.KeyValue{{Key: "key2", Value: "hello2"}}),
|
||||
))
|
||||
})
|
||||
|
||||
It("should HStrLen", func() {
|
||||
hSet := client.HSet(ctx, "hash", "key", "hello")
|
||||
Expect(hSet.Err()).NotTo(HaveOccurred())
|
||||
|
||||
hStrLen := client.HStrLen(ctx, "hash", "key")
|
||||
Expect(hStrLen.Err()).NotTo(HaveOccurred())
|
||||
Expect(hStrLen.Val()).To(Equal(int64(len("hello"))))
|
||||
|
||||
nonHStrLen := client.HStrLen(ctx, "hash", "keyNon")
|
||||
Expect(hStrLen.Err()).NotTo(HaveOccurred())
|
||||
Expect(nonHStrLen.Val()).To(Equal(int64(0)))
|
||||
|
||||
hDel := client.HDel(ctx, "hash", "key")
|
||||
Expect(hDel.Err()).NotTo(HaveOccurred())
|
||||
Expect(hDel.Val()).To(Equal(int64(1)))
|
||||
})
|
||||
|
||||
It("should HExpire", Label("hash-expiration", "NonRedisEnterprise"), func() {
|
||||
SkipBeforeRedisVersion(7.4, "doesn't work with older redis stack images")
|
||||
res, err := client.HExpire(ctx, "no_such_key", 10*time.Second, "field1", "field2", "field3").Result()
|
||||
Expect(err).To(BeNil())
|
||||
Expect(res).To(BeEquivalentTo([]int64{-2, -2, -2}))
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
sadd := client.HSet(ctx, "myhash", fmt.Sprintf("key%d", i), "hello")
|
||||
Expect(sadd.Err()).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
res, err = client.HExpire(ctx, "myhash", 10*time.Second, "key1", "key2", "key200").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(Equal([]int64{1, 1, -2}))
|
||||
})
|
||||
|
||||
|
||||
It("should HPExpire", Label("hash-expiration", "NonRedisEnterprise"), func() {
|
||||
SkipBeforeRedisVersion(7.4, "doesn't work with older redis stack images")
|
||||
res, err := client.HPExpire(ctx, "no_such_key", 10*time.Second, "field1", "field2", "field3").Result()
|
||||
Expect(err).To(BeNil())
|
||||
Expect(res).To(BeEquivalentTo([]int64{-2, -2, -2}))
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
sadd := client.HSet(ctx, "myhash", fmt.Sprintf("key%d", i), "hello")
|
||||
Expect(sadd.Err()).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
res, err = client.HPExpire(ctx, "myhash", 10*time.Second, "key1", "key2", "key200").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(Equal([]int64{1, 1, -2}))
|
||||
})
|
||||
|
||||
It("should HExpireAt", Label("hash-expiration", "NonRedisEnterprise"), func() {
|
||||
SkipBeforeRedisVersion(7.4, "doesn't work with older redis stack images")
|
||||
resEmpty, err := client.HExpireAt(ctx, "no_such_key", time.Now().Add(10*time.Second), "field1", "field2", "field3").Result()
|
||||
Expect(err).To(BeNil())
|
||||
Expect(resEmpty).To(BeEquivalentTo([]int64{-2, -2, -2}))
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
sadd := client.HSet(ctx, "myhash", fmt.Sprintf("key%d", i), "hello")
|
||||
Expect(sadd.Err()).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
res, err := client.HExpireAt(ctx, "myhash", time.Now().Add(10*time.Second), "key1", "key2", "key200").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(Equal([]int64{1, 1, -2}))
|
||||
})
|
||||
|
||||
It("should HPExpireAt", Label("hash-expiration", "NonRedisEnterprise"), func() {
|
||||
SkipBeforeRedisVersion(7.4, "doesn't work with older redis stack images")
|
||||
resEmpty, err := client.HPExpireAt(ctx, "no_such_key", time.Now().Add(10*time.Second), "field1", "field2", "field3").Result()
|
||||
Expect(err).To(BeNil())
|
||||
Expect(resEmpty).To(BeEquivalentTo([]int64{-2, -2, -2}))
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
sadd := client.HSet(ctx, "myhash", fmt.Sprintf("key%d", i), "hello")
|
||||
Expect(sadd.Err()).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
res, err := client.HPExpireAt(ctx, "myhash", time.Now().Add(10*time.Second), "key1", "key2", "key200").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(Equal([]int64{1, 1, -2}))
|
||||
})
|
||||
|
||||
It("should HPersist", Label("hash-expiration", "NonRedisEnterprise"), func() {
|
||||
SkipBeforeRedisVersion(7.4, "doesn't work with older redis stack images")
|
||||
resEmpty, err := client.HPersist(ctx, "no_such_key", "field1", "field2", "field3").Result()
|
||||
Expect(err).To(BeNil())
|
||||
Expect(resEmpty).To(BeEquivalentTo([]int64{-2, -2, -2}))
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
sadd := client.HSet(ctx, "myhash", fmt.Sprintf("key%d", i), "hello")
|
||||
Expect(sadd.Err()).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
res, err := client.HPersist(ctx, "myhash", "key1", "key2", "key200").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(Equal([]int64{-1, -1, -2}))
|
||||
|
||||
res, err = client.HExpire(ctx, "myhash", 10*time.Second, "key1", "key200").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(Equal([]int64{1, -2}))
|
||||
|
||||
res, err = client.HPersist(ctx, "myhash", "key1", "key2", "key200").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(Equal([]int64{1, -1, -2}))
|
||||
})
|
||||
|
||||
It("should HExpireTime", Label("hash-expiration", "NonRedisEnterprise"), func() {
|
||||
SkipBeforeRedisVersion(7.4, "doesn't work with older redis stack images")
|
||||
resEmpty, err := client.HExpireTime(ctx, "no_such_key", "field1", "field2", "field3").Result()
|
||||
Expect(err).To(BeNil())
|
||||
Expect(resEmpty).To(BeEquivalentTo([]int64{-2, -2, -2}))
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
sadd := client.HSet(ctx, "myhash", fmt.Sprintf("key%d", i), "hello")
|
||||
Expect(sadd.Err()).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
res, err := client.HExpire(ctx, "myhash", 10*time.Second, "key1", "key200").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(Equal([]int64{1, -2}))
|
||||
|
||||
res, err = client.HExpireTime(ctx, "myhash", "key1", "key2", "key200").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res[0]).To(BeNumerically("~", time.Now().Add(10*time.Second).Unix(), 1))
|
||||
})
|
||||
|
||||
It("should HPExpireTime", Label("hash-expiration", "NonRedisEnterprise"), func() {
|
||||
SkipBeforeRedisVersion(7.4, "doesn't work with older redis stack images")
|
||||
resEmpty, err := client.HPExpireTime(ctx, "no_such_key", "field1", "field2", "field3").Result()
|
||||
Expect(err).To(BeNil())
|
||||
Expect(resEmpty).To(BeEquivalentTo([]int64{-2, -2, -2}))
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
sadd := client.HSet(ctx, "myhash", fmt.Sprintf("key%d", i), "hello")
|
||||
Expect(sadd.Err()).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
expireAt := time.Now().Add(10 * time.Second)
|
||||
res, err := client.HPExpireAt(ctx, "myhash", expireAt, "key1", "key200").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(Equal([]int64{1, -2}))
|
||||
|
||||
res, err = client.HPExpireTime(ctx, "myhash", "key1", "key2", "key200").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(BeEquivalentTo([]int64{expireAt.UnixMilli(), -1, -2}))
|
||||
})
|
||||
|
||||
It("should HTTL", Label("hash-expiration", "NonRedisEnterprise"), func() {
|
||||
SkipBeforeRedisVersion(7.4, "doesn't work with older redis stack images")
|
||||
resEmpty, err := client.HTTL(ctx, "no_such_key", "field1", "field2", "field3").Result()
|
||||
Expect(err).To(BeNil())
|
||||
Expect(resEmpty).To(BeEquivalentTo([]int64{-2, -2, -2}))
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
sadd := client.HSet(ctx, "myhash", fmt.Sprintf("key%d", i), "hello")
|
||||
Expect(sadd.Err()).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
res, err := client.HExpire(ctx, "myhash", 10*time.Second, "key1", "key200").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(Equal([]int64{1, -2}))
|
||||
|
||||
res, err = client.HTTL(ctx, "myhash", "key1", "key2", "key200").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(Equal([]int64{10, -1, -2}))
|
||||
})
|
||||
|
||||
It("should HPTTL", Label("hash-expiration", "NonRedisEnterprise"), func() {
|
||||
SkipBeforeRedisVersion(7.4, "doesn't work with older redis stack images")
|
||||
resEmpty, err := client.HPTTL(ctx, "no_such_key", "field1", "field2", "field3").Result()
|
||||
Expect(err).To(BeNil())
|
||||
Expect(resEmpty).To(BeEquivalentTo([]int64{-2, -2, -2}))
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
sadd := client.HSet(ctx, "myhash", fmt.Sprintf("key%d", i), "hello")
|
||||
Expect(sadd.Err()).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
res, err := client.HExpire(ctx, "myhash", 10*time.Second, "key1", "key200").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(Equal([]int64{1, -2}))
|
||||
|
||||
res, err = client.HPTTL(ctx, "myhash", "key1", "key2", "key200").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res[0]).To(BeNumerically("~", 10*time.Second.Milliseconds(), 1))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("hyperloglog", func() {
|
||||
@ -5687,6 +6074,81 @@ var _ = Describe("Commands", func() {
|
||||
Expect(err).To(Equal(redis.Nil))
|
||||
})
|
||||
|
||||
It("should XRead LastEntry", Label("NonRedisEnterprise"), func() {
|
||||
SkipBeforeRedisVersion(7.4, "doesn't work with older redis stack images")
|
||||
res, err := client.XRead(ctx, &redis.XReadArgs{
|
||||
Streams: []string{"stream"},
|
||||
Count: 2, // we expect 1 message
|
||||
ID: "+",
|
||||
}).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(Equal([]redis.XStream{
|
||||
{
|
||||
Stream: "stream",
|
||||
Messages: []redis.XMessage{
|
||||
{ID: "3-0", Values: map[string]interface{}{"tres": "troix"}},
|
||||
},
|
||||
},
|
||||
}))
|
||||
})
|
||||
|
||||
It("should XRead LastEntry from two streams", Label("NonRedisEnterprise"), func() {
|
||||
SkipBeforeRedisVersion(7.4, "doesn't work with older redis stack images")
|
||||
res, err := client.XRead(ctx, &redis.XReadArgs{
|
||||
Streams: []string{"stream", "stream"},
|
||||
ID: "+",
|
||||
}).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(Equal([]redis.XStream{
|
||||
{
|
||||
Stream: "stream",
|
||||
Messages: []redis.XMessage{
|
||||
{ID: "3-0", Values: map[string]interface{}{"tres": "troix"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
Stream: "stream",
|
||||
Messages: []redis.XMessage{
|
||||
{ID: "3-0", Values: map[string]interface{}{"tres": "troix"}},
|
||||
},
|
||||
},
|
||||
}))
|
||||
})
|
||||
|
||||
It("should XRead LastEntry blocks", Label("NonRedisEnterprise"), func() {
|
||||
SkipBeforeRedisVersion(7.4, "doesn't work with older redis stack images")
|
||||
start := time.Now()
|
||||
go func() {
|
||||
defer GinkgoRecover()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
id, err := client.XAdd(ctx, &redis.XAddArgs{
|
||||
Stream: "empty",
|
||||
ID: "4-0",
|
||||
Values: map[string]interface{}{"quatro": "quatre"},
|
||||
}).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(id).To(Equal("4-0"))
|
||||
}()
|
||||
|
||||
res, err := client.XRead(ctx, &redis.XReadArgs{
|
||||
Streams: []string{"empty"},
|
||||
Block: 500 * time.Millisecond,
|
||||
ID: "+",
|
||||
}).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
// Ensure that the XRead call with LastEntry option blocked for at least 100ms.
|
||||
Expect(time.Since(start)).To(BeNumerically(">=", 100*time.Millisecond))
|
||||
Expect(res).To(Equal([]redis.XStream{
|
||||
{
|
||||
Stream: "empty",
|
||||
Messages: []redis.XMessage{
|
||||
{ID: "4-0", Values: map[string]interface{}{"quatro": "quatre"}},
|
||||
},
|
||||
},
|
||||
}))
|
||||
})
|
||||
|
||||
Describe("group", func() {
|
||||
BeforeEach(func() {
|
||||
err := client.XGroupCreate(ctx, "stream", "group", "0").Err()
|
||||
@ -6190,14 +6652,12 @@ var _ = Describe("Commands", func() {
|
||||
|
||||
res, err := client.ZRangeWithScores(ctx, "result", 0, -1).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(ContainElement(redis.Z{
|
||||
Score: 190.44242984775784,
|
||||
Member: "Palermo",
|
||||
}))
|
||||
Expect(res).To(ContainElement(redis.Z{
|
||||
Score: 56.4412578701582,
|
||||
Member: "Catania",
|
||||
}))
|
||||
Expect(len(res)).To(Equal(2))
|
||||
var palermo, catania redis.Z
|
||||
Expect(res).To(ContainElement(HaveField("Member", "Palermo"), &palermo))
|
||||
Expect(res).To(ContainElement(HaveField("Member", "Catania"), &catania))
|
||||
Expect(palermo.Score).To(BeNumerically("~", 190, 1))
|
||||
Expect(catania.Score).To(BeNumerically("~", 56, 1))
|
||||
})
|
||||
|
||||
It("should search geo radius with options", func() {
|
||||
@ -6509,16 +6969,13 @@ var _ = Describe("Commands", func() {
|
||||
|
||||
v, err := client.ZRangeWithScores(ctx, "key2", 0, -1).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(v).To(Equal([]redis.Z{
|
||||
{
|
||||
Score: 56.441257870158204,
|
||||
Member: "Catania",
|
||||
},
|
||||
{
|
||||
Score: 190.44242984775784,
|
||||
Member: "Palermo",
|
||||
},
|
||||
}))
|
||||
|
||||
Expect(len(v)).To(Equal(2))
|
||||
var palermo, catania redis.Z
|
||||
Expect(v).To(ContainElement(HaveField("Member", "Palermo"), &palermo))
|
||||
Expect(v).To(ContainElement(HaveField("Member", "Catania"), &catania))
|
||||
Expect(palermo.Score).To(BeNumerically("~", 190, 1))
|
||||
Expect(catania.Score).To(BeNumerically("~", 56, 1))
|
||||
})
|
||||
})
|
||||
|
||||
@ -6908,6 +7365,7 @@ var _ = Describe("Commands", func() {
|
||||
})
|
||||
|
||||
It("Shows function stats", func() {
|
||||
SkipBeforeRedisVersion(7.4, "doesn't work with older redis stack images")
|
||||
defer client.FunctionKill(ctx)
|
||||
|
||||
// We can not run blocking commands in Redis functions, so we're using an infinite loop,
|
||||
|
101
docker-compose.yml
Normal file
101
docker-compose.yml
Normal file
@ -0,0 +1,101 @@
|
||||
---
|
||||
|
||||
services:
|
||||
redis:
|
||||
image: ${CLIENT_LIBS_TEST_IMAGE:-redislabs/client-libs-test:rs-7.4.0-v2}
|
||||
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}
|
||||
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}
|
||||
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}
|
||||
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}
|
||||
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
|
6
dockers/.gitignore
vendored
Normal file
6
dockers/.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
osscluster/
|
||||
ring/
|
||||
standalone/
|
||||
sentinel-cluster/
|
||||
sentinel/
|
||||
|
5
dockers/sentinel.conf
Normal file
5
dockers/sentinel.conf
Normal file
@ -0,0 +1,5 @@
|
||||
sentinel resolve-hostnames yes
|
||||
sentinel monitor go-redis-test 127.0.0.1 9121 2
|
||||
sentinel down-after-milliseconds go-redis-test 5000
|
||||
sentinel failover-timeout go-redis-test 60000
|
||||
sentinel parallel-syncs go-redis-test 1
|
85
doctests/bf_tutorial_test.go
Normal file
85
doctests/bf_tutorial_test.go
Normal file
@ -0,0 +1,85 @@
|
||||
// EXAMPLE: bf_tutorial
|
||||
// HIDE_START
|
||||
package example_commands_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
// HIDE_END
|
||||
|
||||
func ExampleClient_bloom() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// make sure we are working with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "bikes:models")
|
||||
// REMOVE_END
|
||||
|
||||
// STEP_START bloom
|
||||
res1, err := rdb.BFReserve(ctx, "bikes:models", 0.01, 1000).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res1) // >>> OK
|
||||
|
||||
res2, err := rdb.BFAdd(ctx, "bikes:models", "Smoky Mountain Striker").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res2) // >>> true
|
||||
|
||||
res3, err := rdb.BFExists(ctx, "bikes:models", "Smoky Mountain Striker").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res3) // >>> true
|
||||
|
||||
res4, err := rdb.BFMAdd(ctx, "bikes:models",
|
||||
"Rocky Mountain Racer",
|
||||
"Cloudy City Cruiser",
|
||||
"Windy City Wippet",
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res4) // >>> [true true true]
|
||||
|
||||
res5, err := rdb.BFMExists(ctx, "bikes:models",
|
||||
"Rocky Mountain Racer",
|
||||
"Cloudy City Cruiser",
|
||||
"Windy City Wippet",
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res5) // >>> [true true true]
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// OK
|
||||
// true
|
||||
// true
|
||||
// [true true true]
|
||||
// [true true true]
|
||||
}
|
81
doctests/bitfield_tutorial_test.go
Normal file
81
doctests/bitfield_tutorial_test.go
Normal file
@ -0,0 +1,81 @@
|
||||
// EXAMPLE: bitfield_tutorial
|
||||
// HIDE_START
|
||||
package example_commands_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
// HIDE_END
|
||||
|
||||
func ExampleClient_bf() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// make sure we are working with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "bike:1:stats")
|
||||
// REMOVE_END
|
||||
|
||||
// STEP_START bf
|
||||
res1, err := rdb.BitField(ctx, "bike:1:stats",
|
||||
"set", "u32", "#0", "1000",
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res1) // >>> [0]
|
||||
|
||||
res2, err := rdb.BitField(ctx,
|
||||
"bike:1:stats",
|
||||
"incrby", "u32", "#0", "-50",
|
||||
"incrby", "u32", "#1", "1",
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res2) // >>> [950 1]
|
||||
|
||||
res3, err := rdb.BitField(ctx,
|
||||
"bike:1:stats",
|
||||
"incrby", "u32", "#0", "500",
|
||||
"incrby", "u32", "#1", "1",
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res3) // >>> [1450 2]
|
||||
|
||||
res4, err := rdb.BitField(ctx, "bike:1:stats",
|
||||
"get", "u32", "#0",
|
||||
"get", "u32", "#1",
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res4) // >>> [1450 2]
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// [0]
|
||||
// [950 1]
|
||||
// [1450 2]
|
||||
// [1450 2]
|
||||
}
|
96
doctests/bitmap_tutorial_test.go
Normal file
96
doctests/bitmap_tutorial_test.go
Normal file
@ -0,0 +1,96 @@
|
||||
// EXAMPLE: bitmap_tutorial
|
||||
// HIDE_START
|
||||
package example_commands_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
// HIDE_END
|
||||
|
||||
func ExampleClient_ping() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// make sure we are working with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "pings:2024-01-01-00:00")
|
||||
// REMOVE_END
|
||||
|
||||
// STEP_START ping
|
||||
res1, err := rdb.SetBit(ctx, "pings:2024-01-01-00:00", 123, 1).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res1) // >>> 0
|
||||
|
||||
res2, err := rdb.GetBit(ctx, "pings:2024-01-01-00:00", 123).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res2) // >>> 1
|
||||
|
||||
res3, err := rdb.GetBit(ctx, "pings:2024-01-01-00:00", 456).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res3) // >>> 0
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// 0
|
||||
// 1
|
||||
// 0
|
||||
}
|
||||
|
||||
func ExampleClient_bitcount() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// start with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
_, err := rdb.SetBit(ctx, "pings:2024-01-01-00:00", 123, 1).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// REMOVE_END
|
||||
|
||||
// STEP_START bitcount
|
||||
res4, err := rdb.BitCount(ctx, "pings:2024-01-01-00:00",
|
||||
&redis.BitCount{
|
||||
Start: 0,
|
||||
End: 456,
|
||||
}).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res4) // >>> 1
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// 1
|
||||
}
|
200
doctests/cmds_generic_test.go
Normal file
200
doctests/cmds_generic_test.go
Normal file
@ -0,0 +1,200 @@
|
||||
// EXAMPLE: cmds_generic
|
||||
// HIDE_START
|
||||
package example_commands_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
// HIDE_END
|
||||
|
||||
func ExampleClient_del_cmd() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// make sure we are working with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "key1", "key2", "key3")
|
||||
// REMOVE_END
|
||||
|
||||
// STEP_START del
|
||||
delResult1, err := rdb.Set(ctx, "key1", "Hello", 0).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(delResult1) // >>> OK
|
||||
|
||||
delResult2, err := rdb.Set(ctx, "key2", "World", 0).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(delResult2) // >>> OK
|
||||
|
||||
delResult3, err := rdb.Del(ctx, "key1", "key2", "key3").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(delResult3) // >>> 2
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// OK
|
||||
// OK
|
||||
// 2
|
||||
}
|
||||
|
||||
func ExampleClient_expire_cmd() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// start with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "mykey")
|
||||
// REMOVE_END
|
||||
|
||||
// STEP_START expire
|
||||
expireResult1, err := rdb.Set(ctx, "mykey", "Hello", 0).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(expireResult1) // >>> OK
|
||||
|
||||
expireResult2, err := rdb.Expire(ctx, "mykey", 10*time.Second).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(expireResult2) // >>> true
|
||||
|
||||
expireResult3, err := rdb.TTL(ctx, "mykey").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(math.Round(expireResult3.Seconds())) // >>> 10
|
||||
|
||||
expireResult4, err := rdb.Set(ctx, "mykey", "Hello World", 0).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(expireResult4) // >>> OK
|
||||
|
||||
expireResult5, err := rdb.TTL(ctx, "mykey").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(expireResult5) // >>> -1ns
|
||||
|
||||
expireResult6, err := rdb.ExpireXX(ctx, "mykey", 10*time.Second).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(expireResult6) // >>> false
|
||||
|
||||
expireResult7, err := rdb.TTL(ctx, "mykey").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(expireResult7) // >>> -1ns
|
||||
|
||||
expireResult8, err := rdb.ExpireNX(ctx, "mykey", 10*time.Second).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(expireResult8) // >>> true
|
||||
|
||||
expireResult9, err := rdb.TTL(ctx, "mykey").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(math.Round(expireResult9.Seconds())) // >>> 10
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// OK
|
||||
// true
|
||||
// 10
|
||||
// OK
|
||||
// -1ns
|
||||
// false
|
||||
// -1ns
|
||||
// true
|
||||
// 10
|
||||
}
|
||||
|
||||
func ExampleClient_ttl_cmd() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// start with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "mykey")
|
||||
// REMOVE_END
|
||||
|
||||
// STEP_START ttl
|
||||
ttlResult1, err := rdb.Set(ctx, "mykey", "Hello", 10*time.Second).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(ttlResult1) // >>> OK
|
||||
|
||||
ttlResult2, err := rdb.TTL(ctx, "mykey").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(math.Round(ttlResult2.Seconds())) // >>> 10
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// OK
|
||||
// 10
|
||||
}
|
249
doctests/cmds_hash_test.go
Normal file
249
doctests/cmds_hash_test.go
Normal file
@ -0,0 +1,249 @@
|
||||
// EXAMPLE: cmds_hash
|
||||
// HIDE_START
|
||||
package example_commands_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
// HIDE_END
|
||||
|
||||
func ExampleClient_hset() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// make sure we are working with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "myhash")
|
||||
// REMOVE_END
|
||||
|
||||
// STEP_START hset
|
||||
res1, err := rdb.HSet(ctx, "myhash", "field1", "Hello").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res1) // >>> 1
|
||||
|
||||
res2, err := rdb.HGet(ctx, "myhash", "field1").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res2) // >>> Hello
|
||||
|
||||
res3, err := rdb.HSet(ctx, "myhash",
|
||||
"field2", "Hi",
|
||||
"field3", "World",
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res3) // >>> 2
|
||||
|
||||
res4, err := rdb.HGet(ctx, "myhash", "field2").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res4) // >>> Hi
|
||||
|
||||
res5, err := rdb.HGet(ctx, "myhash", "field3").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res5) // >>> World
|
||||
|
||||
res6, err := rdb.HGetAll(ctx, "myhash").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
keys := make([]string, 0, len(res6))
|
||||
|
||||
for key, _ := range res6 {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
|
||||
sort.Strings(keys)
|
||||
|
||||
for _, key := range keys {
|
||||
fmt.Printf("Key: %v, value: %v\n", key, res6[key])
|
||||
}
|
||||
// >>> Key: field1, value: Hello
|
||||
// >>> Key: field2, value: Hi
|
||||
// >>> Key: field3, value: World
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// 1
|
||||
// Hello
|
||||
// 2
|
||||
// Hi
|
||||
// World
|
||||
// Key: field1, value: Hello
|
||||
// Key: field2, value: Hi
|
||||
// Key: field3, value: World
|
||||
}
|
||||
|
||||
func ExampleClient_hget() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// start with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "myhash")
|
||||
// REMOVE_END
|
||||
|
||||
// STEP_START hget
|
||||
res7, err := rdb.HSet(ctx, "myhash", "field1", "foo").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res7) // >>> 1
|
||||
|
||||
res8, err := rdb.HGet(ctx, "myhash", "field1").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res8) // >>> foo
|
||||
|
||||
res9, err := rdb.HGet(ctx, "myhash", "field2").Result()
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
fmt.Println(res9) // >>> <empty string>
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// 1
|
||||
// foo
|
||||
// redis: nil
|
||||
}
|
||||
|
||||
func ExampleClient_hgetall() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// start with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "myhash")
|
||||
// REMOVE_END
|
||||
|
||||
// STEP_START hgetall
|
||||
hGetAllResult1, err := rdb.HSet(ctx, "myhash",
|
||||
"field1", "Hello",
|
||||
"field2", "World",
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(hGetAllResult1) // >>> 2
|
||||
|
||||
hGetAllResult2, err := rdb.HGetAll(ctx, "myhash").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
keys := make([]string, 0, len(hGetAllResult2))
|
||||
|
||||
for key, _ := range hGetAllResult2 {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
|
||||
sort.Strings(keys)
|
||||
|
||||
for _, key := range keys {
|
||||
fmt.Printf("Key: %v, value: %v\n", key, hGetAllResult2[key])
|
||||
}
|
||||
// >>> Key: field1, value: Hello
|
||||
// >>> Key: field2, value: World
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// 2
|
||||
// Key: field1, value: Hello
|
||||
// Key: field2, value: World
|
||||
}
|
||||
|
||||
func ExampleClient_hvals() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// start with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "myhash")
|
||||
// REMOVE_END
|
||||
|
||||
// STEP_START hvals
|
||||
hValsResult1, err := rdb.HSet(ctx, "myhash",
|
||||
"field1", "Hello",
|
||||
"field2", "World",
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(hValsResult1) // >>> 2
|
||||
|
||||
hValsResult2, err := rdb.HVals(ctx, "myhash").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
sort.Strings(hValsResult2)
|
||||
|
||||
fmt.Println(hValsResult2) // >>> [Hello World]
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// 2
|
||||
// [Hello World]
|
||||
}
|
76
doctests/cmds_servermgmt_test.go
Normal file
76
doctests/cmds_servermgmt_test.go
Normal file
@ -0,0 +1,76 @@
|
||||
// EXAMPLE: cmds_servermgmt
|
||||
// HIDE_START
|
||||
package example_commands_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
// HIDE_END
|
||||
|
||||
func ExampleClient_cmd_flushall() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// STEP_START flushall
|
||||
// REMOVE_START
|
||||
// make sure we are working with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Set(ctx, "testkey1", "1", 0)
|
||||
rdb.Set(ctx, "testkey2", "2", 0)
|
||||
rdb.Set(ctx, "testkey3", "3", 0)
|
||||
// REMOVE_END
|
||||
flushAllResult1, err := rdb.FlushAll(ctx).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(flushAllResult1) // >>> OK
|
||||
|
||||
flushAllResult2, err := rdb.Keys(ctx, "*").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(flushAllResult2) // >>> []
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// OK
|
||||
// []
|
||||
}
|
||||
|
||||
func ExampleClient_cmd_info() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// STEP_START info
|
||||
infoResult, err := rdb.Info(ctx).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Check the first 8 characters (the full info string contains
|
||||
// much more text than this).
|
||||
fmt.Println(infoResult[:8]) // >>> # Server
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// # Server
|
||||
}
|
228
doctests/cmds_sorted_set_test.go
Normal file
228
doctests/cmds_sorted_set_test.go
Normal file
@ -0,0 +1,228 @@
|
||||
// EXAMPLE: cmds_sorted_set
|
||||
// HIDE_START
|
||||
package example_commands_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
// HIDE_END
|
||||
|
||||
func ExampleClient_zadd_cmd() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// make sure we are working with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "myzset")
|
||||
// REMOVE_END
|
||||
|
||||
// STEP_START zadd
|
||||
zAddResult1, err := rdb.ZAdd(ctx, "myzset",
|
||||
redis.Z{Member: "one", Score: 1},
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(zAddResult1) // >>> 1
|
||||
|
||||
zAddResult2, err := rdb.ZAdd(ctx, "myzset",
|
||||
redis.Z{Member: "uno", Score: 1},
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(zAddResult2)
|
||||
|
||||
zAddResult3, err := rdb.ZAdd(ctx, "myzset",
|
||||
redis.Z{Member: "two", Score: 2},
|
||||
redis.Z{Member: "three", Score: 3},
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(zAddResult3) // >>> 2
|
||||
|
||||
zAddResult4, err := rdb.ZRangeWithScores(ctx, "myzset", 0, -1).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(zAddResult4) // >>> [{1 one} {1 uno} {2 two} {3 three}]
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// 1
|
||||
// 1
|
||||
// 2
|
||||
// [{1 one} {1 uno} {2 two} {3 three}]
|
||||
}
|
||||
|
||||
func ExampleClient_zrange1() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// start with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "myzset")
|
||||
// REMOVE_END
|
||||
|
||||
// STEP_START zrange1
|
||||
zrangeResult1, err := rdb.ZAdd(ctx, "myzset",
|
||||
redis.Z{Member: "one", Score: 1},
|
||||
redis.Z{Member: "two", Score: 2},
|
||||
redis.Z{Member: "three", Score: 3},
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(zrangeResult1) // >>> 3
|
||||
|
||||
zrangeResult2, err := rdb.ZRange(ctx, "myzset", 0, -1).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(zrangeResult2) // >>> [one two three]
|
||||
|
||||
zrangeResult3, err := rdb.ZRange(ctx, "myzset", 2, 3).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(zrangeResult3) // >>> [three]
|
||||
|
||||
zrangeResult4, err := rdb.ZRange(ctx, "myzset", -2, -1).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(zrangeResult4) // >>> [two three]
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// 3
|
||||
// [one two three]
|
||||
// [three]
|
||||
// [two three]
|
||||
}
|
||||
|
||||
func ExampleClient_zrange2() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// start with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "myzset")
|
||||
// REMOVE_END
|
||||
|
||||
// STEP_START zrange2
|
||||
zRangeResult5, err := rdb.ZAdd(ctx, "myzset",
|
||||
redis.Z{Member: "one", Score: 1},
|
||||
redis.Z{Member: "two", Score: 2},
|
||||
redis.Z{Member: "three", Score: 3},
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(zRangeResult5) // >>> 3
|
||||
|
||||
zRangeResult6, err := rdb.ZRangeWithScores(ctx, "myzset", 0, 1).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(zRangeResult6) // >>> [{1 one} {2 two}]
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// 3
|
||||
// [{1 one} {2 two}]
|
||||
}
|
||||
|
||||
func ExampleClient_zrange3() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// start with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "myzset")
|
||||
// REMOVE_END
|
||||
|
||||
// STEP_START zrange3
|
||||
zRangeResult7, err := rdb.ZAdd(ctx, "myzset",
|
||||
redis.Z{Member: "one", Score: 1},
|
||||
redis.Z{Member: "two", Score: 2},
|
||||
redis.Z{Member: "three", Score: 3},
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(zRangeResult7) // >>> 3
|
||||
|
||||
zRangeResult8, err := rdb.ZRangeArgs(ctx,
|
||||
redis.ZRangeArgs{
|
||||
Key: "myzset",
|
||||
ByScore: true,
|
||||
Start: "(1",
|
||||
Stop: "+inf",
|
||||
Offset: 1,
|
||||
Count: 1,
|
||||
},
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(zRangeResult8) // >>> [three]
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// 3
|
||||
// [three]
|
||||
}
|
59
doctests/cmds_string_test.go
Normal file
59
doctests/cmds_string_test.go
Normal file
@ -0,0 +1,59 @@
|
||||
// EXAMPLE: cmds_string
|
||||
// HIDE_START
|
||||
package example_commands_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
// HIDE_END
|
||||
|
||||
func ExampleClient_cmd_incr() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// make sure we are working with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "mykey")
|
||||
// REMOVE_END
|
||||
|
||||
// STEP_START incr
|
||||
incrResult1, err := rdb.Set(ctx, "mykey", "10", 0).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(incrResult1) // >>> OK
|
||||
|
||||
incrResult2, err := rdb.Incr(ctx, "mykey").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(incrResult2) // >>> 11
|
||||
|
||||
incrResult3, err := rdb.Get(ctx, "mykey").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(incrResult3) // >>> 11
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// OK
|
||||
// 11
|
||||
// 11
|
||||
}
|
86
doctests/cms_tutorial_test.go
Normal file
86
doctests/cms_tutorial_test.go
Normal file
@ -0,0 +1,86 @@
|
||||
// EXAMPLE: cms_tutorial
|
||||
// HIDE_START
|
||||
package example_commands_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
// HIDE_END
|
||||
|
||||
func ExampleClient_cms() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// make sure we are working with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "bikes:profit")
|
||||
// REMOVE_END
|
||||
|
||||
// STEP_START cms
|
||||
res1, err := rdb.CMSInitByProb(ctx, "bikes:profit", 0.001, 0.002).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res1) // >>> OK
|
||||
|
||||
res2, err := rdb.CMSIncrBy(ctx, "bikes:profit",
|
||||
"Smoky Mountain Striker", 100,
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res2) // >>> [100]
|
||||
|
||||
res3, err := rdb.CMSIncrBy(ctx, "bikes:profit",
|
||||
"Rocky Mountain Racer", 200,
|
||||
"Cloudy City Cruiser", 150,
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res3) // >>> [200 150]
|
||||
|
||||
res4, err := rdb.CMSQuery(ctx, "bikes:profit",
|
||||
"Smoky Mountain Striker",
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res4) // >>> [100]
|
||||
|
||||
res5, err := rdb.CMSInfo(ctx, "bikes:profit").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("Width: %v, Depth: %v, Count: %v",
|
||||
res5.Width, res5.Depth, res5.Count)
|
||||
// >>> Width: 2000, Depth: 9, Count: 450
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// OK
|
||||
// [100]
|
||||
// [200 150]
|
||||
// [100]
|
||||
// Width: 2000, Depth: 9, Count: 450
|
||||
}
|
77
doctests/cuckoo_tutorial_test.go
Normal file
77
doctests/cuckoo_tutorial_test.go
Normal file
@ -0,0 +1,77 @@
|
||||
// EXAMPLE: cuckoo_tutorial
|
||||
// HIDE_START
|
||||
package example_commands_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
// HIDE_END
|
||||
|
||||
func ExampleClient_cuckoo() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// make sure we are working with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "bikes:models")
|
||||
// REMOVE_END
|
||||
|
||||
// STEP_START cuckoo
|
||||
res1, err := rdb.CFReserve(ctx, "bikes:models", 1000000).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res1) // >>> OK
|
||||
|
||||
res2, err := rdb.CFAdd(ctx, "bikes:models", "Smoky Mountain Striker").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res2) // >>> true
|
||||
|
||||
res3, err := rdb.CFExists(ctx, "bikes:models", "Smoky Mountain Striker").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res3) // >>> true
|
||||
|
||||
res4, err := rdb.CFExists(ctx, "bikes:models", "Terrible Bike Name").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res4) // >>> false
|
||||
|
||||
res5, err := rdb.CFDel(ctx, "bikes:models", "Smoky Mountain Striker").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res5) // >>> true
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// OK
|
||||
// true
|
||||
// true
|
||||
// false
|
||||
// true
|
||||
}
|
209
doctests/geo_index_test.go
Normal file
209
doctests/geo_index_test.go
Normal file
@ -0,0 +1,209 @@
|
||||
// EXAMPLE: geoindex
|
||||
// HIDE_START
|
||||
package example_commands_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
// HIDE_END
|
||||
|
||||
func ExampleClient_geoindex() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
Protocol: 2,
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// make sure we are working with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.FTDropIndex(ctx, "productidx")
|
||||
rdb.FTDropIndex(ctx, "geomidx")
|
||||
rdb.Del(ctx, "product:46885", "product:46886", "shape:1", "shape:2", "shape:3", "shape:4")
|
||||
// REMOVE_END
|
||||
|
||||
// STEP_START create_geo_idx
|
||||
geoCreateResult, err := rdb.FTCreate(ctx,
|
||||
"productidx",
|
||||
&redis.FTCreateOptions{
|
||||
OnJSON: true,
|
||||
Prefix: []interface{}{"product:"},
|
||||
},
|
||||
&redis.FieldSchema{
|
||||
FieldName: "$.location",
|
||||
As: "location",
|
||||
FieldType: redis.SearchFieldTypeGeo,
|
||||
},
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(geoCreateResult) // >>> OK
|
||||
// STEP_END
|
||||
|
||||
// STEP_START add_geo_json
|
||||
prd46885 := map[string]interface{}{
|
||||
"description": "Navy Blue Slippers",
|
||||
"price": 45.99,
|
||||
"city": "Denver",
|
||||
"location": "-104.991531, 39.742043",
|
||||
}
|
||||
|
||||
gjResult1, err := rdb.JSONSet(ctx, "product:46885", "$", prd46885).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(gjResult1) // >>> OK
|
||||
|
||||
prd46886 := map[string]interface{}{
|
||||
"description": "Bright Green Socks",
|
||||
"price": 25.50,
|
||||
"city": "Fort Collins",
|
||||
"location": "-105.0618814,40.5150098",
|
||||
}
|
||||
|
||||
gjResult2, err := rdb.JSONSet(ctx, "product:46886", "$", prd46886).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(gjResult2) // >>> OK
|
||||
// STEP_END
|
||||
|
||||
// STEP_START geo_query
|
||||
geoQueryResult, err := rdb.FTSearch(ctx, "productidx",
|
||||
"@location:[-104.800644 38.846127 100 mi]",
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(geoQueryResult)
|
||||
// >>> {1 [{product:46885...
|
||||
// STEP_END
|
||||
|
||||
// STEP_START create_gshape_idx
|
||||
geomCreateResult, err := rdb.FTCreate(ctx, "geomidx",
|
||||
&redis.FTCreateOptions{
|
||||
OnJSON: true,
|
||||
Prefix: []interface{}{"shape:"},
|
||||
},
|
||||
&redis.FieldSchema{
|
||||
FieldName: "$.name",
|
||||
As: "name",
|
||||
FieldType: redis.SearchFieldTypeText,
|
||||
},
|
||||
&redis.FieldSchema{
|
||||
FieldName: "$.geom",
|
||||
As: "geom",
|
||||
FieldType: redis.SearchFieldTypeGeoShape,
|
||||
GeoShapeFieldType: "FLAT",
|
||||
},
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(geomCreateResult) // >>> OK
|
||||
// STEP_END
|
||||
|
||||
// STEP_START add_gshape_json
|
||||
shape1 := map[string]interface{}{
|
||||
"name": "Green Square",
|
||||
"geom": "POLYGON ((1 1, 1 3, 3 3, 3 1, 1 1))",
|
||||
}
|
||||
|
||||
gmjResult1, err := rdb.JSONSet(ctx, "shape:1", "$", shape1).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(gmjResult1) // >>> OK
|
||||
|
||||
shape2 := map[string]interface{}{
|
||||
"name": "Red Rectangle",
|
||||
"geom": "POLYGON ((2 2.5, 2 3.5, 3.5 3.5, 3.5 2.5, 2 2.5))",
|
||||
}
|
||||
|
||||
gmjResult2, err := rdb.JSONSet(ctx, "shape:2", "$", shape2).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(gmjResult2) // >>> OK
|
||||
|
||||
shape3 := map[string]interface{}{
|
||||
"name": "Blue Triangle",
|
||||
"geom": "POLYGON ((3.5 1, 3.75 2, 4 1, 3.5 1))",
|
||||
}
|
||||
|
||||
gmjResult3, err := rdb.JSONSet(ctx, "shape:3", "$", shape3).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(gmjResult3) // >>> OK
|
||||
|
||||
shape4 := map[string]interface{}{
|
||||
"name": "Purple Point",
|
||||
"geom": "POINT (2 2)",
|
||||
}
|
||||
|
||||
gmjResult4, err := rdb.JSONSet(ctx, "shape:4", "$", shape4).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(gmjResult4) // >>> OK
|
||||
// STEP_END
|
||||
|
||||
// STEP_START gshape_query
|
||||
geomQueryResult, err := rdb.FTSearchWithArgs(ctx, "geomidx",
|
||||
"(-@name:(Green Square) @geom:[WITHIN $qshape])",
|
||||
&redis.FTSearchOptions{
|
||||
Params: map[string]interface{}{
|
||||
"qshape": "POLYGON ((1 1, 1 3, 3 3, 3 1, 1 1))",
|
||||
},
|
||||
DialectVersion: 4,
|
||||
Limit: 1,
|
||||
},
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(geomQueryResult)
|
||||
// >>> {1 [{shape:4...
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// OK
|
||||
// OK
|
||||
// OK
|
||||
// {1 [{product:46885 <nil> <nil> <nil> map[$:{"city":"Denver","description":"Navy Blue Slippers","location":"-104.991531, 39.742043","price":45.99}]}]}
|
||||
// OK
|
||||
// OK
|
||||
// OK
|
||||
// OK
|
||||
// OK
|
||||
// {1 [{shape:4 <nil> <nil> <nil> map[$:[{"geom":"POINT (2 2)","name":"Purple Point"}]]}]}
|
||||
}
|
143
doctests/geo_tutorial_test.go
Normal file
143
doctests/geo_tutorial_test.go
Normal file
@ -0,0 +1,143 @@
|
||||
// EXAMPLE: geo_tutorial
|
||||
// HIDE_START
|
||||
package example_commands_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
// HIDE_END
|
||||
|
||||
func ExampleClient_geoadd() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// make sure we are working with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "bikes:rentable")
|
||||
// REMOVE_END
|
||||
|
||||
// STEP_START geoadd
|
||||
res1, err := rdb.GeoAdd(ctx, "bikes:rentable",
|
||||
&redis.GeoLocation{
|
||||
Longitude: -122.27652,
|
||||
Latitude: 37.805186,
|
||||
Name: "station:1",
|
||||
}).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res1) // >>> 1
|
||||
|
||||
res2, err := rdb.GeoAdd(ctx, "bikes:rentable",
|
||||
&redis.GeoLocation{
|
||||
Longitude: -122.2674626,
|
||||
Latitude: 37.8062344,
|
||||
Name: "station:2",
|
||||
}).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res2) // >>> 1
|
||||
|
||||
res3, err := rdb.GeoAdd(ctx, "bikes:rentable",
|
||||
&redis.GeoLocation{
|
||||
Longitude: -122.2469854,
|
||||
Latitude: 37.8104049,
|
||||
Name: "station:3",
|
||||
}).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res3) // >>> 1
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// 1
|
||||
// 1
|
||||
// 1
|
||||
}
|
||||
|
||||
func ExampleClient_geosearch() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// start with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "bikes:rentable")
|
||||
|
||||
_, err := rdb.GeoAdd(ctx, "bikes:rentable",
|
||||
&redis.GeoLocation{
|
||||
Longitude: -122.27652,
|
||||
Latitude: 37.805186,
|
||||
Name: "station:1",
|
||||
}).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
_, err = rdb.GeoAdd(ctx, "bikes:rentable",
|
||||
&redis.GeoLocation{
|
||||
Longitude: -122.2674626,
|
||||
Latitude: 37.8062344,
|
||||
Name: "station:2",
|
||||
}).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
_, err = rdb.GeoAdd(ctx, "bikes:rentable",
|
||||
&redis.GeoLocation{
|
||||
Longitude: -122.2469854,
|
||||
Latitude: 37.8104049,
|
||||
Name: "station:3",
|
||||
}).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// REMOVE_END
|
||||
|
||||
// STEP_START geosearch
|
||||
res4, err := rdb.GeoSearch(ctx, "bikes:rentable",
|
||||
&redis.GeoSearchQuery{
|
||||
Longitude: -122.27652,
|
||||
Latitude: 37.805186,
|
||||
Radius: 5,
|
||||
RadiusUnit: "km",
|
||||
},
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res4) // >>> [station:1 station:2 station:3]
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// [station:1 station:2 station:3]
|
||||
}
|
289
doctests/hash_tutorial_test.go
Normal file
289
doctests/hash_tutorial_test.go
Normal file
@ -0,0 +1,289 @@
|
||||
// EXAMPLE: hash_tutorial
|
||||
// HIDE_START
|
||||
package example_commands_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
// HIDE_END
|
||||
|
||||
func ExampleClient_set_get_all() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// make sure we are working with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "bike:1")
|
||||
// REMOVE_END
|
||||
|
||||
// STEP_START set_get_all
|
||||
hashFields := []string{
|
||||
"model", "Deimos",
|
||||
"brand", "Ergonom",
|
||||
"type", "Enduro bikes",
|
||||
"price", "4972",
|
||||
}
|
||||
|
||||
res1, err := rdb.HSet(ctx, "bike:1", hashFields).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res1) // >>> 4
|
||||
|
||||
res2, err := rdb.HGet(ctx, "bike:1", "model").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res2) // >>> Deimos
|
||||
|
||||
res3, err := rdb.HGet(ctx, "bike:1", "price").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res3) // >>> 4972
|
||||
|
||||
cmdReturn := rdb.HGetAll(ctx, "bike:1")
|
||||
res4, err := cmdReturn.Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res4)
|
||||
// >>> map[brand:Ergonom model:Deimos price:4972 type:Enduro bikes]
|
||||
|
||||
type BikeInfo struct {
|
||||
Model string `redis:"model"`
|
||||
Brand string `redis:"brand"`
|
||||
Type string `redis:"type"`
|
||||
Price int `redis:"price"`
|
||||
}
|
||||
|
||||
var res4a BikeInfo
|
||||
|
||||
if err := cmdReturn.Scan(&res4a); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("Model: %v, Brand: %v, Type: %v, Price: $%v\n",
|
||||
res4a.Model, res4a.Brand, res4a.Type, res4a.Price)
|
||||
// >>> Model: Deimos, Brand: Ergonom, Type: Enduro bikes, Price: $4972
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// 4
|
||||
// Deimos
|
||||
// 4972
|
||||
// map[brand:Ergonom model:Deimos price:4972 type:Enduro bikes]
|
||||
// Model: Deimos, Brand: Ergonom, Type: Enduro bikes, Price: $4972
|
||||
}
|
||||
|
||||
func ExampleClient_hmget() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// start with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "bike:1")
|
||||
// REMOVE_END
|
||||
|
||||
hashFields := []string{
|
||||
"model", "Deimos",
|
||||
"brand", "Ergonom",
|
||||
"type", "Enduro bikes",
|
||||
"price", "4972",
|
||||
}
|
||||
|
||||
_, err := rdb.HSet(ctx, "bike:1", hashFields).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// STEP_START hmget
|
||||
cmdReturn := rdb.HMGet(ctx, "bike:1", "model", "price")
|
||||
res5, err := cmdReturn.Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res5) // >>> [Deimos 4972]
|
||||
|
||||
type BikeInfo struct {
|
||||
Model string `redis:"model"`
|
||||
Brand string `redis:"-"`
|
||||
Type string `redis:"-"`
|
||||
Price int `redis:"price"`
|
||||
}
|
||||
|
||||
var res5a BikeInfo
|
||||
|
||||
if err := cmdReturn.Scan(&res5a); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("Model: %v, Price: $%v\n", res5a.Model, res5a.Price)
|
||||
// >>> Model: Deimos, Price: $4972
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// [Deimos 4972]
|
||||
// Model: Deimos, Price: $4972
|
||||
}
|
||||
|
||||
func ExampleClient_hincrby() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// start with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "bike:1")
|
||||
// REMOVE_END
|
||||
|
||||
hashFields := []string{
|
||||
"model", "Deimos",
|
||||
"brand", "Ergonom",
|
||||
"type", "Enduro bikes",
|
||||
"price", "4972",
|
||||
}
|
||||
|
||||
_, err := rdb.HSet(ctx, "bike:1", hashFields).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// STEP_START hincrby
|
||||
res6, err := rdb.HIncrBy(ctx, "bike:1", "price", 100).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res6) // >>> 5072
|
||||
|
||||
res7, err := rdb.HIncrBy(ctx, "bike:1", "price", -100).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res7) // >>> 4972
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// 5072
|
||||
// 4972
|
||||
}
|
||||
|
||||
func ExampleClient_incrby_get_mget() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// start with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "bike:1:stats")
|
||||
// REMOVE_END
|
||||
|
||||
// STEP_START incrby_get_mget
|
||||
res8, err := rdb.HIncrBy(ctx, "bike:1:stats", "rides", 1).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res8) // >>> 1
|
||||
|
||||
res9, err := rdb.HIncrBy(ctx, "bike:1:stats", "rides", 1).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res9) // >>> 2
|
||||
|
||||
res10, err := rdb.HIncrBy(ctx, "bike:1:stats", "rides", 1).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res10) // >>> 3
|
||||
|
||||
res11, err := rdb.HIncrBy(ctx, "bike:1:stats", "crashes", 1).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res11) // >>> 1
|
||||
|
||||
res12, err := rdb.HIncrBy(ctx, "bike:1:stats", "owners", 1).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res12) // >>> 1
|
||||
|
||||
res13, err := rdb.HGet(ctx, "bike:1:stats", "rides").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res13) // >>> 3
|
||||
|
||||
res14, err := rdb.HMGet(ctx, "bike:1:stats", "crashes", "owners").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res14) // >>> [1 1]
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// 1
|
||||
// 2
|
||||
// 3
|
||||
// 1
|
||||
// 1
|
||||
// 3
|
||||
// [1 1]
|
||||
}
|
77
doctests/hll_tutorial_test.go
Normal file
77
doctests/hll_tutorial_test.go
Normal file
@ -0,0 +1,77 @@
|
||||
// EXAMPLE: hll_tutorial
|
||||
// HIDE_START
|
||||
package example_commands_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
// HIDE_END
|
||||
|
||||
func ExampleClient_pfadd() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// make sure we are working with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "bikes", "commuter_bikes", "all_bikes")
|
||||
// REMOVE_END
|
||||
|
||||
// STEP_START pfadd
|
||||
res1, err := rdb.PFAdd(ctx, "bikes", "Hyperion", "Deimos", "Phoebe", "Quaoar").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res1) // 1
|
||||
|
||||
res2, err := rdb.PFCount(ctx, "bikes").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res2) // 4
|
||||
|
||||
res3, err := rdb.PFAdd(ctx, "commuter_bikes", "Salacia", "Mimas", "Quaoar").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res3) // 1
|
||||
|
||||
res4, err := rdb.PFMerge(ctx, "all_bikes", "bikes", "commuter_bikes").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res4) // OK
|
||||
|
||||
res5, err := rdb.PFCount(ctx, "all_bikes").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res5) // 6
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// 1
|
||||
// 4
|
||||
// 1
|
||||
// OK
|
||||
// 6
|
||||
}
|
201
doctests/home_json_example_test.go
Normal file
201
doctests/home_json_example_test.go
Normal file
@ -0,0 +1,201 @@
|
||||
// EXAMPLE: go_home_json
|
||||
// HIDE_START
|
||||
package example_commands_test
|
||||
|
||||
// HIDE_END
|
||||
// STEP_START import
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
// STEP_END
|
||||
|
||||
func ExampleClient_search_json() {
|
||||
// STEP_START connect
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
Protocol: 2,
|
||||
})
|
||||
// STEP_END
|
||||
// REMOVE_START
|
||||
// make sure we are working with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "user:1", "user:2", "user:3")
|
||||
rdb.FTDropIndex(ctx, "idx:users")
|
||||
// REMOVE_END
|
||||
|
||||
// STEP_START create_data
|
||||
user1 := map[string]interface{}{
|
||||
"name": "Paul John",
|
||||
"email": "paul.john@example.com",
|
||||
"age": 42,
|
||||
"city": "London",
|
||||
}
|
||||
|
||||
user2 := map[string]interface{}{
|
||||
"name": "Eden Zamir",
|
||||
"email": "eden.zamir@example.com",
|
||||
"age": 29,
|
||||
"city": "Tel Aviv",
|
||||
}
|
||||
|
||||
user3 := map[string]interface{}{
|
||||
"name": "Paul Zamir",
|
||||
"email": "paul.zamir@example.com",
|
||||
"age": 35,
|
||||
"city": "Tel Aviv",
|
||||
}
|
||||
// STEP_END
|
||||
|
||||
// STEP_START make_index
|
||||
_, err := rdb.FTCreate(
|
||||
ctx,
|
||||
"idx:users",
|
||||
// Options:
|
||||
&redis.FTCreateOptions{
|
||||
OnJSON: true,
|
||||
Prefix: []interface{}{"user:"},
|
||||
},
|
||||
// Index schema fields:
|
||||
&redis.FieldSchema{
|
||||
FieldName: "$.name",
|
||||
As: "name",
|
||||
FieldType: redis.SearchFieldTypeText,
|
||||
},
|
||||
&redis.FieldSchema{
|
||||
FieldName: "$.city",
|
||||
As: "city",
|
||||
FieldType: redis.SearchFieldTypeTag,
|
||||
},
|
||||
&redis.FieldSchema{
|
||||
FieldName: "$.age",
|
||||
As: "age",
|
||||
FieldType: redis.SearchFieldTypeNumeric,
|
||||
},
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// STEP_END
|
||||
|
||||
// STEP_START add_data
|
||||
_, err = rdb.JSONSet(ctx, "user:1", "$", user1).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
_, err = rdb.JSONSet(ctx, "user:2", "$", user2).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
_, err = rdb.JSONSet(ctx, "user:3", "$", user3).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// STEP_END
|
||||
|
||||
// STEP_START query1
|
||||
findPaulResult, err := rdb.FTSearch(
|
||||
ctx,
|
||||
"idx:users",
|
||||
"Paul @age:[30 40]",
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(findPaulResult)
|
||||
// >>> {1 [{user:3 <nil> <nil> <nil> map[$:{"age":35,"city":"Tel Aviv"...
|
||||
// STEP_END
|
||||
|
||||
// STEP_START query2
|
||||
citiesResult, err := rdb.FTSearchWithArgs(
|
||||
ctx,
|
||||
"idx:users",
|
||||
"Paul",
|
||||
&redis.FTSearchOptions{
|
||||
Return: []redis.FTSearchReturn{
|
||||
{
|
||||
FieldName: "$.city",
|
||||
As: "city",
|
||||
},
|
||||
},
|
||||
},
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
sort.Slice(citiesResult.Docs, func(i, j int) bool {
|
||||
return citiesResult.Docs[i].Fields["city"] < citiesResult.Docs[j].Fields["city"]
|
||||
})
|
||||
|
||||
for _, result := range citiesResult.Docs {
|
||||
fmt.Println(result.Fields["city"])
|
||||
}
|
||||
// >>> London
|
||||
// >>> Tel Aviv
|
||||
// STEP_END
|
||||
|
||||
// STEP_START query3
|
||||
aggOptions := redis.FTAggregateOptions{
|
||||
GroupBy: []redis.FTAggregateGroupBy{
|
||||
{
|
||||
Fields: []interface{}{"@city"},
|
||||
Reduce: []redis.FTAggregateReducer{
|
||||
{
|
||||
Reducer: redis.SearchCount,
|
||||
As: "count",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
aggResult, err := rdb.FTAggregateWithArgs(
|
||||
ctx,
|
||||
"idx:users",
|
||||
"*",
|
||||
&aggOptions,
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
sort.Slice(aggResult.Rows, func(i, j int) bool {
|
||||
return aggResult.Rows[i].Fields["city"].(string) <
|
||||
aggResult.Rows[j].Fields["city"].(string)
|
||||
})
|
||||
|
||||
for _, row := range aggResult.Rows {
|
||||
fmt.Printf("%v - %v\n",
|
||||
row.Fields["city"], row.Fields["count"],
|
||||
)
|
||||
}
|
||||
// >>> City: London - 1
|
||||
// >>> City: Tel Aviv - 2
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// {1 [{user:3 <nil> <nil> <nil> map[$:{"age":35,"city":"Tel Aviv","email":"paul.zamir@example.com","name":"Paul Zamir"}]}]}
|
||||
// London
|
||||
// Tel Aviv
|
||||
// London - 1
|
||||
// Tel Aviv - 2
|
||||
}
|
1157
doctests/json_tutorial_test.go
Normal file
1157
doctests/json_tutorial_test.go
Normal file
File diff suppressed because it is too large
Load Diff
788
doctests/list_tutorial_test.go
Normal file
788
doctests/list_tutorial_test.go
Normal file
@ -0,0 +1,788 @@
|
||||
// EXAMPLE: list_tutorial
|
||||
// HIDE_START
|
||||
package example_commands_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
// HIDE_END
|
||||
|
||||
func ExampleClient_queue() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// make sure we are working with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "bikes:repairs")
|
||||
// REMOVE_END
|
||||
|
||||
// STEP_START queue
|
||||
res1, err := rdb.LPush(ctx, "bikes:repairs", "bike:1").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res1) // >>> 1
|
||||
|
||||
res2, err := rdb.LPush(ctx, "bikes:repairs", "bike:2").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res2) // >>> 2
|
||||
|
||||
res3, err := rdb.RPop(ctx, "bikes:repairs").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res3) // >>> bike:1
|
||||
|
||||
res4, err := rdb.RPop(ctx, "bikes:repairs").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res4) // >>> bike:2
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// 1
|
||||
// 2
|
||||
// bike:1
|
||||
// bike:2
|
||||
}
|
||||
|
||||
func ExampleClient_stack() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// start with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "bikes:repairs")
|
||||
// REMOVE_END
|
||||
|
||||
// STEP_START stack
|
||||
res5, err := rdb.LPush(ctx, "bikes:repairs", "bike:1").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res5) // >>> 1
|
||||
|
||||
res6, err := rdb.LPush(ctx, "bikes:repairs", "bike:2").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res6) // >>> 2
|
||||
|
||||
res7, err := rdb.LPop(ctx, "bikes:repairs").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res7) // >>> bike:2
|
||||
|
||||
res8, err := rdb.LPop(ctx, "bikes:repairs").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res8) // >>> bike:1
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// 1
|
||||
// 2
|
||||
// bike:2
|
||||
// bike:1
|
||||
}
|
||||
|
||||
func ExampleClient_llen() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// start with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "bikes:repairs")
|
||||
// REMOVE_END
|
||||
|
||||
// STEP_START llen
|
||||
res9, err := rdb.LLen(ctx, "bikes:repairs").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res9) // >>> 0
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// 0
|
||||
}
|
||||
|
||||
func ExampleClient_lmove_lrange() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// start with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "bikes:repairs")
|
||||
rdb.Del(ctx, "bikes:finished")
|
||||
// REMOVE_END
|
||||
|
||||
// STEP_START lmove_lrange
|
||||
res10, err := rdb.LPush(ctx, "bikes:repairs", "bike:1").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res10) // >>> 1
|
||||
|
||||
res11, err := rdb.LPush(ctx, "bikes:repairs", "bike:2").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res11) // >>> 2
|
||||
|
||||
res12, err := rdb.LMove(ctx, "bikes:repairs", "bikes:finished", "LEFT", "LEFT").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res12) // >>> bike:2
|
||||
|
||||
res13, err := rdb.LRange(ctx, "bikes:repairs", 0, -1).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res13) // >>> [bike:1]
|
||||
|
||||
res14, err := rdb.LRange(ctx, "bikes:finished", 0, -1).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res14) // >>> [bike:2]
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// 1
|
||||
// 2
|
||||
// bike:2
|
||||
// [bike:1]
|
||||
// [bike:2]
|
||||
}
|
||||
|
||||
func ExampleClient_lpush_rpush() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// start with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "bikes:repairs")
|
||||
// REMOVE_END
|
||||
|
||||
// STEP_START lpush_rpush
|
||||
res15, err := rdb.RPush(ctx, "bikes:repairs", "bike:1").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res15) // >>> 1
|
||||
|
||||
res16, err := rdb.RPush(ctx, "bikes:repairs", "bike:2").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res16) // >>> 2
|
||||
|
||||
res17, err := rdb.LPush(ctx, "bikes:repairs", "bike:important_bike").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res17) // >>> 3
|
||||
|
||||
res18, err := rdb.LRange(ctx, "bikes:repairs", 0, -1).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res18) // >>> [bike:important_bike bike:1 bike:2]
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// 1
|
||||
// 2
|
||||
// 3
|
||||
// [bike:important_bike bike:1 bike:2]
|
||||
}
|
||||
|
||||
func ExampleClient_variadic() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// start with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "bikes:repairs")
|
||||
// REMOVE_END
|
||||
|
||||
// STEP_START variadic
|
||||
res19, err := rdb.RPush(ctx, "bikes:repairs", "bike:1", "bike:2", "bike:3").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res19) // >>> 3
|
||||
|
||||
res20, err := rdb.LPush(ctx, "bikes:repairs", "bike:important_bike", "bike:very_important_bike").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res20) // >>> 5
|
||||
|
||||
res21, err := rdb.LRange(ctx, "bikes:repairs", 0, -1).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res21) // >>> [bike:very_important_bike bike:important_bike bike:1 bike:2 bike:3]
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// 3
|
||||
// 5
|
||||
// [bike:very_important_bike bike:important_bike bike:1 bike:2 bike:3]
|
||||
}
|
||||
|
||||
func ExampleClient_lpop_rpop() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// start with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "bikes:repairs")
|
||||
// REMOVE_END
|
||||
|
||||
// STEP_START lpop_rpop
|
||||
res22, err := rdb.RPush(ctx, "bikes:repairs", "bike:1", "bike:2", "bike:3").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res22) // >>> 3
|
||||
|
||||
res23, err := rdb.RPop(ctx, "bikes:repairs").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res23) // >>> bike:3
|
||||
|
||||
res24, err := rdb.LPop(ctx, "bikes:repairs").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res24) // >>> bike:1
|
||||
|
||||
res25, err := rdb.RPop(ctx, "bikes:repairs").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res25) // >>> bike:2
|
||||
|
||||
res26, err := rdb.RPop(ctx, "bikes:repairs").Result()
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err) // >>> redis: nil
|
||||
}
|
||||
|
||||
fmt.Println(res26) // >>> <empty string>
|
||||
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// 3
|
||||
// bike:3
|
||||
// bike:1
|
||||
// bike:2
|
||||
// redis: nil
|
||||
//
|
||||
}
|
||||
|
||||
func ExampleClient_ltrim() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// start with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "bikes:repairs")
|
||||
// REMOVE_END
|
||||
|
||||
// STEP_START ltrim
|
||||
res27, err := rdb.RPush(ctx, "bikes:repairs", "bike:1", "bike:2", "bike:3", "bike:4", "bike:5").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res27) // >>> 5
|
||||
|
||||
res28, err := rdb.LTrim(ctx, "bikes:repairs", 0, 2).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res28) // >>> OK
|
||||
|
||||
res29, err := rdb.LRange(ctx, "bikes:repairs", 0, -1).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res29) // >>> [bike:1 bike:2 bike:3]
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// 5
|
||||
// OK
|
||||
// [bike:1 bike:2 bike:3]
|
||||
}
|
||||
|
||||
func ExampleClient_ltrim_end_of_list() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// start with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "bikes:repairs")
|
||||
// REMOVE_END
|
||||
|
||||
// STEP_START ltrim_end_of_list
|
||||
res30, err := rdb.RPush(ctx, "bikes:repairs", "bike:1", "bike:2", "bike:3", "bike:4", "bike:5").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res30) // >>> 5
|
||||
|
||||
res31, err := rdb.LTrim(ctx, "bikes:repairs", -3, -1).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res31) // >>> OK
|
||||
|
||||
res32, err := rdb.LRange(ctx, "bikes:repairs", 0, -1).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res32) // >>> [bike:3 bike:4 bike:5]
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// 5
|
||||
// OK
|
||||
// [bike:3 bike:4 bike:5]
|
||||
}
|
||||
|
||||
func ExampleClient_brpop() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// start with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "bikes:repairs")
|
||||
// REMOVE_END
|
||||
|
||||
// STEP_START brpop
|
||||
res33, err := rdb.RPush(ctx, "bikes:repairs", "bike:1", "bike:2").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res33) // >>> 2
|
||||
|
||||
res34, err := rdb.BRPop(ctx, 1, "bikes:repairs").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res34) // >>> [bikes:repairs bike:2]
|
||||
|
||||
res35, err := rdb.BRPop(ctx, 1, "bikes:repairs").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res35) // >>> [bikes:repairs bike:1]
|
||||
|
||||
res36, err := rdb.BRPop(ctx, 1, "bikes:repairs").Result()
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err) // >>> redis: nil
|
||||
}
|
||||
|
||||
fmt.Println(res36) // >>> []
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// 2
|
||||
// [bikes:repairs bike:2]
|
||||
// [bikes:repairs bike:1]
|
||||
// redis: nil
|
||||
// []
|
||||
}
|
||||
|
||||
func ExampleClient_rule1() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// start with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "new_bikes")
|
||||
// REMOVE_END
|
||||
|
||||
// STEP_START rule_1
|
||||
res37, err := rdb.Del(ctx, "new_bikes").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res37) // >>> 0
|
||||
|
||||
res38, err := rdb.LPush(ctx, "new_bikes", "bike:1", "bike:2", "bike:3").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res38) // >>> 3
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// 0
|
||||
// 3
|
||||
}
|
||||
|
||||
func ExampleClient_rule11() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
rdb.Del(ctx, "new_bikes")
|
||||
// REMOVE_END
|
||||
|
||||
// STEP_START rule_1.1
|
||||
res39, err := rdb.Set(ctx, "new_bikes", "bike:1", 0).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res39) // >>> OK
|
||||
|
||||
res40, err := rdb.Type(ctx, "new_bikes").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res40) // >>> string
|
||||
|
||||
res41, err := rdb.LPush(ctx, "new_bikes", "bike:2", "bike:3").Result()
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
// >>> WRONGTYPE Operation against a key holding the wrong kind of value
|
||||
}
|
||||
|
||||
fmt.Println(res41)
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// OK
|
||||
// string
|
||||
// WRONGTYPE Operation against a key holding the wrong kind of value
|
||||
// 0
|
||||
}
|
||||
|
||||
func ExampleClient_rule2() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
rdb.Del(ctx, "bikes:repairs")
|
||||
// REMOVE_END
|
||||
|
||||
// STEP_START rule_2
|
||||
res42, err := rdb.LPush(ctx, "bikes:repairs", "bike:1", "bike:2", "bike:3").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res42) // >>> 3
|
||||
|
||||
res43, err := rdb.Exists(ctx, "bikes:repairs").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res43) // >>> 1
|
||||
|
||||
res44, err := rdb.LPop(ctx, "bikes:repairs").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res44) // >>> bike:3
|
||||
|
||||
res45, err := rdb.LPop(ctx, "bikes:repairs").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res45) // >>> bike:2
|
||||
|
||||
res46, err := rdb.LPop(ctx, "bikes:repairs").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res46) // >>> bike:1
|
||||
|
||||
res47, err := rdb.Exists(ctx, "bikes:repairs").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res47) // >>> 0
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// 3
|
||||
// 1
|
||||
// bike:3
|
||||
// bike:2
|
||||
// bike:1
|
||||
// 0
|
||||
}
|
||||
|
||||
func ExampleClient_rule3() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
rdb.Del(ctx, "bikes:repairs")
|
||||
// REMOVE_END
|
||||
|
||||
// STEP_START rule_3
|
||||
res48, err := rdb.Del(ctx, "bikes:repairs").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res48) // >>> 0
|
||||
|
||||
res49, err := rdb.LLen(ctx, "bikes:repairs").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res49) // >>> 0
|
||||
|
||||
res50, err := rdb.LPop(ctx, "bikes:repairs").Result()
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err) // >>> redis: nil
|
||||
}
|
||||
|
||||
fmt.Println(res50) // >>> <empty string>
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// 0
|
||||
// 0
|
||||
// redis: nil
|
||||
//
|
||||
}
|
||||
|
||||
func ExampleClient_ltrim1() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
rdb.Del(ctx, "bikes:repairs")
|
||||
// REMOVE_END
|
||||
|
||||
// STEP_START ltrim.1
|
||||
res51, err := rdb.LPush(ctx, "bikes:repairs", "bike:1", "bike:2", "bike:3", "bike:4", "bike:5").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res51) // >>> 5
|
||||
|
||||
res52, err := rdb.LTrim(ctx, "bikes:repairs", 0, 2).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res52) // >>> OK
|
||||
|
||||
res53, err := rdb.LRange(ctx, "bikes:repairs", 0, -1).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res53) // >>> [bike:5 bike:4 bike:3]
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// 5
|
||||
// OK
|
||||
// [bike:5 bike:4 bike:3]
|
||||
}
|
182
doctests/pipe_trans_example_test.go
Normal file
182
doctests/pipe_trans_example_test.go
Normal file
@ -0,0 +1,182 @@
|
||||
// EXAMPLE: pipe_trans_tutorial
|
||||
// HIDE_START
|
||||
package example_commands_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
// HIDE_END
|
||||
|
||||
func ExampleClient_transactions() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
// REMOVE_START
|
||||
// make sure we are working with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
for i := 0; i < 5; i++ {
|
||||
rdb.Del(ctx, fmt.Sprintf("seat:%d", i))
|
||||
}
|
||||
|
||||
rdb.Del(ctx, "counter:1", "counter:2", "counter:3", "shellpath")
|
||||
// REMOVE_END
|
||||
|
||||
// STEP_START basic_pipe
|
||||
pipe := rdb.Pipeline()
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
pipe.Set(ctx, fmt.Sprintf("seat:%v", i), fmt.Sprintf("#%v", i), 0)
|
||||
}
|
||||
|
||||
cmds, err := pipe.Exec(ctx)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for _, c := range cmds {
|
||||
fmt.Printf("%v;", c.(*redis.StatusCmd).Val())
|
||||
}
|
||||
|
||||
fmt.Println("")
|
||||
// >>> OK;OK;OK;OK;OK;
|
||||
|
||||
pipe = rdb.Pipeline()
|
||||
|
||||
get0Result := pipe.Get(ctx, "seat:0")
|
||||
get3Result := pipe.Get(ctx, "seat:3")
|
||||
get4Result := pipe.Get(ctx, "seat:4")
|
||||
|
||||
cmds, err = pipe.Exec(ctx)
|
||||
|
||||
// The results are available only after the pipeline
|
||||
// has finished executing.
|
||||
fmt.Println(get0Result.Val()) // >>> #0
|
||||
fmt.Println(get3Result.Val()) // >>> #3
|
||||
fmt.Println(get4Result.Val()) // >>> #4
|
||||
// STEP_END
|
||||
|
||||
// STEP_START basic_pipe_pipelined
|
||||
var pd0Result *redis.StatusCmd
|
||||
var pd3Result *redis.StatusCmd
|
||||
var pd4Result *redis.StatusCmd
|
||||
|
||||
cmds, err = rdb.Pipelined(ctx, func(pipe redis.Pipeliner) error {
|
||||
pd0Result = (*redis.StatusCmd)(pipe.Get(ctx, "seat:0"))
|
||||
pd3Result = (*redis.StatusCmd)(pipe.Get(ctx, "seat:3"))
|
||||
pd4Result = (*redis.StatusCmd)(pipe.Get(ctx, "seat:4"))
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// The results are available only after the pipeline
|
||||
// has finished executing.
|
||||
fmt.Println(pd0Result.Val()) // >>> #0
|
||||
fmt.Println(pd3Result.Val()) // >>> #3
|
||||
fmt.Println(pd4Result.Val()) // >>> #4
|
||||
// STEP_END
|
||||
|
||||
// STEP_START basic_trans
|
||||
trans := rdb.TxPipeline()
|
||||
|
||||
trans.IncrBy(ctx, "counter:1", 1)
|
||||
trans.IncrBy(ctx, "counter:2", 2)
|
||||
trans.IncrBy(ctx, "counter:3", 3)
|
||||
|
||||
cmds, err = trans.Exec(ctx)
|
||||
|
||||
for _, c := range cmds {
|
||||
fmt.Println(c.(*redis.IntCmd).Val())
|
||||
}
|
||||
// >>> 1
|
||||
// >>> 2
|
||||
// >>> 3
|
||||
// STEP_END
|
||||
|
||||
// STEP_START basic_trans_txpipelined
|
||||
var tx1Result *redis.IntCmd
|
||||
var tx2Result *redis.IntCmd
|
||||
var tx3Result *redis.IntCmd
|
||||
|
||||
cmds, err = rdb.TxPipelined(ctx, func(trans redis.Pipeliner) error {
|
||||
tx1Result = trans.IncrBy(ctx, "counter:1", 1)
|
||||
tx2Result = trans.IncrBy(ctx, "counter:2", 2)
|
||||
tx3Result = trans.IncrBy(ctx, "counter:3", 3)
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(tx1Result.Val()) // >>> 2
|
||||
fmt.Println(tx2Result.Val()) // >>> 4
|
||||
fmt.Println(tx3Result.Val()) // >>> 6
|
||||
// STEP_END
|
||||
|
||||
// STEP_START trans_watch
|
||||
// Set initial value of `shellpath`.
|
||||
rdb.Set(ctx, "shellpath", "/usr/syscmds/", 0)
|
||||
|
||||
const maxRetries = 1000
|
||||
|
||||
// Retry if the key has been changed.
|
||||
for i := 0; i < maxRetries; i++ {
|
||||
err := rdb.Watch(ctx,
|
||||
func(tx *redis.Tx) error {
|
||||
currentPath, err := rdb.Get(ctx, "shellpath").Result()
|
||||
newPath := currentPath + ":/usr/mycmds/"
|
||||
|
||||
_, err = tx.TxPipelined(ctx, func(pipe redis.Pipeliner) error {
|
||||
pipe.Set(ctx, "shellpath", newPath, 0)
|
||||
return nil
|
||||
})
|
||||
|
||||
return err
|
||||
},
|
||||
"shellpath",
|
||||
)
|
||||
|
||||
if err == nil {
|
||||
// Success.
|
||||
break
|
||||
} else if err == redis.TxFailedErr {
|
||||
// Optimistic lock lost. Retry the transaction.
|
||||
continue
|
||||
} else {
|
||||
// Panic for any other error.
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println(rdb.Get(ctx, "shellpath").Val())
|
||||
// >>> /usr/syscmds/:/usr/mycmds/
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// OK;OK;OK;OK;OK;
|
||||
// #0
|
||||
// #3
|
||||
// #4
|
||||
// #0
|
||||
// #3
|
||||
// #4
|
||||
// 1
|
||||
// 2
|
||||
// 3
|
||||
// 2
|
||||
// 4
|
||||
// 6
|
||||
// /usr/syscmds/:/usr/mycmds/
|
||||
}
|
435
doctests/query_agg_test.go
Normal file
435
doctests/query_agg_test.go
Normal file
@ -0,0 +1,435 @@
|
||||
// EXAMPLE: query_agg
|
||||
// HIDE_START
|
||||
package example_commands_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
func ExampleClient_query_agg() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
Protocol: 2,
|
||||
})
|
||||
// HIDE_END
|
||||
// REMOVE_START
|
||||
// start with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.FTDropIndex(ctx, "idx:bicycle")
|
||||
rdb.FTDropIndex(ctx, "idx:email")
|
||||
// REMOVE_END
|
||||
|
||||
_, err := rdb.FTCreate(ctx, "idx:bicycle",
|
||||
&redis.FTCreateOptions{
|
||||
OnJSON: true,
|
||||
Prefix: []interface{}{"bicycle:"},
|
||||
},
|
||||
&redis.FieldSchema{
|
||||
FieldName: "$.brand",
|
||||
As: "brand",
|
||||
FieldType: redis.SearchFieldTypeText,
|
||||
},
|
||||
&redis.FieldSchema{
|
||||
FieldName: "$.model",
|
||||
As: "model",
|
||||
FieldType: redis.SearchFieldTypeText,
|
||||
},
|
||||
&redis.FieldSchema{
|
||||
FieldName: "$.description",
|
||||
As: "description",
|
||||
FieldType: redis.SearchFieldTypeText,
|
||||
},
|
||||
&redis.FieldSchema{
|
||||
FieldName: "$.price",
|
||||
As: "price",
|
||||
FieldType: redis.SearchFieldTypeNumeric,
|
||||
},
|
||||
&redis.FieldSchema{
|
||||
FieldName: "$.condition",
|
||||
As: "condition",
|
||||
FieldType: redis.SearchFieldTypeTag,
|
||||
},
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
exampleJsons := []map[string]interface{}{
|
||||
{
|
||||
"pickup_zone": "POLYGON((-74.0610 40.7578, -73.9510 40.7578, -73.9510 40.6678, " +
|
||||
"-74.0610 40.6678, -74.0610 40.7578))",
|
||||
"store_location": "-74.0060,40.7128",
|
||||
"brand": "Velorim",
|
||||
"model": "Jigger",
|
||||
"price": 270,
|
||||
"description": "Small and powerful, the Jigger is the best ride for the smallest of tikes! " +
|
||||
"This is the tiniest kids’ pedal bike on the market available without a coaster brake, the Jigger " +
|
||||
"is the vehicle of choice for the rare tenacious little rider raring to go.",
|
||||
"condition": "new",
|
||||
},
|
||||
{
|
||||
"pickup_zone": "POLYGON((-118.2887 34.0972, -118.1987 34.0972, -118.1987 33.9872, " +
|
||||
"-118.2887 33.9872, -118.2887 34.0972))",
|
||||
"store_location": "-118.2437,34.0522",
|
||||
"brand": "Bicyk",
|
||||
"model": "Hillcraft",
|
||||
"price": 1200,
|
||||
"description": "Kids want to ride with as little weight as possible. Especially " +
|
||||
"on an incline! They may be at the age when a 27.5'' wheel bike is just too clumsy coming " +
|
||||
"off a 24'' bike. The Hillcraft 26 is just the solution they need!",
|
||||
"condition": "used",
|
||||
},
|
||||
{
|
||||
"pickup_zone": "POLYGON((-87.6848 41.9331, -87.5748 41.9331, -87.5748 41.8231, " +
|
||||
"-87.6848 41.8231, -87.6848 41.9331))",
|
||||
"store_location": "-87.6298,41.8781",
|
||||
"brand": "Nord",
|
||||
"model": "Chook air 5",
|
||||
"price": 815,
|
||||
"description": "The Chook Air 5 gives kids aged six years and older a durable " +
|
||||
"and uberlight mountain bike for their first experience on tracks and easy cruising through " +
|
||||
"forests and fields. The lower top tube makes it easy to mount and dismount in any " +
|
||||
"situation, giving your kids greater safety on the trails.",
|
||||
"condition": "used",
|
||||
},
|
||||
{
|
||||
"pickup_zone": "POLYGON((-80.2433 25.8067, -80.1333 25.8067, -80.1333 25.6967, " +
|
||||
"-80.2433 25.6967, -80.2433 25.8067))",
|
||||
"store_location": "-80.1918,25.7617",
|
||||
"brand": "Eva",
|
||||
"model": "Eva 291",
|
||||
"price": 3400,
|
||||
"description": "The sister company to Nord, Eva launched in 2005 as the first " +
|
||||
"and only women-dedicated bicycle brand. Designed by women for women, allEva bikes " +
|
||||
"are optimized for the feminine physique using analytics from a body metrics database. " +
|
||||
"If you like 29ers, try the Eva 291. It’s a brand new bike for 2022.. This " +
|
||||
"full-suspension, cross-country ride has been designed for velocity. The 291 has " +
|
||||
"100mm of front and rear travel, a superlight aluminum frame and fast-rolling " +
|
||||
"29-inch wheels. Yippee!",
|
||||
"condition": "used",
|
||||
},
|
||||
{
|
||||
"pickup_zone": "POLYGON((-122.4644 37.8199, -122.3544 37.8199, -122.3544 37.7099, " +
|
||||
"-122.4644 37.7099, -122.4644 37.8199))",
|
||||
"store_location": "-122.4194,37.7749",
|
||||
"brand": "Noka Bikes",
|
||||
"model": "Kahuna",
|
||||
"price": 3200,
|
||||
"description": "Whether you want to try your hand at XC racing or are looking " +
|
||||
"for a lively trail bike that's just as inspiring on the climbs as it is over rougher " +
|
||||
"ground, the Wilder is one heck of a bike built specifically for short women. Both the " +
|
||||
"frames and components have been tweaked to include a women’s saddle, different bars " +
|
||||
"and unique colourway.",
|
||||
"condition": "used",
|
||||
},
|
||||
{
|
||||
"pickup_zone": "POLYGON((-0.1778 51.5524, 0.0822 51.5524, 0.0822 51.4024, " +
|
||||
"-0.1778 51.4024, -0.1778 51.5524))",
|
||||
"store_location": "-0.1278,51.5074",
|
||||
"brand": "Breakout",
|
||||
"model": "XBN 2.1 Alloy",
|
||||
"price": 810,
|
||||
"description": "The XBN 2.1 Alloy is our entry-level road bike – but that’s " +
|
||||
"not to say that it’s a basic machine. With an internal weld aluminium frame, a full " +
|
||||
"carbon fork, and the slick-shifting Claris gears from Shimano’s, this is a bike which " +
|
||||
"doesn’t break the bank and delivers craved performance.",
|
||||
"condition": "new",
|
||||
},
|
||||
{
|
||||
"pickup_zone": "POLYGON((2.1767 48.9016, 2.5267 48.9016, 2.5267 48.5516, " +
|
||||
"2.1767 48.5516, 2.1767 48.9016))",
|
||||
"store_location": "2.3522,48.8566",
|
||||
"brand": "ScramBikes",
|
||||
"model": "WattBike",
|
||||
"price": 2300,
|
||||
"description": "The WattBike is the best e-bike for people who still " +
|
||||
"feel young at heart. It has a Bafang 1000W mid-drive system and a 48V 17.5AH " +
|
||||
"Samsung Lithium-Ion battery, allowing you to ride for more than 60 miles on one " +
|
||||
"charge. It’s great for tackling hilly terrain or if you just fancy a more " +
|
||||
"leisurely ride. With three working modes, you can choose between E-bike, " +
|
||||
"assisted bicycle, and normal bike modes.",
|
||||
"condition": "new",
|
||||
},
|
||||
{
|
||||
"pickup_zone": "POLYGON((13.3260 52.5700, 13.6550 52.5700, 13.6550 52.2700, " +
|
||||
"13.3260 52.2700, 13.3260 52.5700))",
|
||||
"store_location": "13.4050,52.5200",
|
||||
"brand": "Peaknetic",
|
||||
"model": "Secto",
|
||||
"price": 430,
|
||||
"description": "If you struggle with stiff fingers or a kinked neck or " +
|
||||
"back after a few minutes on the road, this lightweight, aluminum bike alleviates " +
|
||||
"those issues and allows you to enjoy the ride. From the ergonomic grips to the " +
|
||||
"lumbar-supporting seat position, the Roll Low-Entry offers incredible comfort. " +
|
||||
"The rear-inclined seat tube facilitates stability by allowing you to put a foot " +
|
||||
"on the ground to balance at a stop, and the low step-over frame makes it " +
|
||||
"accessible for all ability and mobility levels. The saddle is very soft, with " +
|
||||
"a wide back to support your hip joints and a cutout in the center to redistribute " +
|
||||
"that pressure. Rim brakes deliver satisfactory braking control, and the wide tires " +
|
||||
"provide a smooth, stable ride on paved roads and gravel. Rack and fender mounts " +
|
||||
"facilitate setting up the Roll Low-Entry as your preferred commuter, and the " +
|
||||
"BMX-like handlebar offers space for mounting a flashlight, bell, or phone holder.",
|
||||
"condition": "new",
|
||||
},
|
||||
{
|
||||
"pickup_zone": "POLYGON((1.9450 41.4301, 2.4018 41.4301, 2.4018 41.1987, " +
|
||||
"1.9450 41.1987, 1.9450 41.4301))",
|
||||
"store_location": "2.1734, 41.3851",
|
||||
"brand": "nHill",
|
||||
"model": "Summit",
|
||||
"price": 1200,
|
||||
"description": "This budget mountain bike from nHill performs well both " +
|
||||
"on bike paths and on the trail. The fork with 100mm of travel absorbs rough " +
|
||||
"terrain. Fat Kenda Booster tires give you grip in corners and on wet trails. " +
|
||||
"The Shimano Tourney drivetrain offered enough gears for finding a comfortable " +
|
||||
"pace to ride uphill, and the Tektro hydraulic disc brakes break smoothly. " +
|
||||
"Whether you want an affordable bike that you can take to work, but also take " +
|
||||
"trail in mountains on the weekends or you’re just after a stable, comfortable " +
|
||||
"ride for the bike path, the Summit gives a good value for money.",
|
||||
"condition": "new",
|
||||
},
|
||||
{
|
||||
"pickup_zone": "POLYGON((12.4464 42.1028, 12.5464 42.1028, " +
|
||||
"12.5464 41.7028, 12.4464 41.7028, 12.4464 42.1028))",
|
||||
"store_location": "12.4964,41.9028",
|
||||
"model": "ThrillCycle",
|
||||
"brand": "BikeShind",
|
||||
"price": 815,
|
||||
"description": "An artsy, retro-inspired bicycle that’s as " +
|
||||
"functional as it is pretty: The ThrillCycle steel frame offers a smooth ride. " +
|
||||
"A 9-speed drivetrain has enough gears for coasting in the city, but we wouldn’t " +
|
||||
"suggest taking it to the mountains. Fenders protect you from mud, and a rear " +
|
||||
"basket lets you transport groceries, flowers and books. The ThrillCycle comes " +
|
||||
"with a limited lifetime warranty, so this little guy will last you long " +
|
||||
"past graduation.",
|
||||
"condition": "refurbished",
|
||||
},
|
||||
}
|
||||
|
||||
for i, json := range exampleJsons {
|
||||
_, err := rdb.JSONSet(ctx, fmt.Sprintf("bicycle:%v", i), "$", json).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// STEP_START agg1
|
||||
res1, err := rdb.FTAggregateWithArgs(ctx,
|
||||
"idx:bicycle",
|
||||
"@condition:{new}",
|
||||
&redis.FTAggregateOptions{
|
||||
Apply: []redis.FTAggregateApply{
|
||||
{
|
||||
Field: "@price - (@price * 0.1)",
|
||||
As: "discounted",
|
||||
},
|
||||
},
|
||||
Load: []redis.FTAggregateLoad{
|
||||
{Field: "__key"},
|
||||
{Field: "price"},
|
||||
},
|
||||
},
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(len(res1.Rows)) // >>> 5
|
||||
|
||||
sort.Slice(res1.Rows, func(i, j int) bool {
|
||||
return res1.Rows[i].Fields["__key"].(string) <
|
||||
res1.Rows[j].Fields["__key"].(string)
|
||||
})
|
||||
|
||||
for _, row := range res1.Rows {
|
||||
fmt.Printf(
|
||||
"__key=%v, discounted=%v, price=%v\n",
|
||||
row.Fields["__key"],
|
||||
row.Fields["discounted"],
|
||||
row.Fields["price"],
|
||||
)
|
||||
}
|
||||
// >>> __key=bicycle:0, discounted=243, price=270
|
||||
// >>> __key=bicycle:5, discounted=729, price=810
|
||||
// >>> __key=bicycle:6, discounted=2070, price=2300
|
||||
// >>> __key=bicycle:7, discounted=387, price=430
|
||||
// >>> __key=bicycle:8, discounted=1080, price=1200
|
||||
// STEP_END
|
||||
|
||||
// STEP_START agg2
|
||||
res2, err := rdb.FTAggregateWithArgs(ctx,
|
||||
"idx:bicycle", "*",
|
||||
&redis.FTAggregateOptions{
|
||||
Load: []redis.FTAggregateLoad{
|
||||
{Field: "price"},
|
||||
},
|
||||
Apply: []redis.FTAggregateApply{
|
||||
{
|
||||
Field: "@price<1000",
|
||||
As: "price_category",
|
||||
},
|
||||
},
|
||||
GroupBy: []redis.FTAggregateGroupBy{
|
||||
{
|
||||
Fields: []interface{}{"@condition"},
|
||||
Reduce: []redis.FTAggregateReducer{
|
||||
{
|
||||
Reducer: redis.SearchSum,
|
||||
Args: []interface{}{"@price_category"},
|
||||
As: "num_affordable",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(len(res2.Rows)) // >>> 3
|
||||
|
||||
sort.Slice(res2.Rows, func(i, j int) bool {
|
||||
return res2.Rows[i].Fields["condition"].(string) <
|
||||
res2.Rows[j].Fields["condition"].(string)
|
||||
})
|
||||
|
||||
for _, row := range res2.Rows {
|
||||
fmt.Printf(
|
||||
"condition=%v, num_affordable=%v\n",
|
||||
row.Fields["condition"],
|
||||
row.Fields["num_affordable"],
|
||||
)
|
||||
}
|
||||
// >>> condition=new, num_affordable=3
|
||||
// >>> condition=refurbished, num_affordable=1
|
||||
// >>> condition=used, num_affordable=1
|
||||
// STEP_END
|
||||
|
||||
// STEP_START agg3
|
||||
|
||||
res3, err := rdb.FTAggregateWithArgs(ctx,
|
||||
"idx:bicycle", "*",
|
||||
&redis.FTAggregateOptions{
|
||||
Apply: []redis.FTAggregateApply{
|
||||
{
|
||||
Field: "'bicycle'",
|
||||
As: "type",
|
||||
},
|
||||
},
|
||||
GroupBy: []redis.FTAggregateGroupBy{
|
||||
{
|
||||
Fields: []interface{}{"@type"},
|
||||
Reduce: []redis.FTAggregateReducer{
|
||||
{
|
||||
Reducer: redis.SearchCount,
|
||||
As: "num_total",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(len(res3.Rows)) // >>> 1
|
||||
|
||||
for _, row := range res3.Rows {
|
||||
fmt.Printf(
|
||||
"type=%v, num_total=%v\n",
|
||||
row.Fields["type"],
|
||||
row.Fields["num_total"],
|
||||
)
|
||||
}
|
||||
// type=bicycle, num_total=10
|
||||
// STEP_END
|
||||
|
||||
// STEP_START agg4
|
||||
res4, err := rdb.FTAggregateWithArgs(ctx,
|
||||
"idx:bicycle", "*",
|
||||
&redis.FTAggregateOptions{
|
||||
Load: []redis.FTAggregateLoad{
|
||||
{Field: "__key"},
|
||||
},
|
||||
GroupBy: []redis.FTAggregateGroupBy{
|
||||
{
|
||||
Fields: []interface{}{"@condition"},
|
||||
Reduce: []redis.FTAggregateReducer{
|
||||
{
|
||||
Reducer: redis.SearchToList,
|
||||
Args: []interface{}{"__key"},
|
||||
As: "bicycles",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(len(res4.Rows)) // >>> 3
|
||||
|
||||
sort.Slice(res4.Rows, func(i, j int) bool {
|
||||
return res4.Rows[i].Fields["condition"].(string) <
|
||||
res4.Rows[j].Fields["condition"].(string)
|
||||
})
|
||||
|
||||
for _, row := range res4.Rows {
|
||||
rowBikes := row.Fields["bicycles"].([]interface{})
|
||||
bikes := make([]string, len(rowBikes))
|
||||
|
||||
for i, rowBike := range rowBikes {
|
||||
bikes[i] = rowBike.(string)
|
||||
}
|
||||
|
||||
sort.Slice(bikes, func(i, j int) bool {
|
||||
return bikes[i] < bikes[j]
|
||||
})
|
||||
|
||||
fmt.Printf(
|
||||
"condition=%v, bicycles=%v\n",
|
||||
row.Fields["condition"],
|
||||
bikes,
|
||||
)
|
||||
}
|
||||
// >>> condition=new, bicycles=[bicycle:0 bicycle:5 bicycle:6 bicycle:7 bicycle:8]
|
||||
// >>> condition=refurbished, bicycles=[bicycle:9]
|
||||
// >>> condition=used, bicycles=[bicycle:1 bicycle:2 bicycle:3 bicycle:4]
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// 5
|
||||
// __key=bicycle:0, discounted=243, price=270
|
||||
// __key=bicycle:5, discounted=729, price=810
|
||||
// __key=bicycle:6, discounted=2070, price=2300
|
||||
// __key=bicycle:7, discounted=387, price=430
|
||||
// __key=bicycle:8, discounted=1080, price=1200
|
||||
// 3
|
||||
// condition=new, num_affordable=3
|
||||
// condition=refurbished, num_affordable=1
|
||||
// condition=used, num_affordable=1
|
||||
// 1
|
||||
// type=bicycle, num_total=10
|
||||
// 3
|
||||
// condition=new, bicycles=[bicycle:0 bicycle:5 bicycle:6 bicycle:7 bicycle:8]
|
||||
// condition=refurbished, bicycles=[bicycle:9]
|
||||
// condition=used, bicycles=[bicycle:1 bicycle:2 bicycle:3 bicycle:4]
|
||||
}
|
372
doctests/query_em_test.go
Normal file
372
doctests/query_em_test.go
Normal file
@ -0,0 +1,372 @@
|
||||
// EXAMPLE: query_em
|
||||
// HIDE_START
|
||||
package example_commands_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
func ExampleClient_query_em() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
Protocol: 2,
|
||||
})
|
||||
|
||||
// HIDE_END
|
||||
// REMOVE_START
|
||||
// start with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.FTDropIndex(ctx, "idx:bicycle")
|
||||
rdb.FTDropIndex(ctx, "idx:email")
|
||||
// REMOVE_END
|
||||
|
||||
_, err := rdb.FTCreate(ctx, "idx:bicycle",
|
||||
&redis.FTCreateOptions{
|
||||
OnJSON: true,
|
||||
Prefix: []interface{}{"bicycle:"},
|
||||
},
|
||||
&redis.FieldSchema{
|
||||
FieldName: "$.brand",
|
||||
As: "brand",
|
||||
FieldType: redis.SearchFieldTypeText,
|
||||
},
|
||||
&redis.FieldSchema{
|
||||
FieldName: "$.model",
|
||||
As: "model",
|
||||
FieldType: redis.SearchFieldTypeText,
|
||||
},
|
||||
&redis.FieldSchema{
|
||||
FieldName: "$.description",
|
||||
As: "description",
|
||||
FieldType: redis.SearchFieldTypeText,
|
||||
},
|
||||
&redis.FieldSchema{
|
||||
FieldName: "$.price",
|
||||
As: "price",
|
||||
FieldType: redis.SearchFieldTypeNumeric,
|
||||
},
|
||||
&redis.FieldSchema{
|
||||
FieldName: "$.condition",
|
||||
As: "condition",
|
||||
FieldType: redis.SearchFieldTypeTag,
|
||||
},
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
exampleJsons := []map[string]interface{}{
|
||||
{
|
||||
"pickup_zone": "POLYGON((-74.0610 40.7578, -73.9510 40.7578, -73.9510 40.6678, " +
|
||||
"-74.0610 40.6678, -74.0610 40.7578))",
|
||||
"store_location": "-74.0060,40.7128",
|
||||
"brand": "Velorim",
|
||||
"model": "Jigger",
|
||||
"price": 270,
|
||||
"description": "Small and powerful, the Jigger is the best ride for the smallest of tikes! " +
|
||||
"This is the tiniest kids pedal bike on the market available without a coaster brake, the Jigger " +
|
||||
"is the vehicle of choice for the rare tenacious little rider raring to go.",
|
||||
"condition": "new",
|
||||
},
|
||||
{
|
||||
"pickup_zone": "POLYGON((-118.2887 34.0972, -118.1987 34.0972, -118.1987 33.9872, " +
|
||||
"-118.2887 33.9872, -118.2887 34.0972))",
|
||||
"store_location": "-118.2437,34.0522",
|
||||
"brand": "Bicyk",
|
||||
"model": "Hillcraft",
|
||||
"price": 1200,
|
||||
"description": "Kids want to ride with as little weight as possible. Especially " +
|
||||
"on an incline! They may be at the age when a 27.5'' wheel bike is just too clumsy coming " +
|
||||
"off a 24'' bike. The Hillcraft 26 is just the solution they need!",
|
||||
"condition": "used",
|
||||
},
|
||||
{
|
||||
"pickup_zone": "POLYGON((-87.6848 41.9331, -87.5748 41.9331, -87.5748 41.8231, " +
|
||||
"-87.6848 41.8231, -87.6848 41.9331))",
|
||||
"store_location": "-87.6298,41.8781",
|
||||
"brand": "Nord",
|
||||
"model": "Chook air 5",
|
||||
"price": 815,
|
||||
"description": "The Chook Air 5 gives kids aged six years and older a durable " +
|
||||
"and uberlight mountain bike for their first experience on tracks and easy cruising through " +
|
||||
"forests and fields. The lower top tube makes it easy to mount and dismount in any " +
|
||||
"situation, giving your kids greater safety on the trails.",
|
||||
"condition": "used",
|
||||
},
|
||||
{
|
||||
"pickup_zone": "POLYGON((-80.2433 25.8067, -80.1333 25.8067, -80.1333 25.6967, " +
|
||||
"-80.2433 25.6967, -80.2433 25.8067))",
|
||||
"store_location": "-80.1918,25.7617",
|
||||
"brand": "Eva",
|
||||
"model": "Eva 291",
|
||||
"price": 3400,
|
||||
"description": "The sister company to Nord, Eva launched in 2005 as the first " +
|
||||
"and only women-dedicated bicycle brand. Designed by women for women, allEva bikes " +
|
||||
"are optimized for the feminine physique using analytics from a body metrics database. " +
|
||||
"If you like 29ers, try the Eva 291. It’s a brand new bike for 2022.. This " +
|
||||
"full-suspension, cross-country ride has been designed for velocity. The 291 has " +
|
||||
"100mm of front and rear travel, a superlight aluminum frame and fast-rolling " +
|
||||
"29-inch wheels. Yippee!",
|
||||
"condition": "used",
|
||||
},
|
||||
{
|
||||
"pickup_zone": "POLYGON((-122.4644 37.8199, -122.3544 37.8199, -122.3544 37.7099, " +
|
||||
"-122.4644 37.7099, -122.4644 37.8199))",
|
||||
"store_location": "-122.4194,37.7749",
|
||||
"brand": "Noka Bikes",
|
||||
"model": "Kahuna",
|
||||
"price": 3200,
|
||||
"description": "Whether you want to try your hand at XC racing or are looking " +
|
||||
"for a lively trail bike that's just as inspiring on the climbs as it is over rougher " +
|
||||
"ground, the Wilder is one heck of a bike built specifically for short women. Both the " +
|
||||
"frames and components have been tweaked to include a women’s saddle, different bars " +
|
||||
"and unique colourway.",
|
||||
"condition": "used",
|
||||
},
|
||||
{
|
||||
"pickup_zone": "POLYGON((-0.1778 51.5524, 0.0822 51.5524, 0.0822 51.4024, " +
|
||||
"-0.1778 51.4024, -0.1778 51.5524))",
|
||||
"store_location": "-0.1278,51.5074",
|
||||
"brand": "Breakout",
|
||||
"model": "XBN 2.1 Alloy",
|
||||
"price": 810,
|
||||
"description": "The XBN 2.1 Alloy is our entry-level road bike – but that’s " +
|
||||
"not to say that it’s a basic machine. With an internal weld aluminium frame, a full " +
|
||||
"carbon fork, and the slick-shifting Claris gears from Shimano’s, this is a bike which " +
|
||||
"doesn’t break the bank and delivers craved performance.",
|
||||
"condition": "new",
|
||||
},
|
||||
{
|
||||
"pickup_zone": "POLYGON((2.1767 48.9016, 2.5267 48.9016, 2.5267 48.5516, " +
|
||||
"2.1767 48.5516, 2.1767 48.9016))",
|
||||
"store_location": "2.3522,48.8566",
|
||||
"brand": "ScramBikes",
|
||||
"model": "WattBike",
|
||||
"price": 2300,
|
||||
"description": "The WattBike is the best e-bike for people who still " +
|
||||
"feel young at heart. It has a Bafang 1000W mid-drive system and a 48V 17.5AH " +
|
||||
"Samsung Lithium-Ion battery, allowing you to ride for more than 60 miles on one " +
|
||||
"charge. It’s great for tackling hilly terrain or if you just fancy a more " +
|
||||
"leisurely ride. With three working modes, you can choose between E-bike, " +
|
||||
"assisted bicycle, and normal bike modes.",
|
||||
"condition": "new",
|
||||
},
|
||||
{
|
||||
"pickup_zone": "POLYGON((13.3260 52.5700, 13.6550 52.5700, 13.6550 52.2700, " +
|
||||
"13.3260 52.2700, 13.3260 52.5700))",
|
||||
"store_location": "13.4050,52.5200",
|
||||
"brand": "Peaknetic",
|
||||
"model": "Secto",
|
||||
"price": 430,
|
||||
"description": "If you struggle with stiff fingers or a kinked neck or " +
|
||||
"back after a few minutes on the road, this lightweight, aluminum bike alleviates " +
|
||||
"those issues and allows you to enjoy the ride. From the ergonomic grips to the " +
|
||||
"lumbar-supporting seat position, the Roll Low-Entry offers incredible comfort. " +
|
||||
"The rear-inclined seat tube facilitates stability by allowing you to put a foot " +
|
||||
"on the ground to balance at a stop, and the low step-over frame makes it " +
|
||||
"accessible for all ability and mobility levels. The saddle is very soft, with " +
|
||||
"a wide back to support your hip joints and a cutout in the center to redistribute " +
|
||||
"that pressure. Rim brakes deliver satisfactory braking control, and the wide tires " +
|
||||
"provide a smooth, stable ride on paved roads and gravel. Rack and fender mounts " +
|
||||
"facilitate setting up the Roll Low-Entry as your preferred commuter, and the " +
|
||||
"BMX-like handlebar offers space for mounting a flashlight, bell, or phone holder.",
|
||||
"condition": "new",
|
||||
},
|
||||
{
|
||||
"pickup_zone": "POLYGON((1.9450 41.4301, 2.4018 41.4301, 2.4018 41.1987, " +
|
||||
"1.9450 41.1987, 1.9450 41.4301))",
|
||||
"store_location": "2.1734, 41.3851",
|
||||
"brand": "nHill",
|
||||
"model": "Summit",
|
||||
"price": 1200,
|
||||
"description": "This budget mountain bike from nHill performs well both " +
|
||||
"on bike paths and on the trail. The fork with 100mm of travel absorbs rough " +
|
||||
"terrain. Fat Kenda Booster tires give you grip in corners and on wet trails. " +
|
||||
"The Shimano Tourney drivetrain offered enough gears for finding a comfortable " +
|
||||
"pace to ride uphill, and the Tektro hydraulic disc brakes break smoothly. " +
|
||||
"Whether you want an affordable bike that you can take to work, but also take " +
|
||||
"trail in mountains on the weekends or you’re just after a stable, comfortable " +
|
||||
"ride for the bike path, the Summit gives a good value for money.",
|
||||
"condition": "new",
|
||||
},
|
||||
{
|
||||
"pickup_zone": "POLYGON((12.4464 42.1028, 12.5464 42.1028, " +
|
||||
"12.5464 41.7028, 12.4464 41.7028, 12.4464 42.1028))",
|
||||
"store_location": "12.4964,41.9028",
|
||||
"model": "ThrillCycle",
|
||||
"brand": "BikeShind",
|
||||
"price": 815,
|
||||
"description": "An artsy, retro-inspired bicycle that’s as " +
|
||||
"functional as it is pretty: The ThrillCycle steel frame offers a smooth ride. " +
|
||||
"A 9-speed drivetrain has enough gears for coasting in the city, but we wouldn’t " +
|
||||
"suggest taking it to the mountains. Fenders protect you from mud, and a rear " +
|
||||
"basket lets you transport groceries, flowers and books. The ThrillCycle comes " +
|
||||
"with a limited lifetime warranty, so this little guy will last you long " +
|
||||
"past graduation.",
|
||||
"condition": "refurbished",
|
||||
},
|
||||
}
|
||||
|
||||
for i, json := range exampleJsons {
|
||||
_, err := rdb.JSONSet(ctx, fmt.Sprintf("bicycle:%v", i), "$", json).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// STEP_START em1
|
||||
res1, err := rdb.FTSearch(ctx,
|
||||
"idx:bicycle", "@price:[270 270]",
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res1.Total) // >>> 1
|
||||
|
||||
for _, doc := range res1.Docs {
|
||||
fmt.Println(doc.ID)
|
||||
}
|
||||
// >>> bicycle:0
|
||||
|
||||
res2, err := rdb.FTSearchWithArgs(ctx,
|
||||
"idx:bicycle",
|
||||
"*",
|
||||
&redis.FTSearchOptions{
|
||||
Filters: []redis.FTSearchFilter{
|
||||
{
|
||||
FieldName: "price",
|
||||
Min: 270,
|
||||
Max: 270,
|
||||
},
|
||||
},
|
||||
},
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res2.Total) // >>> 1
|
||||
|
||||
for _, doc := range res2.Docs {
|
||||
fmt.Println(doc.ID)
|
||||
}
|
||||
// >>> bicycle:0
|
||||
// STEP_END
|
||||
|
||||
// STEP_START em2
|
||||
res3, err := rdb.FTSearch(ctx,
|
||||
"idx:bicycle", "@condition:{new}",
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res3.Total) // >>> 5
|
||||
|
||||
docs := res3.Docs
|
||||
slices.SortFunc(docs, func(a, b redis.Document) int {
|
||||
return strings.Compare(a.ID, b.ID)
|
||||
})
|
||||
|
||||
for _, doc := range docs {
|
||||
fmt.Println(doc.ID)
|
||||
}
|
||||
// >>> bicycle:0
|
||||
// >>> bicycle:5
|
||||
// >>> bicycle:6
|
||||
// >>> bicycle:7
|
||||
// >>> bicycle:8
|
||||
// STEP_END
|
||||
|
||||
// STEP_START em3
|
||||
res4, err := rdb.FTCreate(ctx,
|
||||
"idx:email",
|
||||
&redis.FTCreateOptions{
|
||||
OnJSON: true,
|
||||
Prefix: []interface{}{"key:"},
|
||||
},
|
||||
&redis.FieldSchema{
|
||||
FieldName: "$.email",
|
||||
As: "email",
|
||||
FieldType: redis.SearchFieldTypeTag,
|
||||
},
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res4) // >>> OK
|
||||
|
||||
res5, err := rdb.JSONSet(ctx, "key:1", "$",
|
||||
map[string]interface{}{
|
||||
"email": "test@redis.com",
|
||||
},
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res5) // >>> OK
|
||||
|
||||
res6, err := rdb.FTSearch(ctx, "idx:email",
|
||||
"@email:{test\\@redis\\.com}",
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res6.Total) // >>> 1
|
||||
// STEP_END
|
||||
|
||||
// STEP_START em4
|
||||
res7, err := rdb.FTSearch(ctx,
|
||||
"idx:bicycle", "@description:\"rough terrain\"",
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res7.Total) // >>> 1
|
||||
|
||||
for _, doc := range res7.Docs {
|
||||
fmt.Println(doc.ID)
|
||||
}
|
||||
// >>> bicycle:8
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// 1
|
||||
// bicycle:0
|
||||
// 1
|
||||
// bicycle:0
|
||||
// 5
|
||||
// bicycle:0
|
||||
// bicycle:5
|
||||
// bicycle:6
|
||||
// bicycle:7
|
||||
// bicycle:8
|
||||
// OK
|
||||
// OK
|
||||
// 1
|
||||
// 1
|
||||
// bicycle:8
|
||||
}
|
333
doctests/query_ft_test.go
Normal file
333
doctests/query_ft_test.go
Normal file
@ -0,0 +1,333 @@
|
||||
// EXAMPLE: query_ft
|
||||
// HIDE_START
|
||||
package example_commands_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
func ExampleClient_query_ft() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
Protocol: 2,
|
||||
})
|
||||
// HIDE_END
|
||||
// REMOVE_START
|
||||
// start with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.FTDropIndex(ctx, "idx:bicycle")
|
||||
rdb.FTDropIndex(ctx, "idx:email")
|
||||
// REMOVE_END
|
||||
|
||||
_, err := rdb.FTCreate(ctx, "idx:bicycle",
|
||||
&redis.FTCreateOptions{
|
||||
OnJSON: true,
|
||||
Prefix: []interface{}{"bicycle:"},
|
||||
},
|
||||
&redis.FieldSchema{
|
||||
FieldName: "$.brand",
|
||||
As: "brand",
|
||||
FieldType: redis.SearchFieldTypeText,
|
||||
},
|
||||
&redis.FieldSchema{
|
||||
FieldName: "$.model",
|
||||
As: "model",
|
||||
FieldType: redis.SearchFieldTypeText,
|
||||
},
|
||||
&redis.FieldSchema{
|
||||
FieldName: "$.description",
|
||||
As: "description",
|
||||
FieldType: redis.SearchFieldTypeText,
|
||||
},
|
||||
&redis.FieldSchema{
|
||||
FieldName: "$.price",
|
||||
As: "price",
|
||||
FieldType: redis.SearchFieldTypeNumeric,
|
||||
},
|
||||
&redis.FieldSchema{
|
||||
FieldName: "$.condition",
|
||||
As: "condition",
|
||||
FieldType: redis.SearchFieldTypeTag,
|
||||
},
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
exampleJsons := []map[string]interface{}{
|
||||
{
|
||||
"pickup_zone": "POLYGON((-74.0610 40.7578, -73.9510 40.7578, -73.9510 40.6678, " +
|
||||
"-74.0610 40.6678, -74.0610 40.7578))",
|
||||
"store_location": "-74.0060,40.7128",
|
||||
"brand": "Velorim",
|
||||
"model": "Jigger",
|
||||
"price": 270,
|
||||
"description": "Small and powerful, the Jigger is the best ride for the smallest of tikes! " +
|
||||
"This is the tiniest kids’ pedal bike on the market available without a coaster brake, the Jigger " +
|
||||
"is the vehicle of choice for the rare tenacious little rider raring to go.",
|
||||
"condition": "new",
|
||||
},
|
||||
{
|
||||
"pickup_zone": "POLYGON((-118.2887 34.0972, -118.1987 34.0972, -118.1987 33.9872, " +
|
||||
"-118.2887 33.9872, -118.2887 34.0972))",
|
||||
"store_location": "-118.2437,34.0522",
|
||||
"brand": "Bicyk",
|
||||
"model": "Hillcraft",
|
||||
"price": 1200,
|
||||
"description": "Kids want to ride with as little weight as possible. Especially " +
|
||||
"on an incline! They may be at the age when a 27.5'' wheel bike is just too clumsy coming " +
|
||||
"off a 24'' bike. The Hillcraft 26 is just the solution they need!",
|
||||
"condition": "used",
|
||||
},
|
||||
{
|
||||
"pickup_zone": "POLYGON((-87.6848 41.9331, -87.5748 41.9331, -87.5748 41.8231, " +
|
||||
"-87.6848 41.8231, -87.6848 41.9331))",
|
||||
"store_location": "-87.6298,41.8781",
|
||||
"brand": "Nord",
|
||||
"model": "Chook air 5",
|
||||
"price": 815,
|
||||
"description": "The Chook Air 5 gives kids aged six years and older a durable " +
|
||||
"and uberlight mountain bike for their first experience on tracks and easy cruising through " +
|
||||
"forests and fields. The lower top tube makes it easy to mount and dismount in any " +
|
||||
"situation, giving your kids greater safety on the trails.",
|
||||
"condition": "used",
|
||||
},
|
||||
{
|
||||
"pickup_zone": "POLYGON((-80.2433 25.8067, -80.1333 25.8067, -80.1333 25.6967, " +
|
||||
"-80.2433 25.6967, -80.2433 25.8067))",
|
||||
"store_location": "-80.1918,25.7617",
|
||||
"brand": "Eva",
|
||||
"model": "Eva 291",
|
||||
"price": 3400,
|
||||
"description": "The sister company to Nord, Eva launched in 2005 as the first " +
|
||||
"and only women-dedicated bicycle brand. Designed by women for women, allEva bikes " +
|
||||
"are optimized for the feminine physique using analytics from a body metrics database. " +
|
||||
"If you like 29ers, try the Eva 291. It’s a brand new bike for 2022.. This " +
|
||||
"full-suspension, cross-country ride has been designed for velocity. The 291 has " +
|
||||
"100mm of front and rear travel, a superlight aluminum frame and fast-rolling " +
|
||||
"29-inch wheels. Yippee!",
|
||||
"condition": "used",
|
||||
},
|
||||
{
|
||||
"pickup_zone": "POLYGON((-122.4644 37.8199, -122.3544 37.8199, -122.3544 37.7099, " +
|
||||
"-122.4644 37.7099, -122.4644 37.8199))",
|
||||
"store_location": "-122.4194,37.7749",
|
||||
"brand": "Noka Bikes",
|
||||
"model": "Kahuna",
|
||||
"price": 3200,
|
||||
"description": "Whether you want to try your hand at XC racing or are looking " +
|
||||
"for a lively trail bike that's just as inspiring on the climbs as it is over rougher " +
|
||||
"ground, the Wilder is one heck of a bike built specifically for short women. Both the " +
|
||||
"frames and components have been tweaked to include a women’s saddle, different bars " +
|
||||
"and unique colourway.",
|
||||
"condition": "used",
|
||||
},
|
||||
{
|
||||
"pickup_zone": "POLYGON((-0.1778 51.5524, 0.0822 51.5524, 0.0822 51.4024, " +
|
||||
"-0.1778 51.4024, -0.1778 51.5524))",
|
||||
"store_location": "-0.1278,51.5074",
|
||||
"brand": "Breakout",
|
||||
"model": "XBN 2.1 Alloy",
|
||||
"price": 810,
|
||||
"description": "The XBN 2.1 Alloy is our entry-level road bike – but that’s " +
|
||||
"not to say that it’s a basic machine. With an internal weld aluminium frame, a full " +
|
||||
"carbon fork, and the slick-shifting Claris gears from Shimano’s, this is a bike which " +
|
||||
"doesn’t break the bank and delivers craved performance.",
|
||||
"condition": "new",
|
||||
},
|
||||
{
|
||||
"pickup_zone": "POLYGON((2.1767 48.9016, 2.5267 48.9016, 2.5267 48.5516, " +
|
||||
"2.1767 48.5516, 2.1767 48.9016))",
|
||||
"store_location": "2.3522,48.8566",
|
||||
"brand": "ScramBikes",
|
||||
"model": "WattBike",
|
||||
"price": 2300,
|
||||
"description": "The WattBike is the best e-bike for people who still " +
|
||||
"feel young at heart. It has a Bafang 1000W mid-drive system and a 48V 17.5AH " +
|
||||
"Samsung Lithium-Ion battery, allowing you to ride for more than 60 miles on one " +
|
||||
"charge. It’s great for tackling hilly terrain or if you just fancy a more " +
|
||||
"leisurely ride. With three working modes, you can choose between E-bike, " +
|
||||
"assisted bicycle, and normal bike modes.",
|
||||
"condition": "new",
|
||||
},
|
||||
{
|
||||
"pickup_zone": "POLYGON((13.3260 52.5700, 13.6550 52.5700, 13.6550 52.2700, " +
|
||||
"13.3260 52.2700, 13.3260 52.5700))",
|
||||
"store_location": "13.4050,52.5200",
|
||||
"brand": "Peaknetic",
|
||||
"model": "Secto",
|
||||
"price": 430,
|
||||
"description": "If you struggle with stiff fingers or a kinked neck or " +
|
||||
"back after a few minutes on the road, this lightweight, aluminum bike alleviates " +
|
||||
"those issues and allows you to enjoy the ride. From the ergonomic grips to the " +
|
||||
"lumbar-supporting seat position, the Roll Low-Entry offers incredible comfort. " +
|
||||
"The rear-inclined seat tube facilitates stability by allowing you to put a foot " +
|
||||
"on the ground to balance at a stop, and the low step-over frame makes it " +
|
||||
"accessible for all ability and mobility levels. The saddle is very soft, with " +
|
||||
"a wide back to support your hip joints and a cutout in the center to redistribute " +
|
||||
"that pressure. Rim brakes deliver satisfactory braking control, and the wide tires " +
|
||||
"provide a smooth, stable ride on paved roads and gravel. Rack and fender mounts " +
|
||||
"facilitate setting up the Roll Low-Entry as your preferred commuter, and the " +
|
||||
"BMX-like handlebar offers space for mounting a flashlight, bell, or phone holder.",
|
||||
"condition": "new",
|
||||
},
|
||||
{
|
||||
"pickup_zone": "POLYGON((1.9450 41.4301, 2.4018 41.4301, 2.4018 41.1987, " +
|
||||
"1.9450 41.1987, 1.9450 41.4301))",
|
||||
"store_location": "2.1734, 41.3851",
|
||||
"brand": "nHill",
|
||||
"model": "Summit",
|
||||
"price": 1200,
|
||||
"description": "This budget mountain bike from nHill performs well both " +
|
||||
"on bike paths and on the trail. The fork with 100mm of travel absorbs rough " +
|
||||
"terrain. Fat Kenda Booster tires give you grip in corners and on wet trails. " +
|
||||
"The Shimano Tourney drivetrain offered enough gears for finding a comfortable " +
|
||||
"pace to ride uphill, and the Tektro hydraulic disc brakes break smoothly. " +
|
||||
"Whether you want an affordable bike that you can take to work, but also take " +
|
||||
"trail in mountains on the weekends or you’re just after a stable, comfortable " +
|
||||
"ride for the bike path, the Summit gives a good value for money.",
|
||||
"condition": "new",
|
||||
},
|
||||
{
|
||||
"pickup_zone": "POLYGON((12.4464 42.1028, 12.5464 42.1028, " +
|
||||
"12.5464 41.7028, 12.4464 41.7028, 12.4464 42.1028))",
|
||||
"store_location": "12.4964,41.9028",
|
||||
"model": "ThrillCycle",
|
||||
"brand": "BikeShind",
|
||||
"price": 815,
|
||||
"description": "An artsy, retro-inspired bicycle that’s as " +
|
||||
"functional as it is pretty: The ThrillCycle steel frame offers a smooth ride. " +
|
||||
"A 9-speed drivetrain has enough gears for coasting in the city, but we wouldn’t " +
|
||||
"suggest taking it to the mountains. Fenders protect you from mud, and a rear " +
|
||||
"basket lets you transport groceries, flowers and books. The ThrillCycle comes " +
|
||||
"with a limited lifetime warranty, so this little guy will last you long " +
|
||||
"past graduation.",
|
||||
"condition": "refurbished",
|
||||
},
|
||||
}
|
||||
|
||||
for i, json := range exampleJsons {
|
||||
_, err := rdb.JSONSet(ctx, fmt.Sprintf("bicycle:%v", i), "$", json).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// STEP_START ft1
|
||||
res1, err := rdb.FTSearch(ctx,
|
||||
"idx:bicycle", "@description: kids",
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res1.Total) // >>> 2
|
||||
|
||||
sort.Slice(res1.Docs, func(i, j int) bool {
|
||||
return res1.Docs[i].ID < res1.Docs[j].ID
|
||||
})
|
||||
|
||||
for _, doc := range res1.Docs {
|
||||
fmt.Println(doc.ID)
|
||||
}
|
||||
// >>> bicycle:1
|
||||
// >>> bicycle:2
|
||||
// STEP_END
|
||||
|
||||
// STEP_START ft2
|
||||
res2, err := rdb.FTSearch(ctx,
|
||||
"idx:bicycle", "@model: ka*",
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res2.Total) // >>> 1
|
||||
|
||||
for _, doc := range res2.Docs {
|
||||
fmt.Println(doc.ID)
|
||||
}
|
||||
// >>> bicycle:4
|
||||
// STEP_END
|
||||
|
||||
// STEP_START ft3
|
||||
res3, err := rdb.FTSearch(ctx,
|
||||
"idx:bicycle", "@brand: *bikes",
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res3.Total) // >>> 2
|
||||
|
||||
sort.Slice(res3.Docs, func(i, j int) bool {
|
||||
return res3.Docs[i].ID < res3.Docs[j].ID
|
||||
})
|
||||
for _, doc := range res3.Docs {
|
||||
fmt.Println(doc.ID)
|
||||
}
|
||||
// >>> bicycle:4
|
||||
// >>> bicycle:6
|
||||
// STEP_END
|
||||
|
||||
// STEP_START ft4
|
||||
res4, err := rdb.FTSearch(ctx,
|
||||
"idx:bicycle", "%optamized%",
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res4.Total) // >>> 1
|
||||
|
||||
for _, doc := range res4.Docs {
|
||||
fmt.Println(doc.ID)
|
||||
}
|
||||
// >>> bicycle:3
|
||||
// STEP_END
|
||||
|
||||
// STEP_START ft5
|
||||
res5, err := rdb.FTSearch(ctx,
|
||||
"idx:bicycle", "%%optamised%%",
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res5.Total) // >>> 1
|
||||
|
||||
for _, doc := range res5.Docs {
|
||||
fmt.Println(doc.ID)
|
||||
}
|
||||
// >>> bicycle:3
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// 2
|
||||
// bicycle:1
|
||||
// bicycle:2
|
||||
// 1
|
||||
// bicycle:4
|
||||
// 2
|
||||
// bicycle:4
|
||||
// bicycle:6
|
||||
// 1
|
||||
// bicycle:3
|
||||
// 1
|
||||
// bicycle:3
|
||||
}
|
329
doctests/query_geo_test.go
Normal file
329
doctests/query_geo_test.go
Normal file
@ -0,0 +1,329 @@
|
||||
// EXAMPLE: query_geo
|
||||
// HIDE_START
|
||||
package example_commands_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
func ExampleClient_query_geo() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
Protocol: 2,
|
||||
})
|
||||
// HIDE_END
|
||||
// REMOVE_START
|
||||
// start with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.FTDropIndex(ctx, "idx:bicycle")
|
||||
// REMOVE_END
|
||||
|
||||
_, err := rdb.FTCreate(ctx, "idx:bicycle",
|
||||
&redis.FTCreateOptions{
|
||||
OnJSON: true,
|
||||
Prefix: []interface{}{"bicycle:"},
|
||||
},
|
||||
&redis.FieldSchema{
|
||||
FieldName: "$.brand",
|
||||
As: "brand",
|
||||
FieldType: redis.SearchFieldTypeText,
|
||||
},
|
||||
&redis.FieldSchema{
|
||||
FieldName: "$.model",
|
||||
As: "model",
|
||||
FieldType: redis.SearchFieldTypeText,
|
||||
},
|
||||
&redis.FieldSchema{
|
||||
FieldName: "$.description",
|
||||
As: "description",
|
||||
FieldType: redis.SearchFieldTypeText,
|
||||
},
|
||||
&redis.FieldSchema{
|
||||
FieldName: "$.price",
|
||||
As: "price",
|
||||
FieldType: redis.SearchFieldTypeNumeric,
|
||||
},
|
||||
&redis.FieldSchema{
|
||||
FieldName: "$.condition",
|
||||
As: "condition",
|
||||
FieldType: redis.SearchFieldTypeTag,
|
||||
},
|
||||
&redis.FieldSchema{
|
||||
FieldName: "$.store_location",
|
||||
As: "store_location",
|
||||
FieldType: redis.SearchFieldTypeGeo,
|
||||
},
|
||||
&redis.FieldSchema{
|
||||
FieldName: "$.pickup_zone",
|
||||
As: "pickup_zone",
|
||||
FieldType: redis.SearchFieldTypeGeoShape,
|
||||
GeoShapeFieldType: "FLAT",
|
||||
},
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
exampleJsons := []map[string]interface{}{
|
||||
{
|
||||
"pickup_zone": "POLYGON((-74.0610 40.7578, -73.9510 40.7578, -73.9510 40.6678, " +
|
||||
"-74.0610 40.6678, -74.0610 40.7578))",
|
||||
"store_location": "-74.0060,40.7128",
|
||||
"brand": "Velorim",
|
||||
"model": "Jigger",
|
||||
"price": 270,
|
||||
"description": "Small and powerful, the Jigger is the best ride for the smallest of tikes! " +
|
||||
"This is the tiniest kids pedal bike on the market available without a coaster brake, the Jigger " +
|
||||
"is the vehicle of choice for the rare tenacious little rider raring to go.",
|
||||
"condition": "new",
|
||||
},
|
||||
{
|
||||
"pickup_zone": "POLYGON((-118.2887 34.0972, -118.1987 34.0972, -118.1987 33.9872, " +
|
||||
"-118.2887 33.9872, -118.2887 34.0972))",
|
||||
"store_location": "-118.2437,34.0522",
|
||||
"brand": "Bicyk",
|
||||
"model": "Hillcraft",
|
||||
"price": 1200,
|
||||
"description": "Kids want to ride with as little weight as possible. Especially " +
|
||||
"on an incline! They may be at the age when a 27.5'' wheel bike is just too clumsy coming " +
|
||||
"off a 24'' bike. The Hillcraft 26 is just the solution they need!",
|
||||
"condition": "used",
|
||||
},
|
||||
{
|
||||
"pickup_zone": "POLYGON((-87.6848 41.9331, -87.5748 41.9331, -87.5748 41.8231, " +
|
||||
"-87.6848 41.8231, -87.6848 41.9331))",
|
||||
"store_location": "-87.6298,41.8781",
|
||||
"brand": "Nord",
|
||||
"model": "Chook air 5",
|
||||
"price": 815,
|
||||
"description": "The Chook Air 5 gives kids aged six years and older a durable " +
|
||||
"and uberlight mountain bike for their first experience on tracks and easy cruising through " +
|
||||
"forests and fields. The lower top tube makes it easy to mount and dismount in any " +
|
||||
"situation, giving your kids greater safety on the trails.",
|
||||
"condition": "used",
|
||||
},
|
||||
{
|
||||
"pickup_zone": "POLYGON((-80.2433 25.8067, -80.1333 25.8067, -80.1333 25.6967, " +
|
||||
"-80.2433 25.6967, -80.2433 25.8067))",
|
||||
"store_location": "-80.1918,25.7617",
|
||||
"brand": "Eva",
|
||||
"model": "Eva 291",
|
||||
"price": 3400,
|
||||
"description": "The sister company to Nord, Eva launched in 2005 as the first " +
|
||||
"and only women-dedicated bicycle brand. Designed by women for women, allEva bikes " +
|
||||
"are optimized for the feminine physique using analytics from a body metrics database. " +
|
||||
"If you like 29ers, try the Eva 291. It’s a brand new bike for 2022.. This " +
|
||||
"full-suspension, cross-country ride has been designed for velocity. The 291 has " +
|
||||
"100mm of front and rear travel, a superlight aluminum frame and fast-rolling " +
|
||||
"29-inch wheels. Yippee!",
|
||||
"condition": "used",
|
||||
},
|
||||
{
|
||||
"pickup_zone": "POLYGON((-122.4644 37.8199, -122.3544 37.8199, -122.3544 37.7099, " +
|
||||
"-122.4644 37.7099, -122.4644 37.8199))",
|
||||
"store_location": "-122.4194,37.7749",
|
||||
"brand": "Noka Bikes",
|
||||
"model": "Kahuna",
|
||||
"price": 3200,
|
||||
"description": "Whether you want to try your hand at XC racing or are looking " +
|
||||
"for a lively trail bike that's just as inspiring on the climbs as it is over rougher " +
|
||||
"ground, the Wilder is one heck of a bike built specifically for short women. Both the " +
|
||||
"frames and components have been tweaked to include a women’s saddle, different bars " +
|
||||
"and unique colourway.",
|
||||
"condition": "used",
|
||||
},
|
||||
{
|
||||
"pickup_zone": "POLYGON((-0.1778 51.5524, 0.0822 51.5524, 0.0822 51.4024, " +
|
||||
"-0.1778 51.4024, -0.1778 51.5524))",
|
||||
"store_location": "-0.1278,51.5074",
|
||||
"brand": "Breakout",
|
||||
"model": "XBN 2.1 Alloy",
|
||||
"price": 810,
|
||||
"description": "The XBN 2.1 Alloy is our entry-level road bike – but that’s " +
|
||||
"not to say that it’s a basic machine. With an internal weld aluminium frame, a full " +
|
||||
"carbon fork, and the slick-shifting Claris gears from Shimano’s, this is a bike which " +
|
||||
"doesn’t break the bank and delivers craved performance.",
|
||||
"condition": "new",
|
||||
},
|
||||
{
|
||||
"pickup_zone": "POLYGON((2.1767 48.9016, 2.5267 48.9016, 2.5267 48.5516, " +
|
||||
"2.1767 48.5516, 2.1767 48.9016))",
|
||||
"store_location": "2.3522,48.8566",
|
||||
"brand": "ScramBikes",
|
||||
"model": "WattBike",
|
||||
"price": 2300,
|
||||
"description": "The WattBike is the best e-bike for people who still " +
|
||||
"feel young at heart. It has a Bafang 1000W mid-drive system and a 48V 17.5AH " +
|
||||
"Samsung Lithium-Ion battery, allowing you to ride for more than 60 miles on one " +
|
||||
"charge. It’s great for tackling hilly terrain or if you just fancy a more " +
|
||||
"leisurely ride. With three working modes, you can choose between E-bike, " +
|
||||
"assisted bicycle, and normal bike modes.",
|
||||
"condition": "new",
|
||||
},
|
||||
{
|
||||
"pickup_zone": "POLYGON((13.3260 52.5700, 13.6550 52.5700, 13.6550 52.2700, " +
|
||||
"13.3260 52.2700, 13.3260 52.5700))",
|
||||
"store_location": "13.4050,52.5200",
|
||||
"brand": "Peaknetic",
|
||||
"model": "Secto",
|
||||
"price": 430,
|
||||
"description": "If you struggle with stiff fingers or a kinked neck or " +
|
||||
"back after a few minutes on the road, this lightweight, aluminum bike alleviates " +
|
||||
"those issues and allows you to enjoy the ride. From the ergonomic grips to the " +
|
||||
"lumbar-supporting seat position, the Roll Low-Entry offers incredible comfort. " +
|
||||
"The rear-inclined seat tube facilitates stability by allowing you to put a foot " +
|
||||
"on the ground to balance at a stop, and the low step-over frame makes it " +
|
||||
"accessible for all ability and mobility levels. The saddle is very soft, with " +
|
||||
"a wide back to support your hip joints and a cutout in the center to redistribute " +
|
||||
"that pressure. Rim brakes deliver satisfactory braking control, and the wide tires " +
|
||||
"provide a smooth, stable ride on paved roads and gravel. Rack and fender mounts " +
|
||||
"facilitate setting up the Roll Low-Entry as your preferred commuter, and the " +
|
||||
"BMX-like handlebar offers space for mounting a flashlight, bell, or phone holder.",
|
||||
"condition": "new",
|
||||
},
|
||||
{
|
||||
"pickup_zone": "POLYGON((1.9450 41.4301, 2.4018 41.4301, 2.4018 41.1987, " +
|
||||
"1.9450 41.1987, 1.9450 41.4301))",
|
||||
"store_location": "2.1734, 41.3851",
|
||||
"brand": "nHill",
|
||||
"model": "Summit",
|
||||
"price": 1200,
|
||||
"description": "This budget mountain bike from nHill performs well both " +
|
||||
"on bike paths and on the trail. The fork with 100mm of travel absorbs rough " +
|
||||
"terrain. Fat Kenda Booster tires give you grip in corners and on wet trails. " +
|
||||
"The Shimano Tourney drivetrain offered enough gears for finding a comfortable " +
|
||||
"pace to ride uphill, and the Tektro hydraulic disc brakes break smoothly. " +
|
||||
"Whether you want an affordable bike that you can take to work, but also take " +
|
||||
"trail in mountains on the weekends or you’re just after a stable, comfortable " +
|
||||
"ride for the bike path, the Summit gives a good value for money.",
|
||||
"condition": "new",
|
||||
},
|
||||
{
|
||||
"pickup_zone": "POLYGON((12.4464 42.1028, 12.5464 42.1028, " +
|
||||
"12.5464 41.7028, 12.4464 41.7028, 12.4464 42.1028))",
|
||||
"store_location": "12.4964,41.9028",
|
||||
"model": "ThrillCycle",
|
||||
"brand": "BikeShind",
|
||||
"price": 815,
|
||||
"description": "An artsy, retro-inspired bicycle that’s as " +
|
||||
"functional as it is pretty: The ThrillCycle steel frame offers a smooth ride. " +
|
||||
"A 9-speed drivetrain has enough gears for coasting in the city, but we wouldn’t " +
|
||||
"suggest taking it to the mountains. Fenders protect you from mud, and a rear " +
|
||||
"basket lets you transport groceries, flowers and books. The ThrillCycle comes " +
|
||||
"with a limited lifetime warranty, so this little guy will last you long " +
|
||||
"past graduation.",
|
||||
"condition": "refurbished",
|
||||
},
|
||||
}
|
||||
|
||||
for i, json := range exampleJsons {
|
||||
_, err := rdb.JSONSet(ctx, fmt.Sprintf("bicycle:%v", i), "$", json).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// STEP_START geo1
|
||||
res1, err := rdb.FTSearchWithArgs(ctx,
|
||||
"idx:bicycle", "@store_location:[$lon $lat $radius $units]",
|
||||
&redis.FTSearchOptions{
|
||||
Params: map[string]interface{}{
|
||||
"lon": -0.1778,
|
||||
"lat": 51.5524,
|
||||
"radius": 20,
|
||||
"units": "mi",
|
||||
},
|
||||
DialectVersion: 2,
|
||||
},
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res1.Total) // >>> 1
|
||||
|
||||
for _, doc := range res1.Docs {
|
||||
fmt.Println(doc.ID)
|
||||
}
|
||||
// >>> bicycle:5
|
||||
// STEP_END
|
||||
|
||||
// STEP_START geo2
|
||||
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()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res2.Total) // >>> 1
|
||||
|
||||
for _, doc := range res2.Docs {
|
||||
fmt.Println(doc.ID)
|
||||
}
|
||||
// >>> bicycle:5
|
||||
// STEP_END
|
||||
|
||||
// STEP_START geo3
|
||||
res3, err := rdb.FTSearchWithArgs(ctx,
|
||||
"idx:bicycle",
|
||||
"@pickup_zone:[WITHIN $europe]",
|
||||
&redis.FTSearchOptions{
|
||||
Params: map[string]interface{}{
|
||||
"europe": "POLYGON((-25 35, 40 35, 40 70, -25 70, -25 35))",
|
||||
},
|
||||
DialectVersion: 3,
|
||||
},
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res3.Total) // >>> 5
|
||||
|
||||
sort.Slice(res3.Docs, func(i, j int) bool {
|
||||
return res3.Docs[i].ID < res3.Docs[j].ID
|
||||
})
|
||||
|
||||
for _, doc := range res3.Docs {
|
||||
fmt.Println(doc.ID)
|
||||
}
|
||||
// >>> bicycle:5
|
||||
// >>> bicycle:6
|
||||
// >>> bicycle:7
|
||||
// >>> bicycle:8
|
||||
// >>> bicycle:9
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// 1
|
||||
// bicycle:5
|
||||
// 1
|
||||
// bicycle:5
|
||||
// 5
|
||||
// bicycle:5
|
||||
// bicycle:6
|
||||
// bicycle:7
|
||||
// bicycle:8
|
||||
// bicycle:9
|
||||
}
|
394
doctests/query_range_test.go
Normal file
394
doctests/query_range_test.go
Normal file
@ -0,0 +1,394 @@
|
||||
// EXAMPLE: query_range
|
||||
// HIDE_START
|
||||
package example_commands_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
func ExampleClient_query_range() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
Protocol: 2,
|
||||
})
|
||||
|
||||
// HIDE_END
|
||||
// REMOVE_START
|
||||
rdb.FTDropIndex(ctx, "idx:bicycle")
|
||||
rdb.FTDropIndex(ctx, "idx:email")
|
||||
// REMOVE_END
|
||||
|
||||
_, err := rdb.FTCreate(ctx, "idx:bicycle",
|
||||
&redis.FTCreateOptions{
|
||||
OnJSON: true,
|
||||
Prefix: []interface{}{"bicycle:"},
|
||||
},
|
||||
&redis.FieldSchema{
|
||||
FieldName: "$.brand",
|
||||
As: "brand",
|
||||
FieldType: redis.SearchFieldTypeText,
|
||||
},
|
||||
&redis.FieldSchema{
|
||||
FieldName: "$.model",
|
||||
As: "model",
|
||||
FieldType: redis.SearchFieldTypeText,
|
||||
},
|
||||
&redis.FieldSchema{
|
||||
FieldName: "$.description",
|
||||
As: "description",
|
||||
FieldType: redis.SearchFieldTypeText,
|
||||
},
|
||||
&redis.FieldSchema{
|
||||
FieldName: "$.price",
|
||||
As: "price",
|
||||
FieldType: redis.SearchFieldTypeNumeric,
|
||||
},
|
||||
&redis.FieldSchema{
|
||||
FieldName: "$.condition",
|
||||
As: "condition",
|
||||
FieldType: redis.SearchFieldTypeTag,
|
||||
},
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
exampleJsons := []map[string]interface{}{
|
||||
{
|
||||
"pickup_zone": "POLYGON((-74.0610 40.7578, -73.9510 40.7578, -73.9510 40.6678, " +
|
||||
"-74.0610 40.6678, -74.0610 40.7578))",
|
||||
"store_location": "-74.0060,40.7128",
|
||||
"brand": "Velorim",
|
||||
"model": "Jigger",
|
||||
"price": 270,
|
||||
"description": "Small and powerful, the Jigger is the best ride for the smallest of tikes! " +
|
||||
"This is the tiniest kids pedal bike on the market available without a coaster brake, the Jigger " +
|
||||
"is the vehicle of choice for the rare tenacious little rider raring to go.",
|
||||
"condition": "new",
|
||||
},
|
||||
{
|
||||
"pickup_zone": "POLYGON((-118.2887 34.0972, -118.1987 34.0972, -118.1987 33.9872, " +
|
||||
"-118.2887 33.9872, -118.2887 34.0972))",
|
||||
"store_location": "-118.2437,34.0522",
|
||||
"brand": "Bicyk",
|
||||
"model": "Hillcraft",
|
||||
"price": 1200,
|
||||
"description": "Kids want to ride with as little weight as possible. Especially " +
|
||||
"on an incline! They may be at the age when a 27.5'' wheel bike is just too clumsy coming " +
|
||||
"off a 24'' bike. The Hillcraft 26 is just the solution they need!",
|
||||
"condition": "used",
|
||||
},
|
||||
{
|
||||
"pickup_zone": "POLYGON((-87.6848 41.9331, -87.5748 41.9331, -87.5748 41.8231, " +
|
||||
"-87.6848 41.8231, -87.6848 41.9331))",
|
||||
"store_location": "-87.6298,41.8781",
|
||||
"brand": "Nord",
|
||||
"model": "Chook air 5",
|
||||
"price": 815,
|
||||
"description": "The Chook Air 5 gives kids aged six years and older a durable " +
|
||||
"and uberlight mountain bike for their first experience on tracks and easy cruising through " +
|
||||
"forests and fields. The lower top tube makes it easy to mount and dismount in any " +
|
||||
"situation, giving your kids greater safety on the trails.",
|
||||
"condition": "used",
|
||||
},
|
||||
{
|
||||
"pickup_zone": "POLYGON((-80.2433 25.8067, -80.1333 25.8067, -80.1333 25.6967, " +
|
||||
"-80.2433 25.6967, -80.2433 25.8067))",
|
||||
"store_location": "-80.1918,25.7617",
|
||||
"brand": "Eva",
|
||||
"model": "Eva 291",
|
||||
"price": 3400,
|
||||
"description": "The sister company to Nord, Eva launched in 2005 as the first " +
|
||||
"and only women-dedicated bicycle brand. Designed by women for women, allEva bikes " +
|
||||
"are optimized for the feminine physique using analytics from a body metrics database. " +
|
||||
"If you like 29ers, try the Eva 291. It’s a brand new bike for 2022.. This " +
|
||||
"full-suspension, cross-country ride has been designed for velocity. The 291 has " +
|
||||
"100mm of front and rear travel, a superlight aluminum frame and fast-rolling " +
|
||||
"29-inch wheels. Yippee!",
|
||||
"condition": "used",
|
||||
},
|
||||
{
|
||||
"pickup_zone": "POLYGON((-122.4644 37.8199, -122.3544 37.8199, -122.3544 37.7099, " +
|
||||
"-122.4644 37.7099, -122.4644 37.8199))",
|
||||
"store_location": "-122.4194,37.7749",
|
||||
"brand": "Noka Bikes",
|
||||
"model": "Kahuna",
|
||||
"price": 3200,
|
||||
"description": "Whether you want to try your hand at XC racing or are looking " +
|
||||
"for a lively trail bike that's just as inspiring on the climbs as it is over rougher " +
|
||||
"ground, the Wilder is one heck of a bike built specifically for short women. Both the " +
|
||||
"frames and components have been tweaked to include a women’s saddle, different bars " +
|
||||
"and unique colourway.",
|
||||
"condition": "used",
|
||||
},
|
||||
{
|
||||
"pickup_zone": "POLYGON((-0.1778 51.5524, 0.0822 51.5524, 0.0822 51.4024, " +
|
||||
"-0.1778 51.4024, -0.1778 51.5524))",
|
||||
"store_location": "-0.1278,51.5074",
|
||||
"brand": "Breakout",
|
||||
"model": "XBN 2.1 Alloy",
|
||||
"price": 810,
|
||||
"description": "The XBN 2.1 Alloy is our entry-level road bike – but that’s " +
|
||||
"not to say that it’s a basic machine. With an internal weld aluminium frame, a full " +
|
||||
"carbon fork, and the slick-shifting Claris gears from Shimano’s, this is a bike which " +
|
||||
"doesn’t break the bank and delivers craved performance.",
|
||||
"condition": "new",
|
||||
},
|
||||
{
|
||||
"pickup_zone": "POLYGON((2.1767 48.9016, 2.5267 48.9016, 2.5267 48.5516, " +
|
||||
"2.1767 48.5516, 2.1767 48.9016))",
|
||||
"store_location": "2.3522,48.8566",
|
||||
"brand": "ScramBikes",
|
||||
"model": "WattBike",
|
||||
"price": 2300,
|
||||
"description": "The WattBike is the best e-bike for people who still " +
|
||||
"feel young at heart. It has a Bafang 1000W mid-drive system and a 48V 17.5AH " +
|
||||
"Samsung Lithium-Ion battery, allowing you to ride for more than 60 miles on one " +
|
||||
"charge. It’s great for tackling hilly terrain or if you just fancy a more " +
|
||||
"leisurely ride. With three working modes, you can choose between E-bike, " +
|
||||
"assisted bicycle, and normal bike modes.",
|
||||
"condition": "new",
|
||||
},
|
||||
{
|
||||
"pickup_zone": "POLYGON((13.3260 52.5700, 13.6550 52.5700, 13.6550 52.2700, " +
|
||||
"13.3260 52.2700, 13.3260 52.5700))",
|
||||
"store_location": "13.4050,52.5200",
|
||||
"brand": "Peaknetic",
|
||||
"model": "Secto",
|
||||
"price": 430,
|
||||
"description": "If you struggle with stiff fingers or a kinked neck or " +
|
||||
"back after a few minutes on the road, this lightweight, aluminum bike alleviates " +
|
||||
"those issues and allows you to enjoy the ride. From the ergonomic grips to the " +
|
||||
"lumbar-supporting seat position, the Roll Low-Entry offers incredible comfort. " +
|
||||
"The rear-inclined seat tube facilitates stability by allowing you to put a foot " +
|
||||
"on the ground to balance at a stop, and the low step-over frame makes it " +
|
||||
"accessible for all ability and mobility levels. The saddle is very soft, with " +
|
||||
"a wide back to support your hip joints and a cutout in the center to redistribute " +
|
||||
"that pressure. Rim brakes deliver satisfactory braking control, and the wide tires " +
|
||||
"provide a smooth, stable ride on paved roads and gravel. Rack and fender mounts " +
|
||||
"facilitate setting up the Roll Low-Entry as your preferred commuter, and the " +
|
||||
"BMX-like handlebar offers space for mounting a flashlight, bell, or phone holder.",
|
||||
"condition": "new",
|
||||
},
|
||||
{
|
||||
"pickup_zone": "POLYGON((1.9450 41.4301, 2.4018 41.4301, 2.4018 41.1987, " +
|
||||
"1.9450 41.1987, 1.9450 41.4301))",
|
||||
"store_location": "2.1734, 41.3851",
|
||||
"brand": "nHill",
|
||||
"model": "Summit",
|
||||
"price": 1200,
|
||||
"description": "This budget mountain bike from nHill performs well both " +
|
||||
"on bike paths and on the trail. The fork with 100mm of travel absorbs rough " +
|
||||
"terrain. Fat Kenda Booster tires give you grip in corners and on wet trails. " +
|
||||
"The Shimano Tourney drivetrain offered enough gears for finding a comfortable " +
|
||||
"pace to ride uphill, and the Tektro hydraulic disc brakes break smoothly. " +
|
||||
"Whether you want an affordable bike that you can take to work, but also take " +
|
||||
"trail in mountains on the weekends or you’re just after a stable, comfortable " +
|
||||
"ride for the bike path, the Summit gives a good value for money.",
|
||||
"condition": "new",
|
||||
},
|
||||
{
|
||||
"pickup_zone": "POLYGON((12.4464 42.1028, 12.5464 42.1028, " +
|
||||
"12.5464 41.7028, 12.4464 41.7028, 12.4464 42.1028))",
|
||||
"store_location": "12.4964,41.9028",
|
||||
"model": "ThrillCycle",
|
||||
"brand": "BikeShind",
|
||||
"price": 815,
|
||||
"description": "An artsy, retro-inspired bicycle that’s as " +
|
||||
"functional as it is pretty: The ThrillCycle steel frame offers a smooth ride. " +
|
||||
"A 9-speed drivetrain has enough gears for coasting in the city, but we wouldn’t " +
|
||||
"suggest taking it to the mountains. Fenders protect you from mud, and a rear " +
|
||||
"basket lets you transport groceries, flowers and books. The ThrillCycle comes " +
|
||||
"with a limited lifetime warranty, so this little guy will last you long " +
|
||||
"past graduation.",
|
||||
"condition": "refurbished",
|
||||
},
|
||||
}
|
||||
|
||||
for i, json := range exampleJsons {
|
||||
_, err := rdb.JSONSet(ctx, fmt.Sprintf("bicycle:%v", i), "$", json).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// STEP_START range1
|
||||
res1, err := rdb.FTSearchWithArgs(ctx,
|
||||
"idx:bicycle", "@price:[500 1000]",
|
||||
&redis.FTSearchOptions{
|
||||
Return: []redis.FTSearchReturn{
|
||||
{
|
||||
FieldName: "price",
|
||||
},
|
||||
},
|
||||
SortBy: []redis.FTSearchSortBy{
|
||||
{
|
||||
FieldName: "price",
|
||||
Asc: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res1.Total) // >>> 3
|
||||
|
||||
for _, doc := range res1.Docs {
|
||||
fmt.Printf("%v : price %v\n", doc.ID, doc.Fields["price"])
|
||||
}
|
||||
// >>> bicycle:2 : price 815
|
||||
// >>> bicycle:5 : price 810
|
||||
// >>> bicycle:9 : price 815
|
||||
// STEP_END
|
||||
|
||||
// STEP_START range2
|
||||
res2, err := rdb.FTSearchWithArgs(ctx,
|
||||
"idx:bicycle", "*",
|
||||
&redis.FTSearchOptions{
|
||||
Filters: []redis.FTSearchFilter{
|
||||
{
|
||||
FieldName: "price",
|
||||
Min: 500,
|
||||
Max: 1000,
|
||||
},
|
||||
},
|
||||
Return: []redis.FTSearchReturn{
|
||||
{
|
||||
FieldName: "price",
|
||||
},
|
||||
},
|
||||
SortBy: []redis.FTSearchSortBy{
|
||||
{
|
||||
FieldName: "price",
|
||||
Asc: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res2.Total) // >>> 3
|
||||
|
||||
for _, doc := range res2.Docs {
|
||||
fmt.Printf("%v : price %v\n", doc.ID, doc.Fields["price"])
|
||||
}
|
||||
// >>> bicycle:2 : price 815
|
||||
// >>> bicycle:5 : price 810
|
||||
// >>> bicycle:9 : price 815
|
||||
// STEP_END
|
||||
|
||||
// STEP_START range3
|
||||
res3, err := rdb.FTSearchWithArgs(ctx,
|
||||
"idx:bicycle", "*",
|
||||
&redis.FTSearchOptions{
|
||||
Return: []redis.FTSearchReturn{
|
||||
{
|
||||
FieldName: "price",
|
||||
},
|
||||
},
|
||||
SortBy: []redis.FTSearchSortBy{
|
||||
{
|
||||
FieldName: "price",
|
||||
Asc: true,
|
||||
},
|
||||
},
|
||||
Filters: []redis.FTSearchFilter{
|
||||
{
|
||||
FieldName: "price",
|
||||
Min: "(1000",
|
||||
Max: "+inf",
|
||||
},
|
||||
},
|
||||
},
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res3.Total) // >>> 5
|
||||
|
||||
for _, doc := range res3.Docs {
|
||||
fmt.Printf("%v : price %v\n", doc.ID, doc.Fields["price"])
|
||||
}
|
||||
// >>> bicycle:1 : price 1200
|
||||
// >>> bicycle:4 : price 3200
|
||||
// >>> bicycle:6 : price 2300
|
||||
// >>> bicycle:3 : price 3400
|
||||
// >>> bicycle:8 : price 1200
|
||||
// STEP_END
|
||||
|
||||
// STEP_START range4
|
||||
res4, err := rdb.FTSearchWithArgs(ctx,
|
||||
"idx:bicycle",
|
||||
"@price:[-inf 2000]",
|
||||
&redis.FTSearchOptions{
|
||||
Return: []redis.FTSearchReturn{
|
||||
{
|
||||
FieldName: "price",
|
||||
},
|
||||
},
|
||||
SortBy: []redis.FTSearchSortBy{
|
||||
{
|
||||
FieldName: "price",
|
||||
Asc: true,
|
||||
},
|
||||
},
|
||||
LimitOffset: 0,
|
||||
Limit: 5,
|
||||
},
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res4.Total) // >>> 7
|
||||
|
||||
for _, doc := range res4.Docs {
|
||||
fmt.Printf("%v : price %v\n", doc.ID, doc.Fields["price"])
|
||||
}
|
||||
// >>> bicycle:0 : price 270
|
||||
// >>> bicycle:7 : price 430
|
||||
// >>> bicycle:5 : price 810
|
||||
// >>> bicycle:2 : price 815
|
||||
// >>> bicycle:9 : price 815
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// 3
|
||||
// bicycle:5 : price 810
|
||||
// bicycle:2 : price 815
|
||||
// bicycle:9 : price 815
|
||||
// 3
|
||||
// bicycle:5 : price 810
|
||||
// bicycle:2 : price 815
|
||||
// bicycle:9 : price 815
|
||||
// 5
|
||||
// bicycle:1 : price 1200
|
||||
// bicycle:8 : price 1200
|
||||
// bicycle:6 : price 2300
|
||||
// bicycle:4 : price 3200
|
||||
// bicycle:3 : price 3400
|
||||
// 7
|
||||
// bicycle:0 : price 270
|
||||
// bicycle:7 : price 430
|
||||
// bicycle:5 : price 810
|
||||
// bicycle:2 : price 815
|
||||
// bicycle:9 : price 815
|
||||
}
|
@ -21,6 +21,8 @@ func ExampleClient_Set_and_get() {
|
||||
// HIDE_END
|
||||
|
||||
// REMOVE_START
|
||||
// start with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
errFlush := rdb.FlushDB(ctx).Err() // Clear the database before each test
|
||||
if errFlush != nil {
|
||||
panic(errFlush)
|
||||
|
473
doctests/sets_example_test.go
Normal file
473
doctests/sets_example_test.go
Normal file
@ -0,0 +1,473 @@
|
||||
// EXAMPLE: sets_tutorial
|
||||
// HIDE_START
|
||||
package example_commands_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
// HIDE_END
|
||||
func ExampleClient_sadd() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// start with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "bikes:racing:france")
|
||||
rdb.Del(ctx, "bikes:racing:usa")
|
||||
// REMOVE_END
|
||||
|
||||
// STEP_START sadd
|
||||
res1, err := rdb.SAdd(ctx, "bikes:racing:france", "bike:1").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res1) // >>> 1
|
||||
|
||||
res2, err := rdb.SAdd(ctx, "bikes:racing:france", "bike:1").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res2) // >>> 0
|
||||
|
||||
res3, err := rdb.SAdd(ctx, "bikes:racing:france", "bike:2", "bike:3").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res3) // >>> 2
|
||||
|
||||
res4, err := rdb.SAdd(ctx, "bikes:racing:usa", "bike:1", "bike:4").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res4) // >>> 2
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// 1
|
||||
// 0
|
||||
// 2
|
||||
// 2
|
||||
}
|
||||
|
||||
func ExampleClient_sismember() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// start with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "bikes:racing:france")
|
||||
rdb.Del(ctx, "bikes:racing:usa")
|
||||
// REMOVE_END
|
||||
|
||||
_, err := rdb.SAdd(ctx, "bikes:racing:france", "bike:1", "bike:2", "bike:3").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
_, err = rdb.SAdd(ctx, "bikes:racing:usa", "bike:1", "bike:4").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// STEP_START sismember
|
||||
res5, err := rdb.SIsMember(ctx, "bikes:racing:usa", "bike:1").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res5) // >>> true
|
||||
|
||||
res6, err := rdb.SIsMember(ctx, "bikes:racing:usa", "bike:2").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res6) // >>> false
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// false
|
||||
}
|
||||
|
||||
func ExampleClient_sinter() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// start with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "bikes:racing:france")
|
||||
rdb.Del(ctx, "bikes:racing:usa")
|
||||
// REMOVE_END
|
||||
|
||||
_, err := rdb.SAdd(ctx, "bikes:racing:france", "bike:1", "bike:2", "bike:3").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
_, err = rdb.SAdd(ctx, "bikes:racing:usa", "bike:1", "bike:4").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// STEP_START sinter
|
||||
res7, err := rdb.SInter(ctx, "bikes:racing:france", "bikes:racing:usa").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res7) // >>> [bike:1]
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// [bike:1]
|
||||
}
|
||||
|
||||
func ExampleClient_scard() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// start with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "bikes:racing:france")
|
||||
// REMOVE_END
|
||||
|
||||
_, err := rdb.SAdd(ctx, "bikes:racing:france", "bike:1", "bike:2", "bike:3").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// STEP_START scard
|
||||
res8, err := rdb.SCard(ctx, "bikes:racing:france").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res8) // >>> 3
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// 3
|
||||
}
|
||||
|
||||
func ExampleClient_saddsmembers() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// start with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "bikes:racing:france")
|
||||
// REMOVE_END
|
||||
|
||||
// STEP_START sadd_smembers
|
||||
res9, err := rdb.SAdd(ctx, "bikes:racing:france", "bike:1", "bike:2", "bike:3").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res9) // >>> 3
|
||||
|
||||
res10, err := rdb.SMembers(ctx, "bikes:racing:france").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Sort the strings in the slice to make sure the output is lexicographical
|
||||
sort.Strings(res10)
|
||||
|
||||
fmt.Println(res10) // >>> [bike:1 bike:2 bike:3]
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// 3
|
||||
// [bike:1 bike:2 bike:3]
|
||||
}
|
||||
|
||||
func ExampleClient_smismember() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// start with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "bikes:racing:france")
|
||||
// REMOVE_END
|
||||
|
||||
_, err := rdb.SAdd(ctx, "bikes:racing:france", "bike:1", "bike:2", "bike:3").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// STEP_START smismember
|
||||
res11, err := rdb.SIsMember(ctx, "bikes:racing:france", "bike:1").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res11) // >>> true
|
||||
|
||||
res12, err := rdb.SMIsMember(ctx, "bikes:racing:france", "bike:2", "bike:3", "bike:4").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res12) // >>> [true true false]
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// true
|
||||
// [true true false]
|
||||
}
|
||||
|
||||
func ExampleClient_sdiff() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// start with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "bikes:racing:france")
|
||||
rdb.Del(ctx, "bikes:racing:usa")
|
||||
// REMOVE_END
|
||||
|
||||
// STEP_START sdiff
|
||||
_, err := rdb.SAdd(ctx, "bikes:racing:france", "bike:1", "bike:2", "bike:3").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
_, err = rdb.SAdd(ctx, "bikes:racing:usa", "bike:1", "bike:4").Result()
|
||||
|
||||
res13, err := rdb.SDiff(ctx, "bikes:racing:france", "bikes:racing:usa").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Sort the strings in the slice to make sure the output is lexicographical
|
||||
sort.Strings(res13)
|
||||
|
||||
fmt.Println(res13) // >>> [bike:2 bike:3]
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// [bike:2 bike:3]
|
||||
}
|
||||
|
||||
func ExampleClient_multisets() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// start with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "bikes:racing:france")
|
||||
rdb.Del(ctx, "bikes:racing:usa")
|
||||
rdb.Del(ctx, "bikes:racing:italy")
|
||||
// REMOVE_END
|
||||
|
||||
// STEP_START multisets
|
||||
_, err := rdb.SAdd(ctx, "bikes:racing:france", "bike:1", "bike:2", "bike:3").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
_, err = rdb.SAdd(ctx, "bikes:racing:usa", "bike:1", "bike:4").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
_, err = rdb.SAdd(ctx, "bikes:racing:italy", "bike:1", "bike:2", "bike:3", "bike:4").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
res14, err := rdb.SInter(ctx, "bikes:racing:france", "bikes:racing:usa", "bikes:racing:italy").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res14) // >>> [bike:1]
|
||||
|
||||
res15, err := rdb.SUnion(ctx, "bikes:racing:france", "bikes:racing:usa", "bikes:racing:italy").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Sort the strings in the slice to make sure the output is lexicographical
|
||||
sort.Strings(res15)
|
||||
|
||||
fmt.Println(res15) // >>> [bike:1 bike:2 bike:3 bike:4]
|
||||
|
||||
res16, err := rdb.SDiff(ctx, "bikes:racing:france", "bikes:racing:usa", "bikes:racing:italy").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res16) // >>> []
|
||||
|
||||
res17, err := rdb.SDiff(ctx, "bikes:racing:usa", "bikes:racing:france").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res17) // >>> [bike:4]
|
||||
|
||||
res18, err := rdb.SDiff(ctx, "bikes:racing:france", "bikes:racing:usa").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Sort the strings in the slice to make sure the output is lexicographical
|
||||
sort.Strings(res18)
|
||||
|
||||
fmt.Println(res18) // >>> [bike:2 bike:3]
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// [bike:1]
|
||||
// [bike:1 bike:2 bike:3 bike:4]
|
||||
// []
|
||||
// [bike:4]
|
||||
// [bike:2 bike:3]
|
||||
}
|
||||
|
||||
func ExampleClient_srem() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// start with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "bikes:racing:france")
|
||||
// REMOVE_END
|
||||
|
||||
// STEP_START srem
|
||||
_, err := rdb.SAdd(ctx, "bikes:racing:france", "bike:1", "bike:2", "bike:3", "bike:4", "bike:5").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
res19, err := rdb.SRem(ctx, "bikes:racing:france", "bike:1").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res19) // >>> 1
|
||||
|
||||
res20, err := rdb.SPop(ctx, "bikes:racing:france").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res20) // >>> <random element>
|
||||
|
||||
res21, err := rdb.SMembers(ctx, "bikes:racing:france").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res21) // >>> <remaining elements>
|
||||
|
||||
res22, err := rdb.SRandMember(ctx, "bikes:racing:france").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res22) // >>> <random element>
|
||||
// STEP_END
|
||||
|
||||
// Testable examples not available because the test output
|
||||
// is not deterministic.
|
||||
}
|
453
doctests/ss_tutorial_test.go
Normal file
453
doctests/ss_tutorial_test.go
Normal file
@ -0,0 +1,453 @@
|
||||
// EXAMPLE: ss_tutorial
|
||||
// HIDE_START
|
||||
package example_commands_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
// HIDE_END
|
||||
func ExampleClient_zadd() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// start with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "racer_scores")
|
||||
// REMOVE_END
|
||||
|
||||
// STEP_START zadd
|
||||
res1, err := rdb.ZAdd(ctx, "racer_scores",
|
||||
redis.Z{Member: "Norem", Score: 10},
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res1) // >>> 1
|
||||
|
||||
res2, err := rdb.ZAdd(ctx, "racer_scores",
|
||||
redis.Z{Member: "Castilla", Score: 12},
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res2) // >>> 1
|
||||
|
||||
res3, err := rdb.ZAdd(ctx, "racer_scores",
|
||||
redis.Z{Member: "Norem", Score: 10},
|
||||
redis.Z{Member: "Sam-Bodden", Score: 8},
|
||||
redis.Z{Member: "Royce", Score: 10},
|
||||
redis.Z{Member: "Ford", Score: 6},
|
||||
redis.Z{Member: "Prickett", Score: 14},
|
||||
redis.Z{Member: "Castilla", Score: 12},
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res3) // >>> 4
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// 1
|
||||
// 1
|
||||
// 4
|
||||
}
|
||||
|
||||
func ExampleClient_zrange() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// start with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "racer_scores")
|
||||
// REMOVE_END
|
||||
|
||||
_, err := rdb.ZAdd(ctx, "racer_scores",
|
||||
redis.Z{Member: "Norem", Score: 10},
|
||||
redis.Z{Member: "Sam-Bodden", Score: 8},
|
||||
redis.Z{Member: "Royce", Score: 10},
|
||||
redis.Z{Member: "Ford", Score: 6},
|
||||
redis.Z{Member: "Prickett", Score: 14},
|
||||
redis.Z{Member: "Castilla", Score: 12},
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// STEP_START zrange
|
||||
res4, err := rdb.ZRange(ctx, "racer_scores", 0, -1).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res4)
|
||||
// >>> [Ford Sam-Bodden Norem Royce Castilla Prickett]
|
||||
|
||||
res5, err := rdb.ZRevRange(ctx, "racer_scores", 0, -1).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res5)
|
||||
// >>> [Prickett Castilla Royce Norem Sam-Bodden Ford]
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// [Ford Sam-Bodden Norem Royce Castilla Prickett]
|
||||
// [Prickett Castilla Royce Norem Sam-Bodden Ford]
|
||||
}
|
||||
|
||||
func ExampleClient_zrangewithscores() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// start with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "racer_scores")
|
||||
// REMOVE_END
|
||||
|
||||
_, err := rdb.ZAdd(ctx, "racer_scores",
|
||||
redis.Z{Member: "Norem", Score: 10},
|
||||
redis.Z{Member: "Sam-Bodden", Score: 8},
|
||||
redis.Z{Member: "Royce", Score: 10},
|
||||
redis.Z{Member: "Ford", Score: 6},
|
||||
redis.Z{Member: "Prickett", Score: 14},
|
||||
redis.Z{Member: "Castilla", Score: 12},
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// STEP_START zrange_withscores
|
||||
res6, err := rdb.ZRangeWithScores(ctx, "racer_scores", 0, -1).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res6)
|
||||
// >>> [{6 Ford} {8 Sam-Bodden} {10 Norem} {10 Royce} {12 Castilla} {14 Prickett}]
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// [{6 Ford} {8 Sam-Bodden} {10 Norem} {10 Royce} {12 Castilla} {14 Prickett}]
|
||||
}
|
||||
|
||||
func ExampleClient_zrangebyscore() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// start with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "racer_scores")
|
||||
// REMOVE_END
|
||||
|
||||
_, err := rdb.ZAdd(ctx, "racer_scores",
|
||||
redis.Z{Member: "Norem", Score: 10},
|
||||
redis.Z{Member: "Sam-Bodden", Score: 8},
|
||||
redis.Z{Member: "Royce", Score: 10},
|
||||
redis.Z{Member: "Ford", Score: 6},
|
||||
redis.Z{Member: "Prickett", Score: 14},
|
||||
redis.Z{Member: "Castilla", Score: 12},
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// STEP_START zrangebyscore
|
||||
res7, err := rdb.ZRangeByScore(ctx, "racer_scores",
|
||||
&redis.ZRangeBy{Min: "-inf", Max: "10"},
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res7)
|
||||
// >>> [Ford Sam-Bodden Norem Royce]
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// [Ford Sam-Bodden Norem Royce]
|
||||
}
|
||||
|
||||
func ExampleClient_zremrangebyscore() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// start with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "racer_scores")
|
||||
// REMOVE_END
|
||||
|
||||
_, err := rdb.ZAdd(ctx, "racer_scores",
|
||||
redis.Z{Member: "Norem", Score: 10},
|
||||
redis.Z{Member: "Sam-Bodden", Score: 8},
|
||||
redis.Z{Member: "Royce", Score: 10},
|
||||
redis.Z{Member: "Ford", Score: 6},
|
||||
redis.Z{Member: "Prickett", Score: 14},
|
||||
redis.Z{Member: "Castilla", Score: 12},
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// STEP_START zremrangebyscore
|
||||
res8, err := rdb.ZRem(ctx, "racer_scores", "Castilla").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res8) // >>> 1
|
||||
|
||||
res9, err := rdb.ZRemRangeByScore(ctx, "racer_scores", "-inf", "9").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res9) // >>> 2
|
||||
|
||||
res10, err := rdb.ZRange(ctx, "racer_scores", 0, -1).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res10)
|
||||
// >>> [Norem Royce Prickett]
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// 1
|
||||
// 2
|
||||
// [Norem Royce Prickett]
|
||||
}
|
||||
|
||||
func ExampleClient_zrank() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// start with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "racer_scores")
|
||||
// REMOVE_END
|
||||
|
||||
_, err := rdb.ZAdd(ctx, "racer_scores",
|
||||
redis.Z{Member: "Norem", Score: 10},
|
||||
redis.Z{Member: "Royce", Score: 10},
|
||||
redis.Z{Member: "Prickett", Score: 14},
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// STEP_START zrank
|
||||
res11, err := rdb.ZRank(ctx, "racer_scores", "Norem").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res11) // >>> 0
|
||||
|
||||
res12, err := rdb.ZRevRank(ctx, "racer_scores", "Norem").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res12) // >>> 2
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// 0
|
||||
// 2
|
||||
}
|
||||
|
||||
func ExampleClient_zaddlex() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// start with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "racer_scores")
|
||||
// REMOVE_END
|
||||
|
||||
_, err := rdb.ZAdd(ctx, "racer_scores",
|
||||
redis.Z{Member: "Norem", Score: 0},
|
||||
redis.Z{Member: "Royce", Score: 0},
|
||||
redis.Z{Member: "Prickett", Score: 0},
|
||||
).Result()
|
||||
|
||||
// STEP_START zadd_lex
|
||||
res13, err := rdb.ZAdd(ctx, "racer_scores",
|
||||
redis.Z{Member: "Norem", Score: 0},
|
||||
redis.Z{Member: "Sam-Bodden", Score: 0},
|
||||
redis.Z{Member: "Royce", Score: 0},
|
||||
redis.Z{Member: "Ford", Score: 0},
|
||||
redis.Z{Member: "Prickett", Score: 0},
|
||||
redis.Z{Member: "Castilla", Score: 0},
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res13) // >>> 3
|
||||
|
||||
res14, err := rdb.ZRange(ctx, "racer_scores", 0, -1).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res14)
|
||||
// >>> [Castilla Ford Norem Prickett Royce Sam-Bodden]
|
||||
|
||||
res15, err := rdb.ZRangeByLex(ctx, "racer_scores", &redis.ZRangeBy{
|
||||
Min: "[A", Max: "[L",
|
||||
}).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res15) // >>> [Castilla Ford]
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// 3
|
||||
// [Castilla Ford Norem Prickett Royce Sam-Bodden]
|
||||
// [Castilla Ford]
|
||||
}
|
||||
|
||||
func ExampleClient_leaderboard() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// start with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "racer_scores")
|
||||
// REMOVE_END
|
||||
|
||||
// STEP_START leaderboard
|
||||
res16, err := rdb.ZAdd(ctx, "racer_scores",
|
||||
redis.Z{Member: "Wood", Score: 100},
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res16) // >>> 1
|
||||
|
||||
res17, err := rdb.ZAdd(ctx, "racer_scores",
|
||||
redis.Z{Member: "Henshaw", Score: 100},
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res17) // >>> 1
|
||||
|
||||
res18, err := rdb.ZAdd(ctx, "racer_scores",
|
||||
redis.Z{Member: "Henshaw", Score: 150},
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res18) // >>> 0
|
||||
|
||||
res19, err := rdb.ZIncrBy(ctx, "racer_scores", 50, "Wood").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res19) // >>> 150
|
||||
|
||||
res20, err := rdb.ZIncrBy(ctx, "racer_scores", 50, "Henshaw").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res20) // >>> 200
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// 1
|
||||
// 1
|
||||
// 0
|
||||
// 150
|
||||
// 200
|
||||
}
|
1091
doctests/stream_tutorial_test.go
Normal file
1091
doctests/stream_tutorial_test.go
Normal file
File diff suppressed because it is too large
Load Diff
181
doctests/string_example_test.go
Normal file
181
doctests/string_example_test.go
Normal file
@ -0,0 +1,181 @@
|
||||
// EXAMPLE: set_tutorial
|
||||
// HIDE_START
|
||||
package example_commands_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
// HIDE_END
|
||||
func ExampleClient_set_get() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// start with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "bike:1")
|
||||
// REMOVE_END
|
||||
|
||||
// STEP_START set_get
|
||||
res1, err := rdb.Set(ctx, "bike:1", "Deimos", 0).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res1) // >>> OK
|
||||
|
||||
res2, err := rdb.Get(ctx, "bike:1").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res2) // >>> Deimos
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// OK
|
||||
// Deimos
|
||||
}
|
||||
|
||||
func ExampleClient_setnx_xx() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// start with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Set(ctx, "bike:1", "Deimos", 0)
|
||||
// REMOVE_END
|
||||
|
||||
// STEP_START setnx_xx
|
||||
res3, err := rdb.SetNX(ctx, "bike:1", "bike", 0).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res3) // >>> false
|
||||
|
||||
res4, err := rdb.Get(ctx, "bike:1").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res4) // >>> Deimos
|
||||
|
||||
res5, err := rdb.SetXX(ctx, "bike:1", "bike", 0).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res5) // >>> OK
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// false
|
||||
// Deimos
|
||||
// true
|
||||
}
|
||||
|
||||
func ExampleClient_mset() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// start with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "bike:1", "bike:2", "bike:3")
|
||||
// REMOVE_END
|
||||
|
||||
// STEP_START mset
|
||||
res6, err := rdb.MSet(ctx, "bike:1", "Deimos", "bike:2", "Ares", "bike:3", "Vanth").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res6) // >>> OK
|
||||
|
||||
res7, err := rdb.MGet(ctx, "bike:1", "bike:2", "bike:3").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res7) // >>> [Deimos Ares Vanth]
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// OK
|
||||
// [Deimos Ares Vanth]
|
||||
}
|
||||
|
||||
func ExampleClient_incr() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// start with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "total_crashes")
|
||||
// REMOVE_END
|
||||
|
||||
// STEP_START incr
|
||||
res8, err := rdb.Set(ctx, "total_crashes", "0", 0).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res8) // >>> OK
|
||||
|
||||
res9, err := rdb.Incr(ctx, "total_crashes").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res9) // >>> 1
|
||||
|
||||
res10, err := rdb.IncrBy(ctx, "total_crashes", 10).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res10) // >>> 11
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// OK
|
||||
// 1
|
||||
// 11
|
||||
}
|
261
doctests/tdigest_tutorial_test.go
Normal file
261
doctests/tdigest_tutorial_test.go
Normal file
@ -0,0 +1,261 @@
|
||||
// EXAMPLE: tdigest_tutorial
|
||||
// HIDE_START
|
||||
package example_commands_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
// HIDE_END
|
||||
|
||||
func ExampleClient_tdigstart() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// start with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "racer_ages", "bikes:sales")
|
||||
// REMOVE_END
|
||||
|
||||
// STEP_START tdig_start
|
||||
res1, err := rdb.TDigestCreate(ctx, "bikes:sales").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res1) // >>> OK
|
||||
|
||||
res2, err := rdb.TDigestAdd(ctx, "bikes:sales", 21).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res2) // >>> OK
|
||||
|
||||
res3, err := rdb.TDigestAdd(ctx, "bikes:sales",
|
||||
150, 95, 75, 34,
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res3) // >>> OK
|
||||
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// OK
|
||||
// OK
|
||||
// OK
|
||||
}
|
||||
|
||||
func ExampleClient_tdigcdf() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// start with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "racer_ages", "bikes:sales")
|
||||
// REMOVE_END
|
||||
|
||||
// STEP_START tdig_cdf
|
||||
res4, err := rdb.TDigestCreate(ctx, "racer_ages").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res4) // >>> OK
|
||||
|
||||
res5, err := rdb.TDigestAdd(ctx, "racer_ages",
|
||||
45.88, 44.2, 58.03, 19.76, 39.84, 69.28,
|
||||
50.97, 25.41, 19.27, 85.71, 42.63,
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res5) // >>> OK
|
||||
|
||||
res6, err := rdb.TDigestRank(ctx, "racer_ages", 50).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res6) // >>> [7]
|
||||
|
||||
res7, err := rdb.TDigestRank(ctx, "racer_ages", 50, 40).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res7) // >>> [7 4]
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// OK
|
||||
// OK
|
||||
// [7]
|
||||
// [7 4]
|
||||
}
|
||||
|
||||
func ExampleClient_tdigquant() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// start with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "racer_ages")
|
||||
// REMOVE_END
|
||||
|
||||
_, err := rdb.TDigestCreate(ctx, "racer_ages").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
_, err = rdb.TDigestAdd(ctx, "racer_ages",
|
||||
45.88, 44.2, 58.03, 19.76, 39.84, 69.28,
|
||||
50.97, 25.41, 19.27, 85.71, 42.63,
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// STEP_START tdig_quant
|
||||
res8, err := rdb.TDigestQuantile(ctx, "racer_ages", 0.5).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res8) // >>> [44.2]
|
||||
|
||||
res9, err := rdb.TDigestByRank(ctx, "racer_ages", 4).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res9) // >>> [42.63]
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// [44.2]
|
||||
// [42.63]
|
||||
}
|
||||
|
||||
func ExampleClient_tdigmin() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// start with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "racer_ages")
|
||||
// REMOVE_END
|
||||
|
||||
_, err := rdb.TDigestCreate(ctx, "racer_ages").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
_, err = rdb.TDigestAdd(ctx, "racer_ages",
|
||||
45.88, 44.2, 58.03, 19.76, 39.84, 69.28,
|
||||
50.97, 25.41, 19.27, 85.71, 42.63,
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// STEP_START tdig_min
|
||||
res10, err := rdb.TDigestMin(ctx, "racer_ages").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res10) // >>> 19.27
|
||||
|
||||
res11, err := rdb.TDigestMax(ctx, "racer_ages").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res11) // >>> 85.71
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// 19.27
|
||||
// 85.71
|
||||
}
|
||||
|
||||
func ExampleClient_tdigreset() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// start with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "racer_ages")
|
||||
// REMOVE_END
|
||||
_, err := rdb.TDigestCreate(ctx, "racer_ages").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// STEP_START tdig_reset
|
||||
res12, err := rdb.TDigestReset(ctx, "racer_ages").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res12) // >>> OK
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// OK
|
||||
}
|
77
doctests/topk_tutorial_test.go
Normal file
77
doctests/topk_tutorial_test.go
Normal file
@ -0,0 +1,77 @@
|
||||
// EXAMPLE: topk_tutorial
|
||||
// HIDE_START
|
||||
package example_commands_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
// HIDE_END
|
||||
|
||||
func ExampleClient_topk() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password docs
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
|
||||
// REMOVE_START
|
||||
// start with fresh database
|
||||
rdb.FlushDB(ctx)
|
||||
rdb.Del(ctx, "bikes:keywords")
|
||||
// REMOVE_END
|
||||
|
||||
// STEP_START topk
|
||||
res1, err := rdb.TopKReserve(ctx, "bikes:keywords", 5).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res1) // >>> OK
|
||||
|
||||
res2, err := rdb.TopKAdd(ctx, "bikes:keywords",
|
||||
"store",
|
||||
"seat",
|
||||
"handlebars",
|
||||
"handles",
|
||||
"pedals",
|
||||
"tires",
|
||||
"store",
|
||||
"seat",
|
||||
).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res2) // >>> [ handlebars ]
|
||||
|
||||
res3, err := rdb.TopKList(ctx, "bikes:keywords").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res3) // [store seat pedals tires handles]
|
||||
|
||||
res4, err := rdb.TopKQuery(ctx, "bikes:keywords", "store", "handlebars").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(res4) // [true false]
|
||||
// STEP_END
|
||||
|
||||
// Output:
|
||||
// OK
|
||||
// [ handlebars ]
|
||||
// [store seat pedals tires handles]
|
||||
// [true false]
|
||||
}
|
12
error.go
12
error.go
@ -7,6 +7,7 @@ import (
|
||||
"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"
|
||||
)
|
||||
@ -37,6 +38,15 @@ type Error interface {
|
||||
|
||||
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:
|
||||
@ -129,7 +139,9 @@ func isMovedError(err error) (moved bool, ask bool, addr string) {
|
||||
if ind == -1 {
|
||||
return false, false, ""
|
||||
}
|
||||
|
||||
addr = s[ind+1:]
|
||||
addr = internal.GetAddr(addr)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -5,12 +5,12 @@ go 1.18
|
||||
replace github.com/redis/go-redis/v9 => ../..
|
||||
|
||||
require (
|
||||
github.com/redis/go-redis/v9 v9.4.0
|
||||
github.com/redis/go-redis/v9 v9.7.1
|
||||
go.uber.org/zap v1.24.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
go.uber.org/atomic v1.10.0 // indirect
|
||||
go.uber.org/multierr v1.9.0 // indirect
|
||||
|
@ -1,8 +1,8 @@
|
||||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||
github.com/bsm/ginkgo/v2 v2.7.0 h1:ItPMPH90RbmZJt5GtkcNvIRuGEdwlBItdNVoyzaNQao=
|
||||
github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
|
@ -4,9 +4,9 @@ go 1.18
|
||||
|
||||
replace github.com/redis/go-redis/v9 => ../..
|
||||
|
||||
require github.com/redis/go-redis/v9 v9.4.0
|
||||
require github.com/redis/go-redis/v9 v9.7.1
|
||||
|
||||
require (
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
)
|
||||
|
@ -1,6 +1,6 @@
|
||||
github.com/bsm/ginkgo/v2 v2.7.0 h1:ItPMPH90RbmZJt5GtkcNvIRuGEdwlBItdNVoyzaNQao=
|
||||
github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
|
7
example/hset-struct/README.md
Normal file
7
example/hset-struct/README.md
Normal file
@ -0,0 +1,7 @@
|
||||
# Example for setting struct fields as hash fields
|
||||
|
||||
To run this example:
|
||||
|
||||
```shell
|
||||
go run .
|
||||
```
|
15
example/hset-struct/go.mod
Normal file
15
example/hset-struct/go.mod
Normal file
@ -0,0 +1,15 @@
|
||||
module github.com/redis/go-redis/example/scan-struct
|
||||
|
||||
go 1.18
|
||||
|
||||
replace github.com/redis/go-redis/v9 => ../..
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/redis/go-redis/v9 v9.6.2
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
)
|
10
example/hset-struct/go.sum
Normal file
10
example/hset-struct/go.sum
Normal file
@ -0,0 +1,10 @@
|
||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
129
example/hset-struct/main.go
Normal file
129
example/hset-struct/main.go
Normal file
@ -0,0 +1,129 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
type Model struct {
|
||||
Str1 string `redis:"str1"`
|
||||
Str2 string `redis:"str2"`
|
||||
Str3 *string `redis:"str3"`
|
||||
Str4 *string `redis:"str4"`
|
||||
Bytes []byte `redis:"bytes"`
|
||||
Int int `redis:"int"`
|
||||
Int2 *int `redis:"int2"`
|
||||
Int3 *int `redis:"int3"`
|
||||
Bool bool `redis:"bool"`
|
||||
Bool2 *bool `redis:"bool2"`
|
||||
Bool3 *bool `redis:"bool3"`
|
||||
Bool4 *bool `redis:"bool4,omitempty"`
|
||||
Time time.Time `redis:"time"`
|
||||
Time2 *time.Time `redis:"time2"`
|
||||
Time3 *time.Time `redis:"time3"`
|
||||
Ignored struct{} `redis:"-"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: ":6379",
|
||||
})
|
||||
|
||||
_ = rdb.FlushDB(ctx).Err()
|
||||
|
||||
t := time.Date(2025, 02, 8, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
data := Model{
|
||||
Str1: "hello",
|
||||
Str2: "world",
|
||||
Str3: ToPtr("hello"),
|
||||
Str4: nil,
|
||||
Bytes: []byte("this is bytes !"),
|
||||
Int: 123,
|
||||
Int2: ToPtr(0),
|
||||
Int3: nil,
|
||||
Bool: true,
|
||||
Bool2: ToPtr(false),
|
||||
Bool3: nil,
|
||||
Time: t,
|
||||
Time2: ToPtr(t),
|
||||
Time3: nil,
|
||||
Ignored: struct{}{},
|
||||
}
|
||||
|
||||
// Set some fields.
|
||||
if _, err := rdb.Pipelined(ctx, func(rdb redis.Pipeliner) error {
|
||||
rdb.HMSet(ctx, "key", data)
|
||||
return nil
|
||||
}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
var model1, model2 Model
|
||||
|
||||
// Scan all fields into the model.
|
||||
if err := rdb.HGetAll(ctx, "key").Scan(&model1); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Or scan a subset of the fields.
|
||||
if err := rdb.HMGet(ctx, "key", "str1", "int").Scan(&model2); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
spew.Dump(model1)
|
||||
// Output:
|
||||
// (main.Model) {
|
||||
// Str1: (string) (len=5) "hello",
|
||||
// Str2: (string) (len=5) "world",
|
||||
// Str3: (*string)(0xc000016970)((len=5) "hello"),
|
||||
// Str4: (*string)(0xc000016980)(""),
|
||||
// Bytes: ([]uint8) (len=15 cap=16) {
|
||||
// 00000000 74 68 69 73 20 69 73 20 62 79 74 65 73 20 21 |this is bytes !|
|
||||
// },
|
||||
// Int: (int) 123,
|
||||
// Int2: (*int)(0xc000014568)(0),
|
||||
// Int3: (*int)(0xc000014560)(0),
|
||||
// Bool: (bool) true,
|
||||
// Bool2: (*bool)(0xc000014570)(false),
|
||||
// Bool3: (*bool)(0xc000014548)(false),
|
||||
// Bool4: (*bool)(<nil>),
|
||||
// Time: (time.Time) 2025-02-08 00:00:00 +0000 UTC,
|
||||
// Time2: (*time.Time)(0xc0000122a0)(2025-02-08 00:00:00 +0000 UTC),
|
||||
// Time3: (*time.Time)(0xc000012288)(0001-01-01 00:00:00 +0000 UTC),
|
||||
// Ignored: (struct {}) {
|
||||
// }
|
||||
// }
|
||||
|
||||
spew.Dump(model2)
|
||||
// Output:
|
||||
// (main.Model) {
|
||||
// Str1: (string) (len=5) "hello",
|
||||
// Str2: (string) "",
|
||||
// Str3: (*string)(<nil>),
|
||||
// Str4: (*string)(<nil>),
|
||||
// Bytes: ([]uint8) <nil>,
|
||||
// Int: (int) 123,
|
||||
// Int2: (*int)(<nil>),
|
||||
// Int3: (*int)(<nil>),
|
||||
// Bool: (bool) false,
|
||||
// Bool2: (*bool)(<nil>),
|
||||
// Bool3: (*bool)(<nil>),
|
||||
// Bool4: (*bool)(<nil>),
|
||||
// Time: (time.Time) 0001-01-01 00:00:00 +0000 UTC,
|
||||
// Time2: (*time.Time)(<nil>),
|
||||
// Time3: (*time.Time)(<nil>),
|
||||
// Ignored: (struct {}) {
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
func ToPtr[T any](v T) *T {
|
||||
return &v
|
||||
}
|
@ -4,9 +4,9 @@ go 1.18
|
||||
|
||||
replace github.com/redis/go-redis/v9 => ../..
|
||||
|
||||
require github.com/redis/go-redis/v9 v9.4.0
|
||||
require github.com/redis/go-redis/v9 v9.7.1
|
||||
|
||||
require (
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
)
|
||||
|
@ -1,6 +1,6 @@
|
||||
github.com/bsm/ginkgo/v2 v2.7.0 h1:ItPMPH90RbmZJt5GtkcNvIRuGEdwlBItdNVoyzaNQao=
|
||||
github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
|
@ -9,39 +9,37 @@ replace github.com/redis/go-redis/extra/redisotel/v9 => ../../extra/redisotel
|
||||
replace github.com/redis/go-redis/extra/rediscmd/v9 => ../../extra/rediscmd
|
||||
|
||||
require (
|
||||
github.com/redis/go-redis/extra/redisotel/v9 v9.4.0
|
||||
github.com/redis/go-redis/v9 v9.4.0
|
||||
github.com/redis/go-redis/extra/redisotel/v9 v9.7.1
|
||||
github.com/redis/go-redis/v9 v9.7.1
|
||||
github.com/uptrace/uptrace-go v1.21.0
|
||||
go.opentelemetry.io/otel v1.21.0
|
||||
go.opentelemetry.io/otel v1.22.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/go-logr/logr v1.4.1 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect
|
||||
github.com/redis/go-redis/extra/rediscmd/v9 v9.4.0 // indirect
|
||||
github.com/redis/go-redis/extra/rediscmd/v9 v9.7.1 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/runtime v0.46.1 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.17.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.43.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.44.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.21.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.21.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.21.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.22.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.22.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v1.21.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.21.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.22.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.0.0 // indirect
|
||||
golang.org/x/net v0.20.0 // indirect
|
||||
golang.org/x/sys v0.16.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/net v0.33.0 // indirect
|
||||
golang.org/x/sys v0.28.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20240108191215-35c7eff3a6b1 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240108191215-35c7eff3a6b1 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1 // indirect
|
||||
google.golang.org/grpc v1.60.1 // indirect
|
||||
google.golang.org/protobuf v1.32.0 // indirect
|
||||
google.golang.org/protobuf v1.33.0 // indirect
|
||||
)
|
||||
|
@ -1,516 +1,68 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
||||
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
||||
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
||||
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/bsm/ginkgo/v2 v2.7.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||
github.com/bsm/gomega v1.26.0/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
|
||||
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
|
||||
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
|
||||
github.com/golang/glog v1.1.1 h1:jxpi2eWoU84wbX9iIEyAeeoac3FLuifZpY9tcNUD9kw=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2 h1:gDLXvp5S9izjldquuoAhDzccbskOL6tDC5jMSyx3zxE=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2/go.mod h1:7pdNwVWBBHGiCxa9lAszqCJMbfTISJ7oMftp8+UGV08=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
|
||||
github.com/uptrace/uptrace-go v1.16.0 h1:yB9vt1hBYYoXWExNx0okubLOjd339d7lH+/5o+Lp+MY=
|
||||
github.com/uptrace/uptrace-go v1.16.0/go.mod h1:Ssc5wLpoL+9V0qkT5FtrIiru9SY4xb7q1UVLjSpxpCg=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/uptrace/uptrace-go v1.21.0 h1:oJoUjhiVT7aiuoG6B3ClVHtJozLn3cK9hQt8U5dQO1M=
|
||||
github.com/uptrace/uptrace-go v1.21.0/go.mod h1:/aXAFGKOqeAFBqWa1xtzLnGX2xJm1GScqz9NJ0TJjLM=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opentelemetry.io/contrib/instrumentation/runtime v0.42.0 h1:EbmAUG9hEAMXyfWEasIt2kmh/WmXUznUksChApTgBGc=
|
||||
go.opentelemetry.io/contrib/instrumentation/runtime v0.42.0/go.mod h1:rD9feqRYP24P14t5kmhNMqsqm1jvKmpx2H2rKVw52V8=
|
||||
go.opentelemetry.io/contrib/instrumentation/runtime v0.46.1 h1:m9ReioVPIffxjJlGNRd0d5poy+9oTro3D+YbiEzUDOc=
|
||||
go.opentelemetry.io/contrib/instrumentation/runtime v0.46.1/go.mod h1:CANkrsXNzqOKXfOomu2zhOmc1/J5UZK9SGjrat6ZCG0=
|
||||
go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s=
|
||||
go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4=
|
||||
go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc=
|
||||
go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo=
|
||||
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.16.0 h1:t4ZwRPU+emrcvM2e9DHd0Fsf0JTPVcbfa/BhTDF03d0=
|
||||
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.16.0/go.mod h1:vLarbg68dH2Wa77g71zmKQqlQ8+8Rq3GRG31uc0WcWI=
|
||||
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.17.0 h1:eU0ffpYuEY7eQ75K+nKr9CI5KcY8h+GPk/9DDlEO1NI=
|
||||
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.17.0/go.mod h1:9P5RK5JS2sjKepuCkqFwPp3etwV/57E0eigLw18Mn1k=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.39.0 h1:f6BwB2OACc3FCbYVznctQ9V6KK7Vq6CjmYXJ7DeSs4E=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.39.0/go.mod h1:UqL5mZ3qs6XYhDnZaW1Ps4upD+PX6LipH40AoeuIlwU=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.43.0 h1:f+VtlQwREKbGdbq/Mx/xMDLrPktBZ1+5PzNMrYSsdXo=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.43.0/go.mod h1:V0A1wlhxQUdvqQk+vMA5+NwT7I6AFSyQv1EXLQBb8dM=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.39.0 h1:rm+Fizi7lTM2UefJ1TO347fSRcwmIsUAaZmYmIGBRAo=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.39.0/go.mod h1:sWFbI3jJ+6JdjOVepA5blpv/TJ20Hw+26561iMbWcwU=
|
||||
go.opentelemetry.io/otel v1.22.0 h1:xS7Ku+7yTFvDfDraDIJVpw7XPyuHlB9MCiqqX5mcJ6Y=
|
||||
go.opentelemetry.io/otel v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.44.0 h1:jd0+5t/YynESZqsSyPz+7PAFdEop0dlN0+PkyHYo8oI=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.44.0/go.mod h1:U707O40ee1FpQGyhvqnzmCJm1Wh6OX6GGBVn0E6Uyyk=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.16.0 h1:cbsD4cUcviQGXdw8+bo5x2wazq10SKz8hEbtCRPcU78=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.16.0/go.mod h1:JgXSGah17croqhJfhByOLVY719k1emAXC8MVhCIJlRs=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 h1:cl5P5/GIfFh4t6xyruOgJP5QiA1pw4fYYdv6nc6CBWw=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0/go.mod h1:zgBdWWAu7oEEMC06MMKc5NLbA/1YDXV1sMpSqEeLQLg=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.16.0 h1:TVQp/bboR4mhZSav+MdgXB8FaRho1RC8UwVn3T0vjVc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.16.0/go.mod h1:I33vtIe0sR96wfrUcilIzLoA3mLHhRmz9S9Te0S3gDo=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 h1:tIqheXEFWAZ7O8A7m+J0aPTmpJN3YQ7qetUAdkkkKpk=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0/go.mod h1:nUeKExfxAQVbiVFn32YXpXZZHZ61Cc3s3Rn1pDBGAb0=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.16.0 h1:+XWJd3jf75RXJq29mxbuXhCXFDG3S3R4vBUeSI2P7tE=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.16.0/go.mod h1:hqgzBPTf4yONMFgdZvL/bK42R/iinTyVQtiWihs3SZc=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.21.0 h1:VhlEQAPp9R1ktYfrPk5SOryw1e9LDDTZCbIPFrho0ec=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.21.0/go.mod h1:kB3ufRbfU+CQ4MlUcqtW8Z7YEOBeK2DJ6CmR5rYYF3E=
|
||||
go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo=
|
||||
go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4=
|
||||
go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4=
|
||||
go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM=
|
||||
go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE=
|
||||
go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4=
|
||||
go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8=
|
||||
go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E=
|
||||
go.opentelemetry.io/otel/sdk/metric v0.39.0 h1:Kun8i1eYf48kHH83RucG93ffz0zGV1sh46FAScOTuDI=
|
||||
go.opentelemetry.io/otel/sdk/metric v0.39.0/go.mod h1:piDIRgjcK7u0HCL5pCA4e74qpK/jk3NiUoAHATVAmiI=
|
||||
go.opentelemetry.io/otel/metric v1.22.0 h1:lypMQnGyJYeuYPhOM/bgjbFM6WE44W1/T45er4d8Hhg=
|
||||
go.opentelemetry.io/otel/metric v1.22.0/go.mod h1:evJGjVpZv0mQ5QBRJoBF64yMuOf4xCWdXjK8pzFvliY=
|
||||
go.opentelemetry.io/otel/sdk v1.22.0 h1:6coWHw9xw7EfClIC/+O31R8IY3/+EiRFHevmHafB2Gw=
|
||||
go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.21.0 h1:smhI5oD714d6jHE6Tie36fPx4WDFIg+Y6RfAY4ICcR0=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.21.0/go.mod h1:FJ8RAsoPGv/wYMgBdUJXOm+6pzFY3YdljnXtv1SBE8Q=
|
||||
go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs=
|
||||
go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0=
|
||||
go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc=
|
||||
go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ=
|
||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||
go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJPI1Nnw=
|
||||
go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U=
|
||||
go.opentelemetry.io/otel/trace v1.22.0 h1:Hg6pPujv0XG9QaVbGOBVHunyuLcCC3jN7WEhPx83XD0=
|
||||
go.opentelemetry.io/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40a21sPw2He1xo=
|
||||
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
|
||||
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
|
||||
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
|
||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
||||
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
|
||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
|
||||
google.golang.org/genproto v0.0.0-20240108191215-35c7eff3a6b1 h1:/IWabOtPziuXTEtI1KYCpM6Ss7vaAkeMxk+uXV/xvZs=
|
||||
google.golang.org/genproto v0.0.0-20240108191215-35c7eff3a6b1/go.mod h1:+Rvu7ElI+aLzyDQhpHMFMMltsD6m7nqpuWDd2CwJw3k=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240108191215-35c7eff3a6b1 h1:OPXtXn7fNMaXwO3JvOmF1QyTc00jsSFFz1vXXBOdCDo=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240108191215-35c7eff3a6b1/go.mod h1:B5xPO//w8qmBDjGReYLpR6UJPnkldGkCSMoH/2vxJeg=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1 h1:gphdwh0npgs8elJ4T6J+DQJHPVF7RsuJHCfwztUb4J4=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1/go.mod h1:daQN87bsDqDoe316QbbvX60nMoJQa4r6Ds0ZuoAe5yA=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||
google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
|
||||
google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc=
|
||||
google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
|
||||
google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU=
|
||||
google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
|
||||
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
|
@ -4,9 +4,9 @@ go 1.18
|
||||
|
||||
replace github.com/redis/go-redis/v9 => ../..
|
||||
|
||||
require github.com/redis/go-redis/v9 v9.4.0
|
||||
require github.com/redis/go-redis/v9 v9.7.1
|
||||
|
||||
require (
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
)
|
||||
|
@ -1,6 +1,6 @@
|
||||
github.com/bsm/ginkgo/v2 v2.7.0 h1:ItPMPH90RbmZJt5GtkcNvIRuGEdwlBItdNVoyzaNQao=
|
||||
github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
|
@ -6,10 +6,10 @@ replace github.com/redis/go-redis/v9 => ../..
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/redis/go-redis/v9 v9.4.0
|
||||
github.com/redis/go-redis/v9 v9.7.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
)
|
||||
|
@ -1,7 +1,7 @@
|
||||
github.com/bsm/ginkgo/v2 v2.7.0 h1:ItPMPH90RbmZJt5GtkcNvIRuGEdwlBItdNVoyzaNQao=
|
||||
github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
|
@ -11,8 +11,12 @@ import (
|
||||
type Model struct {
|
||||
Str1 string `redis:"str1"`
|
||||
Str2 string `redis:"str2"`
|
||||
Str3 *string `redis:"str3"`
|
||||
Bytes []byte `redis:"bytes"`
|
||||
Int int `redis:"int"`
|
||||
Int2 *int `redis:"int2"`
|
||||
Bool bool `redis:"bool"`
|
||||
Bool2 *bool `redis:"bool2"`
|
||||
Ignored struct{} `redis:"-"`
|
||||
}
|
||||
|
||||
@ -22,13 +26,18 @@ func main() {
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: ":6379",
|
||||
})
|
||||
_ = rdb.FlushDB(ctx).Err()
|
||||
|
||||
// Set some fields.
|
||||
if _, err := rdb.Pipelined(ctx, func(rdb redis.Pipeliner) error {
|
||||
rdb.HSet(ctx, "key", "str1", "hello")
|
||||
rdb.HSet(ctx, "key", "str2", "world")
|
||||
rdb.HSet(ctx, "key", "str3", "")
|
||||
rdb.HSet(ctx, "key", "int", 123)
|
||||
rdb.HSet(ctx, "key", "int2", 0)
|
||||
rdb.HSet(ctx, "key", "bool", 1)
|
||||
rdb.HSet(ctx, "key", "bool2", 0)
|
||||
rdb.HSet(ctx, "key", "bytes", []byte("this is bytes !"))
|
||||
return nil
|
||||
}); err != nil {
|
||||
panic(err)
|
||||
@ -47,5 +56,28 @@ func main() {
|
||||
}
|
||||
|
||||
spew.Dump(model1)
|
||||
// Output:
|
||||
// (main.Model) {
|
||||
// Str1: (string) (len=5) "hello",
|
||||
// Str2: (string) (len=5) "world",
|
||||
// Bytes: ([]uint8) (len=15 cap=16) {
|
||||
// 00000000 74 68 69 73 20 69 73 20 62 79 74 65 73 20 21 |this is bytes !|
|
||||
// },
|
||||
// Int: (int) 123,
|
||||
// Bool: (bool) true,
|
||||
// Ignored: (struct {}) {
|
||||
// }
|
||||
// }
|
||||
|
||||
spew.Dump(model2)
|
||||
// Output:
|
||||
// (main.Model) {
|
||||
// Str1: (string) (len=5) "hello",
|
||||
// Str2: (string) "",
|
||||
// Bytes: ([]uint8) <nil>,
|
||||
// Int: (int) 123,
|
||||
// Bool: (bool) false,
|
||||
// Ignored: (struct {}) {
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
@ -154,7 +154,7 @@ func ExampleClient() {
|
||||
// missing_key does not exist
|
||||
}
|
||||
|
||||
func ExampleConn() {
|
||||
func ExampleConn_name() {
|
||||
conn := rdb.Conn()
|
||||
|
||||
err := conn.ClientSetName(ctx, "foobar").Err()
|
||||
@ -175,6 +175,28 @@ func ExampleConn() {
|
||||
// Output: foobar
|
||||
}
|
||||
|
||||
func ExampleConn_client_setInfo_libraryVersion() {
|
||||
conn := rdb.Conn()
|
||||
|
||||
err := conn.ClientSetInfo(ctx, redis.WithLibraryVersion("1.2.3")).Err()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Open other connections.
|
||||
for i := 0; i < 10; i++ {
|
||||
go rdb.Ping(ctx)
|
||||
}
|
||||
|
||||
s, err := conn.ClientInfo(ctx).Result()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(s.LibVer)
|
||||
// Output: 1.2.3
|
||||
}
|
||||
|
||||
func ExampleClient_Set() {
|
||||
// Last argument is expiration. Zero means the key has no
|
||||
// expiration time.
|
||||
@ -460,7 +482,7 @@ func ExampleClient_Watch() {
|
||||
return err
|
||||
}
|
||||
|
||||
// Actual opperation (local in optimistic lock).
|
||||
// Actual operation (local in optimistic lock).
|
||||
n++
|
||||
|
||||
// Operation is committed only if the watched keys remain unchanged.
|
||||
|
@ -1,14 +1,21 @@
|
||||
module github.com/redis/go-redis/extra/rediscensus/v9
|
||||
|
||||
go 1.15
|
||||
go 1.19
|
||||
|
||||
replace github.com/redis/go-redis/v9 => ../..
|
||||
|
||||
replace github.com/redis/go-redis/extra/rediscmd/v9 => ../rediscmd
|
||||
|
||||
require (
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/redis/go-redis/extra/rediscmd/v9 v9.4.0
|
||||
github.com/redis/go-redis/v9 v9.4.0
|
||||
github.com/redis/go-redis/extra/rediscmd/v9 v9.7.1
|
||||
github.com/redis/go-redis/v9 v9.7.1
|
||||
go.opencensus.io v0.24.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
)
|
||||
|
||||
retract v9.5.3 // This version was accidentally released.
|
||||
|
@ -1,12 +1,10 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/bsm/ginkgo/v2 v2.7.0 h1:ItPMPH90RbmZJt5GtkcNvIRuGEdwlBItdNVoyzaNQao=
|
||||
github.com/bsm/ginkgo/v2 v2.7.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w=
|
||||
github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y=
|
||||
github.com/bsm/gomega v1.26.0/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
|
@ -1,11 +1,18 @@
|
||||
module github.com/redis/go-redis/extra/rediscmd/v9
|
||||
|
||||
go 1.15
|
||||
go 1.19
|
||||
|
||||
replace github.com/redis/go-redis/v9 => ../..
|
||||
|
||||
require (
|
||||
github.com/bsm/ginkgo/v2 v2.7.0
|
||||
github.com/bsm/gomega v1.26.0
|
||||
github.com/redis/go-redis/v9 v9.4.0
|
||||
github.com/bsm/ginkgo/v2 v2.12.0
|
||||
github.com/bsm/gomega v1.27.10
|
||||
github.com/redis/go-redis/v9 v9.7.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
)
|
||||
|
||||
retract v9.5.3 // This version was accidentally released.
|
||||
|
@ -1,8 +1,8 @@
|
||||
github.com/bsm/ginkgo/v2 v2.7.0 h1:ItPMPH90RbmZJt5GtkcNvIRuGEdwlBItdNVoyzaNQao=
|
||||
github.com/bsm/ginkgo/v2 v2.7.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w=
|
||||
github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y=
|
||||
github.com/bsm/gomega v1.26.0/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
|
@ -4,7 +4,7 @@ import (
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.12.0"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
|
@ -7,18 +7,20 @@ replace github.com/redis/go-redis/v9 => ../..
|
||||
replace github.com/redis/go-redis/extra/rediscmd/v9 => ../rediscmd
|
||||
|
||||
require (
|
||||
github.com/redis/go-redis/extra/rediscmd/v9 v9.4.0
|
||||
github.com/redis/go-redis/v9 v9.4.0
|
||||
go.opentelemetry.io/otel v1.16.0
|
||||
go.opentelemetry.io/otel/metric v1.16.0
|
||||
go.opentelemetry.io/otel/sdk v1.16.0
|
||||
go.opentelemetry.io/otel/trace v1.16.0
|
||||
github.com/redis/go-redis/extra/rediscmd/v9 v9.7.1
|
||||
github.com/redis/go-redis/v9 v9.7.1
|
||||
go.opentelemetry.io/otel v1.22.0
|
||||
go.opentelemetry.io/otel/metric v1.22.0
|
||||
go.opentelemetry.io/otel/sdk v1.22.0
|
||||
go.opentelemetry.io/otel/trace v1.22.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/go-logr/logr v1.2.4 // indirect
|
||||
github.com/go-logr/logr v1.4.1 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
golang.org/x/sys v0.8.0 // indirect
|
||||
golang.org/x/sys v0.16.0 // indirect
|
||||
)
|
||||
|
||||
retract v9.5.3 // This version was accidentally released.
|
||||
|
@ -1,28 +1,26 @@
|
||||
github.com/bsm/ginkgo/v2 v2.7.0 h1:ItPMPH90RbmZJt5GtkcNvIRuGEdwlBItdNVoyzaNQao=
|
||||
github.com/bsm/ginkgo/v2 v2.7.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w=
|
||||
github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y=
|
||||
github.com/bsm/gomega v1.26.0/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
|
||||
go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s=
|
||||
go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4=
|
||||
go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo=
|
||||
go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4=
|
||||
go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE=
|
||||
go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4=
|
||||
go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs=
|
||||
go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0=
|
||||
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
go.opentelemetry.io/otel v1.22.0 h1:xS7Ku+7yTFvDfDraDIJVpw7XPyuHlB9MCiqqX5mcJ6Y=
|
||||
go.opentelemetry.io/otel v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI=
|
||||
go.opentelemetry.io/otel/metric v1.22.0 h1:lypMQnGyJYeuYPhOM/bgjbFM6WE44W1/T45er4d8Hhg=
|
||||
go.opentelemetry.io/otel/metric v1.22.0/go.mod h1:evJGjVpZv0mQ5QBRJoBF64yMuOf4xCWdXjK8pzFvliY=
|
||||
go.opentelemetry.io/otel/sdk v1.22.0 h1:6coWHw9xw7EfClIC/+O31R8IY3/+EiRFHevmHafB2Gw=
|
||||
go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc=
|
||||
go.opentelemetry.io/otel/trace v1.22.0 h1:Hg6pPujv0XG9QaVbGOBVHunyuLcCC3jN7WEhPx83XD0=
|
||||
go.opentelemetry.io/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40a21sPw2He1xo=
|
||||
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
|
@ -1,61 +0,0 @@
|
||||
package redisotel
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.7.0"
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
type providerFunc func(name string, opts ...trace.TracerOption) trace.Tracer
|
||||
|
||||
func (fn providerFunc) Tracer(name string, opts ...trace.TracerOption) trace.Tracer {
|
||||
return fn(name, opts...)
|
||||
}
|
||||
|
||||
func TestNewWithTracerProvider(t *testing.T) {
|
||||
invoked := false
|
||||
|
||||
tp := providerFunc(func(name string, opts ...trace.TracerOption) trace.Tracer {
|
||||
invoked = true
|
||||
return otel.GetTracerProvider().Tracer(name, opts...)
|
||||
})
|
||||
|
||||
_ = newTracingHook("", WithTracerProvider(tp))
|
||||
|
||||
if !invoked {
|
||||
t.Fatalf("did not call custom TraceProvider")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithDBStatement(t *testing.T) {
|
||||
provider := sdktrace.NewTracerProvider()
|
||||
hook := newTracingHook(
|
||||
"",
|
||||
WithTracerProvider(provider),
|
||||
WithDBStatement(false),
|
||||
)
|
||||
ctx, span := provider.Tracer("redis-test").Start(context.TODO(), "redis-test")
|
||||
cmd := redis.NewCmd(ctx, "ping")
|
||||
defer span.End()
|
||||
|
||||
processHook := hook.ProcessHook(func(ctx context.Context, cmd redis.Cmder) error {
|
||||
attrs := trace.SpanFromContext(ctx).(sdktrace.ReadOnlySpan).Attributes()
|
||||
for _, attr := range attrs {
|
||||
if attr.Key == semconv.DBStatementKey {
|
||||
t.Fatal("Attribute with db statement should not exist")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
err := processHook(ctx, cmd)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
@ -5,11 +5,12 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.10.0"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
"github.com/redis/go-redis/extra/rediscmd/v9"
|
||||
@ -25,22 +26,21 @@ func InstrumentTracing(rdb redis.UniversalClient, opts ...TracingOption) error {
|
||||
case *redis.Client:
|
||||
opt := rdb.Options()
|
||||
connString := formatDBConnString(opt.Network, opt.Addr)
|
||||
opts = addServerAttributes(opts, opt.Addr)
|
||||
rdb.AddHook(newTracingHook(connString, opts...))
|
||||
return nil
|
||||
case *redis.ClusterClient:
|
||||
rdb.AddHook(newTracingHook("", opts...))
|
||||
|
||||
rdb.OnNewNode(func(rdb *redis.Client) {
|
||||
opt := rdb.Options()
|
||||
opts = addServerAttributes(opts, opt.Addr)
|
||||
connString := formatDBConnString(opt.Network, opt.Addr)
|
||||
rdb.AddHook(newTracingHook(connString, opts...))
|
||||
})
|
||||
return nil
|
||||
case *redis.Ring:
|
||||
rdb.AddHook(newTracingHook("", opts...))
|
||||
|
||||
rdb.OnNewNode(func(rdb *redis.Client) {
|
||||
opt := rdb.Options()
|
||||
opts = addServerAttributes(opts, opt.Addr)
|
||||
connString := formatDBConnString(opt.Network, opt.Addr)
|
||||
rdb.AddHook(newTracingHook(connString, opts...))
|
||||
})
|
||||
@ -72,7 +72,7 @@ func newTracingHook(connString string, opts ...TracingOption) *tracingHook {
|
||||
)
|
||||
}
|
||||
if connString != "" {
|
||||
conf.attrs = append(conf.attrs, semconv.DBConnectionStringKey.String(connString))
|
||||
conf.attrs = append(conf.attrs, semconv.DBConnectionString(connString))
|
||||
}
|
||||
|
||||
return &tracingHook{
|
||||
@ -87,10 +87,6 @@ func newTracingHook(connString string, opts ...TracingOption) *tracingHook {
|
||||
|
||||
func (th *tracingHook) DialHook(hook redis.DialHook) redis.DialHook {
|
||||
return func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
if !trace.SpanFromContext(ctx).IsRecording() {
|
||||
return hook(ctx, network, addr)
|
||||
}
|
||||
|
||||
ctx, span := th.conf.tracer.Start(ctx, "redis.dial", th.spanOpts...)
|
||||
defer span.End()
|
||||
|
||||
@ -105,22 +101,18 @@ func (th *tracingHook) DialHook(hook redis.DialHook) redis.DialHook {
|
||||
|
||||
func (th *tracingHook) ProcessHook(hook redis.ProcessHook) redis.ProcessHook {
|
||||
return func(ctx context.Context, cmd redis.Cmder) error {
|
||||
if !trace.SpanFromContext(ctx).IsRecording() {
|
||||
return hook(ctx, cmd)
|
||||
}
|
||||
|
||||
fn, file, line := funcFileLine("github.com/redis/go-redis")
|
||||
|
||||
attrs := make([]attribute.KeyValue, 0, 8)
|
||||
attrs = append(attrs,
|
||||
semconv.CodeFunctionKey.String(fn),
|
||||
semconv.CodeFilepathKey.String(file),
|
||||
semconv.CodeLineNumberKey.Int(line),
|
||||
semconv.CodeFunction(fn),
|
||||
semconv.CodeFilepath(file),
|
||||
semconv.CodeLineNumber(line),
|
||||
)
|
||||
|
||||
if th.conf.dbStmtEnabled {
|
||||
cmdString := rediscmd.CmdString(cmd)
|
||||
attrs = append(attrs, semconv.DBStatementKey.String(cmdString))
|
||||
attrs = append(attrs, semconv.DBStatement(cmdString))
|
||||
}
|
||||
|
||||
opts := th.spanOpts
|
||||
@ -141,23 +133,19 @@ func (th *tracingHook) ProcessPipelineHook(
|
||||
hook redis.ProcessPipelineHook,
|
||||
) redis.ProcessPipelineHook {
|
||||
return func(ctx context.Context, cmds []redis.Cmder) error {
|
||||
if !trace.SpanFromContext(ctx).IsRecording() {
|
||||
return hook(ctx, cmds)
|
||||
}
|
||||
|
||||
fn, file, line := funcFileLine("github.com/redis/go-redis")
|
||||
|
||||
attrs := make([]attribute.KeyValue, 0, 8)
|
||||
attrs = append(attrs,
|
||||
semconv.CodeFunctionKey.String(fn),
|
||||
semconv.CodeFilepathKey.String(file),
|
||||
semconv.CodeLineNumberKey.Int(line),
|
||||
semconv.CodeFunction(fn),
|
||||
semconv.CodeFilepath(file),
|
||||
semconv.CodeLineNumber(line),
|
||||
attribute.Int("db.redis.num_cmd", len(cmds)),
|
||||
)
|
||||
|
||||
summary, cmdsString := rediscmd.CmdsString(cmds)
|
||||
if th.conf.dbStmtEnabled {
|
||||
attrs = append(attrs, semconv.DBStatementKey.String(cmdsString))
|
||||
attrs = append(attrs, semconv.DBStatement(cmdsString))
|
||||
}
|
||||
|
||||
opts := th.spanOpts
|
||||
@ -213,3 +201,28 @@ func funcFileLine(pkg string) (string, string, int) {
|
||||
|
||||
return fn, file, line
|
||||
}
|
||||
|
||||
// Database span attributes semantic conventions recommended server address and port
|
||||
// https://opentelemetry.io/docs/specs/semconv/database/database-spans/#connection-level-attributes
|
||||
func addServerAttributes(opts []TracingOption, addr string) []TracingOption {
|
||||
host, portString, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return opts
|
||||
}
|
||||
|
||||
opts = append(opts, WithAttributes(
|
||||
semconv.ServerAddress(host),
|
||||
))
|
||||
|
||||
// Parse the port string to an integer
|
||||
port, err := strconv.Atoi(portString)
|
||||
if err != nil {
|
||||
return opts
|
||||
}
|
||||
|
||||
opts = append(opts, WithAttributes(
|
||||
semconv.ServerPort(port),
|
||||
))
|
||||
|
||||
return opts
|
||||
}
|
||||
|
240
extra/redisotel/tracing_test.go
Normal file
240
extra/redisotel/tracing_test.go
Normal file
@ -0,0 +1,240 @@
|
||||
package redisotel
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
"go.opentelemetry.io/otel/sdk/trace/tracetest"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.7.0"
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
type providerFunc func(name string, opts ...trace.TracerOption) trace.TracerProvider
|
||||
|
||||
func (fn providerFunc) TracerProvider(name string, opts ...trace.TracerOption) trace.TracerProvider {
|
||||
return fn(name, opts...)
|
||||
}
|
||||
|
||||
func TestNewWithTracerProvider(t *testing.T) {
|
||||
invoked := false
|
||||
|
||||
tp := providerFunc(func(name string, opts ...trace.TracerOption) trace.TracerProvider {
|
||||
invoked = true
|
||||
return otel.GetTracerProvider()
|
||||
})
|
||||
|
||||
_ = newTracingHook("redis-hook", WithTracerProvider(tp.TracerProvider("redis-test")))
|
||||
|
||||
if !invoked {
|
||||
t.Fatalf("did not call custom TraceProvider")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithDBStatement(t *testing.T) {
|
||||
provider := sdktrace.NewTracerProvider()
|
||||
hook := newTracingHook(
|
||||
"",
|
||||
WithTracerProvider(provider),
|
||||
WithDBStatement(false),
|
||||
)
|
||||
ctx, span := provider.Tracer("redis-test").Start(context.TODO(), "redis-test")
|
||||
cmd := redis.NewCmd(ctx, "ping")
|
||||
defer span.End()
|
||||
|
||||
processHook := hook.ProcessHook(func(ctx context.Context, cmd redis.Cmder) error {
|
||||
attrs := trace.SpanFromContext(ctx).(sdktrace.ReadOnlySpan).Attributes()
|
||||
for _, attr := range attrs {
|
||||
if attr.Key == semconv.DBStatementKey {
|
||||
t.Fatal("Attribute with db statement should not exist")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
err := processHook(ctx, cmd)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTracingHook_DialHook(t *testing.T) {
|
||||
imsb := tracetest.NewInMemoryExporter()
|
||||
provider := sdktrace.NewTracerProvider(sdktrace.WithSyncer(imsb))
|
||||
hook := newTracingHook(
|
||||
"redis://localhost:6379",
|
||||
WithTracerProvider(provider),
|
||||
)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
errTest error
|
||||
}{
|
||||
{"nil error", nil},
|
||||
{"test error", fmt.Errorf("test error")},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
defer imsb.Reset()
|
||||
|
||||
dialHook := hook.DialHook(func(ctx context.Context, network, addr string) (conn net.Conn, err error) {
|
||||
return nil, tt.errTest
|
||||
})
|
||||
if _, err := dialHook(context.Background(), "tcp", "localhost:6379"); err != tt.errTest {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assertEqual(t, 1, len(imsb.GetSpans()))
|
||||
|
||||
spanData := imsb.GetSpans()[0]
|
||||
assertEqual(t, instrumName, spanData.InstrumentationLibrary.Name)
|
||||
assertEqual(t, "redis.dial", spanData.Name)
|
||||
assertEqual(t, trace.SpanKindClient, spanData.SpanKind)
|
||||
assertAttributeContains(t, spanData.Attributes, semconv.DBSystemRedis)
|
||||
assertAttributeContains(t, spanData.Attributes, semconv.DBConnectionStringKey.String("redis://localhost:6379"))
|
||||
|
||||
if tt.errTest == nil {
|
||||
assertEqual(t, 0, len(spanData.Events))
|
||||
assertEqual(t, codes.Unset, spanData.Status.Code)
|
||||
assertEqual(t, "", spanData.Status.Description)
|
||||
return
|
||||
}
|
||||
|
||||
assertEqual(t, 1, len(spanData.Events))
|
||||
assertAttributeContains(t, spanData.Events[0].Attributes, semconv.ExceptionTypeKey.String("*errors.errorString"))
|
||||
assertAttributeContains(t, spanData.Events[0].Attributes, semconv.ExceptionMessageKey.String(tt.errTest.Error()))
|
||||
assertEqual(t, codes.Error, spanData.Status.Code)
|
||||
assertEqual(t, tt.errTest.Error(), spanData.Status.Description)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTracingHook_ProcessHook(t *testing.T) {
|
||||
imsb := tracetest.NewInMemoryExporter()
|
||||
provider := sdktrace.NewTracerProvider(sdktrace.WithSyncer(imsb))
|
||||
hook := newTracingHook(
|
||||
"redis://localhost:6379",
|
||||
WithTracerProvider(provider),
|
||||
)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
errTest error
|
||||
}{
|
||||
{"nil error", nil},
|
||||
{"test error", fmt.Errorf("test error")},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
defer imsb.Reset()
|
||||
|
||||
cmd := redis.NewCmd(context.Background(), "ping")
|
||||
processHook := hook.ProcessHook(func(ctx context.Context, cmd redis.Cmder) error {
|
||||
return tt.errTest
|
||||
})
|
||||
assertEqual(t, tt.errTest, processHook(context.Background(), cmd))
|
||||
assertEqual(t, 1, len(imsb.GetSpans()))
|
||||
|
||||
spanData := imsb.GetSpans()[0]
|
||||
assertEqual(t, instrumName, spanData.InstrumentationLibrary.Name)
|
||||
assertEqual(t, "ping", spanData.Name)
|
||||
assertEqual(t, trace.SpanKindClient, spanData.SpanKind)
|
||||
assertAttributeContains(t, spanData.Attributes, semconv.DBSystemRedis)
|
||||
assertAttributeContains(t, spanData.Attributes, semconv.DBConnectionStringKey.String("redis://localhost:6379"))
|
||||
assertAttributeContains(t, spanData.Attributes, semconv.DBStatementKey.String("ping"))
|
||||
|
||||
if tt.errTest == nil {
|
||||
assertEqual(t, 0, len(spanData.Events))
|
||||
assertEqual(t, codes.Unset, spanData.Status.Code)
|
||||
assertEqual(t, "", spanData.Status.Description)
|
||||
return
|
||||
}
|
||||
|
||||
assertEqual(t, 1, len(spanData.Events))
|
||||
assertAttributeContains(t, spanData.Events[0].Attributes, semconv.ExceptionTypeKey.String("*errors.errorString"))
|
||||
assertAttributeContains(t, spanData.Events[0].Attributes, semconv.ExceptionMessageKey.String(tt.errTest.Error()))
|
||||
assertEqual(t, codes.Error, spanData.Status.Code)
|
||||
assertEqual(t, tt.errTest.Error(), spanData.Status.Description)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTracingHook_ProcessPipelineHook(t *testing.T) {
|
||||
imsb := tracetest.NewInMemoryExporter()
|
||||
provider := sdktrace.NewTracerProvider(sdktrace.WithSyncer(imsb))
|
||||
hook := newTracingHook(
|
||||
"redis://localhost:6379",
|
||||
WithTracerProvider(provider),
|
||||
)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
errTest error
|
||||
}{
|
||||
{"nil error", nil},
|
||||
{"test error", fmt.Errorf("test error")},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
defer imsb.Reset()
|
||||
|
||||
cmds := []redis.Cmder{
|
||||
redis.NewCmd(context.Background(), "ping"),
|
||||
redis.NewCmd(context.Background(), "ping"),
|
||||
}
|
||||
processHook := hook.ProcessPipelineHook(func(ctx context.Context, cmds []redis.Cmder) error {
|
||||
return tt.errTest
|
||||
})
|
||||
assertEqual(t, tt.errTest, processHook(context.Background(), cmds))
|
||||
assertEqual(t, 1, len(imsb.GetSpans()))
|
||||
|
||||
spanData := imsb.GetSpans()[0]
|
||||
assertEqual(t, instrumName, spanData.InstrumentationLibrary.Name)
|
||||
assertEqual(t, "redis.pipeline ping", spanData.Name)
|
||||
assertEqual(t, trace.SpanKindClient, spanData.SpanKind)
|
||||
assertAttributeContains(t, spanData.Attributes, semconv.DBSystemRedis)
|
||||
assertAttributeContains(t, spanData.Attributes, semconv.DBConnectionStringKey.String("redis://localhost:6379"))
|
||||
assertAttributeContains(t, spanData.Attributes, semconv.DBStatementKey.String("ping\nping"))
|
||||
|
||||
if tt.errTest == nil {
|
||||
assertEqual(t, 0, len(spanData.Events))
|
||||
assertEqual(t, codes.Unset, spanData.Status.Code)
|
||||
assertEqual(t, "", spanData.Status.Description)
|
||||
return
|
||||
}
|
||||
|
||||
assertEqual(t, 1, len(spanData.Events))
|
||||
assertAttributeContains(t, spanData.Events[0].Attributes, semconv.ExceptionTypeKey.String("*errors.errorString"))
|
||||
assertAttributeContains(t, spanData.Events[0].Attributes, semconv.ExceptionMessageKey.String(tt.errTest.Error()))
|
||||
assertEqual(t, codes.Error, spanData.Status.Code)
|
||||
assertEqual(t, tt.errTest.Error(), spanData.Status.Description)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func assertEqual(t *testing.T, expected, actual interface{}) {
|
||||
t.Helper()
|
||||
if expected != actual {
|
||||
t.Fatalf("expected %v, got %v", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func assertAttributeContains(t *testing.T, attrs []attribute.KeyValue, attr attribute.KeyValue) {
|
||||
t.Helper()
|
||||
for _, a := range attrs {
|
||||
if a == attr {
|
||||
return
|
||||
}
|
||||
}
|
||||
t.Fatalf("attribute %v not found", attr)
|
||||
}
|
@ -1,17 +1,17 @@
|
||||
module github.com/redis/go-redis/extra/redisprometheus/v9
|
||||
|
||||
go 1.17
|
||||
go 1.19
|
||||
|
||||
replace github.com/redis/go-redis/v9 => ../..
|
||||
|
||||
require (
|
||||
github.com/prometheus/client_golang v1.14.0
|
||||
github.com/redis/go-redis/v9 v9.4.0
|
||||
github.com/redis/go-redis/v9 v9.7.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
@ -19,5 +19,7 @@ require (
|
||||
github.com/prometheus/common v0.39.0 // indirect
|
||||
github.com/prometheus/procfs v0.9.0 // indirect
|
||||
golang.org/x/sys v0.4.0 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
google.golang.org/protobuf v1.33.0 // indirect
|
||||
)
|
||||
|
||||
retract v9.5.3 // This version was accidentally released.
|
||||
|
@ -1,9 +1,9 @@
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bsm/ginkgo/v2 v2.7.0 h1:ItPMPH90RbmZJt5GtkcNvIRuGEdwlBItdNVoyzaNQao=
|
||||
github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
@ -29,5 +29,5 @@ golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
|
@ -34,6 +34,7 @@ func libCodeWithConfig(libName string) string {
|
||||
return fmt.Sprintf(lib, libName)
|
||||
}
|
||||
|
||||
// TODO: Drop Gears
|
||||
var _ = Describe("RedisGears commands", Label("gears"), func() {
|
||||
ctx := context.TODO()
|
||||
var client *redis.Client
|
||||
@ -49,6 +50,7 @@ var _ = Describe("RedisGears commands", Label("gears"), func() {
|
||||
})
|
||||
|
||||
It("should TFunctionLoad, TFunctionLoadArgs and TFunctionDelete ", Label("gears", "tfunctionload"), func() {
|
||||
SkipAfterRedisVersion(7.4, "gears are not working in later versions")
|
||||
resultAdd, err := client.TFunctionLoad(ctx, libCode("lib1")).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(resultAdd).To(BeEquivalentTo("OK"))
|
||||
@ -58,6 +60,7 @@ var _ = Describe("RedisGears commands", Label("gears"), func() {
|
||||
Expect(resultAdd).To(BeEquivalentTo("OK"))
|
||||
})
|
||||
It("should TFunctionList", Label("gears", "tfunctionlist"), func() {
|
||||
SkipAfterRedisVersion(7.4, "gears are not working in later versions")
|
||||
resultAdd, err := client.TFunctionLoad(ctx, libCode("lib1")).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(resultAdd).To(BeEquivalentTo("OK"))
|
||||
@ -71,6 +74,7 @@ var _ = Describe("RedisGears commands", Label("gears"), func() {
|
||||
})
|
||||
|
||||
It("should TFCall", Label("gears", "tfcall"), func() {
|
||||
SkipAfterRedisVersion(7.4, "gears are not working in later versions")
|
||||
var resultAdd interface{}
|
||||
resultAdd, err := client.TFunctionLoad(ctx, libCode("lib1")).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
@ -81,6 +85,7 @@ var _ = Describe("RedisGears commands", Label("gears"), func() {
|
||||
})
|
||||
|
||||
It("should TFCallArgs", Label("gears", "tfcallargs"), func() {
|
||||
SkipAfterRedisVersion(7.4, "gears are not working in later versions")
|
||||
var resultAdd interface{}
|
||||
resultAdd, err := client.TFunctionLoad(ctx, libCode("lib1")).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
@ -92,6 +97,7 @@ var _ = Describe("RedisGears commands", Label("gears"), func() {
|
||||
})
|
||||
|
||||
It("should TFCallASYNC", Label("gears", "TFCallASYNC"), func() {
|
||||
SkipAfterRedisVersion(7.4, "gears are not working in later versions")
|
||||
var resultAdd interface{}
|
||||
resultAdd, err := client.TFunctionLoad(ctx, libCode("lib1")).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
@ -102,6 +108,7 @@ var _ = Describe("RedisGears commands", Label("gears"), func() {
|
||||
})
|
||||
|
||||
It("should TFCallASYNCArgs", Label("gears", "TFCallASYNCargs"), func() {
|
||||
SkipAfterRedisVersion(7.4, "gears are not working in later versions")
|
||||
var resultAdd interface{}
|
||||
resultAdd, err := client.TFunctionLoad(ctx, libCode("lib1")).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
@ -19,6 +19,7 @@ type GenericCmdable interface {
|
||||
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
|
||||
@ -159,6 +160,12 @@ func (c cmdable) Move(ctx context.Context, key string, db int) *BoolCmd {
|
||||
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)
|
||||
|
7
go.mod
7
go.mod
@ -5,6 +5,11 @@ go 1.18
|
||||
require (
|
||||
github.com/bsm/ginkgo/v2 v2.12.0
|
||||
github.com/bsm/gomega v1.27.10
|
||||
github.com/cespare/xxhash/v2 v2.2.0
|
||||
github.com/cespare/xxhash/v2 v2.3.0
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f
|
||||
)
|
||||
|
||||
retract (
|
||||
v9.5.4 // This version was accidentally released. Please use version 9.6.0 instead.
|
||||
v9.5.3 // This version was accidentally released. Please use version 9.6.0 instead.
|
||||
)
|
||||
|
4
go.sum
4
go.sum
@ -2,7 +2,7 @@ github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
|
284
hash_commands.go
284
hash_commands.go
@ -1,6 +1,9 @@
|
||||
package redis
|
||||
|
||||
import "context"
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
type HashCmdable interface {
|
||||
HDel(ctx context.Context, key string, fields ...string) *IntCmd
|
||||
@ -16,9 +19,24 @@ type HashCmdable interface {
|
||||
HMSet(ctx context.Context, key string, values ...interface{}) *BoolCmd
|
||||
HSetNX(ctx context.Context, key, field string, value interface{}) *BoolCmd
|
||||
HScan(ctx context.Context, key string, cursor uint64, match string, count int64) *ScanCmd
|
||||
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 {
|
||||
@ -172,3 +190,267 @@ func (c cmdable) HScan(ctx context.Context, key string, cursor uint64, match str
|
||||
_ = 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...)
|
||||
_ = 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.
|
||||
// For more information - 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.
|
||||
// For more information - 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.
|
||||
// For more information - 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
|
||||
}
|
||||
|
||||
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.
|
||||
// For more information - 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.
|
||||
// For more information - https://redis.io/commands/hpexpireat/
|
||||
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.
|
||||
// For more information - 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.
|
||||
// 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.
|
||||
// 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.
|
||||
// For more information - 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.
|
||||
// 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
|
||||
}
|
||||
|
@ -168,9 +168,12 @@ func (p *ConnPool) newConn(ctx context.Context, pooled bool) (*Conn, error) {
|
||||
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 {
|
||||
@ -180,6 +183,11 @@ func (p *ConnPool) newConn(ctx context.Context, pooled bool) (*Conn, error) {
|
||||
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.
|
||||
|
@ -292,8 +292,8 @@ var _ = Describe("race", func() {
|
||||
BeforeEach(func() {
|
||||
C, N = 10, 1000
|
||||
if testing.Short() {
|
||||
C = 4
|
||||
N = 100
|
||||
C = 2
|
||||
N = 50
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -66,56 +66,95 @@ func (w *Writer) WriteArg(v interface{}) error {
|
||||
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 {
|
||||
@ -123,6 +162,9 @@ func (w *Writer) WriteArg(v interface{}) error {
|
||||
}
|
||||
return w.int(0)
|
||||
case *bool:
|
||||
if v == nil {
|
||||
return w.int(0)
|
||||
}
|
||||
if *v {
|
||||
return w.int(1)
|
||||
}
|
||||
@ -130,8 +172,19 @@ func (w *Writer) WriteArg(v interface{}) error {
|
||||
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 {
|
||||
|
@ -111,36 +111,61 @@ var _ = Describe("WriteArg", func() {
|
||||
wr = proto.NewWriter(buf)
|
||||
})
|
||||
|
||||
t := time.Date(2025, 2, 8, 00, 00, 00, 0, time.UTC)
|
||||
|
||||
args := map[any]string{
|
||||
"hello": "$5\r\nhello\r\n",
|
||||
int(10): "$2\r\n10\r\n",
|
||||
util.ToPtr(int(10)): "$2\r\n10\r\n",
|
||||
int8(10): "$2\r\n10\r\n",
|
||||
util.ToPtr(int8(10)): "$2\r\n10\r\n",
|
||||
int16(10): "$2\r\n10\r\n",
|
||||
util.ToPtr(int16(10)): "$2\r\n10\r\n",
|
||||
int32(10): "$2\r\n10\r\n",
|
||||
util.ToPtr(int32(10)): "$2\r\n10\r\n",
|
||||
int64(10): "$2\r\n10\r\n",
|
||||
util.ToPtr(int64(10)): "$2\r\n10\r\n",
|
||||
uint(10): "$2\r\n10\r\n",
|
||||
util.ToPtr(uint(10)): "$2\r\n10\r\n",
|
||||
uint8(10): "$2\r\n10\r\n",
|
||||
util.ToPtr(uint8(10)): "$2\r\n10\r\n",
|
||||
uint16(10): "$2\r\n10\r\n",
|
||||
util.ToPtr(uint16(10)): "$2\r\n10\r\n",
|
||||
uint32(10): "$2\r\n10\r\n",
|
||||
util.ToPtr(uint32(10)): "$2\r\n10\r\n",
|
||||
uint64(10): "$2\r\n10\r\n",
|
||||
util.ToPtr(uint64(10)): "$2\r\n10\r\n",
|
||||
float32(10.3): "$18\r\n10.300000190734863\r\n",
|
||||
util.ToPtr(float32(10.3)): "$18\r\n10.300000190734863\r\n",
|
||||
float64(10.3): "$4\r\n10.3\r\n",
|
||||
util.ToPtr(float64(10.3)): "$4\r\n10.3\r\n",
|
||||
bool(true): "$1\r\n1\r\n",
|
||||
bool(false): "$1\r\n0\r\n",
|
||||
util.ToPtr(bool(true)): "$1\r\n1\r\n",
|
||||
util.ToPtr(bool(false)): "$1\r\n0\r\n",
|
||||
"hello": "$5\r\nhello\r\n",
|
||||
util.ToPtr("hello"): "$5\r\nhello\r\n",
|
||||
(*string)(nil): "$0\r\n\r\n",
|
||||
int(10): "$2\r\n10\r\n",
|
||||
util.ToPtr(int(10)): "$2\r\n10\r\n",
|
||||
(*int)(nil): "$1\r\n0\r\n",
|
||||
int8(10): "$2\r\n10\r\n",
|
||||
util.ToPtr(int8(10)): "$2\r\n10\r\n",
|
||||
(*int8)(nil): "$1\r\n0\r\n",
|
||||
int16(10): "$2\r\n10\r\n",
|
||||
util.ToPtr(int16(10)): "$2\r\n10\r\n",
|
||||
(*int16)(nil): "$1\r\n0\r\n",
|
||||
int32(10): "$2\r\n10\r\n",
|
||||
util.ToPtr(int32(10)): "$2\r\n10\r\n",
|
||||
(*int32)(nil): "$1\r\n0\r\n",
|
||||
int64(10): "$2\r\n10\r\n",
|
||||
util.ToPtr(int64(10)): "$2\r\n10\r\n",
|
||||
(*int64)(nil): "$1\r\n0\r\n",
|
||||
uint(10): "$2\r\n10\r\n",
|
||||
util.ToPtr(uint(10)): "$2\r\n10\r\n",
|
||||
(*uint)(nil): "$1\r\n0\r\n",
|
||||
uint8(10): "$2\r\n10\r\n",
|
||||
util.ToPtr(uint8(10)): "$2\r\n10\r\n",
|
||||
(*uint8)(nil): "$0\r\n\r\n",
|
||||
uint16(10): "$2\r\n10\r\n",
|
||||
util.ToPtr(uint16(10)): "$2\r\n10\r\n",
|
||||
(*uint16)(nil): "$1\r\n0\r\n",
|
||||
uint32(10): "$2\r\n10\r\n",
|
||||
util.ToPtr(uint32(10)): "$2\r\n10\r\n",
|
||||
(*uint32)(nil): "$1\r\n0\r\n",
|
||||
uint64(10): "$2\r\n10\r\n",
|
||||
util.ToPtr(uint64(10)): "$2\r\n10\r\n",
|
||||
(*uint64)(nil): "$1\r\n0\r\n",
|
||||
float32(10.3): "$18\r\n10.300000190734863\r\n",
|
||||
util.ToPtr(float32(10.3)): "$18\r\n10.300000190734863\r\n",
|
||||
(*float32)(nil): "$1\r\n0\r\n",
|
||||
float64(10.3): "$4\r\n10.3\r\n",
|
||||
util.ToPtr(float64(10.3)): "$4\r\n10.3\r\n",
|
||||
(*float64)(nil): "$1\r\n0\r\n",
|
||||
bool(true): "$1\r\n1\r\n",
|
||||
bool(false): "$1\r\n0\r\n",
|
||||
util.ToPtr(bool(true)): "$1\r\n1\r\n",
|
||||
util.ToPtr(bool(false)): "$1\r\n0\r\n",
|
||||
(*bool)(nil): "$1\r\n0\r\n",
|
||||
time.Time(t): "$20\r\n2025-02-08T00:00:00Z\r\n",
|
||||
util.ToPtr(time.Time(t)): "$20\r\n2025-02-08T00:00:00Z\r\n",
|
||||
(*time.Time)(nil): "$20\r\n0001-01-01T00:00:00Z\r\n",
|
||||
time.Duration(time.Second): "$10\r\n1000000000\r\n",
|
||||
util.ToPtr(time.Duration(time.Second)): "$10\r\n1000000000\r\n",
|
||||
(*time.Duration)(nil): "$1\r\n0\r\n",
|
||||
(encoding.BinaryMarshaler)(&MyType{}): "$5\r\nhello\r\n",
|
||||
(encoding.BinaryMarshaler)(nil): "$0\r\n\r\n",
|
||||
}
|
||||
|
||||
for arg, expect := range args {
|
||||
|
@ -2,6 +2,9 @@ package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/redis/go-redis/v9/internal/util"
|
||||
@ -44,3 +47,82 @@ func isLower(s string) bool {
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func ReplaceSpaces(s string) string {
|
||||
// Pre-allocate a builder with the same length as s to minimize allocations.
|
||||
// This is a basic optimization; adjust the initial size based on your use case.
|
||||
var builder strings.Builder
|
||||
builder.Grow(len(s))
|
||||
|
||||
for _, char := range s {
|
||||
if char == ' ' {
|
||||
// Replace space with a hyphen.
|
||||
builder.WriteRune('-')
|
||||
} else {
|
||||
// Copy the character as-is.
|
||||
builder.WriteRune(char)
|
||||
}
|
||||
}
|
||||
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
74
internal/util_test.go
Normal file
74
internal/util_test.go
Normal file
@ -0,0 +1,74 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
. "github.com/bsm/ginkgo/v2"
|
||||
. "github.com/bsm/gomega"
|
||||
)
|
||||
|
||||
func BenchmarkToLowerStd(b *testing.B) {
|
||||
str := "AaBbCcDdEeFfGgHhIiJjKk"
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = strings.ToLower(str)
|
||||
}
|
||||
}
|
||||
|
||||
// util.ToLower is 3x faster than strings.ToLower.
|
||||
func BenchmarkToLowerInternal(b *testing.B) {
|
||||
str := "AaBbCcDdEeFfGgHhIiJjKk"
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = ToLower(str)
|
||||
}
|
||||
}
|
||||
|
||||
func TestToLower(t *testing.T) {
|
||||
It("toLower", func() {
|
||||
str := "AaBbCcDdEeFfGg"
|
||||
Expect(ToLower(str)).To(Equal(strings.ToLower(str)))
|
||||
|
||||
str = "ABCDE"
|
||||
Expect(ToLower(str)).To(Equal(strings.ToLower(str)))
|
||||
|
||||
str = "ABCDE"
|
||||
Expect(ToLower(str)).To(Equal(strings.ToLower(str)))
|
||||
|
||||
str = "abced"
|
||||
Expect(ToLower(str)).To(Equal(strings.ToLower(str)))
|
||||
})
|
||||
}
|
||||
|
||||
func TestIsLower(t *testing.T) {
|
||||
It("isLower", func() {
|
||||
str := "AaBbCcDdEeFfGg"
|
||||
Expect(isLower(str)).To(BeFalse())
|
||||
|
||||
str = "ABCDE"
|
||||
Expect(isLower(str)).To(BeFalse())
|
||||
|
||||
str = "abcdefg"
|
||||
Expect(isLower(str)).To(BeTrue())
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetAddr(t *testing.T) {
|
||||
It("getAddr", func() {
|
||||
str := "127.0.0.1:1234"
|
||||
Expect(GetAddr(str)).To(Equal(str))
|
||||
|
||||
str = "[::1]:1234"
|
||||
Expect(GetAddr(str)).To(Equal(str))
|
||||
|
||||
str = "[fd01:abcd::7d03]:6379"
|
||||
Expect(GetAddr(str)).To(Equal(str))
|
||||
|
||||
Expect(GetAddr("::1:1234")).To(Equal("[::1]:1234"))
|
||||
|
||||
Expect(GetAddr("fd01:abcd::7d03:6379")).To(Equal("[fd01:abcd::7d03]:6379"))
|
||||
|
||||
Expect(GetAddr("127.0.0.1")).To(Equal(""))
|
||||
|
||||
Expect(GetAddr("127")).To(Equal(""))
|
||||
})
|
||||
}
|
@ -85,6 +85,7 @@ var _ = Describe("ScanIterator", func() {
|
||||
})
|
||||
|
||||
It("should hscan across multiple pages", func() {
|
||||
SkipBeforeRedisVersion(7.4, "doesn't work with older redis stack images")
|
||||
Expect(hashSeed(71)).NotTo(HaveOccurred())
|
||||
|
||||
var vals []string
|
||||
@ -96,6 +97,23 @@ var _ = Describe("ScanIterator", func() {
|
||||
Expect(vals).To(HaveLen(71 * 2))
|
||||
Expect(vals).To(ContainElement("K01"))
|
||||
Expect(vals).To(ContainElement("K71"))
|
||||
Expect(vals).To(ContainElement("x"))
|
||||
})
|
||||
|
||||
It("should hscan without values across multiple pages", Label("NonRedisEnterprise"), func() {
|
||||
SkipBeforeRedisVersion(7.4, "doesn't work with older redis stack images")
|
||||
Expect(hashSeed(71)).NotTo(HaveOccurred())
|
||||
|
||||
var vals []string
|
||||
iter := client.HScanNoValues(ctx, hashKey, 0, "", 10).Iterator()
|
||||
for iter.Next(ctx) {
|
||||
vals = append(vals, iter.Val())
|
||||
}
|
||||
Expect(iter.Err()).NotTo(HaveOccurred())
|
||||
Expect(vals).To(HaveLen(71))
|
||||
Expect(vals).To(ContainElement("K01"))
|
||||
Expect(vals).To(ContainElement("K71"))
|
||||
Expect(vals).NotTo(ContainElement("x"))
|
||||
})
|
||||
|
||||
It("should scan to page borders", func() {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user