mirror of https://github.com/go-redis/redis.git
Merge branch 'master' of https://github.com/redis/go-redis into search-support
This commit is contained in:
commit
b0616719b1
|
@ -0,0 +1 @@
|
|||
doctests/* @dmaier-redislabs
|
|
@ -16,7 +16,7 @@ jobs:
|
|||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
go-version: [1.19.x, 1.20.x]
|
||||
go-version: [1.19.x, 1.20.x, 1.21.x]
|
||||
|
||||
services:
|
||||
redis:
|
||||
|
@ -33,7 +33,7 @@ jobs:
|
|||
go-version: ${{ matrix.go-version }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Test
|
||||
run: make test
|
||||
|
|
|
@ -25,7 +25,7 @@ jobs:
|
|||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
go-version: [ "1.18", "1.19", "1.20" ]
|
||||
go-version: [ "1.18", "1.19", "1.20", "1.21" ]
|
||||
|
||||
steps:
|
||||
- name: Set up ${{ matrix.go-version }}
|
||||
|
@ -34,7 +34,7 @@ jobs:
|
|||
go-version: ${{ matrix.go-version }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Test doc examples
|
||||
working-directory: ./doctests
|
||||
|
|
|
@ -21,6 +21,6 @@ jobs:
|
|||
name: lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
|
|
|
@ -6,9 +6,9 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
- name: Check Spelling
|
||||
uses: rojopolis/spellcheck-github-actions@0.33.1
|
||||
uses: rojopolis/spellcheck-github-actions@0.34.0
|
||||
with:
|
||||
config_path: .github/spellcheck-settings.yml
|
||||
task_name: Markdown
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
name: "Close stale issues"
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
|
||||
permissions: {}
|
||||
jobs:
|
||||
stale:
|
||||
permissions:
|
||||
issues: write # to close stale issues (actions/stale)
|
||||
pull-requests: write # to close stale PRs (actions/stale)
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v8
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
stale-issue-message: 'This issue is marked stale. It will be closed in 30 days if it is not updated.'
|
||||
stale-pr-message: 'This pull request is marked stale. It will be closed in 30 days if it is not updated.'
|
||||
days-before-stale: 365
|
||||
days-before-close: 30
|
||||
stale-issue-label: "Stale"
|
||||
stale-pr-label: "Stale"
|
||||
operations-per-run: 10
|
||||
remove-stale-when-updated: true
|
|
@ -2,3 +2,5 @@
|
|||
testdata/*
|
||||
.idea/
|
||||
.DS_Store
|
||||
*.tar.gz
|
||||
*.dic
|
|
@ -0,0 +1,101 @@
|
|||
# Contributing
|
||||
|
||||
## Introduction
|
||||
|
||||
We appreciate your interest in considering contributing to go-redis.
|
||||
Community contributions mean a lot to us.
|
||||
|
||||
## Contributions we need
|
||||
|
||||
You may already know how you'd like to contribute, whether it's a fix for a bug you
|
||||
encountered, or a new feature your team wants to use.
|
||||
|
||||
If you don't know where to start, consider improving
|
||||
documentation, bug triaging, and writing tutorials are all examples of
|
||||
helpful contributions that mean less work for you.
|
||||
|
||||
## Your First Contribution
|
||||
|
||||
Unsure where to begin contributing? You can start by looking through
|
||||
[help-wanted
|
||||
issues](https://github.com/redis/go-redis/issues?q=is%3Aopen+is%3Aissue+label%3ahelp-wanted).
|
||||
|
||||
Never contributed to open source before? Here are a couple of friendly
|
||||
tutorials:
|
||||
|
||||
- <http://makeapullrequest.com/>
|
||||
- <http://www.firsttimersonly.com/>
|
||||
|
||||
## Getting Started
|
||||
|
||||
Here's how to get started with your code contribution:
|
||||
|
||||
1. Create your own fork of go-redis
|
||||
2. Do the changes in your fork
|
||||
3. If you need a development environment, run `make 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`
|
||||
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.
|
||||
|
||||
Continuous Integration uses these same wrappers to run all of these
|
||||
tests against multiple versions of python. Feel free to test your
|
||||
changes against all the go versions supported, as declared by the
|
||||
[build.yml](./.github/workflows/build.yml) file.
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
If you get any errors when running `make test`, make sure
|
||||
that you are using supported versions of Docker and go.
|
||||
|
||||
## How to Report a Bug
|
||||
|
||||
### Security Vulnerabilities
|
||||
|
||||
**NOTE**: If you find a security vulnerability, do NOT open an issue.
|
||||
Email [Redis Open Source (<oss@redis.com>)](mailto:oss@redis.com) instead.
|
||||
|
||||
In order to determine whether you are dealing with a security issue, ask
|
||||
yourself these two questions:
|
||||
|
||||
- Can I access something that's not mine, or something I shouldn't
|
||||
have access to?
|
||||
- Can I disable something for other people?
|
||||
|
||||
If the answer to either of those two questions are *yes*, then you're
|
||||
probably dealing with a security issue. Note that even if you answer
|
||||
*no* to both questions, you may still be dealing with a security
|
||||
issue, so if you're unsure, just email [us](mailto:oss@redis.com).
|
||||
|
||||
### Everything Else
|
||||
|
||||
When filing an issue, make sure to answer these five questions:
|
||||
|
||||
1. What version of go-redis are you using?
|
||||
2. What version of redis are you using?
|
||||
3. What did you do?
|
||||
4. What did you expect to see?
|
||||
5. What did you see instead?
|
||||
|
||||
## Suggest a feature or enhancement
|
||||
|
||||
If you'd like to contribute a new feature, make sure you check our
|
||||
issue list to see if someone has already proposed it. Work may already
|
||||
be underway on the feature you want or we may have rejected a
|
||||
feature like it already.
|
||||
|
||||
If you don't see anything, open a new issue that describes the feature
|
||||
you would like and how it should work.
|
||||
|
||||
## Code review process
|
||||
|
||||
The core team regularly looks at pull requests. We will provide
|
||||
feedback as soon as possible. After receiving our feedback, please respond
|
||||
within two weeks. After that time, we may close your PR if it isn't
|
||||
showing any activity.
|
9
Makefile
9
Makefile
|
@ -19,17 +19,20 @@ testdeps: testdata/redis/src/redis-server
|
|||
bench: testdeps
|
||||
go test ./... -test.run=NONE -test.bench=. -test.benchmem
|
||||
|
||||
.PHONY: all test testdeps bench
|
||||
.PHONY: all test testdeps bench fmt
|
||||
|
||||
build:
|
||||
go build .
|
||||
|
||||
testdata/redis:
|
||||
mkdir -p $@
|
||||
wget -qO- https://download.redis.io/releases/redis-7.2-rc3.tar.gz | tar xvz --strip-components=1 -C $@
|
||||
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:
|
||||
gofmt -w -s ./
|
||||
gofumpt -w ./
|
||||
goimports -w -local github.com/redis/go-redis ./
|
||||
|
||||
go_mod_tidy:
|
||||
|
|
39
README.md
39
README.md
|
@ -10,8 +10,22 @@
|
|||
> use it to monitor applications and set up automatic alerts to receive notifications via email,
|
||||
> Slack, Telegram, and others.
|
||||
>
|
||||
> See [OpenTelemetry](example/otel) example which demonstrates how you can use Uptrace to monitor
|
||||
> go-redis.
|
||||
> See [OpenTelemetry](https://github.com/redis/go-redis/tree/master/example/otel) example which
|
||||
> demonstrates how you can use Uptrace to monitor go-redis.
|
||||
|
||||
## How do I Redis?
|
||||
|
||||
[Learn for free at Redis University](https://university.redis.com/)
|
||||
|
||||
[Build faster with the Redis Launchpad](https://launchpad.redis.com/)
|
||||
|
||||
[Try the Redis Cloud](https://redis.com/try-free/)
|
||||
|
||||
[Dive in developer tutorials](https://developer.redis.com/)
|
||||
|
||||
[Join the Redis community](https://redis.com/community/)
|
||||
|
||||
[Work at Redis](https://redis.com/company/careers/jobs/)
|
||||
|
||||
## Documentation
|
||||
|
||||
|
@ -69,8 +83,9 @@ go get github.com/redis/go-redis/v9
|
|||
```go
|
||||
import (
|
||||
"context"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"fmt"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
var ctx = context.Background()
|
||||
|
@ -106,7 +121,8 @@ func ExampleClient() {
|
|||
}
|
||||
```
|
||||
|
||||
The above can be modified to specify the version of the RESP protocol by adding the `protocol` option to the `Options` struct:
|
||||
The above can be modified to specify the version of the RESP protocol by adding the `protocol`
|
||||
option to the `Options` struct:
|
||||
|
||||
```go
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
|
@ -120,13 +136,17 @@ The above can be modified to specify the version of the RESP protocol by adding
|
|||
|
||||
### Connecting via a redis url
|
||||
|
||||
go-redis also supports connecting via the [redis uri specification](https://github.com/redis/redis-specifications/tree/master/uri/redis.txt). The example below demonstrates how the connection can easily be configured using a string, adhering to this specification.
|
||||
go-redis also supports connecting via the
|
||||
[redis uri specification](https://github.com/redis/redis-specifications/tree/master/uri/redis.txt).
|
||||
The example below demonstrates how the connection can easily be configured using a string, adhering
|
||||
to this specification.
|
||||
|
||||
```go
|
||||
import (
|
||||
"context"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"fmt"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
var ctx = context.Background()
|
||||
|
@ -140,6 +160,10 @@ func ExampleClient() {
|
|||
rdb := redis.NewClient(opts)
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
Please see [out contributing guidelines](CONTRIBUTING.md) to help us improve this library!
|
||||
|
||||
## Look and feel
|
||||
|
||||
Some corner cases:
|
||||
|
@ -202,7 +226,8 @@ Lastly, run:
|
|||
go test
|
||||
```
|
||||
|
||||
Another option is to run your specific tests with an already running redis. The example below, tests against a redis running on port 9999.:
|
||||
Another option is to run your specific tests with an already running redis. The example below, tests
|
||||
against a redis running on port 9999.:
|
||||
|
||||
```shell
|
||||
REDIS_PORT=9999 go test <your options>
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
package redis
|
||||
|
||||
import "context"
|
||||
|
||||
type ACLCmdable interface {
|
||||
ACLDryRun(ctx context.Context, username string, command ...interface{}) *StringCmd
|
||||
ACLLog(ctx context.Context, count int64) *ACLLogCmd
|
||||
ACLLogReset(ctx context.Context) *StatusCmd
|
||||
}
|
||||
|
||||
func (c cmdable) ACLDryRun(ctx context.Context, username string, command ...interface{}) *StringCmd {
|
||||
args := make([]interface{}, 0, 3+len(command))
|
||||
args = append(args, "acl", "dryrun", username)
|
||||
args = append(args, command...)
|
||||
cmd := NewStringCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ACLLog(ctx context.Context, count int64) *ACLLogCmd {
|
||||
args := make([]interface{}, 0, 3)
|
||||
args = append(args, "acl", "log")
|
||||
if count > 0 {
|
||||
args = append(args, count)
|
||||
}
|
||||
cmd := NewACLLogCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ACLLogReset(ctx context.Context) *StatusCmd {
|
||||
cmd := NewStatusCmd(ctx, "acl", "log", "reset")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
|
@ -30,6 +30,7 @@ func NewClientStub(resp []byte) *ClientStub {
|
|||
Dialer: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
return stub.stubConn(initHello), nil
|
||||
},
|
||||
DisableIndentity: true,
|
||||
})
|
||||
return stub
|
||||
}
|
||||
|
@ -45,6 +46,8 @@ func NewClusterClientStub(resp []byte) *ClientStub {
|
|||
Dialer: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
return stub.stubConn(initHello), nil
|
||||
},
|
||||
DisableIndentity: true,
|
||||
|
||||
ClusterSlots: func(_ context.Context) ([]ClusterSlot, error) {
|
||||
return []ClusterSlot{
|
||||
{
|
||||
|
|
|
@ -0,0 +1,129 @@
|
|||
package redis
|
||||
|
||||
import "context"
|
||||
|
||||
type BitMapCmdable interface {
|
||||
GetBit(ctx context.Context, key string, offset int64) *IntCmd
|
||||
SetBit(ctx context.Context, key string, offset int64, value int) *IntCmd
|
||||
BitCount(ctx context.Context, key string, bitCount *BitCount) *IntCmd
|
||||
BitOpAnd(ctx context.Context, destKey string, keys ...string) *IntCmd
|
||||
BitOpOr(ctx context.Context, destKey string, keys ...string) *IntCmd
|
||||
BitOpXor(ctx context.Context, destKey string, keys ...string) *IntCmd
|
||||
BitOpNot(ctx context.Context, destKey string, key string) *IntCmd
|
||||
BitPos(ctx context.Context, key string, bit int64, pos ...int64) *IntCmd
|
||||
BitPosSpan(ctx context.Context, key string, bit int8, start, end int64, span string) *IntCmd
|
||||
BitField(ctx context.Context, key string, values ...interface{}) *IntSliceCmd
|
||||
}
|
||||
|
||||
func (c cmdable) GetBit(ctx context.Context, key string, offset int64) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "getbit", key, offset)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) SetBit(ctx context.Context, key string, offset int64, value int) *IntCmd {
|
||||
cmd := NewIntCmd(
|
||||
ctx,
|
||||
"setbit",
|
||||
key,
|
||||
offset,
|
||||
value,
|
||||
)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
type BitCount struct {
|
||||
Start, End int64
|
||||
}
|
||||
|
||||
func (c cmdable) BitCount(ctx context.Context, key string, bitCount *BitCount) *IntCmd {
|
||||
args := []interface{}{"bitcount", key}
|
||||
if bitCount != nil {
|
||||
args = append(
|
||||
args,
|
||||
bitCount.Start,
|
||||
bitCount.End,
|
||||
)
|
||||
}
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) bitOp(ctx context.Context, op, destKey string, keys ...string) *IntCmd {
|
||||
args := make([]interface{}, 3+len(keys))
|
||||
args[0] = "bitop"
|
||||
args[1] = op
|
||||
args[2] = destKey
|
||||
for i, key := range keys {
|
||||
args[3+i] = key
|
||||
}
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) BitOpAnd(ctx context.Context, destKey string, keys ...string) *IntCmd {
|
||||
return c.bitOp(ctx, "and", destKey, keys...)
|
||||
}
|
||||
|
||||
func (c cmdable) BitOpOr(ctx context.Context, destKey string, keys ...string) *IntCmd {
|
||||
return c.bitOp(ctx, "or", destKey, keys...)
|
||||
}
|
||||
|
||||
func (c cmdable) BitOpXor(ctx context.Context, destKey string, keys ...string) *IntCmd {
|
||||
return c.bitOp(ctx, "xor", destKey, keys...)
|
||||
}
|
||||
|
||||
func (c cmdable) BitOpNot(ctx context.Context, destKey string, key string) *IntCmd {
|
||||
return c.bitOp(ctx, "not", destKey, key)
|
||||
}
|
||||
|
||||
// BitPos is an API before Redis version 7.0, cmd: bitpos key bit start end
|
||||
// if you need the `byte | bit` parameter, please use `BitPosSpan`.
|
||||
func (c cmdable) BitPos(ctx context.Context, key string, bit int64, pos ...int64) *IntCmd {
|
||||
args := make([]interface{}, 3+len(pos))
|
||||
args[0] = "bitpos"
|
||||
args[1] = key
|
||||
args[2] = bit
|
||||
switch len(pos) {
|
||||
case 0:
|
||||
case 1:
|
||||
args[3] = pos[0]
|
||||
case 2:
|
||||
args[3] = pos[0]
|
||||
args[4] = pos[1]
|
||||
default:
|
||||
panic("too many arguments")
|
||||
}
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// BitPosSpan supports the `byte | bit` parameters in redis version 7.0,
|
||||
// the bitpos command defaults to using byte type for the `start-end` range,
|
||||
// which means it counts in bytes from start to end. you can set the value
|
||||
// of "span" to determine the type of `start-end`.
|
||||
// span = "bit", cmd: bitpos key bit start end bit
|
||||
// span = "byte", cmd: bitpos key bit start end byte
|
||||
func (c cmdable) BitPosSpan(ctx context.Context, key string, bit int8, start, end int64, span string) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "bitpos", key, bit, start, end, span)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// BitField accepts multiple values:
|
||||
// - BitField("set", "i1", "offset1", "value1","cmd2", "type2", "offset2", "value2")
|
||||
// - BitField([]string{"cmd1", "type1", "offset1", "value1","cmd2", "type2", "offset2", "value2"})
|
||||
// - BitField([]interface{}{"cmd1", "type1", "offset1", "value1","cmd2", "type2", "offset2", "value2"})
|
||||
func (c cmdable) BitField(ctx context.Context, key string, values ...interface{}) *IntSliceCmd {
|
||||
args := make([]interface{}, 2, 2+len(values))
|
||||
args[0] = "bitfield"
|
||||
args[1] = key
|
||||
args = appendArgs(args, values)
|
||||
cmd := NewIntSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
|
@ -1,109 +1,192 @@
|
|||
package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
import "context"
|
||||
|
||||
func (c *ClusterClient) DBSize(ctx context.Context) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "dbsize")
|
||||
_ = c.withProcessHook(ctx, cmd, func(ctx context.Context, _ Cmder) error {
|
||||
var size int64
|
||||
err := c.ForEachMaster(ctx, func(ctx context.Context, master *Client) error {
|
||||
n, err := master.DBSize(ctx).Result()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
atomic.AddInt64(&size, n)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
cmd.SetErr(err)
|
||||
} else {
|
||||
cmd.val = size
|
||||
}
|
||||
return nil
|
||||
})
|
||||
type ClusterCmdable interface {
|
||||
ClusterMyShardID(ctx context.Context) *StringCmd
|
||||
ClusterSlots(ctx context.Context) *ClusterSlotsCmd
|
||||
ClusterShards(ctx context.Context) *ClusterShardsCmd
|
||||
ClusterLinks(ctx context.Context) *ClusterLinksCmd
|
||||
ClusterNodes(ctx context.Context) *StringCmd
|
||||
ClusterMeet(ctx context.Context, host, port string) *StatusCmd
|
||||
ClusterForget(ctx context.Context, nodeID string) *StatusCmd
|
||||
ClusterReplicate(ctx context.Context, nodeID string) *StatusCmd
|
||||
ClusterResetSoft(ctx context.Context) *StatusCmd
|
||||
ClusterResetHard(ctx context.Context) *StatusCmd
|
||||
ClusterInfo(ctx context.Context) *StringCmd
|
||||
ClusterKeySlot(ctx context.Context, key string) *IntCmd
|
||||
ClusterGetKeysInSlot(ctx context.Context, slot int, count int) *StringSliceCmd
|
||||
ClusterCountFailureReports(ctx context.Context, nodeID string) *IntCmd
|
||||
ClusterCountKeysInSlot(ctx context.Context, slot int) *IntCmd
|
||||
ClusterDelSlots(ctx context.Context, slots ...int) *StatusCmd
|
||||
ClusterDelSlotsRange(ctx context.Context, min, max int) *StatusCmd
|
||||
ClusterSaveConfig(ctx context.Context) *StatusCmd
|
||||
ClusterSlaves(ctx context.Context, nodeID string) *StringSliceCmd
|
||||
ClusterFailover(ctx context.Context) *StatusCmd
|
||||
ClusterAddSlots(ctx context.Context, slots ...int) *StatusCmd
|
||||
ClusterAddSlotsRange(ctx context.Context, min, max int) *StatusCmd
|
||||
ReadOnly(ctx context.Context) *StatusCmd
|
||||
ReadWrite(ctx context.Context) *StatusCmd
|
||||
}
|
||||
|
||||
func (c cmdable) ClusterMyShardID(ctx context.Context) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "cluster", "myshardid")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c *ClusterClient) ScriptLoad(ctx context.Context, script string) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "script", "load", script)
|
||||
_ = c.withProcessHook(ctx, cmd, func(ctx context.Context, _ Cmder) error {
|
||||
var mu sync.Mutex
|
||||
err := c.ForEachShard(ctx, func(ctx context.Context, shard *Client) error {
|
||||
val, err := shard.ScriptLoad(ctx, script).Result()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
if cmd.Val() == "" {
|
||||
cmd.val = val
|
||||
}
|
||||
mu.Unlock()
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
cmd.SetErr(err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
func (c cmdable) ClusterSlots(ctx context.Context) *ClusterSlotsCmd {
|
||||
cmd := NewClusterSlotsCmd(ctx, "cluster", "slots")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c *ClusterClient) ScriptFlush(ctx context.Context) *StatusCmd {
|
||||
cmd := NewStatusCmd(ctx, "script", "flush")
|
||||
_ = c.withProcessHook(ctx, cmd, func(ctx context.Context, _ Cmder) error {
|
||||
err := c.ForEachShard(ctx, func(ctx context.Context, shard *Client) error {
|
||||
return shard.ScriptFlush(ctx).Err()
|
||||
})
|
||||
if err != nil {
|
||||
cmd.SetErr(err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
func (c cmdable) ClusterShards(ctx context.Context) *ClusterShardsCmd {
|
||||
cmd := NewClusterShardsCmd(ctx, "cluster", "shards")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c *ClusterClient) ScriptExists(ctx context.Context, hashes ...string) *BoolSliceCmd {
|
||||
args := make([]interface{}, 2+len(hashes))
|
||||
args[0] = "script"
|
||||
args[1] = "exists"
|
||||
for i, hash := range hashes {
|
||||
args[2+i] = hash
|
||||
func (c cmdable) ClusterLinks(ctx context.Context) *ClusterLinksCmd {
|
||||
cmd := NewClusterLinksCmd(ctx, "cluster", "links")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ClusterNodes(ctx context.Context) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "cluster", "nodes")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ClusterMeet(ctx context.Context, host, port string) *StatusCmd {
|
||||
cmd := NewStatusCmd(ctx, "cluster", "meet", host, port)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ClusterForget(ctx context.Context, nodeID string) *StatusCmd {
|
||||
cmd := NewStatusCmd(ctx, "cluster", "forget", nodeID)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ClusterReplicate(ctx context.Context, nodeID string) *StatusCmd {
|
||||
cmd := NewStatusCmd(ctx, "cluster", "replicate", nodeID)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ClusterResetSoft(ctx context.Context) *StatusCmd {
|
||||
cmd := NewStatusCmd(ctx, "cluster", "reset", "soft")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ClusterResetHard(ctx context.Context) *StatusCmd {
|
||||
cmd := NewStatusCmd(ctx, "cluster", "reset", "hard")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ClusterInfo(ctx context.Context) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "cluster", "info")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ClusterKeySlot(ctx context.Context, key string) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "cluster", "keyslot", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ClusterGetKeysInSlot(ctx context.Context, slot int, count int) *StringSliceCmd {
|
||||
cmd := NewStringSliceCmd(ctx, "cluster", "getkeysinslot", slot, count)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ClusterCountFailureReports(ctx context.Context, nodeID string) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "cluster", "count-failure-reports", nodeID)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ClusterCountKeysInSlot(ctx context.Context, slot int) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "cluster", "countkeysinslot", slot)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ClusterDelSlots(ctx context.Context, slots ...int) *StatusCmd {
|
||||
args := make([]interface{}, 2+len(slots))
|
||||
args[0] = "cluster"
|
||||
args[1] = "delslots"
|
||||
for i, slot := range slots {
|
||||
args[2+i] = slot
|
||||
}
|
||||
cmd := NewBoolSliceCmd(ctx, args...)
|
||||
|
||||
result := make([]bool, len(hashes))
|
||||
for i := range result {
|
||||
result[i] = true
|
||||
}
|
||||
|
||||
_ = c.withProcessHook(ctx, cmd, func(ctx context.Context, _ Cmder) error {
|
||||
var mu sync.Mutex
|
||||
err := c.ForEachShard(ctx, func(ctx context.Context, shard *Client) error {
|
||||
val, err := shard.ScriptExists(ctx, hashes...).Result()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
for i, v := range val {
|
||||
result[i] = result[i] && v
|
||||
}
|
||||
mu.Unlock()
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
cmd.SetErr(err)
|
||||
} else {
|
||||
cmd.val = result
|
||||
}
|
||||
return nil
|
||||
})
|
||||
cmd := NewStatusCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ClusterDelSlotsRange(ctx context.Context, min, max int) *StatusCmd {
|
||||
size := max - min + 1
|
||||
slots := make([]int, size)
|
||||
for i := 0; i < size; i++ {
|
||||
slots[i] = min + i
|
||||
}
|
||||
return c.ClusterDelSlots(ctx, slots...)
|
||||
}
|
||||
|
||||
func (c cmdable) ClusterSaveConfig(ctx context.Context) *StatusCmd {
|
||||
cmd := NewStatusCmd(ctx, "cluster", "saveconfig")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ClusterSlaves(ctx context.Context, nodeID string) *StringSliceCmd {
|
||||
cmd := NewStringSliceCmd(ctx, "cluster", "slaves", nodeID)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ClusterFailover(ctx context.Context) *StatusCmd {
|
||||
cmd := NewStatusCmd(ctx, "cluster", "failover")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ClusterAddSlots(ctx context.Context, slots ...int) *StatusCmd {
|
||||
args := make([]interface{}, 2+len(slots))
|
||||
args[0] = "cluster"
|
||||
args[1] = "addslots"
|
||||
for i, num := range slots {
|
||||
args[2+i] = num
|
||||
}
|
||||
cmd := NewStatusCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ClusterAddSlotsRange(ctx context.Context, min, max int) *StatusCmd {
|
||||
size := max - min + 1
|
||||
slots := make([]int, size)
|
||||
for i := 0; i < size; i++ {
|
||||
slots[i] = min + i
|
||||
}
|
||||
return c.ClusterAddSlots(ctx, slots...)
|
||||
}
|
||||
|
||||
func (c cmdable) ReadOnly(ctx context.Context) *StatusCmd {
|
||||
cmd := NewStatusCmd(ctx, "readonly")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ReadWrite(ctx context.Context) *StatusCmd {
|
||||
cmd := NewStatusCmd(ctx, "readwrite")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
|
91
command.go
91
command.go
|
@ -1,9 +1,11 @@
|
|||
package redis
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -4324,7 +4326,6 @@ func (cmd *FunctionStatsCmd) readDuration(rd *proto.Reader) (time.Duration, erro
|
|||
}
|
||||
|
||||
func (cmd *FunctionStatsCmd) readCommand(rd *proto.Reader) ([]string, error) {
|
||||
|
||||
n, err := rd.ReadArrayLen()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -4341,6 +4342,7 @@ func (cmd *FunctionStatsCmd) readCommand(rd *proto.Reader) ([]string, error) {
|
|||
|
||||
return command, nil
|
||||
}
|
||||
|
||||
func (cmd *FunctionStatsCmd) readRunningScripts(rd *proto.Reader) ([]RunningScript, bool, error) {
|
||||
n, err := rd.ReadArrayLen()
|
||||
if err != nil {
|
||||
|
@ -5292,3 +5294,90 @@ func (cmd *ACLLogCmd) readReply(rd *proto.Reader) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
// LibraryInfo holds the library info.
|
||||
type LibraryInfo struct {
|
||||
LibName *string
|
||||
LibVer *string
|
||||
}
|
||||
|
||||
// -------------------------------------------
|
||||
|
||||
type InfoCmd struct {
|
||||
baseCmd
|
||||
val map[string]map[string]string
|
||||
}
|
||||
|
||||
var _ Cmder = (*InfoCmd)(nil)
|
||||
|
||||
func NewInfoCmd(ctx context.Context, args ...interface{}) *InfoCmd {
|
||||
return &InfoCmd{
|
||||
baseCmd: baseCmd{
|
||||
ctx: ctx,
|
||||
args: args,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *InfoCmd) SetVal(val map[string]map[string]string) {
|
||||
cmd.val = val
|
||||
}
|
||||
|
||||
func (cmd *InfoCmd) Val() map[string]map[string]string {
|
||||
return cmd.val
|
||||
}
|
||||
|
||||
func (cmd *InfoCmd) Result() (map[string]map[string]string, error) {
|
||||
return cmd.Val(), cmd.Err()
|
||||
}
|
||||
|
||||
func (cmd *InfoCmd) String() string {
|
||||
return cmdString(cmd, cmd.val)
|
||||
}
|
||||
|
||||
func (cmd *InfoCmd) readReply(rd *proto.Reader) error {
|
||||
val, err := rd.ReadString()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
section := ""
|
||||
scanner := bufio.NewScanner(strings.NewReader(val))
|
||||
moduleRe := regexp.MustCompile(`module:name=(.+?),(.+)$`)
|
||||
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if strings.HasPrefix(line, "#") {
|
||||
if cmd.val == nil {
|
||||
cmd.val = make(map[string]map[string]string)
|
||||
}
|
||||
section = strings.TrimPrefix(line, "# ")
|
||||
cmd.val[section] = make(map[string]string)
|
||||
} else if line != "" {
|
||||
if section == "Modules" {
|
||||
kv := moduleRe.FindStringSubmatch(line)
|
||||
if len(kv) == 3 {
|
||||
cmd.val[section][kv[1]] = kv[2]
|
||||
}
|
||||
} else {
|
||||
kv := strings.SplitN(line, ":", 2)
|
||||
if len(kv) == 2 {
|
||||
cmd.val[section][kv[0]] = kv[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (cmd *InfoCmd) Item(section, key string) string {
|
||||
if cmd.val == nil {
|
||||
return ""
|
||||
} else if cmd.val[section] == nil {
|
||||
return ""
|
||||
} else {
|
||||
return cmd.val[section][key]
|
||||
}
|
||||
}
|
||||
|
|
3411
commands.go
3411
commands.go
File diff suppressed because it is too large
Load Diff
105
commands_test.go
105
commands_test.go
|
@ -95,6 +95,18 @@ var _ = Describe("Commands", func() {
|
|||
Expect(time.Now()).To(BeTemporally("~", start.Add(wait), 3*time.Second))
|
||||
})
|
||||
|
||||
It("should WaitAOF", func() {
|
||||
const waitAOF = 3 * time.Second
|
||||
Skip("flaky test")
|
||||
|
||||
// assuming that the redis instance doesn't have AOF enabled
|
||||
start := time.Now()
|
||||
val, err := client.WaitAOF(ctx, 1, 1, waitAOF).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(val).NotTo(ContainSubstring("ERR WAITAOF cannot be used when numlocal is set but appendonly is disabled"))
|
||||
Expect(time.Now()).To(BeTemporally("~", start.Add(waitAOF), 3*time.Second))
|
||||
})
|
||||
|
||||
It("should Select", func() {
|
||||
pipe := client.Pipeline()
|
||||
sel := pipe.Select(ctx, 1)
|
||||
|
@ -231,6 +243,56 @@ var _ = Describe("Commands", func() {
|
|||
Expect(get.Val()).To(Equal("theclientname"))
|
||||
})
|
||||
|
||||
It("should ClientSetInfo", func() {
|
||||
pipe := client.Pipeline()
|
||||
|
||||
// Test setting the libName
|
||||
libName := "go-redis"
|
||||
libInfo := redis.LibraryInfo{LibName: &libName}
|
||||
setInfo := pipe.ClientSetInfo(ctx, libInfo)
|
||||
_, err := pipe.Exec(ctx)
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(setInfo.Err()).NotTo(HaveOccurred())
|
||||
Expect(setInfo.Val()).To(Equal("OK"))
|
||||
|
||||
// Test setting the libVer
|
||||
libVer := "vX.x"
|
||||
libInfo = redis.LibraryInfo{LibVer: &libVer}
|
||||
setInfo = pipe.ClientSetInfo(ctx, libInfo)
|
||||
_, err = pipe.Exec(ctx)
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(setInfo.Err()).NotTo(HaveOccurred())
|
||||
Expect(setInfo.Val()).To(Equal("OK"))
|
||||
|
||||
// Test setting both fields, expect a panic
|
||||
libInfo = redis.LibraryInfo{LibName: &libName, LibVer: &libVer}
|
||||
|
||||
Expect(func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err := r.(error)
|
||||
Expect(err).To(MatchError("both LibName and LibVer cannot be set at the same time"))
|
||||
}
|
||||
}()
|
||||
pipe.ClientSetInfo(ctx, libInfo)
|
||||
}).To(Panic())
|
||||
|
||||
// Test setting neither field, expect a panic
|
||||
libInfo = redis.LibraryInfo{}
|
||||
|
||||
Expect(func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err := r.(error)
|
||||
Expect(err).To(MatchError("at least one of LibName and LibVer should be set"))
|
||||
}
|
||||
}()
|
||||
pipe.ClientSetInfo(ctx, libInfo)
|
||||
}).To(Panic())
|
||||
})
|
||||
|
||||
It("should ConfigGet", func() {
|
||||
val, err := client.ConfigGet(ctx, "*").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
@ -273,6 +335,20 @@ var _ = Describe("Commands", func() {
|
|||
Expect(info.Val()).NotTo(Equal(""))
|
||||
})
|
||||
|
||||
It("should InfoMap", Label("redis.info"), func() {
|
||||
info := client.InfoMap(ctx)
|
||||
Expect(info.Err()).NotTo(HaveOccurred())
|
||||
Expect(info.Val()).NotTo(BeNil())
|
||||
|
||||
info = client.InfoMap(ctx, "dummy")
|
||||
Expect(info.Err()).NotTo(HaveOccurred())
|
||||
Expect(info.Val()).To(BeNil())
|
||||
|
||||
info = client.InfoMap(ctx, "server")
|
||||
Expect(info.Err()).NotTo(HaveOccurred())
|
||||
Expect(info.Val()).To(HaveLen(1))
|
||||
})
|
||||
|
||||
It("should Info cpu", func() {
|
||||
info := client.Info(ctx, "cpu")
|
||||
Expect(info.Err()).NotTo(HaveOccurred())
|
||||
|
@ -362,7 +438,6 @@ var _ = Describe("Commands", func() {
|
|||
})
|
||||
|
||||
It("should filter commands by ACL category", func() {
|
||||
|
||||
filter := &redis.FilterBy{
|
||||
ACLCat: "admin",
|
||||
}
|
||||
|
@ -529,7 +604,6 @@ var _ = Describe("Commands", func() {
|
|||
n, err = client.Exists(ctx, "key").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(n).To(Equal(int64(0)))
|
||||
|
||||
})
|
||||
|
||||
It("should Keys", func() {
|
||||
|
@ -676,7 +750,6 @@ var _ = Describe("Commands", func() {
|
|||
})
|
||||
|
||||
It("should PExpireTime", func() {
|
||||
|
||||
// The command returns -1 if the key exists but has no associated expiration time.
|
||||
// The command returns -2 if the key does not exist.
|
||||
pExpireTime := client.PExpireTime(ctx, "key")
|
||||
|
@ -915,7 +988,6 @@ var _ = Describe("Commands", func() {
|
|||
})
|
||||
|
||||
It("should ExpireTime", func() {
|
||||
|
||||
// The command returns -1 if the key exists but has no associated expiration time.
|
||||
// The command returns -2 if the key does not exist.
|
||||
expireTimeCmd := client.ExpireTime(ctx, "key")
|
||||
|
@ -937,7 +1009,6 @@ var _ = Describe("Commands", func() {
|
|||
})
|
||||
|
||||
It("should TTL", func() {
|
||||
|
||||
// The command returns -1 if the key exists but has no associated expire
|
||||
// The command returns -2 if the key does not exist.
|
||||
ttl := client.TTL(ctx, "key")
|
||||
|
@ -1202,6 +1273,10 @@ var _ = Describe("Commands", func() {
|
|||
nn, err := client.BitField(ctx, "mykey", "INCRBY", "i5", 100, 1, "GET", "u4", 0).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(nn).To(Equal([]int64{1, 0}))
|
||||
|
||||
nn, err = client.BitField(ctx, "mykey", "set", "i1", 1, 1, "GET", "u4", 0).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(nn).To(Equal([]int64{0, 4}))
|
||||
})
|
||||
|
||||
It("should Decr", func() {
|
||||
|
@ -1987,7 +2062,6 @@ var _ = Describe("Commands", func() {
|
|||
})
|
||||
|
||||
It("should ACL LOG", func() {
|
||||
|
||||
err := client.Do(ctx, "acl", "setuser", "test", ">test", "on", "allkeys", "+get").Err()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
|
@ -2001,10 +2075,9 @@ var _ = Describe("Commands", func() {
|
|||
|
||||
logEntries, err := client.ACLLog(ctx, 10).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(logEntries)).To(Equal(3))
|
||||
Expect(len(logEntries)).To(Equal(4))
|
||||
|
||||
for _, entry := range logEntries {
|
||||
Expect(entry.Count).To(BeNumerically("==", 1))
|
||||
Expect(entry.Reason).To(Equal("command"))
|
||||
Expect(entry.Context).To(Equal("toplevel"))
|
||||
Expect(entry.Object).NotTo(BeEmpty())
|
||||
|
@ -2019,7 +2092,6 @@ var _ = Describe("Commands", func() {
|
|||
limitedLogEntries, err := client.ACLLog(ctx, 2).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(limitedLogEntries)).To(Equal(2))
|
||||
|
||||
})
|
||||
|
||||
It("should ACL LOG RESET", func() {
|
||||
|
@ -2033,7 +2105,6 @@ var _ = Describe("Commands", func() {
|
|||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(logEntries)).To(Equal(0))
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
Describe("hashes", func() {
|
||||
|
@ -2645,7 +2716,6 @@ var _ = Describe("Commands", func() {
|
|||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(key).To(Equal("list2"))
|
||||
Expect(val).To(Equal([]string{"a", "b", "c", "d"}))
|
||||
|
||||
})
|
||||
|
||||
It("should BLMPopBlocks", func() {
|
||||
|
@ -2667,7 +2737,7 @@ var _ = Describe("Commands", func() {
|
|||
case <-done:
|
||||
Fail("BLMPop is not blocked")
|
||||
case <-time.After(time.Second):
|
||||
//ok
|
||||
// ok
|
||||
}
|
||||
|
||||
_, err := client.LPush(ctx, "list_list", "a").Result()
|
||||
|
@ -2675,7 +2745,7 @@ var _ = Describe("Commands", func() {
|
|||
|
||||
select {
|
||||
case <-done:
|
||||
//ok
|
||||
// ok
|
||||
case <-time.After(time.Second):
|
||||
Fail("BLMPop is still blocked")
|
||||
}
|
||||
|
@ -4130,7 +4200,6 @@ var _ = Describe("Commands", func() {
|
|||
})
|
||||
|
||||
It("should ZMPop", func() {
|
||||
|
||||
err := client.ZAdd(ctx, "zset", redis.Z{Score: 1, Member: "one"}).Err()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
err = client.ZAdd(ctx, "zset", redis.Z{Score: 2, Member: "two"}).Err()
|
||||
|
@ -4202,11 +4271,9 @@ var _ = Describe("Commands", func() {
|
|||
Score: 6,
|
||||
Member: "six",
|
||||
}}))
|
||||
|
||||
})
|
||||
|
||||
It("should BZMPop", func() {
|
||||
|
||||
err := client.ZAdd(ctx, "zset", redis.Z{Score: 1, Member: "one"}).Err()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
err = client.ZAdd(ctx, "zset", redis.Z{Score: 2, Member: "two"}).Err()
|
||||
|
@ -4306,7 +4373,7 @@ var _ = Describe("Commands", func() {
|
|||
case <-done:
|
||||
Fail("BZMPop is not blocked")
|
||||
case <-time.After(time.Second):
|
||||
//ok
|
||||
// ok
|
||||
}
|
||||
|
||||
err := client.ZAdd(ctx, "list_list", redis.Z{Score: 1, Member: "one"}).Err()
|
||||
|
@ -4314,7 +4381,7 @@ var _ = Describe("Commands", func() {
|
|||
|
||||
select {
|
||||
case <-done:
|
||||
//ok
|
||||
// ok
|
||||
case <-time.After(time.Second):
|
||||
Fail("BZMPop is still blocked")
|
||||
}
|
||||
|
@ -6874,7 +6941,6 @@ var _ = Describe("Commands", func() {
|
|||
|
||||
close(started)
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
Describe("SlowLogGet", func() {
|
||||
|
@ -6895,7 +6961,6 @@ var _ = Describe("Commands", func() {
|
|||
Expect(len(result)).NotTo(BeZero())
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
type numberStruct struct {
|
||||
|
|
|
@ -5,10 +5,11 @@ package example_commands_test
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
func ExampleLPushLRange() {
|
||||
func ExampleClient_LPush_and_lrange() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
|
|
|
@ -5,10 +5,11 @@ package example_commands_test
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
func ExampleSetGet() {
|
||||
func ExampleClient_Set_and_get() {
|
||||
ctx := context.Background()
|
||||
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
|
|
7
error.go
7
error.go
|
@ -2,6 +2,7 @@ package redis
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
|
@ -15,11 +16,11 @@ var ErrClosed = pool.ErrClosed
|
|||
|
||||
// HasErrorPrefix checks if the err is a Redis error and the message contains a prefix.
|
||||
func HasErrorPrefix(err error, prefix string) bool {
|
||||
err, ok := err.(Error)
|
||||
if !ok {
|
||||
var rErr Error
|
||||
if !errors.As(err, &rErr) {
|
||||
return false
|
||||
}
|
||||
msg := err.Error()
|
||||
msg := rErr.Error()
|
||||
msg = strings.TrimPrefix(msg, "ERR ") // KVRocks adds such prefix
|
||||
return strings.HasPrefix(msg, prefix)
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ go 1.18
|
|||
replace github.com/redis/go-redis/v9 => ../..
|
||||
|
||||
require (
|
||||
github.com/redis/go-redis/v9 v9.1.0
|
||||
github.com/redis/go-redis/v9 v9.3.0
|
||||
go.uber.org/zap v1.24.0
|
||||
)
|
||||
|
||||
|
|
|
@ -6,8 +6,9 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
|
|
@ -4,7 +4,7 @@ go 1.18
|
|||
|
||||
replace github.com/redis/go-redis/v9 => ../..
|
||||
|
||||
require github.com/redis/go-redis/v9 v9.1.0
|
||||
require github.com/redis/go-redis/v9 v9.3.0
|
||||
|
||||
require (
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
|
|
|
@ -4,7 +4,7 @@ go 1.18
|
|||
|
||||
replace github.com/redis/go-redis/v9 => ../..
|
||||
|
||||
require github.com/redis/go-redis/v9 v9.1.0
|
||||
require github.com/redis/go-redis/v9 v9.3.0
|
||||
|
||||
require (
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
|
|
|
@ -9,8 +9,8 @@ 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.1.0
|
||||
github.com/redis/go-redis/v9 v9.1.0
|
||||
github.com/redis/go-redis/extra/redisotel/v9 v9.3.0
|
||||
github.com/redis/go-redis/v9 v9.3.0
|
||||
github.com/uptrace/uptrace-go v1.16.0
|
||||
go.opentelemetry.io/otel v1.16.0
|
||||
)
|
||||
|
@ -23,7 +23,7 @@ require (
|
|||
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.15.2 // indirect
|
||||
github.com/redis/go-redis/extra/rediscmd/v9 v9.1.0 // indirect
|
||||
github.com/redis/go-redis/extra/rediscmd/v9 v9.3.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/runtime v0.42.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.16.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.39.0 // indirect
|
||||
|
@ -36,10 +36,10 @@ require (
|
|||
go.opentelemetry.io/otel/sdk/metric v0.39.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.16.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v0.19.0 // indirect
|
||||
golang.org/x/net v0.10.0 // indirect
|
||||
golang.org/x/sys v0.8.0 // indirect
|
||||
golang.org/x/text v0.9.0 // indirect
|
||||
golang.org/x/net v0.17.0 // indirect
|
||||
golang.org/x/sys v0.13.0 // indirect
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
|
||||
google.golang.org/grpc v1.55.0 // indirect
|
||||
google.golang.org/grpc v1.56.3 // indirect
|
||||
google.golang.org/protobuf v1.30.0 // indirect
|
||||
)
|
||||
|
|
|
@ -35,10 +35,12 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
|
|||
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 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/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=
|
||||
|
@ -257,8 +259,8 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R
|
|||
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.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
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=
|
||||
|
@ -301,8 +303,8 @@ golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
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.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||
golang.org/x/sys v0.8.0/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/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=
|
||||
|
@ -310,8 +312,8 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3
|
|||
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.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
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/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=
|
||||
|
@ -430,8 +432,8 @@ google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTp
|
|||
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.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag=
|
||||
google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
|
||||
google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc=
|
||||
google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
|
||||
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=
|
||||
|
|
|
@ -4,7 +4,7 @@ go 1.18
|
|||
|
||||
replace github.com/redis/go-redis/v9 => ../..
|
||||
|
||||
require github.com/redis/go-redis/v9 v9.1.0
|
||||
require github.com/redis/go-redis/v9 v9.3.0
|
||||
|
||||
require (
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
|
|
|
@ -6,7 +6,7 @@ replace github.com/redis/go-redis/v9 => ../..
|
|||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/redis/go-redis/v9 v9.1.0
|
||||
github.com/redis/go-redis/v9 v9.3.0
|
||||
)
|
||||
|
||||
require (
|
||||
|
|
|
@ -8,7 +8,7 @@ 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.1.0
|
||||
github.com/redis/go-redis/v9 v9.1.0
|
||||
github.com/redis/go-redis/extra/rediscmd/v9 v9.3.0
|
||||
github.com/redis/go-redis/v9 v9.3.0
|
||||
go.opencensus.io v0.24.0
|
||||
)
|
||||
|
|
|
@ -7,5 +7,5 @@ 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.1.0
|
||||
github.com/redis/go-redis/v9 v9.3.0
|
||||
)
|
||||
|
|
|
@ -7,8 +7,8 @@ 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.1.0
|
||||
github.com/redis/go-redis/v9 v9.1.0
|
||||
github.com/redis/go-redis/extra/rediscmd/v9 v9.3.0
|
||||
github.com/redis/go-redis/v9 v9.3.0
|
||||
go.opentelemetry.io/otel v1.16.0
|
||||
go.opentelemetry.io/otel/metric v1.16.0
|
||||
go.opentelemetry.io/otel/sdk v1.16.0
|
||||
|
|
|
@ -6,10 +6,11 @@ import (
|
|||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
// InstrumentMetrics starts reporting OpenTelemetry Metrics.
|
||||
|
@ -192,11 +193,13 @@ func (mh *metricsHook) DialHook(hook redis.DialHook) redis.DialHook {
|
|||
|
||||
conn, err := hook(ctx, network, addr)
|
||||
|
||||
dur := time.Since(start)
|
||||
|
||||
attrs := make([]attribute.KeyValue, 0, len(mh.attrs)+1)
|
||||
attrs = append(attrs, mh.attrs...)
|
||||
attrs = append(attrs, statusAttr(err))
|
||||
|
||||
mh.createTime.Record(ctx, milliseconds(time.Since(start)), metric.WithAttributes(attrs...))
|
||||
mh.createTime.Record(ctx, milliseconds(dur), metric.WithAttributes(attrs...))
|
||||
return conn, err
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,12 +29,12 @@ var _ prometheus.Collector = (*Collector)(nil)
|
|||
// The given namespace and subsystem are used to build the fully qualified metric name,
|
||||
// i.e. "{namespace}_{subsystem}_{metric}".
|
||||
// The provided metrics are:
|
||||
// * pool_hit_total
|
||||
// * pool_miss_total
|
||||
// * pool_timeout_total
|
||||
// * pool_conn_total_current
|
||||
// * pool_conn_idle_current
|
||||
// * pool_conn_stale_total
|
||||
// - pool_hit_total
|
||||
// - pool_miss_total
|
||||
// - pool_timeout_total
|
||||
// - pool_conn_total_current
|
||||
// - pool_conn_idle_current
|
||||
// - pool_conn_stale_total
|
||||
func NewCollector(namespace, subsystem string, getter StatGetter) *Collector {
|
||||
return &Collector{
|
||||
getter: getter,
|
||||
|
|
|
@ -6,7 +6,7 @@ replace github.com/redis/go-redis/v9 => ../..
|
|||
|
||||
require (
|
||||
github.com/prometheus/client_golang v1.14.0
|
||||
github.com/redis/go-redis/v9 v9.1.0
|
||||
github.com/redis/go-redis/v9 v9.3.0
|
||||
)
|
||||
|
||||
require (
|
||||
|
|
|
@ -6,7 +6,7 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
type gearsCmdable interface {
|
||||
type GearsCmdable interface {
|
||||
TFunctionLoad(ctx context.Context, lib string) *StatusCmd
|
||||
TFunctionLoadArgs(ctx context.Context, lib string, options *TFunctionLoadOptions) *StatusCmd
|
||||
TFunctionDelete(ctx context.Context, libName string) *StatusCmd
|
||||
|
@ -17,6 +17,7 @@ type gearsCmdable interface {
|
|||
TFCallASYNC(ctx context.Context, libName string, funcName string, numKeys int) *Cmd
|
||||
TFCallASYNCArgs(ctx context.Context, libName string, funcName string, numKeys int, options *TFCallOptions) *Cmd
|
||||
}
|
||||
|
||||
type TFunctionLoadOptions struct {
|
||||
Replace bool
|
||||
Config string
|
||||
|
@ -88,7 +89,6 @@ func (c cmdable) TFunctionListArgs(ctx context.Context, options *TFunctionListOp
|
|||
}
|
||||
if options.Library != "" {
|
||||
args = append(args, "LIBRARY", options.Library)
|
||||
|
||||
}
|
||||
}
|
||||
cmd := NewMapStringInterfaceSliceCmd(ctx, args...)
|
||||
|
@ -110,17 +110,11 @@ func (c cmdable) TFCallArgs(ctx context.Context, libName string, funcName string
|
|||
lf := libName + "." + funcName
|
||||
args := []interface{}{"TFCALL", lf, numKeys}
|
||||
if options != nil {
|
||||
if options.Keys != nil {
|
||||
for _, key := range options.Keys {
|
||||
|
||||
args = append(args, key)
|
||||
}
|
||||
for _, key := range options.Keys {
|
||||
args = append(args, key)
|
||||
}
|
||||
if options.Arguments != nil {
|
||||
for _, key := range options.Arguments {
|
||||
|
||||
args = append(args, key)
|
||||
}
|
||||
for _, key := range options.Arguments {
|
||||
args = append(args, key)
|
||||
}
|
||||
}
|
||||
cmd := NewCmd(ctx, args...)
|
||||
|
@ -142,17 +136,11 @@ func (c cmdable) TFCallASYNCArgs(ctx context.Context, libName string, funcName s
|
|||
lf := fmt.Sprintf("%s.%s", libName, funcName)
|
||||
args := []interface{}{"TFCALLASYNC", lf, numKeys}
|
||||
if options != nil {
|
||||
if options.Keys != nil {
|
||||
for _, key := range options.Keys {
|
||||
|
||||
args = append(args, key)
|
||||
}
|
||||
for _, key := range options.Keys {
|
||||
args = append(args, key)
|
||||
}
|
||||
if options.Arguments != nil {
|
||||
for _, key := range options.Arguments {
|
||||
|
||||
args = append(args, key)
|
||||
}
|
||||
for _, key := range options.Arguments {
|
||||
args = append(args, key)
|
||||
}
|
||||
}
|
||||
cmd := NewCmd(ctx, args...)
|
|
@ -6,6 +6,7 @@ import (
|
|||
|
||||
. "github.com/bsm/ginkgo/v2"
|
||||
. "github.com/bsm/gomega"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
|
@ -48,7 +49,6 @@ var _ = Describe("RedisGears commands", Label("gears"), func() {
|
|||
})
|
||||
|
||||
It("should TFunctionLoad, TFunctionLoadArgs and TFunctionDelete ", Label("gears", "tfunctionload"), func() {
|
||||
|
||||
resultAdd, err := client.TFunctionLoad(ctx, libCode("lib1")).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(resultAdd).To(BeEquivalentTo("OK"))
|
||||
|
@ -56,7 +56,6 @@ var _ = Describe("RedisGears commands", Label("gears"), func() {
|
|||
resultAdd, err = client.TFunctionLoadArgs(ctx, libCodeWithConfig("lib1"), opt).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(resultAdd).To(BeEquivalentTo("OK"))
|
||||
|
||||
})
|
||||
It("should TFunctionList", Label("gears", "tfunctionlist"), func() {
|
||||
resultAdd, err := client.TFunctionLoad(ctx, libCode("lib1")).Result()
|
||||
|
@ -112,5 +111,4 @@ var _ = Describe("RedisGears commands", Label("gears"), func() {
|
|||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(resultAdd).To(BeEquivalentTo("bar"))
|
||||
})
|
||||
|
||||
})
|
|
@ -0,0 +1,377 @@
|
|||
package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
type GenericCmdable interface {
|
||||
Del(ctx context.Context, keys ...string) *IntCmd
|
||||
Dump(ctx context.Context, key string) *StringCmd
|
||||
Exists(ctx context.Context, keys ...string) *IntCmd
|
||||
Expire(ctx context.Context, key string, expiration time.Duration) *BoolCmd
|
||||
ExpireAt(ctx context.Context, key string, tm time.Time) *BoolCmd
|
||||
ExpireTime(ctx context.Context, key string) *DurationCmd
|
||||
ExpireNX(ctx context.Context, key string, expiration time.Duration) *BoolCmd
|
||||
ExpireXX(ctx context.Context, key string, expiration time.Duration) *BoolCmd
|
||||
ExpireGT(ctx context.Context, key string, expiration time.Duration) *BoolCmd
|
||||
ExpireLT(ctx context.Context, key string, expiration time.Duration) *BoolCmd
|
||||
Keys(ctx context.Context, pattern string) *StringSliceCmd
|
||||
Migrate(ctx context.Context, host, port, key string, db int, timeout time.Duration) *StatusCmd
|
||||
Move(ctx context.Context, key string, db int) *BoolCmd
|
||||
ObjectRefCount(ctx context.Context, key string) *IntCmd
|
||||
ObjectEncoding(ctx context.Context, key string) *StringCmd
|
||||
ObjectIdleTime(ctx context.Context, key string) *DurationCmd
|
||||
Persist(ctx context.Context, key string) *BoolCmd
|
||||
PExpire(ctx context.Context, key string, expiration time.Duration) *BoolCmd
|
||||
PExpireAt(ctx context.Context, key string, tm time.Time) *BoolCmd
|
||||
PExpireTime(ctx context.Context, key string) *DurationCmd
|
||||
PTTL(ctx context.Context, key string) *DurationCmd
|
||||
RandomKey(ctx context.Context) *StringCmd
|
||||
Rename(ctx context.Context, key, newkey string) *StatusCmd
|
||||
RenameNX(ctx context.Context, key, newkey string) *BoolCmd
|
||||
Restore(ctx context.Context, key string, ttl time.Duration, value string) *StatusCmd
|
||||
RestoreReplace(ctx context.Context, key string, ttl time.Duration, value string) *StatusCmd
|
||||
Sort(ctx context.Context, key string, sort *Sort) *StringSliceCmd
|
||||
SortRO(ctx context.Context, key string, sort *Sort) *StringSliceCmd
|
||||
SortStore(ctx context.Context, key, store string, sort *Sort) *IntCmd
|
||||
SortInterfaces(ctx context.Context, key string, sort *Sort) *SliceCmd
|
||||
Touch(ctx context.Context, keys ...string) *IntCmd
|
||||
TTL(ctx context.Context, key string) *DurationCmd
|
||||
Type(ctx context.Context, key string) *StatusCmd
|
||||
Copy(ctx context.Context, sourceKey string, destKey string, db int, replace bool) *IntCmd
|
||||
|
||||
Scan(ctx context.Context, cursor uint64, match string, count int64) *ScanCmd
|
||||
ScanType(ctx context.Context, cursor uint64, match string, count int64, keyType string) *ScanCmd
|
||||
}
|
||||
|
||||
func (c cmdable) Del(ctx context.Context, keys ...string) *IntCmd {
|
||||
args := make([]interface{}, 1+len(keys))
|
||||
args[0] = "del"
|
||||
for i, key := range keys {
|
||||
args[1+i] = key
|
||||
}
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) Unlink(ctx context.Context, keys ...string) *IntCmd {
|
||||
args := make([]interface{}, 1+len(keys))
|
||||
args[0] = "unlink"
|
||||
for i, key := range keys {
|
||||
args[1+i] = key
|
||||
}
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) Dump(ctx context.Context, key string) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "dump", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) Exists(ctx context.Context, keys ...string) *IntCmd {
|
||||
args := make([]interface{}, 1+len(keys))
|
||||
args[0] = "exists"
|
||||
for i, key := range keys {
|
||||
args[1+i] = key
|
||||
}
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) Expire(ctx context.Context, key string, expiration time.Duration) *BoolCmd {
|
||||
return c.expire(ctx, key, expiration, "")
|
||||
}
|
||||
|
||||
func (c cmdable) ExpireNX(ctx context.Context, key string, expiration time.Duration) *BoolCmd {
|
||||
return c.expire(ctx, key, expiration, "NX")
|
||||
}
|
||||
|
||||
func (c cmdable) ExpireXX(ctx context.Context, key string, expiration time.Duration) *BoolCmd {
|
||||
return c.expire(ctx, key, expiration, "XX")
|
||||
}
|
||||
|
||||
func (c cmdable) ExpireGT(ctx context.Context, key string, expiration time.Duration) *BoolCmd {
|
||||
return c.expire(ctx, key, expiration, "GT")
|
||||
}
|
||||
|
||||
func (c cmdable) ExpireLT(ctx context.Context, key string, expiration time.Duration) *BoolCmd {
|
||||
return c.expire(ctx, key, expiration, "LT")
|
||||
}
|
||||
|
||||
func (c cmdable) expire(
|
||||
ctx context.Context, key string, expiration time.Duration, mode string,
|
||||
) *BoolCmd {
|
||||
args := make([]interface{}, 3, 4)
|
||||
args[0] = "expire"
|
||||
args[1] = key
|
||||
args[2] = formatSec(ctx, expiration)
|
||||
if mode != "" {
|
||||
args = append(args, mode)
|
||||
}
|
||||
|
||||
cmd := NewBoolCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ExpireAt(ctx context.Context, key string, tm time.Time) *BoolCmd {
|
||||
cmd := NewBoolCmd(ctx, "expireat", key, tm.Unix())
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ExpireTime(ctx context.Context, key string) *DurationCmd {
|
||||
cmd := NewDurationCmd(ctx, time.Second, "expiretime", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) Keys(ctx context.Context, pattern string) *StringSliceCmd {
|
||||
cmd := NewStringSliceCmd(ctx, "keys", pattern)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) Migrate(ctx context.Context, host, port, key string, db int, timeout time.Duration) *StatusCmd {
|
||||
cmd := NewStatusCmd(
|
||||
ctx,
|
||||
"migrate",
|
||||
host,
|
||||
port,
|
||||
key,
|
||||
db,
|
||||
formatMs(ctx, timeout),
|
||||
)
|
||||
cmd.setReadTimeout(timeout)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) Move(ctx context.Context, key string, db int) *BoolCmd {
|
||||
cmd := NewBoolCmd(ctx, "move", key, db)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ObjectRefCount(ctx context.Context, key string) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "object", "refcount", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ObjectEncoding(ctx context.Context, key string) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "object", "encoding", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ObjectIdleTime(ctx context.Context, key string) *DurationCmd {
|
||||
cmd := NewDurationCmd(ctx, time.Second, "object", "idletime", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) Persist(ctx context.Context, key string) *BoolCmd {
|
||||
cmd := NewBoolCmd(ctx, "persist", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) PExpire(ctx context.Context, key string, expiration time.Duration) *BoolCmd {
|
||||
cmd := NewBoolCmd(ctx, "pexpire", key, formatMs(ctx, expiration))
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) PExpireAt(ctx context.Context, key string, tm time.Time) *BoolCmd {
|
||||
cmd := NewBoolCmd(
|
||||
ctx,
|
||||
"pexpireat",
|
||||
key,
|
||||
tm.UnixNano()/int64(time.Millisecond),
|
||||
)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) PExpireTime(ctx context.Context, key string) *DurationCmd {
|
||||
cmd := NewDurationCmd(ctx, time.Millisecond, "pexpiretime", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) PTTL(ctx context.Context, key string) *DurationCmd {
|
||||
cmd := NewDurationCmd(ctx, time.Millisecond, "pttl", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) RandomKey(ctx context.Context) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "randomkey")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) Rename(ctx context.Context, key, newkey string) *StatusCmd {
|
||||
cmd := NewStatusCmd(ctx, "rename", key, newkey)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) RenameNX(ctx context.Context, key, newkey string) *BoolCmd {
|
||||
cmd := NewBoolCmd(ctx, "renamenx", key, newkey)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) Restore(ctx context.Context, key string, ttl time.Duration, value string) *StatusCmd {
|
||||
cmd := NewStatusCmd(
|
||||
ctx,
|
||||
"restore",
|
||||
key,
|
||||
formatMs(ctx, ttl),
|
||||
value,
|
||||
)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) RestoreReplace(ctx context.Context, key string, ttl time.Duration, value string) *StatusCmd {
|
||||
cmd := NewStatusCmd(
|
||||
ctx,
|
||||
"restore",
|
||||
key,
|
||||
formatMs(ctx, ttl),
|
||||
value,
|
||||
"replace",
|
||||
)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
type Sort struct {
|
||||
By string
|
||||
Offset, Count int64
|
||||
Get []string
|
||||
Order string
|
||||
Alpha bool
|
||||
}
|
||||
|
||||
func (sort *Sort) args(command, key string) []interface{} {
|
||||
args := []interface{}{command, key}
|
||||
|
||||
if sort.By != "" {
|
||||
args = append(args, "by", sort.By)
|
||||
}
|
||||
if sort.Offset != 0 || sort.Count != 0 {
|
||||
args = append(args, "limit", sort.Offset, sort.Count)
|
||||
}
|
||||
for _, get := range sort.Get {
|
||||
args = append(args, "get", get)
|
||||
}
|
||||
if sort.Order != "" {
|
||||
args = append(args, sort.Order)
|
||||
}
|
||||
if sort.Alpha {
|
||||
args = append(args, "alpha")
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
func (c cmdable) SortRO(ctx context.Context, key string, sort *Sort) *StringSliceCmd {
|
||||
cmd := NewStringSliceCmd(ctx, sort.args("sort_ro", key)...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) Sort(ctx context.Context, key string, sort *Sort) *StringSliceCmd {
|
||||
cmd := NewStringSliceCmd(ctx, sort.args("sort", key)...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) SortStore(ctx context.Context, key, store string, sort *Sort) *IntCmd {
|
||||
args := sort.args("sort", key)
|
||||
if store != "" {
|
||||
args = append(args, "store", store)
|
||||
}
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) SortInterfaces(ctx context.Context, key string, sort *Sort) *SliceCmd {
|
||||
cmd := NewSliceCmd(ctx, sort.args("sort", key)...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) Touch(ctx context.Context, keys ...string) *IntCmd {
|
||||
args := make([]interface{}, len(keys)+1)
|
||||
args[0] = "touch"
|
||||
for i, key := range keys {
|
||||
args[i+1] = key
|
||||
}
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) TTL(ctx context.Context, key string) *DurationCmd {
|
||||
cmd := NewDurationCmd(ctx, time.Second, "ttl", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) Type(ctx context.Context, key string) *StatusCmd {
|
||||
cmd := NewStatusCmd(ctx, "type", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) Copy(ctx context.Context, sourceKey, destKey string, db int, replace bool) *IntCmd {
|
||||
args := []interface{}{"copy", sourceKey, destKey, "DB", db}
|
||||
if replace {
|
||||
args = append(args, "REPLACE")
|
||||
}
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
func (c cmdable) Scan(ctx context.Context, cursor uint64, match string, count int64) *ScanCmd {
|
||||
args := []interface{}{"scan", cursor}
|
||||
if match != "" {
|
||||
args = append(args, "match", match)
|
||||
}
|
||||
if count > 0 {
|
||||
args = append(args, "count", count)
|
||||
}
|
||||
cmd := NewScanCmd(ctx, c, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ScanType(ctx context.Context, cursor uint64, match string, count int64, keyType string) *ScanCmd {
|
||||
args := []interface{}{"scan", cursor}
|
||||
if match != "" {
|
||||
args = append(args, "match", match)
|
||||
}
|
||||
if count > 0 {
|
||||
args = append(args, "count", count)
|
||||
}
|
||||
if keyType != "" {
|
||||
args = append(args, "type", keyType)
|
||||
}
|
||||
cmd := NewScanCmd(ctx, c, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
|
@ -0,0 +1,155 @@
|
|||
package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
)
|
||||
|
||||
type GeoCmdable interface {
|
||||
GeoAdd(ctx context.Context, key string, geoLocation ...*GeoLocation) *IntCmd
|
||||
GeoPos(ctx context.Context, key string, members ...string) *GeoPosCmd
|
||||
GeoRadius(ctx context.Context, key string, longitude, latitude float64, query *GeoRadiusQuery) *GeoLocationCmd
|
||||
GeoRadiusStore(ctx context.Context, key string, longitude, latitude float64, query *GeoRadiusQuery) *IntCmd
|
||||
GeoRadiusByMember(ctx context.Context, key, member string, query *GeoRadiusQuery) *GeoLocationCmd
|
||||
GeoRadiusByMemberStore(ctx context.Context, key, member string, query *GeoRadiusQuery) *IntCmd
|
||||
GeoSearch(ctx context.Context, key string, q *GeoSearchQuery) *StringSliceCmd
|
||||
GeoSearchLocation(ctx context.Context, key string, q *GeoSearchLocationQuery) *GeoSearchLocationCmd
|
||||
GeoSearchStore(ctx context.Context, key, store string, q *GeoSearchStoreQuery) *IntCmd
|
||||
GeoDist(ctx context.Context, key string, member1, member2, unit string) *FloatCmd
|
||||
GeoHash(ctx context.Context, key string, members ...string) *StringSliceCmd
|
||||
}
|
||||
|
||||
func (c cmdable) GeoAdd(ctx context.Context, key string, geoLocation ...*GeoLocation) *IntCmd {
|
||||
args := make([]interface{}, 2+3*len(geoLocation))
|
||||
args[0] = "geoadd"
|
||||
args[1] = key
|
||||
for i, eachLoc := range geoLocation {
|
||||
args[2+3*i] = eachLoc.Longitude
|
||||
args[2+3*i+1] = eachLoc.Latitude
|
||||
args[2+3*i+2] = eachLoc.Name
|
||||
}
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// GeoRadius is a read-only GEORADIUS_RO command.
|
||||
func (c cmdable) GeoRadius(
|
||||
ctx context.Context, key string, longitude, latitude float64, query *GeoRadiusQuery,
|
||||
) *GeoLocationCmd {
|
||||
cmd := NewGeoLocationCmd(ctx, query, "georadius_ro", key, longitude, latitude)
|
||||
if query.Store != "" || query.StoreDist != "" {
|
||||
cmd.SetErr(errors.New("GeoRadius does not support Store or StoreDist"))
|
||||
return cmd
|
||||
}
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// GeoRadiusStore is a writing GEORADIUS command.
|
||||
func (c cmdable) GeoRadiusStore(
|
||||
ctx context.Context, key string, longitude, latitude float64, query *GeoRadiusQuery,
|
||||
) *IntCmd {
|
||||
args := geoLocationArgs(query, "georadius", key, longitude, latitude)
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
if query.Store == "" && query.StoreDist == "" {
|
||||
cmd.SetErr(errors.New("GeoRadiusStore requires Store or StoreDist"))
|
||||
return cmd
|
||||
}
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// GeoRadiusByMember is a read-only GEORADIUSBYMEMBER_RO command.
|
||||
func (c cmdable) GeoRadiusByMember(
|
||||
ctx context.Context, key, member string, query *GeoRadiusQuery,
|
||||
) *GeoLocationCmd {
|
||||
cmd := NewGeoLocationCmd(ctx, query, "georadiusbymember_ro", key, member)
|
||||
if query.Store != "" || query.StoreDist != "" {
|
||||
cmd.SetErr(errors.New("GeoRadiusByMember does not support Store or StoreDist"))
|
||||
return cmd
|
||||
}
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// GeoRadiusByMemberStore is a writing GEORADIUSBYMEMBER command.
|
||||
func (c cmdable) GeoRadiusByMemberStore(
|
||||
ctx context.Context, key, member string, query *GeoRadiusQuery,
|
||||
) *IntCmd {
|
||||
args := geoLocationArgs(query, "georadiusbymember", key, member)
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
if query.Store == "" && query.StoreDist == "" {
|
||||
cmd.SetErr(errors.New("GeoRadiusByMemberStore requires Store or StoreDist"))
|
||||
return cmd
|
||||
}
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) GeoSearch(ctx context.Context, key string, q *GeoSearchQuery) *StringSliceCmd {
|
||||
args := make([]interface{}, 0, 13)
|
||||
args = append(args, "geosearch", key)
|
||||
args = geoSearchArgs(q, args)
|
||||
cmd := NewStringSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) GeoSearchLocation(
|
||||
ctx context.Context, key string, q *GeoSearchLocationQuery,
|
||||
) *GeoSearchLocationCmd {
|
||||
args := make([]interface{}, 0, 16)
|
||||
args = append(args, "geosearch", key)
|
||||
args = geoSearchLocationArgs(q, args)
|
||||
cmd := NewGeoSearchLocationCmd(ctx, q, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) GeoSearchStore(ctx context.Context, key, store string, q *GeoSearchStoreQuery) *IntCmd {
|
||||
args := make([]interface{}, 0, 15)
|
||||
args = append(args, "geosearchstore", store, key)
|
||||
args = geoSearchArgs(&q.GeoSearchQuery, args)
|
||||
if q.StoreDist {
|
||||
args = append(args, "storedist")
|
||||
}
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) GeoDist(
|
||||
ctx context.Context, key string, member1, member2, unit string,
|
||||
) *FloatCmd {
|
||||
if unit == "" {
|
||||
unit = "km"
|
||||
}
|
||||
cmd := NewFloatCmd(ctx, "geodist", key, member1, member2, unit)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) GeoHash(ctx context.Context, key string, members ...string) *StringSliceCmd {
|
||||
args := make([]interface{}, 2+len(members))
|
||||
args[0] = "geohash"
|
||||
args[1] = key
|
||||
for i, member := range members {
|
||||
args[2+i] = member
|
||||
}
|
||||
cmd := NewStringSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) GeoPos(ctx context.Context, key string, members ...string) *GeoPosCmd {
|
||||
args := make([]interface{}, 2+len(members))
|
||||
args[0] = "geopos"
|
||||
args[1] = key
|
||||
for i, member := range members {
|
||||
args[2+i] = member
|
||||
}
|
||||
cmd := NewGeoPosCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
4
go.mod
4
go.mod
|
@ -3,8 +3,8 @@ module github.com/redis/go-redis/v9
|
|||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/bsm/ginkgo/v2 v2.9.5
|
||||
github.com/bsm/gomega v1.26.0
|
||||
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/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f
|
||||
)
|
||||
|
|
8
go.sum
8
go.sum
|
@ -1,7 +1,7 @@
|
|||
github.com/bsm/ginkgo/v2 v2.9.5 h1:rtVBYPs3+TC5iLUVOis1B9tjLTup7Cj5IfzosKtvTJ0=
|
||||
github.com/bsm/ginkgo/v2 v2.9.5/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||
github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y=
|
||||
github.com/bsm/gomega v1.26.0/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||
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/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
|
|
|
@ -0,0 +1,174 @@
|
|||
package redis
|
||||
|
||||
import "context"
|
||||
|
||||
type HashCmdable interface {
|
||||
HDel(ctx context.Context, key string, fields ...string) *IntCmd
|
||||
HExists(ctx context.Context, key, field string) *BoolCmd
|
||||
HGet(ctx context.Context, key, field string) *StringCmd
|
||||
HGetAll(ctx context.Context, key string) *MapStringStringCmd
|
||||
HIncrBy(ctx context.Context, key, field string, incr int64) *IntCmd
|
||||
HIncrByFloat(ctx context.Context, key, field string, incr float64) *FloatCmd
|
||||
HKeys(ctx context.Context, key string) *StringSliceCmd
|
||||
HLen(ctx context.Context, key string) *IntCmd
|
||||
HMGet(ctx context.Context, key string, fields ...string) *SliceCmd
|
||||
HSet(ctx context.Context, key string, values ...interface{}) *IntCmd
|
||||
HMSet(ctx context.Context, key string, values ...interface{}) *BoolCmd
|
||||
HSetNX(ctx context.Context, key, field string, value interface{}) *BoolCmd
|
||||
HScan(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
|
||||
}
|
||||
|
||||
func (c cmdable) HDel(ctx context.Context, key string, fields ...string) *IntCmd {
|
||||
args := make([]interface{}, 2+len(fields))
|
||||
args[0] = "hdel"
|
||||
args[1] = key
|
||||
for i, field := range fields {
|
||||
args[2+i] = field
|
||||
}
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) HExists(ctx context.Context, key, field string) *BoolCmd {
|
||||
cmd := NewBoolCmd(ctx, "hexists", key, field)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) HGet(ctx context.Context, key, field string) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "hget", key, field)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) HGetAll(ctx context.Context, key string) *MapStringStringCmd {
|
||||
cmd := NewMapStringStringCmd(ctx, "hgetall", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) HIncrBy(ctx context.Context, key, field string, incr int64) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "hincrby", key, field, incr)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) HIncrByFloat(ctx context.Context, key, field string, incr float64) *FloatCmd {
|
||||
cmd := NewFloatCmd(ctx, "hincrbyfloat", key, field, incr)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) HKeys(ctx context.Context, key string) *StringSliceCmd {
|
||||
cmd := NewStringSliceCmd(ctx, "hkeys", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) HLen(ctx context.Context, key string) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "hlen", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// HMGet returns the values for the specified fields in the hash stored at key.
|
||||
// It returns an interface{} to distinguish between empty string and nil value.
|
||||
func (c cmdable) HMGet(ctx context.Context, key string, fields ...string) *SliceCmd {
|
||||
args := make([]interface{}, 2+len(fields))
|
||||
args[0] = "hmget"
|
||||
args[1] = key
|
||||
for i, field := range fields {
|
||||
args[2+i] = field
|
||||
}
|
||||
cmd := NewSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// HSet accepts values in following formats:
|
||||
//
|
||||
// - HSet("myhash", "key1", "value1", "key2", "value2")
|
||||
//
|
||||
// - HSet("myhash", []string{"key1", "value1", "key2", "value2"})
|
||||
//
|
||||
// - HSet("myhash", map[string]interface{}{"key1": "value1", "key2": "value2"})
|
||||
//
|
||||
// Playing struct With "redis" tag.
|
||||
// type MyHash struct { Key1 string `redis:"key1"`; Key2 int `redis:"key2"` }
|
||||
//
|
||||
// - HSet("myhash", MyHash{"value1", "value2"}) Warn: redis-server >= 4.0
|
||||
//
|
||||
// For struct, can be a structure pointer type, we only parse the field whose tag is redis.
|
||||
// if you don't want the field to be read, you can use the `redis:"-"` flag to ignore it,
|
||||
// or you don't need to set the redis tag.
|
||||
// For the type of structure field, we only support simple data types:
|
||||
// string, int/uint(8,16,32,64), float(32,64), time.Time(to RFC3339Nano), time.Duration(to Nanoseconds ),
|
||||
// if you are other more complex or custom data types, please implement the encoding.BinaryMarshaler interface.
|
||||
//
|
||||
// Note that in older versions of Redis server(redis-server < 4.0), HSet only supports a single key-value pair.
|
||||
// redis-docs: https://redis.io/commands/hset (Starting with Redis version 4.0.0: Accepts multiple field and value arguments.)
|
||||
// If you are using a Struct type and the number of fields is greater than one,
|
||||
// you will receive an error similar to "ERR wrong number of arguments", you can use HMSet as a substitute.
|
||||
func (c cmdable) HSet(ctx context.Context, key string, values ...interface{}) *IntCmd {
|
||||
args := make([]interface{}, 2, 2+len(values))
|
||||
args[0] = "hset"
|
||||
args[1] = key
|
||||
args = appendArgs(args, values)
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// HMSet is a deprecated version of HSet left for compatibility with Redis 3.
|
||||
func (c cmdable) HMSet(ctx context.Context, key string, values ...interface{}) *BoolCmd {
|
||||
args := make([]interface{}, 2, 2+len(values))
|
||||
args[0] = "hmset"
|
||||
args[1] = key
|
||||
args = appendArgs(args, values)
|
||||
cmd := NewBoolCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) HSetNX(ctx context.Context, key, field string, value interface{}) *BoolCmd {
|
||||
cmd := NewBoolCmd(ctx, "hsetnx", key, field, value)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) HVals(ctx context.Context, key string) *StringSliceCmd {
|
||||
cmd := NewStringSliceCmd(ctx, "hvals", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// HRandField redis-server version >= 6.2.0.
|
||||
func (c cmdable) HRandField(ctx context.Context, key string, count int) *StringSliceCmd {
|
||||
cmd := NewStringSliceCmd(ctx, "hrandfield", key, count)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// HRandFieldWithValues redis-server version >= 6.2.0.
|
||||
func (c cmdable) HRandFieldWithValues(ctx context.Context, key string, count int) *KeyValueSliceCmd {
|
||||
cmd := NewKeyValueSliceCmd(ctx, "hrandfield", key, count, "withvalues")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) HScan(ctx context.Context, key string, cursor uint64, match string, count int64) *ScanCmd {
|
||||
args := []interface{}{"hscan", key, cursor}
|
||||
if match != "" {
|
||||
args = append(args, "match", match)
|
||||
}
|
||||
if count > 0 {
|
||||
args = append(args, "count", count)
|
||||
}
|
||||
cmd := NewScanCmd(ctx, c, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package redis
|
||||
|
||||
import "context"
|
||||
|
||||
type HyperLogLogCmdable interface {
|
||||
PFAdd(ctx context.Context, key string, els ...interface{}) *IntCmd
|
||||
PFCount(ctx context.Context, keys ...string) *IntCmd
|
||||
PFMerge(ctx context.Context, dest string, keys ...string) *StatusCmd
|
||||
}
|
||||
|
||||
func (c cmdable) PFAdd(ctx context.Context, key string, els ...interface{}) *IntCmd {
|
||||
args := make([]interface{}, 2, 2+len(els))
|
||||
args[0] = "pfadd"
|
||||
args[1] = key
|
||||
args = appendArgs(args, els)
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) PFCount(ctx context.Context, keys ...string) *IntCmd {
|
||||
args := make([]interface{}, 1+len(keys))
|
||||
args[0] = "pfcount"
|
||||
for i, key := range keys {
|
||||
args[1+i] = key
|
||||
}
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) PFMerge(ctx context.Context, dest string, keys ...string) *StatusCmd {
|
||||
args := make([]interface{}, 2+len(keys))
|
||||
args[0] = "pfmerge"
|
||||
args[1] = dest
|
||||
for i, key := range keys {
|
||||
args[2+i] = key
|
||||
}
|
||||
cmd := NewStatusCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
|
@ -4,6 +4,7 @@ import (
|
|||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
|
|
|
@ -3,8 +3,9 @@ package setval_test
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/redis/go-redis/internal/customvet/checks/setval"
|
||||
"golang.org/x/tools/go/analysis/analysistest"
|
||||
|
||||
"github.com/redis/go-redis/internal/customvet/checks/setval"
|
||||
)
|
||||
|
||||
func Test(t *testing.T) {
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/redis/go-redis/internal/customvet/checks/setval"
|
||||
"golang.org/x/tools/go/analysis/multichecker"
|
||||
|
||||
"github.com/redis/go-redis/internal/customvet/checks/setval"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build linux || darwin || dragonfly || freebsd || netbsd || openbsd || solaris || illumos
|
||||
// +build linux darwin dragonfly freebsd netbsd openbsd solaris illumos
|
||||
|
||||
package pool
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !linux && !darwin && !dragonfly && !freebsd && !netbsd && !openbsd && !solaris && !illumos
|
||||
// +build !linux,!darwin,!dragonfly,!freebsd,!netbsd,!openbsd,!solaris,!illumos
|
||||
|
||||
package pool
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build linux || darwin || dragonfly || freebsd || netbsd || openbsd || solaris || illumos
|
||||
// +build linux darwin dragonfly freebsd netbsd openbsd solaris illumos
|
||||
|
||||
package pool
|
||||
|
||||
|
|
|
@ -15,6 +15,10 @@ var (
|
|||
// ErrClosed performs any operation on the closed client will return this error.
|
||||
ErrClosed = errors.New("redis: client is closed")
|
||||
|
||||
// ErrPoolExhausted is returned from a pool connection method
|
||||
// when the maximum number of database connections in the pool has been reached.
|
||||
ErrPoolExhausted = errors.New("redis: connection pool exhausted")
|
||||
|
||||
// ErrPoolTimeout timed out waiting to get a connection from the connection pool.
|
||||
ErrPoolTimeout = errors.New("redis: connection pool timeout")
|
||||
)
|
||||
|
@ -61,6 +65,7 @@ type Options struct {
|
|||
PoolTimeout time.Duration
|
||||
MinIdleConns int
|
||||
MaxIdleConns int
|
||||
MaxActiveConns int
|
||||
ConnMaxIdleTime time.Duration
|
||||
ConnMaxLifetime time.Duration
|
||||
}
|
||||
|
@ -159,6 +164,14 @@ func (p *ConnPool) NewConn(ctx context.Context) (*Conn, error) {
|
|||
}
|
||||
|
||||
func (p *ConnPool) newConn(ctx context.Context, pooled bool) (*Conn, error) {
|
||||
if p.closed() {
|
||||
return nil, ErrClosed
|
||||
}
|
||||
|
||||
if p.cfg.MaxActiveConns > 0 && p.poolSize >= p.cfg.MaxActiveConns {
|
||||
return nil, ErrPoolExhausted
|
||||
}
|
||||
|
||||
cn, err := p.dialConn(ctx, pooled)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -167,12 +180,6 @@ func (p *ConnPool) newConn(ctx context.Context, pooled bool) (*Conn, error) {
|
|||
p.connsMu.Lock()
|
||||
defer p.connsMu.Unlock()
|
||||
|
||||
// It is not allowed to add new connections to the closed connection pool.
|
||||
if p.closed() {
|
||||
_ = cn.Close()
|
||||
return nil, ErrClosed
|
||||
}
|
||||
|
||||
p.conns = append(p.conns, cn)
|
||||
if pooled {
|
||||
// If pool is full remove the cn on next Put.
|
||||
|
@ -256,6 +263,7 @@ func (p *ConnPool) Get(ctx context.Context) (*Conn, error) {
|
|||
p.connsMu.Unlock()
|
||||
|
||||
if err != nil {
|
||||
p.freeTurn()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
|
|
@ -65,37 +65,68 @@ func (w *Writer) WriteArg(v interface{}) error {
|
|||
return w.string("")
|
||||
case string:
|
||||
return w.string(v)
|
||||
case *string:
|
||||
return w.string(*v)
|
||||
case []byte:
|
||||
return w.bytes(v)
|
||||
case int:
|
||||
return w.int(int64(v))
|
||||
case *int:
|
||||
return w.int(int64(*v))
|
||||
case int8:
|
||||
return w.int(int64(v))
|
||||
case *int8:
|
||||
return w.int(int64(*v))
|
||||
case int16:
|
||||
return w.int(int64(v))
|
||||
case *int16:
|
||||
return w.int(int64(*v))
|
||||
case int32:
|
||||
return w.int(int64(v))
|
||||
case *int32:
|
||||
return w.int(int64(*v))
|
||||
case int64:
|
||||
return w.int(v)
|
||||
case *int64:
|
||||
return w.int(*v)
|
||||
case uint:
|
||||
return w.uint(uint64(v))
|
||||
case *uint:
|
||||
return w.uint(uint64(*v))
|
||||
case uint8:
|
||||
return w.uint(uint64(v))
|
||||
case *uint8:
|
||||
return w.uint(uint64(*v))
|
||||
case uint16:
|
||||
return w.uint(uint64(v))
|
||||
case *uint16:
|
||||
return w.uint(uint64(*v))
|
||||
case uint32:
|
||||
return w.uint(uint64(v))
|
||||
case *uint32:
|
||||
return w.uint(uint64(*v))
|
||||
case uint64:
|
||||
return w.uint(v)
|
||||
case *uint64:
|
||||
return w.uint(*v)
|
||||
case float32:
|
||||
return w.float(float64(v))
|
||||
case *float32:
|
||||
return w.float(float64(*v))
|
||||
case float64:
|
||||
return w.float(v)
|
||||
case *float64:
|
||||
return w.float(*v)
|
||||
case bool:
|
||||
if v {
|
||||
return w.int(1)
|
||||
}
|
||||
return w.int(0)
|
||||
case *bool:
|
||||
if *v {
|
||||
return w.int(1)
|
||||
}
|
||||
return w.int(0)
|
||||
case time.Time:
|
||||
w.numBuf = v.AppendFormat(w.numBuf[:0], time.RFC3339Nano)
|
||||
return w.bytes(w.numBuf)
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
. "github.com/bsm/gomega"
|
||||
|
||||
"github.com/redis/go-redis/v9/internal/proto"
|
||||
"github.com/redis/go-redis/v9/internal/util"
|
||||
)
|
||||
|
||||
type MyType struct{}
|
||||
|
@ -100,3 +101,53 @@ func BenchmarkWriteBuffer_Append(b *testing.B) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
var _ = Describe("WriteArg", func() {
|
||||
var buf *bytes.Buffer
|
||||
var wr *proto.Writer
|
||||
|
||||
BeforeEach(func() {
|
||||
buf = new(bytes.Buffer)
|
||||
wr = proto.NewWriter(buf)
|
||||
})
|
||||
|
||||
args := map[any]string{
|
||||
"hello": "$1\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): "$4\r\n10.3\r\n",
|
||||
util.ToPtr(float32(10.3)): "$4\r\n10.3\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",
|
||||
}
|
||||
|
||||
for arg, expect := range args {
|
||||
It(fmt.Sprintf("should write arg of type %T", arg), func() {
|
||||
err := wr.WriteArg(arg)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(buf.String()).To(Equal(expect))
|
||||
})
|
||||
}
|
||||
})
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build appengine
|
||||
// +build appengine
|
||||
|
||||
package util
|
||||
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
package util
|
||||
|
||||
func ToPtr[T any](v T) *T {
|
||||
return &v
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !appengine
|
||||
// +build !appengine
|
||||
|
||||
package util
|
||||
|
||||
|
|
|
@ -0,0 +1,606 @@
|
|||
package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/redis/go-redis/v9/internal/proto"
|
||||
"github.com/redis/go-redis/v9/internal/util"
|
||||
)
|
||||
|
||||
// -------------------------------------------
|
||||
|
||||
type JSONCmdable interface {
|
||||
JSONArrAppend(ctx context.Context, key, path string, values ...interface{}) *IntSliceCmd
|
||||
JSONArrIndex(ctx context.Context, key, path string, value ...interface{}) *IntSliceCmd
|
||||
JSONArrIndexWithArgs(ctx context.Context, key, path string, options *JSONArrIndexArgs, value ...interface{}) *IntSliceCmd
|
||||
JSONArrInsert(ctx context.Context, key, path string, index int64, values ...interface{}) *IntSliceCmd
|
||||
JSONArrLen(ctx context.Context, key, path string) *IntSliceCmd
|
||||
JSONArrPop(ctx context.Context, key, path string, index int) *StringSliceCmd
|
||||
JSONArrTrim(ctx context.Context, key, path string) *IntSliceCmd
|
||||
JSONArrTrimWithArgs(ctx context.Context, key, path string, options *JSONArrTrimArgs) *IntSliceCmd
|
||||
JSONClear(ctx context.Context, key, path string) *IntCmd
|
||||
JSONDebugMemory(ctx context.Context, key, path string) *IntCmd
|
||||
JSONDel(ctx context.Context, key, path string) *IntCmd
|
||||
JSONForget(ctx context.Context, key, path string) *IntCmd
|
||||
JSONGet(ctx context.Context, key string, paths ...string) *JSONCmd
|
||||
JSONGetWithArgs(ctx context.Context, key string, options *JSONGetArgs, paths ...string) *JSONCmd
|
||||
JSONMerge(ctx context.Context, key, path string, value string) *StatusCmd
|
||||
JSONMSetArgs(ctx context.Context, docs []JSONSetArgs) *StatusCmd
|
||||
JSONMSet(ctx context.Context, params ...interface{}) *StatusCmd
|
||||
JSONMGet(ctx context.Context, path string, keys ...string) *JSONSliceCmd
|
||||
JSONNumIncrBy(ctx context.Context, key, path string, value float64) *JSONCmd
|
||||
JSONObjKeys(ctx context.Context, key, path string) *SliceCmd
|
||||
JSONObjLen(ctx context.Context, key, path string) *IntPointerSliceCmd
|
||||
JSONSet(ctx context.Context, key, path string, value interface{}) *StatusCmd
|
||||
JSONSetMode(ctx context.Context, key, path string, value interface{}, mode string) *StatusCmd
|
||||
JSONStrAppend(ctx context.Context, key, path, value string) *IntPointerSliceCmd
|
||||
JSONStrLen(ctx context.Context, key, path string) *IntPointerSliceCmd
|
||||
JSONToggle(ctx context.Context, key, path string) *IntPointerSliceCmd
|
||||
JSONType(ctx context.Context, key, path string) *JSONSliceCmd
|
||||
}
|
||||
|
||||
type JSONSetArgs struct {
|
||||
Key string
|
||||
Path string
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
type JSONArrIndexArgs struct {
|
||||
Start int
|
||||
Stop *int
|
||||
}
|
||||
|
||||
type JSONArrTrimArgs struct {
|
||||
Start int
|
||||
Stop *int
|
||||
}
|
||||
|
||||
type JSONCmd struct {
|
||||
baseCmd
|
||||
val string
|
||||
expanded []interface{}
|
||||
}
|
||||
|
||||
var _ Cmder = (*JSONCmd)(nil)
|
||||
|
||||
func newJSONCmd(ctx context.Context, args ...interface{}) *JSONCmd {
|
||||
|
||||
return &JSONCmd{
|
||||
baseCmd: baseCmd{
|
||||
ctx: ctx,
|
||||
args: args,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *JSONCmd) String() string {
|
||||
return cmdString(cmd, cmd.val)
|
||||
}
|
||||
|
||||
func (cmd *JSONCmd) SetVal(val string) {
|
||||
cmd.val = val
|
||||
}
|
||||
|
||||
func (cmd *JSONCmd) Val() string {
|
||||
if len(cmd.val) == 0 && cmd.expanded != nil {
|
||||
val, err := json.Marshal(cmd.expanded)
|
||||
if err != nil {
|
||||
cmd.SetErr(err)
|
||||
return ""
|
||||
}
|
||||
return string(val)
|
||||
|
||||
} else {
|
||||
return cmd.val
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (cmd *JSONCmd) Result() (string, error) {
|
||||
return cmd.Val(), cmd.Err()
|
||||
}
|
||||
|
||||
func (cmd JSONCmd) Expanded() (interface{}, error) {
|
||||
|
||||
if len(cmd.val) != 0 && cmd.expanded == nil {
|
||||
err := json.Unmarshal([]byte(cmd.val), &cmd.expanded)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
return cmd.expanded, nil
|
||||
}
|
||||
|
||||
func (cmd *JSONCmd) readReply(rd *proto.Reader) error {
|
||||
|
||||
// nil response from JSON.(M)GET (cmd.baseCmd.err will be "redis: nil")
|
||||
if cmd.baseCmd.Err() == Nil {
|
||||
cmd.val = ""
|
||||
return Nil
|
||||
}
|
||||
|
||||
if readType, err := rd.PeekReplyType(); err != nil {
|
||||
return err
|
||||
} else if readType == proto.RespArray {
|
||||
|
||||
size, err := rd.ReadArrayLen()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var expanded = make([]interface{}, size)
|
||||
|
||||
for i := 0; i < size; i++ {
|
||||
if expanded[i], err = rd.ReadReply(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
cmd.expanded = expanded
|
||||
|
||||
} else {
|
||||
if str, err := rd.ReadString(); err != nil && err != Nil {
|
||||
return err
|
||||
} else if str == "" || err == Nil {
|
||||
cmd.val = ""
|
||||
} else {
|
||||
cmd.val = str
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// -------------------------------------------
|
||||
|
||||
type JSONSliceCmd struct {
|
||||
baseCmd
|
||||
val []interface{}
|
||||
}
|
||||
|
||||
func NewJSONSliceCmd(ctx context.Context, args ...interface{}) *JSONSliceCmd {
|
||||
return &JSONSliceCmd{
|
||||
baseCmd: baseCmd{
|
||||
ctx: ctx,
|
||||
args: args,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *JSONSliceCmd) String() string {
|
||||
return cmdString(cmd, cmd.val)
|
||||
}
|
||||
|
||||
func (cmd *JSONSliceCmd) SetVal(val []interface{}) {
|
||||
cmd.val = val
|
||||
}
|
||||
|
||||
func (cmd *JSONSliceCmd) Val() []interface{} {
|
||||
return cmd.val
|
||||
}
|
||||
|
||||
func (cmd *JSONSliceCmd) Result() ([]interface{}, error) {
|
||||
return cmd.Val(), cmd.Err()
|
||||
}
|
||||
|
||||
func (cmd *JSONSliceCmd) readReply(rd *proto.Reader) error {
|
||||
|
||||
if cmd.baseCmd.Err() == Nil {
|
||||
cmd.val = nil
|
||||
return Nil
|
||||
}
|
||||
|
||||
if readType, err := rd.PeekReplyType(); err != nil {
|
||||
return err
|
||||
} else if readType == proto.RespArray {
|
||||
response, err := rd.ReadReply()
|
||||
if err != nil {
|
||||
return nil
|
||||
} else {
|
||||
cmd.val = response.([]interface{})
|
||||
}
|
||||
|
||||
} else {
|
||||
n, err := rd.ReadArrayLen()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd.val = make([]interface{}, n)
|
||||
for i := 0; i < len(cmd.val); i++ {
|
||||
switch s, err := rd.ReadString(); {
|
||||
case err == Nil:
|
||||
cmd.val[i] = ""
|
||||
case err != nil:
|
||||
return err
|
||||
default:
|
||||
cmd.val[i] = s
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
*
|
||||
* IntPointerSliceCmd
|
||||
* used to represent a RedisJSON response where the result is either an integer or nil
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
type IntPointerSliceCmd struct {
|
||||
baseCmd
|
||||
val []*int64
|
||||
}
|
||||
|
||||
// NewIntPointerSliceCmd initialises an IntPointerSliceCmd
|
||||
func NewIntPointerSliceCmd(ctx context.Context, args ...interface{}) *IntPointerSliceCmd {
|
||||
return &IntPointerSliceCmd{
|
||||
baseCmd: baseCmd{
|
||||
ctx: ctx,
|
||||
args: args,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *IntPointerSliceCmd) String() string {
|
||||
return cmdString(cmd, cmd.val)
|
||||
}
|
||||
|
||||
func (cmd *IntPointerSliceCmd) SetVal(val []*int64) {
|
||||
cmd.val = val
|
||||
}
|
||||
|
||||
func (cmd *IntPointerSliceCmd) Val() []*int64 {
|
||||
return cmd.val
|
||||
}
|
||||
|
||||
func (cmd *IntPointerSliceCmd) Result() ([]*int64, error) {
|
||||
return cmd.Val(), cmd.Err()
|
||||
}
|
||||
|
||||
func (cmd *IntPointerSliceCmd) readReply(rd *proto.Reader) error {
|
||||
|
||||
n, err := rd.ReadArrayLen()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd.val = make([]*int64, n)
|
||||
|
||||
for i := 0; i < len(cmd.val); i++ {
|
||||
val, err := rd.ReadInt()
|
||||
if err != nil && err != Nil {
|
||||
return err
|
||||
} else if err != Nil {
|
||||
cmd.val[i] = &val
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// JSONArrAppend adds the provided JSON values to the end of the array at the given path.
|
||||
// For more information, see https://redis.io/commands/json.arrappend
|
||||
func (c cmdable) JSONArrAppend(ctx context.Context, key, path string, values ...interface{}) *IntSliceCmd {
|
||||
args := []interface{}{"JSON.ARRAPPEND", key, path}
|
||||
args = append(args, values...)
|
||||
cmd := NewIntSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// JSONArrIndex searches for the first occurrence of the provided JSON value in the array at the given path.
|
||||
// For more information, see https://redis.io/commands/json.arrindex
|
||||
func (c cmdable) JSONArrIndex(ctx context.Context, key, path string, value ...interface{}) *IntSliceCmd {
|
||||
args := []interface{}{"JSON.ARRINDEX", key, path}
|
||||
args = append(args, value...)
|
||||
cmd := NewIntSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// JSONArrIndexWithArgs searches for the first occurrence of a JSON value in an array while allowing the start and
|
||||
// stop options to be provided.
|
||||
// For more information, see https://redis.io/commands/json.arrindex
|
||||
func (c cmdable) JSONArrIndexWithArgs(ctx context.Context, key, path string, options *JSONArrIndexArgs, value ...interface{}) *IntSliceCmd {
|
||||
args := []interface{}{"JSON.ARRINDEX", key, path}
|
||||
args = append(args, value...)
|
||||
|
||||
if options != nil {
|
||||
args = append(args, options.Start)
|
||||
if options.Stop != nil {
|
||||
args = append(args, *options.Stop)
|
||||
}
|
||||
}
|
||||
cmd := NewIntSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// JSONArrInsert inserts the JSON values into the array at the specified path before the index (shifts to the right).
|
||||
// For more information, see https://redis.io/commands/json.arrinsert
|
||||
func (c cmdable) JSONArrInsert(ctx context.Context, key, path string, index int64, values ...interface{}) *IntSliceCmd {
|
||||
args := []interface{}{"JSON.ARRINSERT", key, path, index}
|
||||
args = append(args, values...)
|
||||
cmd := NewIntSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// JSONArrLen reports the length of the JSON array at the specified path in the given key.
|
||||
// For more information, see https://redis.io/commands/json.arrlen
|
||||
func (c cmdable) JSONArrLen(ctx context.Context, key, path string) *IntSliceCmd {
|
||||
args := []interface{}{"JSON.ARRLEN", key, path}
|
||||
cmd := NewIntSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// JSONArrPop removes and returns an element from the specified index in the array.
|
||||
// For more information, see https://redis.io/commands/json.arrpop
|
||||
func (c cmdable) JSONArrPop(ctx context.Context, key, path string, index int) *StringSliceCmd {
|
||||
args := []interface{}{"JSON.ARRPOP", key, path, index}
|
||||
cmd := NewStringSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// JSONArrTrim trims an array to contain only the specified inclusive range of elements.
|
||||
// For more information, see https://redis.io/commands/json.arrtrim
|
||||
func (c cmdable) JSONArrTrim(ctx context.Context, key, path string) *IntSliceCmd {
|
||||
args := []interface{}{"JSON.ARRTRIM", key, path}
|
||||
cmd := NewIntSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// JSONArrTrimWithArgs trims an array to contain only the specified inclusive range of elements.
|
||||
// For more information, see https://redis.io/commands/json.arrtrim
|
||||
func (c cmdable) JSONArrTrimWithArgs(ctx context.Context, key, path string, options *JSONArrTrimArgs) *IntSliceCmd {
|
||||
args := []interface{}{"JSON.ARRTRIM", key, path}
|
||||
|
||||
if options != nil {
|
||||
args = append(args, options.Start)
|
||||
|
||||
if options.Stop != nil {
|
||||
args = append(args, *options.Stop)
|
||||
}
|
||||
}
|
||||
cmd := NewIntSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// JSONClear clears container values (arrays/objects) and sets numeric values to 0.
|
||||
// For more information, see https://redis.io/commands/json.clear
|
||||
func (c cmdable) JSONClear(ctx context.Context, key, path string) *IntCmd {
|
||||
args := []interface{}{"JSON.CLEAR", key, path}
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// JSONDebugMemory reports a value's memory usage in bytes (unimplemented)
|
||||
// For more information, see https://redis.io/commands/json.debug-memory
|
||||
func (c cmdable) JSONDebugMemory(ctx context.Context, key, path string) *IntCmd {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
// JSONDel deletes a value.
|
||||
// For more information, see https://redis.io/commands/json.del
|
||||
func (c cmdable) JSONDel(ctx context.Context, key, path string) *IntCmd {
|
||||
args := []interface{}{"JSON.DEL", key, path}
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// JSONForget deletes a value.
|
||||
// For more information, see https://redis.io/commands/json.forget
|
||||
func (c cmdable) JSONForget(ctx context.Context, key, path string) *IntCmd {
|
||||
args := []interface{}{"JSON.FORGET", key, path}
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// JSONGet returns the value at path in JSON serialized form. JSON.GET returns an
|
||||
// array of strings. This function parses out the wrapping array but leaves the
|
||||
// internal strings unprocessed by default (see Val())
|
||||
// For more information - https://redis.io/commands/json.get/
|
||||
func (c cmdable) JSONGet(ctx context.Context, key string, paths ...string) *JSONCmd {
|
||||
args := make([]interface{}, len(paths)+2)
|
||||
args[0] = "JSON.GET"
|
||||
args[1] = key
|
||||
for n, path := range paths {
|
||||
args[n+2] = path
|
||||
}
|
||||
cmd := newJSONCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
type JSONGetArgs struct {
|
||||
Indent string
|
||||
Newline string
|
||||
Space string
|
||||
}
|
||||
|
||||
// JSONGetWithArgs - Retrieves the value of a key from a JSON document.
|
||||
// This function also allows for specifying additional options such as:
|
||||
// Indention, NewLine and Space
|
||||
// For more information - https://redis.io/commands/json.get/
|
||||
func (c cmdable) JSONGetWithArgs(ctx context.Context, key string, options *JSONGetArgs, paths ...string) *JSONCmd {
|
||||
args := []interface{}{"JSON.GET", key}
|
||||
if options != nil {
|
||||
if options.Indent != "" {
|
||||
args = append(args, "INDENT", options.Indent)
|
||||
}
|
||||
if options.Newline != "" {
|
||||
args = append(args, "NEWLINE", options.Newline)
|
||||
}
|
||||
if options.Space != "" {
|
||||
args = append(args, "SPACE", options.Space)
|
||||
}
|
||||
for _, path := range paths {
|
||||
args = append(args, path)
|
||||
}
|
||||
}
|
||||
cmd := newJSONCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// JSONMerge merges a given JSON value into matching paths.
|
||||
// For more information, see https://redis.io/commands/json.merge
|
||||
func (c cmdable) JSONMerge(ctx context.Context, key, path string, value string) *StatusCmd {
|
||||
args := []interface{}{"JSON.MERGE", key, path, value}
|
||||
cmd := NewStatusCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// JSONMGet returns the values at the specified path from multiple key arguments.
|
||||
// Note - the arguments are reversed when compared with `JSON.MGET` as we want
|
||||
// to follow the pattern of having the last argument be variable.
|
||||
// For more information, see https://redis.io/commands/json.mget
|
||||
func (c cmdable) JSONMGet(ctx context.Context, path string, keys ...string) *JSONSliceCmd {
|
||||
args := make([]interface{}, len(keys)+1)
|
||||
args[0] = "JSON.MGET"
|
||||
for n, key := range keys {
|
||||
args[n+1] = key
|
||||
}
|
||||
args = append(args, path)
|
||||
cmd := NewJSONSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// JSONMSetArgs sets or updates one or more JSON values according to the specified key-path-value triplets.
|
||||
// For more information, see https://redis.io/commands/json.mset
|
||||
func (c cmdable) JSONMSetArgs(ctx context.Context, docs []JSONSetArgs) *StatusCmd {
|
||||
args := []interface{}{"JSON.MSET"}
|
||||
for _, doc := range docs {
|
||||
args = append(args, doc.Key, doc.Path, doc.Value)
|
||||
}
|
||||
cmd := NewStatusCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) JSONMSet(ctx context.Context, params ...interface{}) *StatusCmd {
|
||||
args := []interface{}{"JSON.MSET"}
|
||||
args = append(args, params...)
|
||||
cmd := NewStatusCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// JSONNumIncrBy increments the number value stored at the specified path by the provided number.
|
||||
// For more information, see https://redis.io/commands/json.numincreby
|
||||
func (c cmdable) JSONNumIncrBy(ctx context.Context, key, path string, value float64) *JSONCmd {
|
||||
args := []interface{}{"JSON.NUMINCRBY", key, path, value}
|
||||
cmd := newJSONCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// JSONObjKeys returns the keys in the object that's referenced by the specified path.
|
||||
// For more information, see https://redis.io/commands/json.objkeys
|
||||
func (c cmdable) JSONObjKeys(ctx context.Context, key, path string) *SliceCmd {
|
||||
args := []interface{}{"JSON.OBJKEYS", key, path}
|
||||
cmd := NewSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// JSONObjLen reports the number of keys in the JSON object at the specified path in the given key.
|
||||
// For more information, see https://redis.io/commands/json.objlen
|
||||
func (c cmdable) JSONObjLen(ctx context.Context, key, path string) *IntPointerSliceCmd {
|
||||
args := []interface{}{"JSON.OBJLEN", key, path}
|
||||
cmd := NewIntPointerSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// JSONSet sets the JSON value at the given path in the given key. The value must be something that
|
||||
// can be marshaled to JSON (using encoding/JSON) unless the argument is a string or a []byte when we assume that
|
||||
// it can be passed directly as JSON.
|
||||
// For more information, see https://redis.io/commands/json.set
|
||||
func (c cmdable) JSONSet(ctx context.Context, key, path string, value interface{}) *StatusCmd {
|
||||
return c.JSONSetMode(ctx, key, path, value, "")
|
||||
}
|
||||
|
||||
// JSONSetMode sets the JSON value at the given path in the given key and allows the mode to be set
|
||||
// (the mode value must be "XX" or "NX"). The value must be something that can be marshaled to JSON (using encoding/JSON) unless
|
||||
// the argument is a string or []byte when we assume that it can be passed directly as JSON.
|
||||
// For more information, see https://redis.io/commands/json.set
|
||||
func (c cmdable) JSONSetMode(ctx context.Context, key, path string, value interface{}, mode string) *StatusCmd {
|
||||
var bytes []byte
|
||||
var err error
|
||||
switch v := value.(type) {
|
||||
case string:
|
||||
bytes = []byte(v)
|
||||
case []byte:
|
||||
bytes = v
|
||||
default:
|
||||
bytes, err = json.Marshal(v)
|
||||
}
|
||||
args := []interface{}{"JSON.SET", key, path, util.BytesToString(bytes)}
|
||||
if mode != "" {
|
||||
switch strings.ToUpper(mode) {
|
||||
case "XX", "NX":
|
||||
args = append(args, strings.ToUpper(mode))
|
||||
|
||||
default:
|
||||
panic("redis: JSON.SET mode must be NX or XX")
|
||||
}
|
||||
}
|
||||
cmd := NewStatusCmd(ctx, args...)
|
||||
if err != nil {
|
||||
cmd.SetErr(err)
|
||||
} else {
|
||||
_ = c(ctx, cmd)
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
// JSONStrAppend appends the JSON-string values to the string at the specified path.
|
||||
// For more information, see https://redis.io/commands/json.strappend
|
||||
func (c cmdable) JSONStrAppend(ctx context.Context, key, path, value string) *IntPointerSliceCmd {
|
||||
args := []interface{}{"JSON.STRAPPEND", key, path, value}
|
||||
cmd := NewIntPointerSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// JSONStrLen reports the length of the JSON String at the specified path in the given key.
|
||||
// For more information, see https://redis.io/commands/json.strlen
|
||||
func (c cmdable) JSONStrLen(ctx context.Context, key, path string) *IntPointerSliceCmd {
|
||||
args := []interface{}{"JSON.STRLEN", key, path}
|
||||
cmd := NewIntPointerSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// JSONToggle toggles a Boolean value stored at the specified path.
|
||||
// For more information, see https://redis.io/commands/json.toggle
|
||||
func (c cmdable) JSONToggle(ctx context.Context, key, path string) *IntPointerSliceCmd {
|
||||
args := []interface{}{"JSON.TOGGLE", key, path}
|
||||
cmd := NewIntPointerSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// JSONType reports the type of JSON value at the specified path.
|
||||
// For more information, see https://redis.io/commands/json.type
|
||||
func (c cmdable) JSONType(ctx context.Context, key, path string) *JSONSliceCmd {
|
||||
args := []interface{}{"JSON.TYPE", key, path}
|
||||
cmd := NewJSONSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
|
@ -0,0 +1,669 @@
|
|||
package redis_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
. "github.com/bsm/ginkgo/v2"
|
||||
. "github.com/bsm/gomega"
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
type JSONGetTestStruct struct {
|
||||
Hello string `json:"hello"`
|
||||
}
|
||||
|
||||
var _ = Describe("JSON Commands", Label("json"), func() {
|
||||
|
||||
ctx := context.TODO()
|
||||
var client *redis.Client
|
||||
|
||||
BeforeEach(func() {
|
||||
client = redis.NewClient(&redis.Options{Addr: ":6379"})
|
||||
Expect(client.FlushDB(ctx).Err()).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
Expect(client.Close()).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
Describe("arrays", Label("arrays"), func() {
|
||||
|
||||
It("should JSONArrAppend", Label("json.arrappend", "json"), func() {
|
||||
cmd1 := client.JSONSet(ctx, "append2", "$", `{"a": [10], "b": {"a": [12, 13]}}`)
|
||||
Expect(cmd1.Err()).NotTo(HaveOccurred())
|
||||
Expect(cmd1.Val()).To(Equal("OK"))
|
||||
|
||||
cmd2 := client.JSONArrAppend(ctx, "append2", "$..a", 10)
|
||||
Expect(cmd2.Err()).NotTo(HaveOccurred())
|
||||
Expect(cmd2.Val()).To(Equal([]int64{2, 3}))
|
||||
})
|
||||
|
||||
It("should JSONArrIndex and JSONArrIndexWithArgs", Label("json.arrindex", "json"), func() {
|
||||
cmd1, err := client.JSONSet(ctx, "index1", "$", `{"a": [10], "b": {"a": [12, 10]}}`).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(cmd1).To(Equal("OK"))
|
||||
|
||||
cmd2, err := client.JSONArrIndex(ctx, "index1", "$.b.a", 10).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(cmd2).To(Equal([]int64{1}))
|
||||
|
||||
cmd3, err := client.JSONSet(ctx, "index2", "$", `[0,1,2,3,4]`).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(cmd3).To(Equal("OK"))
|
||||
|
||||
res, err := client.JSONArrIndex(ctx, "index2", "$", 1).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res[0]).To(Equal(int64(1)))
|
||||
|
||||
res, err = client.JSONArrIndex(ctx, "index2", "$", 1, 2).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res[0]).To(Equal(int64(-1)))
|
||||
|
||||
res, err = client.JSONArrIndex(ctx, "index2", "$", 4).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res[0]).To(Equal(int64(4)))
|
||||
|
||||
res, err = client.JSONArrIndexWithArgs(ctx, "index2", "$", &redis.JSONArrIndexArgs{}, 4).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res[0]).To(Equal(int64(4)))
|
||||
|
||||
stop := 5000
|
||||
res, err = client.JSONArrIndexWithArgs(ctx, "index2", "$", &redis.JSONArrIndexArgs{Stop: &stop}, 4).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res[0]).To(Equal(int64(4)))
|
||||
|
||||
stop = -1
|
||||
res, err = client.JSONArrIndexWithArgs(ctx, "index2", "$", &redis.JSONArrIndexArgs{Stop: &stop}, 4).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res[0]).To(Equal(int64(-1)))
|
||||
|
||||
})
|
||||
|
||||
It("should JSONArrIndex and JSONArrIndexWithArgs with $", Label("json.arrindex", "json"), func() {
|
||||
doc := `{
|
||||
"store": {
|
||||
"book": [
|
||||
{
|
||||
"category": "reference",
|
||||
"author": "Nigel Rees",
|
||||
"title": "Sayings of the Century",
|
||||
"price": 8.95,
|
||||
"size": [10, 20, 30, 40]
|
||||
},
|
||||
{
|
||||
"category": "fiction",
|
||||
"author": "Evelyn Waugh",
|
||||
"title": "Sword of Honour",
|
||||
"price": 12.99,
|
||||
"size": [50, 60, 70, 80]
|
||||
},
|
||||
{
|
||||
"category": "fiction",
|
||||
"author": "Herman Melville",
|
||||
"title": "Moby Dick",
|
||||
"isbn": "0-553-21311-3",
|
||||
"price": 8.99,
|
||||
"size": [5, 10, 20, 30]
|
||||
},
|
||||
{
|
||||
"category": "fiction",
|
||||
"author": "J. R. R. Tolkien",
|
||||
"title": "The Lord of the Rings",
|
||||
"isbn": "0-395-19395-8",
|
||||
"price": 22.99,
|
||||
"size": [5, 6, 7, 8]
|
||||
}
|
||||
],
|
||||
"bicycle": {"color": "red", "price": 19.95}
|
||||
}
|
||||
}`
|
||||
res, err := client.JSONSet(ctx, "doc1", "$", doc).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(Equal("OK"))
|
||||
|
||||
resGet, err := client.JSONGet(ctx, "doc1", "$.store.book[?(@.price<10)].size").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(resGet).To(Equal("[[10,20,30,40],[5,10,20,30]]"))
|
||||
|
||||
resArr, err := client.JSONArrIndex(ctx, "doc1", "$.store.book[?(@.price<10)].size", 20).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(resArr).To(Equal([]int64{1, 2}))
|
||||
})
|
||||
|
||||
It("should JSONArrInsert", Label("json.arrinsert", "json"), func() {
|
||||
cmd1 := client.JSONSet(ctx, "insert2", "$", `[100, 200, 300, 200]`)
|
||||
Expect(cmd1.Err()).NotTo(HaveOccurred())
|
||||
Expect(cmd1.Val()).To(Equal("OK"))
|
||||
|
||||
cmd2 := client.JSONArrInsert(ctx, "insert2", "$", -1, 1, 2)
|
||||
Expect(cmd2.Err()).NotTo(HaveOccurred())
|
||||
Expect(cmd2.Val()).To(Equal([]int64{6}))
|
||||
|
||||
cmd3 := client.JSONGet(ctx, "insert2")
|
||||
Expect(cmd3.Err()).NotTo(HaveOccurred())
|
||||
// RESP2 vs RESP3
|
||||
Expect(cmd3.Val()).To(Or(
|
||||
Equal(`[100,200,300,1,2,200]`),
|
||||
Equal(`[[100,200,300,1,2,200]]`)))
|
||||
})
|
||||
|
||||
It("should JSONArrLen", Label("json.arrlen", "json"), func() {
|
||||
cmd1 := client.JSONSet(ctx, "length2", "$", `{"a": [10], "b": {"a": [12, 10, 20, 12, 90, 10]}}`)
|
||||
Expect(cmd1.Err()).NotTo(HaveOccurred())
|
||||
Expect(cmd1.Val()).To(Equal("OK"))
|
||||
|
||||
cmd2 := client.JSONArrLen(ctx, "length2", "$..a")
|
||||
Expect(cmd2.Err()).NotTo(HaveOccurred())
|
||||
Expect(cmd2.Val()).To(Equal([]int64{1, 6}))
|
||||
})
|
||||
|
||||
It("should JSONArrPop", Label("json.arrpop"), func() {
|
||||
cmd1 := client.JSONSet(ctx, "pop4", "$", `[100, 200, 300, 200]`)
|
||||
Expect(cmd1.Err()).NotTo(HaveOccurred())
|
||||
Expect(cmd1.Val()).To(Equal("OK"))
|
||||
|
||||
cmd2 := client.JSONArrPop(ctx, "pop4", "$", 2)
|
||||
Expect(cmd2.Err()).NotTo(HaveOccurred())
|
||||
Expect(cmd2.Val()).To(Equal([]string{"300"}))
|
||||
|
||||
cmd3 := client.JSONGet(ctx, "pop4", "$")
|
||||
Expect(cmd3.Err()).NotTo(HaveOccurred())
|
||||
Expect(cmd3.Val()).To(Equal("[[100,200,200]]"))
|
||||
})
|
||||
|
||||
It("should JSONArrTrim", Label("json.arrtrim", "json"), func() {
|
||||
cmd1, err := client.JSONSet(ctx, "trim1", "$", `[0,1,2,3,4]`).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(cmd1).To(Equal("OK"))
|
||||
|
||||
stop := 3
|
||||
cmd2, err := client.JSONArrTrimWithArgs(ctx, "trim1", "$", &redis.JSONArrTrimArgs{Start: 1, Stop: &stop}).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(cmd2).To(Equal([]int64{3}))
|
||||
|
||||
res, err := client.JSONGet(ctx, "trim1", "$").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(Equal(`[[1,2,3]]`))
|
||||
|
||||
cmd3, err := client.JSONSet(ctx, "trim2", "$", `[0,1,2,3,4]`).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(cmd3).To(Equal("OK"))
|
||||
|
||||
stop = 3
|
||||
cmd4, err := client.JSONArrTrimWithArgs(ctx, "trim2", "$", &redis.JSONArrTrimArgs{Start: -1, Stop: &stop}).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(cmd4).To(Equal([]int64{0}))
|
||||
|
||||
cmd5, err := client.JSONSet(ctx, "trim3", "$", `[0,1,2,3,4]`).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(cmd5).To(Equal("OK"))
|
||||
|
||||
stop = 99
|
||||
cmd6, err := client.JSONArrTrimWithArgs(ctx, "trim3", "$", &redis.JSONArrTrimArgs{Start: 3, Stop: &stop}).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(cmd6).To(Equal([]int64{2}))
|
||||
|
||||
cmd7, err := client.JSONSet(ctx, "trim4", "$", `[0,1,2,3,4]`).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(cmd7).To(Equal("OK"))
|
||||
|
||||
stop = 1
|
||||
cmd8, err := client.JSONArrTrimWithArgs(ctx, "trim4", "$", &redis.JSONArrTrimArgs{Start: 9, Stop: &stop}).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(cmd8).To(Equal([]int64{0}))
|
||||
|
||||
cmd9, err := client.JSONSet(ctx, "trim5", "$", `[0,1,2,3,4]`).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(cmd9).To(Equal("OK"))
|
||||
|
||||
stop = 11
|
||||
cmd10, err := client.JSONArrTrimWithArgs(ctx, "trim5", "$", &redis.JSONArrTrimArgs{Start: 9, Stop: &stop}).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(cmd10).To(Equal([]int64{0}))
|
||||
})
|
||||
|
||||
It("should JSONArrPop", Label("json.arrpop", "json"), func() {
|
||||
cmd1 := client.JSONSet(ctx, "pop4", "$", `[100, 200, 300, 200]`)
|
||||
Expect(cmd1.Err()).NotTo(HaveOccurred())
|
||||
Expect(cmd1.Val()).To(Equal("OK"))
|
||||
|
||||
cmd2 := client.JSONArrPop(ctx, "pop4", "$", 2)
|
||||
Expect(cmd2.Err()).NotTo(HaveOccurred())
|
||||
Expect(cmd2.Val()).To(Equal([]string{"300"}))
|
||||
|
||||
cmd3 := client.JSONGet(ctx, "pop4", "$")
|
||||
Expect(cmd3.Err()).NotTo(HaveOccurred())
|
||||
Expect(cmd3.Val()).To(Equal("[[100,200,200]]"))
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
Describe("get/set", Label("getset"), func() {
|
||||
It("should JSONSet", Label("json.set", "json"), func() {
|
||||
cmd := client.JSONSet(ctx, "set1", "$", `{"a": 1, "b": 2, "hello": "world"}`)
|
||||
Expect(cmd.Err()).NotTo(HaveOccurred())
|
||||
Expect(cmd.Val()).To(Equal("OK"))
|
||||
})
|
||||
|
||||
It("should JSONGet", Label("json.get", "json"), func() {
|
||||
res, err := client.JSONSet(ctx, "get3", "$", `{"a": 1, "b": 2}`).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(Equal("OK"))
|
||||
|
||||
res, err = client.JSONGetWithArgs(ctx, "get3", &redis.JSONGetArgs{Indent: "-"}).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(Equal(`[-{--"a":1,--"b":2-}]`))
|
||||
|
||||
res, err = client.JSONGetWithArgs(ctx, "get3", &redis.JSONGetArgs{Indent: "-", Newline: `~`, Space: `!`}).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(Equal(`[~-{~--"a":!1,~--"b":!2~-}~]`))
|
||||
|
||||
})
|
||||
|
||||
It("should JSONMerge", Label("json.merge", "json"), func() {
|
||||
res, err := client.JSONSet(ctx, "merge1", "$", `{"a": 1, "b": 2}`).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(Equal("OK"))
|
||||
|
||||
res, err = client.JSONMerge(ctx, "merge1", "$", `{"b": 3, "c": 4}`).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(Equal("OK"))
|
||||
|
||||
res, err = client.JSONGet(ctx, "merge1", "$").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(Equal(`[{"a":1,"b":3,"c":4}]`))
|
||||
})
|
||||
|
||||
It("should JSONMSet", Label("json.mset", "json"), func() {
|
||||
doc1 := redis.JSONSetArgs{Key: "mset1", Path: "$", Value: `{"a": 1}`}
|
||||
doc2 := redis.JSONSetArgs{Key: "mset2", Path: "$", Value: 2}
|
||||
docs := []redis.JSONSetArgs{doc1, doc2}
|
||||
|
||||
mSetResult, err := client.JSONMSetArgs(ctx, docs).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(mSetResult).To(Equal("OK"))
|
||||
|
||||
res, err := client.JSONMGet(ctx, "$", "mset1").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(Equal([]interface{}{`[{"a":1}]`}))
|
||||
|
||||
res, err = client.JSONMGet(ctx, "$", "mset1", "mset2").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(Equal([]interface{}{`[{"a":1}]`, "[2]"}))
|
||||
|
||||
mSetResult, err = client.JSONMSet(ctx, "mset1", "$.a", 2, "mset3", "$", `[1]`).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("should JSONMGet", Label("json.mget", "json"), func() {
|
||||
cmd1 := client.JSONSet(ctx, "mget2a", "$", `{"a": ["aa", "ab", "ac", "ad"], "b": {"a": ["ba", "bb", "bc", "bd"]}}`)
|
||||
Expect(cmd1.Err()).NotTo(HaveOccurred())
|
||||
Expect(cmd1.Val()).To(Equal("OK"))
|
||||
cmd2 := client.JSONSet(ctx, "mget2b", "$", `{"a": [100, 200, 300, 200], "b": {"a": [100, 200, 300, 200]}}`)
|
||||
Expect(cmd2.Err()).NotTo(HaveOccurred())
|
||||
Expect(cmd2.Val()).To(Equal("OK"))
|
||||
|
||||
cmd3 := client.JSONMGet(ctx, "$..a", "mget2a", "mget2b")
|
||||
Expect(cmd3.Err()).NotTo(HaveOccurred())
|
||||
Expect(cmd3.Val()).To(HaveLen(2))
|
||||
Expect(cmd3.Val()[0]).To(Equal(`[["aa","ab","ac","ad"],["ba","bb","bc","bd"]]`))
|
||||
Expect(cmd3.Val()[1]).To(Equal(`[[100,200,300,200],[100,200,300,200]]`))
|
||||
})
|
||||
|
||||
It("should JSONMget with $", Label("json.mget", "json"), func() {
|
||||
res, err := client.JSONSet(ctx, "doc1", "$", `{"a": 1, "b": 2, "nested": {"a": 3}, "c": "", "nested2": {"a": ""}}`).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(Equal("OK"))
|
||||
|
||||
res, err = client.JSONSet(ctx, "doc2", "$", `{"a": 4, "b": 5, "nested": {"a": 6}, "c": "", "nested2": {"a": [""]}}`).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(Equal("OK"))
|
||||
|
||||
iRes, err := client.JSONMGet(ctx, "$..a", "doc1").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(iRes).To(Equal([]interface{}{`[1,3,""]`}))
|
||||
|
||||
iRes, err = client.JSONMGet(ctx, "$..a", "doc1", "doc2").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(iRes).To(Equal([]interface{}{`[1,3,""]`, `[4,6,[""]]`}))
|
||||
|
||||
iRes, err = client.JSONMGet(ctx, "$..a", "non_existing_doc", "non_existing_doc1").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(iRes).To(Equal([]interface{}{nil, nil}))
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
Describe("Misc", Label("misc"), func() {
|
||||
|
||||
It("should JSONClear", Label("json.clear", "json"), func() {
|
||||
cmd1 := client.JSONSet(ctx, "clear1", "$", `[1]`)
|
||||
Expect(cmd1.Err()).NotTo(HaveOccurred())
|
||||
Expect(cmd1.Val()).To(Equal("OK"))
|
||||
|
||||
cmd2 := client.JSONClear(ctx, "clear1", "$")
|
||||
Expect(cmd2.Err()).NotTo(HaveOccurred())
|
||||
Expect(cmd2.Val()).To(Equal(int64(1)))
|
||||
|
||||
cmd3 := client.JSONGet(ctx, "clear1", "$")
|
||||
Expect(cmd3.Err()).NotTo(HaveOccurred())
|
||||
Expect(cmd3.Val()).To(Equal(`[[]]`))
|
||||
})
|
||||
|
||||
It("should JSONClear with $", Label("json.clear", "json"), func() {
|
||||
doc := `{
|
||||
"nested1": {"a": {"foo": 10, "bar": 20}},
|
||||
"a": ["foo"],
|
||||
"nested2": {"a": "claro"},
|
||||
"nested3": {"a": {"baz": 50}}
|
||||
}`
|
||||
res, err := client.JSONSet(ctx, "doc1", "$", doc).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(Equal("OK"))
|
||||
|
||||
iRes, err := client.JSONClear(ctx, "doc1", "$..a").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(iRes).To(Equal(int64(3)))
|
||||
|
||||
resGet, err := client.JSONGet(ctx, "doc1", `$`).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(resGet).To(Equal(`[{"nested1":{"a":{}},"a":[],"nested2":{"a":"claro"},"nested3":{"a":{}}}]`))
|
||||
|
||||
res, err = client.JSONSet(ctx, "doc1", "$", doc).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(Equal("OK"))
|
||||
|
||||
iRes, err = client.JSONClear(ctx, "doc1", "$.nested1.a").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(iRes).To(Equal(int64(1)))
|
||||
|
||||
resGet, err = client.JSONGet(ctx, "doc1", `$`).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(resGet).To(Equal(`[{"nested1":{"a":{}},"a":["foo"],"nested2":{"a":"claro"},"nested3":{"a":{"baz":50}}}]`))
|
||||
})
|
||||
|
||||
It("should JSONDel", Label("json.del", "json"), func() {
|
||||
cmd1 := client.JSONSet(ctx, "del1", "$", `[1]`)
|
||||
Expect(cmd1.Err()).NotTo(HaveOccurred())
|
||||
Expect(cmd1.Val()).To(Equal("OK"))
|
||||
|
||||
cmd2 := client.JSONDel(ctx, "del1", "$")
|
||||
Expect(cmd2.Err()).NotTo(HaveOccurred())
|
||||
Expect(cmd2.Val()).To(Equal(int64(1)))
|
||||
|
||||
cmd3 := client.JSONGet(ctx, "del1", "$")
|
||||
Expect(cmd3.Err()).NotTo(HaveOccurred())
|
||||
Expect(cmd3.Val()).To(HaveLen(0))
|
||||
})
|
||||
|
||||
It("should JSONDel with $", Label("json.del", "json"), func() {
|
||||
res, err := client.JSONSet(ctx, "del1", "$", `{"a": 1, "nested": {"a": 2, "b": 3}}`).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(Equal("OK"))
|
||||
|
||||
iRes, err := client.JSONDel(ctx, "del1", "$..a").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(iRes).To(Equal(int64(2)))
|
||||
|
||||
resGet, err := client.JSONGet(ctx, "del1", "$").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(resGet).To(Equal(`[{"nested":{"b":3}}]`))
|
||||
|
||||
res, err = client.JSONSet(ctx, "del2", "$", `{"a": {"a": 2, "b": 3}, "b": ["a", "b"], "nested": {"b": [true, "a", "b"]}}`).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(Equal("OK"))
|
||||
|
||||
iRes, err = client.JSONDel(ctx, "del2", "$..a").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(iRes).To(Equal(int64(1)))
|
||||
|
||||
resGet, err = client.JSONGet(ctx, "del2", "$").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(resGet).To(Equal(`[{"nested":{"b":[true,"a","b"]},"b":["a","b"]}]`))
|
||||
|
||||
doc := `[
|
||||
{
|
||||
"ciao": ["non ancora"],
|
||||
"nested": [
|
||||
{"ciao": [1, "a"]},
|
||||
{"ciao": [2, "a"]},
|
||||
{"ciaoc": [3, "non", "ciao"]},
|
||||
{"ciao": [4, "a"]},
|
||||
{"e": [5, "non", "ciao"]}
|
||||
]
|
||||
}
|
||||
]`
|
||||
res, err = client.JSONSet(ctx, "del3", "$", doc).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(Equal("OK"))
|
||||
|
||||
iRes, err = client.JSONDel(ctx, "del3", `$.[0]["nested"]..ciao`).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(iRes).To(Equal(int64(3)))
|
||||
|
||||
resVal := `[[{"ciao":["non ancora"],"nested":[{},{},{"ciaoc":[3,"non","ciao"]},{},{"e":[5,"non","ciao"]}]}]]`
|
||||
resGet, err = client.JSONGet(ctx, "del3", "$").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(resGet).To(Equal(resVal))
|
||||
})
|
||||
|
||||
It("should JSONForget", Label("json.forget", "json"), func() {
|
||||
cmd1 := client.JSONSet(ctx, "forget3", "$", `{"a": [1,2,3], "b": {"a": [1,2,3], "b": "annie"}}`)
|
||||
Expect(cmd1.Err()).NotTo(HaveOccurred())
|
||||
Expect(cmd1.Val()).To(Equal("OK"))
|
||||
|
||||
cmd2 := client.JSONForget(ctx, "forget3", "$..a")
|
||||
Expect(cmd2.Err()).NotTo(HaveOccurred())
|
||||
Expect(cmd2.Val()).To(Equal(int64(2)))
|
||||
|
||||
cmd3 := client.JSONGet(ctx, "forget3", "$")
|
||||
Expect(cmd3.Err()).NotTo(HaveOccurred())
|
||||
Expect(cmd3.Val()).To(Equal(`[{"b":{"b":"annie"}}]`))
|
||||
|
||||
})
|
||||
|
||||
It("should JSONForget with $", Label("json.forget", "json"), func() {
|
||||
res, err := client.JSONSet(ctx, "doc1", "$", `{"a": 1, "nested": {"a": 2, "b": 3}}`).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(Equal("OK"))
|
||||
|
||||
iRes, err := client.JSONForget(ctx, "doc1", "$..a").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(iRes).To(Equal(int64(2)))
|
||||
|
||||
resGet, err := client.JSONGet(ctx, "doc1", "$").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(resGet).To(Equal(`[{"nested":{"b":3}}]`))
|
||||
|
||||
res, err = client.JSONSet(ctx, "doc2", "$", `{"a": {"a": 2, "b": 3}, "b": ["a", "b"], "nested": {"b": [true, "a", "b"]}}`).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(Equal("OK"))
|
||||
|
||||
iRes, err = client.JSONForget(ctx, "doc2", "$..a").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(iRes).To(Equal(int64(1)))
|
||||
|
||||
resGet, err = client.JSONGet(ctx, "doc2", "$").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(resGet).To(Equal(`[{"nested":{"b":[true,"a","b"]},"b":["a","b"]}]`))
|
||||
|
||||
doc := `[
|
||||
{
|
||||
"ciao": ["non ancora"],
|
||||
"nested": [
|
||||
{"ciao": [1, "a"]},
|
||||
{"ciao": [2, "a"]},
|
||||
{"ciaoc": [3, "non", "ciao"]},
|
||||
{"ciao": [4, "a"]},
|
||||
{"e": [5, "non", "ciao"]}
|
||||
]
|
||||
}
|
||||
]`
|
||||
res, err = client.JSONSet(ctx, "doc3", "$", doc).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(Equal("OK"))
|
||||
|
||||
iRes, err = client.JSONForget(ctx, "doc3", `$.[0]["nested"]..ciao`).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(iRes).To(Equal(int64(3)))
|
||||
|
||||
resVal := `[[{"ciao":["non ancora"],"nested":[{},{},{"ciaoc":[3,"non","ciao"]},{},{"e":[5,"non","ciao"]}]}]]`
|
||||
resGet, err = client.JSONGet(ctx, "doc3", "$").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(resGet).To(Equal(resVal))
|
||||
})
|
||||
|
||||
It("should JSONNumIncrBy", Label("json.numincrby", "json"), func() {
|
||||
cmd1 := client.JSONSet(ctx, "incr3", "$", `{"a": [1, 2], "b": {"a": [0, -1]}}`)
|
||||
Expect(cmd1.Err()).NotTo(HaveOccurred())
|
||||
Expect(cmd1.Val()).To(Equal("OK"))
|
||||
|
||||
cmd2 := client.JSONNumIncrBy(ctx, "incr3", "$..a[1]", float64(1))
|
||||
Expect(cmd2.Err()).NotTo(HaveOccurred())
|
||||
Expect(cmd2.Val()).To(Equal(`[3,0]`))
|
||||
})
|
||||
|
||||
It("should JSONNumIncrBy with $", Label("json.numincrby", "json"), func() {
|
||||
res, err := client.JSONSet(ctx, "doc1", "$", `{"a": "b", "b": [{"a": 2}, {"a": 5.0}, {"a": "c"}]}`).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(Equal("OK"))
|
||||
|
||||
res, err = client.JSONNumIncrBy(ctx, "doc1", "$.b[1].a", 2).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(Equal(`[7]`))
|
||||
|
||||
res, err = client.JSONNumIncrBy(ctx, "doc1", "$.b[1].a", 3.5).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(Equal(`[10.5]`))
|
||||
|
||||
res, err = client.JSONSet(ctx, "doc2", "$", `{"a": "b", "b": [{"a": 2}, {"a": 5.0}, {"a": "c"}]}`).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(Equal("OK"))
|
||||
|
||||
res, err = client.JSONNumIncrBy(ctx, "doc2", "$.b[0].a", 3).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(Equal(`[5]`))
|
||||
})
|
||||
|
||||
It("should JSONObjKeys", Label("json.objkeys", "json"), func() {
|
||||
cmd1 := client.JSONSet(ctx, "objkeys1", "$", `{"a": [1, 2], "b": {"a": [0, -1]}}`)
|
||||
Expect(cmd1.Err()).NotTo(HaveOccurred())
|
||||
Expect(cmd1.Val()).To(Equal("OK"))
|
||||
|
||||
cmd2 := client.JSONObjKeys(ctx, "objkeys1", "$..*")
|
||||
Expect(cmd2.Err()).NotTo(HaveOccurred())
|
||||
Expect(cmd2.Val()).To(HaveLen(7))
|
||||
Expect(cmd2.Val()).To(Equal([]interface{}{nil, []interface{}{"a"}, nil, nil, nil, nil, nil}))
|
||||
})
|
||||
|
||||
It("should JSONObjKeys with $", Label("json.objkeys", "json"), func() {
|
||||
doc := `{
|
||||
"nested1": {"a": {"foo": 10, "bar": 20}},
|
||||
"a": ["foo"],
|
||||
"nested2": {"a": {"baz": 50}}
|
||||
}`
|
||||
cmd1, err := client.JSONSet(ctx, "objkeys1", "$", doc).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(cmd1).To(Equal("OK"))
|
||||
|
||||
cmd2, err := client.JSONObjKeys(ctx, "objkeys1", "$.nested1.a").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(cmd2).To(Equal([]interface{}{[]interface{}{"foo", "bar"}}))
|
||||
|
||||
cmd2, err = client.JSONObjKeys(ctx, "objkeys1", ".*.a").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(cmd2).To(Equal([]interface{}{"foo", "bar"}))
|
||||
|
||||
cmd2, err = client.JSONObjKeys(ctx, "objkeys1", ".nested2.a").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(cmd2).To(Equal([]interface{}{"baz"}))
|
||||
|
||||
_, err = client.JSONObjKeys(ctx, "non_existing_doc", "..a").Result()
|
||||
Expect(err).To(HaveOccurred())
|
||||
})
|
||||
|
||||
It("should JSONObjLen", Label("json.objlen", "json"), func() {
|
||||
cmd1 := client.JSONSet(ctx, "objlen2", "$", `{"a": [1, 2], "b": {"a": [0, -1]}}`)
|
||||
Expect(cmd1.Err()).NotTo(HaveOccurred())
|
||||
Expect(cmd1.Val()).To(Equal("OK"))
|
||||
|
||||
cmd2 := client.JSONObjLen(ctx, "objlen2", "$..*")
|
||||
Expect(cmd2.Err()).NotTo(HaveOccurred())
|
||||
Expect(cmd2.Val()).To(HaveLen(7))
|
||||
Expect(cmd2.Val()[0]).To(BeNil())
|
||||
Expect(*cmd2.Val()[1]).To(Equal(int64(1)))
|
||||
})
|
||||
|
||||
It("should JSONStrLen", Label("json.strlen", "json"), func() {
|
||||
cmd1 := client.JSONSet(ctx, "strlen2", "$", `{"a": "alice", "b": "bob", "c": {"a": "alice", "b": "bob"}}`)
|
||||
Expect(cmd1.Err()).NotTo(HaveOccurred())
|
||||
Expect(cmd1.Val()).To(Equal("OK"))
|
||||
|
||||
cmd2 := client.JSONStrLen(ctx, "strlen2", "$..*")
|
||||
Expect(cmd2.Err()).NotTo(HaveOccurred())
|
||||
Expect(cmd2.Val()).To(HaveLen(5))
|
||||
var tmp int64 = 20
|
||||
Expect(cmd2.Val()[0]).To(BeAssignableToTypeOf(&tmp))
|
||||
Expect(*cmd2.Val()[0]).To(Equal(int64(5)))
|
||||
Expect(*cmd2.Val()[1]).To(Equal(int64(3)))
|
||||
Expect(cmd2.Val()[2]).To(BeNil())
|
||||
Expect(*cmd2.Val()[3]).To(Equal(int64(5)))
|
||||
Expect(*cmd2.Val()[4]).To(Equal(int64(3)))
|
||||
})
|
||||
|
||||
It("should JSONStrAppend", Label("json.strappend", "json"), func() {
|
||||
cmd1, err := client.JSONSet(ctx, "strapp1", "$", `"foo"`).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(cmd1).To(Equal("OK"))
|
||||
cmd2, err := client.JSONStrAppend(ctx, "strapp1", "$", `"bar"`).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(*cmd2[0]).To(Equal(int64(6)))
|
||||
cmd3, err := client.JSONGet(ctx, "strapp1", "$").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(cmd3).To(Equal(`["foobar"]`))
|
||||
|
||||
})
|
||||
|
||||
It("should JSONStrAppend and JSONStrLen with $", Label("json.strappend", "json.strlen", "json"), func() {
|
||||
res, err := client.JSONSet(ctx, "doc1", "$", `{"a": "foo", "nested1": {"a": "hello"}, "nested2": {"a": 31}}`).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(Equal("OK"))
|
||||
|
||||
intArrayResult, err := client.JSONStrAppend(ctx, "doc1", "$.nested1.a", `"baz"`).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(*intArrayResult[0]).To(Equal(int64(8)))
|
||||
|
||||
res, err = client.JSONSet(ctx, "doc2", "$", `{"a": "foo", "nested1": {"a": "hello"}, "nested2": {"a": 31}}`).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(Equal("OK"))
|
||||
|
||||
intResult, err := client.JSONStrLen(ctx, "doc2", "$.nested1.a").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(*intResult[0]).To(Equal(int64(5)))
|
||||
})
|
||||
|
||||
It("should JSONToggle", Label("json.toggle", "json"), func() {
|
||||
cmd1 := client.JSONSet(ctx, "toggle1", "$", `[true]`)
|
||||
Expect(cmd1.Err()).NotTo(HaveOccurred())
|
||||
Expect(cmd1.Val()).To(Equal("OK"))
|
||||
|
||||
cmd2 := client.JSONToggle(ctx, "toggle1", "$[0]")
|
||||
Expect(cmd2.Err()).NotTo(HaveOccurred())
|
||||
Expect(cmd2.Val()).To(HaveLen(1))
|
||||
Expect(*cmd2.Val()[0]).To(Equal(int64(0)))
|
||||
})
|
||||
|
||||
It("should JSONType", Label("json.type", "json"), func() {
|
||||
cmd1 := client.JSONSet(ctx, "type1", "$", `[true]`)
|
||||
Expect(cmd1.Err()).NotTo(HaveOccurred())
|
||||
Expect(cmd1.Val()).To(Equal("OK"))
|
||||
|
||||
cmd2 := client.JSONType(ctx, "type1", "$[0]")
|
||||
Expect(cmd2.Err()).NotTo(HaveOccurred())
|
||||
Expect(cmd2.Val()).To(HaveLen(1))
|
||||
// RESP2 v RESP3
|
||||
Expect(cmd2.Val()[0]).To(Or(Equal([]interface{}{"boolean"}), Equal("boolean")))
|
||||
})
|
||||
})
|
||||
})
|
|
@ -0,0 +1,289 @@
|
|||
package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ListCmdable interface {
|
||||
BLPop(ctx context.Context, timeout time.Duration, keys ...string) *StringSliceCmd
|
||||
BLMPop(ctx context.Context, timeout time.Duration, direction string, count int64, keys ...string) *KeyValuesCmd
|
||||
BRPop(ctx context.Context, timeout time.Duration, keys ...string) *StringSliceCmd
|
||||
BRPopLPush(ctx context.Context, source, destination string, timeout time.Duration) *StringCmd
|
||||
LIndex(ctx context.Context, key string, index int64) *StringCmd
|
||||
LInsert(ctx context.Context, key, op string, pivot, value interface{}) *IntCmd
|
||||
LInsertBefore(ctx context.Context, key string, pivot, value interface{}) *IntCmd
|
||||
LInsertAfter(ctx context.Context, key string, pivot, value interface{}) *IntCmd
|
||||
LLen(ctx context.Context, key string) *IntCmd
|
||||
LMPop(ctx context.Context, direction string, count int64, keys ...string) *KeyValuesCmd
|
||||
LPop(ctx context.Context, key string) *StringCmd
|
||||
LPopCount(ctx context.Context, key string, count int) *StringSliceCmd
|
||||
LPos(ctx context.Context, key string, value string, args LPosArgs) *IntCmd
|
||||
LPosCount(ctx context.Context, key string, value string, count int64, args LPosArgs) *IntSliceCmd
|
||||
LPush(ctx context.Context, key string, values ...interface{}) *IntCmd
|
||||
LPushX(ctx context.Context, key string, values ...interface{}) *IntCmd
|
||||
LRange(ctx context.Context, key string, start, stop int64) *StringSliceCmd
|
||||
LRem(ctx context.Context, key string, count int64, value interface{}) *IntCmd
|
||||
LSet(ctx context.Context, key string, index int64, value interface{}) *StatusCmd
|
||||
LTrim(ctx context.Context, key string, start, stop int64) *StatusCmd
|
||||
RPop(ctx context.Context, key string) *StringCmd
|
||||
RPopCount(ctx context.Context, key string, count int) *StringSliceCmd
|
||||
RPopLPush(ctx context.Context, source, destination string) *StringCmd
|
||||
RPush(ctx context.Context, key string, values ...interface{}) *IntCmd
|
||||
RPushX(ctx context.Context, key string, values ...interface{}) *IntCmd
|
||||
LMove(ctx context.Context, source, destination, srcpos, destpos string) *StringCmd
|
||||
BLMove(ctx context.Context, source, destination, srcpos, destpos string, timeout time.Duration) *StringCmd
|
||||
}
|
||||
|
||||
func (c cmdable) BLPop(ctx context.Context, timeout time.Duration, keys ...string) *StringSliceCmd {
|
||||
args := make([]interface{}, 1+len(keys)+1)
|
||||
args[0] = "blpop"
|
||||
for i, key := range keys {
|
||||
args[1+i] = key
|
||||
}
|
||||
args[len(args)-1] = formatSec(ctx, timeout)
|
||||
cmd := NewStringSliceCmd(ctx, args...)
|
||||
cmd.setReadTimeout(timeout)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) BLMPop(ctx context.Context, timeout time.Duration, direction string, count int64, keys ...string) *KeyValuesCmd {
|
||||
args := make([]interface{}, 3+len(keys), 6+len(keys))
|
||||
args[0] = "blmpop"
|
||||
args[1] = formatSec(ctx, timeout)
|
||||
args[2] = len(keys)
|
||||
for i, key := range keys {
|
||||
args[3+i] = key
|
||||
}
|
||||
args = append(args, strings.ToLower(direction), "count", count)
|
||||
cmd := NewKeyValuesCmd(ctx, args...)
|
||||
cmd.setReadTimeout(timeout)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) BRPop(ctx context.Context, timeout time.Duration, keys ...string) *StringSliceCmd {
|
||||
args := make([]interface{}, 1+len(keys)+1)
|
||||
args[0] = "brpop"
|
||||
for i, key := range keys {
|
||||
args[1+i] = key
|
||||
}
|
||||
args[len(keys)+1] = formatSec(ctx, timeout)
|
||||
cmd := NewStringSliceCmd(ctx, args...)
|
||||
cmd.setReadTimeout(timeout)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) BRPopLPush(ctx context.Context, source, destination string, timeout time.Duration) *StringCmd {
|
||||
cmd := NewStringCmd(
|
||||
ctx,
|
||||
"brpoplpush",
|
||||
source,
|
||||
destination,
|
||||
formatSec(ctx, timeout),
|
||||
)
|
||||
cmd.setReadTimeout(timeout)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) LIndex(ctx context.Context, key string, index int64) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "lindex", key, index)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// LMPop Pops one or more elements from the first non-empty list key from the list of provided key names.
|
||||
// direction: left or right, count: > 0
|
||||
// example: client.LMPop(ctx, "left", 3, "key1", "key2")
|
||||
func (c cmdable) LMPop(ctx context.Context, direction string, count int64, keys ...string) *KeyValuesCmd {
|
||||
args := make([]interface{}, 2+len(keys), 5+len(keys))
|
||||
args[0] = "lmpop"
|
||||
args[1] = len(keys)
|
||||
for i, key := range keys {
|
||||
args[2+i] = key
|
||||
}
|
||||
args = append(args, strings.ToLower(direction), "count", count)
|
||||
cmd := NewKeyValuesCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) LInsert(ctx context.Context, key, op string, pivot, value interface{}) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "linsert", key, op, pivot, value)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) LInsertBefore(ctx context.Context, key string, pivot, value interface{}) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "linsert", key, "before", pivot, value)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) LInsertAfter(ctx context.Context, key string, pivot, value interface{}) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "linsert", key, "after", pivot, value)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) LLen(ctx context.Context, key string) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "llen", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) LPop(ctx context.Context, key string) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "lpop", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) LPopCount(ctx context.Context, key string, count int) *StringSliceCmd {
|
||||
cmd := NewStringSliceCmd(ctx, "lpop", key, count)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
type LPosArgs struct {
|
||||
Rank, MaxLen int64
|
||||
}
|
||||
|
||||
func (c cmdable) LPos(ctx context.Context, key string, value string, a LPosArgs) *IntCmd {
|
||||
args := []interface{}{"lpos", key, value}
|
||||
if a.Rank != 0 {
|
||||
args = append(args, "rank", a.Rank)
|
||||
}
|
||||
if a.MaxLen != 0 {
|
||||
args = append(args, "maxlen", a.MaxLen)
|
||||
}
|
||||
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) LPosCount(ctx context.Context, key string, value string, count int64, a LPosArgs) *IntSliceCmd {
|
||||
args := []interface{}{"lpos", key, value, "count", count}
|
||||
if a.Rank != 0 {
|
||||
args = append(args, "rank", a.Rank)
|
||||
}
|
||||
if a.MaxLen != 0 {
|
||||
args = append(args, "maxlen", a.MaxLen)
|
||||
}
|
||||
cmd := NewIntSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) LPush(ctx context.Context, key string, values ...interface{}) *IntCmd {
|
||||
args := make([]interface{}, 2, 2+len(values))
|
||||
args[0] = "lpush"
|
||||
args[1] = key
|
||||
args = appendArgs(args, values)
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) LPushX(ctx context.Context, key string, values ...interface{}) *IntCmd {
|
||||
args := make([]interface{}, 2, 2+len(values))
|
||||
args[0] = "lpushx"
|
||||
args[1] = key
|
||||
args = appendArgs(args, values)
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) LRange(ctx context.Context, key string, start, stop int64) *StringSliceCmd {
|
||||
cmd := NewStringSliceCmd(
|
||||
ctx,
|
||||
"lrange",
|
||||
key,
|
||||
start,
|
||||
stop,
|
||||
)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) LRem(ctx context.Context, key string, count int64, value interface{}) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "lrem", key, count, value)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) LSet(ctx context.Context, key string, index int64, value interface{}) *StatusCmd {
|
||||
cmd := NewStatusCmd(ctx, "lset", key, index, value)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) LTrim(ctx context.Context, key string, start, stop int64) *StatusCmd {
|
||||
cmd := NewStatusCmd(
|
||||
ctx,
|
||||
"ltrim",
|
||||
key,
|
||||
start,
|
||||
stop,
|
||||
)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) RPop(ctx context.Context, key string) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "rpop", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) RPopCount(ctx context.Context, key string, count int) *StringSliceCmd {
|
||||
cmd := NewStringSliceCmd(ctx, "rpop", key, count)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) RPopLPush(ctx context.Context, source, destination string) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "rpoplpush", source, destination)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) RPush(ctx context.Context, key string, values ...interface{}) *IntCmd {
|
||||
args := make([]interface{}, 2, 2+len(values))
|
||||
args[0] = "rpush"
|
||||
args[1] = key
|
||||
args = appendArgs(args, values)
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) RPushX(ctx context.Context, key string, values ...interface{}) *IntCmd {
|
||||
args := make([]interface{}, 2, 2+len(values))
|
||||
args[0] = "rpushx"
|
||||
args[1] = key
|
||||
args = appendArgs(args, values)
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) LMove(ctx context.Context, source, destination, srcpos, destpos string) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "lmove", source, destination, srcpos, destpos)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) BLMove(
|
||||
ctx context.Context, source, destination, srcpos, destpos string, timeout time.Duration,
|
||||
) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "blmove", source, destination, srcpos, destpos, formatSec(ctx, timeout))
|
||||
cmd.setReadTimeout(timeout)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
|
@ -36,8 +36,10 @@ const (
|
|||
sentinelPort3 = "9128"
|
||||
)
|
||||
|
||||
var redisPort = "6380"
|
||||
var redisAddr = ":" + redisPort
|
||||
var (
|
||||
redisPort = "6380"
|
||||
redisAddr = ":" + redisPort
|
||||
)
|
||||
|
||||
var (
|
||||
sentinelAddrs = []string{":" + sentinelPort1, ":" + sentinelPort2, ":" + sentinelPort3}
|
||||
|
|
12
options.go
12
options.go
|
@ -98,8 +98,10 @@ type Options struct {
|
|||
// Note that FIFO has slightly higher overhead compared to LIFO,
|
||||
// but it helps closing idle connections faster reducing the pool size.
|
||||
PoolFIFO bool
|
||||
// Maximum number of socket connections.
|
||||
// Base number of socket connections.
|
||||
// Default is 10 connections per every available CPU as reported by runtime.GOMAXPROCS.
|
||||
// If there is not enough connections in the pool, new connections will be allocated in excess of PoolSize,
|
||||
// you can limit it through MaxActiveConns
|
||||
PoolSize int
|
||||
// Amount of time client waits for connection if all connections
|
||||
// are busy before returning an error.
|
||||
|
@ -112,6 +114,9 @@ type Options struct {
|
|||
// Maximum number of idle connections.
|
||||
// Default is 0. the idle connections are not closed by default.
|
||||
MaxIdleConns int
|
||||
// Maximum number of connections allocated by the pool at a given time.
|
||||
// When zero, there is no limit on the number of connections in the pool.
|
||||
MaxActiveConns int
|
||||
// ConnMaxIdleTime is the maximum amount of time a connection may be idle.
|
||||
// Should be less than server's timeout.
|
||||
//
|
||||
|
@ -136,6 +141,9 @@ type Options struct {
|
|||
|
||||
// Enables read only queries on slave/follower nodes.
|
||||
readOnly bool
|
||||
|
||||
// Disable set-lib on connect. Default is false.
|
||||
DisableIndentity bool
|
||||
}
|
||||
|
||||
func (opt *Options) init() {
|
||||
|
@ -453,6 +461,7 @@ func setupConnParams(u *url.URL, o *Options) (*Options, error) {
|
|||
o.PoolTimeout = q.duration("pool_timeout")
|
||||
o.MinIdleConns = q.int("min_idle_conns")
|
||||
o.MaxIdleConns = q.int("max_idle_conns")
|
||||
o.MaxActiveConns = q.int("max_active_conns")
|
||||
if q.has("conn_max_idle_time") {
|
||||
o.ConnMaxIdleTime = q.duration("conn_max_idle_time")
|
||||
} else {
|
||||
|
@ -499,6 +508,7 @@ func newConnPool(
|
|||
PoolTimeout: opt.PoolTimeout,
|
||||
MinIdleConns: opt.MinIdleConns,
|
||||
MaxIdleConns: opt.MaxIdleConns,
|
||||
MaxActiveConns: opt.MaxActiveConns,
|
||||
ConnMaxIdleTime: opt.ConnMaxIdleTime,
|
||||
ConnMaxLifetime: opt.ConnMaxLifetime,
|
||||
})
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build go1.7
|
||||
// +build go1.7
|
||||
|
||||
package redis
|
||||
|
||||
|
|
|
@ -80,10 +80,12 @@ type ClusterOptions struct {
|
|||
PoolTimeout time.Duration
|
||||
MinIdleConns int
|
||||
MaxIdleConns int
|
||||
MaxActiveConns int // applies per cluster node and not for the whole cluster
|
||||
ConnMaxIdleTime time.Duration
|
||||
ConnMaxLifetime time.Duration
|
||||
|
||||
TLSConfig *tls.Config
|
||||
TLSConfig *tls.Config
|
||||
DisableIndentity bool // Disable set-lib on connect. Default is false.
|
||||
}
|
||||
|
||||
func (opt *ClusterOptions) init() {
|
||||
|
@ -232,6 +234,8 @@ func setupClusterQueryParams(u *url.URL, o *ClusterOptions) (*ClusterOptions, er
|
|||
o.PoolFIFO = q.bool("pool_fifo")
|
||||
o.PoolSize = q.int("pool_size")
|
||||
o.MinIdleConns = q.int("min_idle_conns")
|
||||
o.MaxIdleConns = q.int("max_idle_conns")
|
||||
o.MaxActiveConns = q.int("max_active_conns")
|
||||
o.PoolTimeout = q.duration("pool_timeout")
|
||||
o.ConnMaxLifetime = q.duration("conn_max_lifetime")
|
||||
o.ConnMaxIdleTime = q.duration("conn_max_idle_time")
|
||||
|
@ -273,19 +277,21 @@ func (opt *ClusterOptions) clientOptions() *Options {
|
|||
MinRetryBackoff: opt.MinRetryBackoff,
|
||||
MaxRetryBackoff: opt.MaxRetryBackoff,
|
||||
|
||||
DialTimeout: opt.DialTimeout,
|
||||
ReadTimeout: opt.ReadTimeout,
|
||||
WriteTimeout: opt.WriteTimeout,
|
||||
DialTimeout: opt.DialTimeout,
|
||||
ReadTimeout: opt.ReadTimeout,
|
||||
WriteTimeout: opt.WriteTimeout,
|
||||
ContextTimeoutEnabled: opt.ContextTimeoutEnabled,
|
||||
|
||||
PoolFIFO: opt.PoolFIFO,
|
||||
PoolSize: opt.PoolSize,
|
||||
PoolTimeout: opt.PoolTimeout,
|
||||
MinIdleConns: opt.MinIdleConns,
|
||||
MaxIdleConns: opt.MaxIdleConns,
|
||||
ConnMaxIdleTime: opt.ConnMaxIdleTime,
|
||||
ConnMaxLifetime: opt.ConnMaxLifetime,
|
||||
|
||||
TLSConfig: opt.TLSConfig,
|
||||
PoolFIFO: opt.PoolFIFO,
|
||||
PoolSize: opt.PoolSize,
|
||||
PoolTimeout: opt.PoolTimeout,
|
||||
MinIdleConns: opt.MinIdleConns,
|
||||
MaxIdleConns: opt.MaxIdleConns,
|
||||
MaxActiveConns: opt.MaxActiveConns,
|
||||
ConnMaxIdleTime: opt.ConnMaxIdleTime,
|
||||
ConnMaxLifetime: opt.ConnMaxLifetime,
|
||||
DisableIndentity: opt.DisableIndentity,
|
||||
TLSConfig: opt.TLSConfig,
|
||||
// If ClusterSlots is populated, then we probably have an artificial
|
||||
// cluster whose nodes are not in clustering mode (otherwise there isn't
|
||||
// much use for ClusterSlots config). This means we cannot execute the
|
|
@ -0,0 +1,109 @@
|
|||
package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
func (c *ClusterClient) DBSize(ctx context.Context) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "dbsize")
|
||||
_ = c.withProcessHook(ctx, cmd, func(ctx context.Context, _ Cmder) error {
|
||||
var size int64
|
||||
err := c.ForEachMaster(ctx, func(ctx context.Context, master *Client) error {
|
||||
n, err := master.DBSize(ctx).Result()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
atomic.AddInt64(&size, n)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
cmd.SetErr(err)
|
||||
} else {
|
||||
cmd.val = size
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c *ClusterClient) ScriptLoad(ctx context.Context, script string) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "script", "load", script)
|
||||
_ = c.withProcessHook(ctx, cmd, func(ctx context.Context, _ Cmder) error {
|
||||
var mu sync.Mutex
|
||||
err := c.ForEachShard(ctx, func(ctx context.Context, shard *Client) error {
|
||||
val, err := shard.ScriptLoad(ctx, script).Result()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
if cmd.Val() == "" {
|
||||
cmd.val = val
|
||||
}
|
||||
mu.Unlock()
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
cmd.SetErr(err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c *ClusterClient) ScriptFlush(ctx context.Context) *StatusCmd {
|
||||
cmd := NewStatusCmd(ctx, "script", "flush")
|
||||
_ = c.withProcessHook(ctx, cmd, func(ctx context.Context, _ Cmder) error {
|
||||
err := c.ForEachShard(ctx, func(ctx context.Context, shard *Client) error {
|
||||
return shard.ScriptFlush(ctx).Err()
|
||||
})
|
||||
if err != nil {
|
||||
cmd.SetErr(err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c *ClusterClient) ScriptExists(ctx context.Context, hashes ...string) *BoolSliceCmd {
|
||||
args := make([]interface{}, 2+len(hashes))
|
||||
args[0] = "script"
|
||||
args[1] = "exists"
|
||||
for i, hash := range hashes {
|
||||
args[2+i] = hash
|
||||
}
|
||||
cmd := NewBoolSliceCmd(ctx, args...)
|
||||
|
||||
result := make([]bool, len(hashes))
|
||||
for i := range result {
|
||||
result[i] = true
|
||||
}
|
||||
|
||||
_ = c.withProcessHook(ctx, cmd, func(ctx context.Context, _ Cmder) error {
|
||||
var mu sync.Mutex
|
||||
err := c.ForEachShard(ctx, func(ctx context.Context, shard *Client) error {
|
||||
val, err := shard.ScriptExists(ctx, hashes...).Result()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
for i, v := range val {
|
||||
result[i] = result[i] && v
|
||||
}
|
||||
mu.Unlock()
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
cmd.SetErr(err)
|
||||
} else {
|
||||
cmd.val = result
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return cmd
|
||||
}
|
|
@ -13,6 +13,7 @@ import (
|
|||
|
||||
. "github.com/bsm/ginkgo/v2"
|
||||
. "github.com/bsm/gomega"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
"github.com/redis/go-redis/v9/internal/hashtag"
|
||||
)
|
||||
|
@ -1457,7 +1458,7 @@ var _ = Describe("ClusterClient timeout", func() {
|
|||
})
|
||||
|
||||
var _ = Describe("ClusterClient ParseURL", func() {
|
||||
var cases = []struct {
|
||||
cases := []struct {
|
||||
test string
|
||||
url string
|
||||
o *redis.ClusterOptions // expected value
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "redis",
|
||||
"version": "9.1.0",
|
||||
"version": "9.3.0",
|
||||
"main": "index.js",
|
||||
"repository": "git@github.com:redis/go-redis.git",
|
||||
"author": "Vladimir Mihailenco <vladimir.webdev@gmail.com>",
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
"github.com/redis/go-redis/v9/internal/proto"
|
||||
)
|
||||
|
||||
type probabilisticCmdable interface {
|
||||
type ProbabilisticCmdable interface {
|
||||
BFAdd(ctx context.Context, key string, element interface{}) *BoolCmd
|
||||
BFCard(ctx context.Context, key string) *IntCmd
|
||||
BFExists(ctx context.Context, key string, element interface{}) *BoolCmd
|
||||
|
@ -24,7 +24,7 @@ type probabilisticCmdable interface {
|
|||
BFReserve(ctx context.Context, key string, errorRate float64, capacity int64) *StatusCmd
|
||||
BFReserveExpansion(ctx context.Context, key string, errorRate float64, capacity, expansion int64) *StatusCmd
|
||||
BFReserveNonScaling(ctx context.Context, key string, errorRate float64, capacity int64) *StatusCmd
|
||||
BFReserveArgs(ctx context.Context, key string, options *BFReserveOptions) *StatusCmd
|
||||
BFReserveWithArgs(ctx context.Context, key string, options *BFReserveOptions) *StatusCmd
|
||||
BFScanDump(ctx context.Context, key string, iterator int64) *ScanDumpCmd
|
||||
BFLoadChunk(ctx context.Context, key string, iterator int64, data interface{}) *StatusCmd
|
||||
|
||||
|
@ -38,7 +38,7 @@ type probabilisticCmdable interface {
|
|||
CFInsertNX(ctx context.Context, key string, options *CFInsertOptions, elements ...interface{}) *IntSliceCmd
|
||||
CFMExists(ctx context.Context, key string, elements ...interface{}) *BoolSliceCmd
|
||||
CFReserve(ctx context.Context, key string, capacity int64) *StatusCmd
|
||||
CFReserveArgs(ctx context.Context, key string, options *CFReserveOptions) *StatusCmd
|
||||
CFReserveWithArgs(ctx context.Context, key string, options *CFReserveOptions) *StatusCmd
|
||||
CFReserveExpansion(ctx context.Context, key string, capacity int64, expansion int64) *StatusCmd
|
||||
CFReserveBucketSize(ctx context.Context, key string, capacity int64, bucketsize int64) *StatusCmd
|
||||
CFReserveMaxIterations(ctx context.Context, key string, capacity int64, maxiterations int64) *StatusCmd
|
||||
|
@ -143,19 +143,14 @@ func (c cmdable) BFReserveNonScaling(ctx context.Context, key string, errorRate
|
|||
return cmd
|
||||
}
|
||||
|
||||
// BFReserveArgs creates an empty Bloom filter with a single sub-filter
|
||||
// BFReserveWithArgs creates an empty Bloom filter with a single sub-filter
|
||||
// for the initial specified capacity and with an upper bound error_rate.
|
||||
// This function also allows for specifying additional options such as expansion rate and non-scaling behavior.
|
||||
// For more information - https://redis.io/commands/bf.reserve/
|
||||
func (c cmdable) BFReserveArgs(ctx context.Context, key string, options *BFReserveOptions) *StatusCmd {
|
||||
func (c cmdable) BFReserveWithArgs(ctx context.Context, key string, options *BFReserveOptions) *StatusCmd {
|
||||
args := []interface{}{"BF.RESERVE", key}
|
||||
if options != nil {
|
||||
if options.Error != 0 {
|
||||
args = append(args, options.Error)
|
||||
}
|
||||
if options.Capacity != 0 {
|
||||
args = append(args, options.Capacity)
|
||||
}
|
||||
args = append(args, options.Error, options.Capacity)
|
||||
if options.Expansion != 0 {
|
||||
args = append(args, "EXPANSION", options.Expansion)
|
||||
}
|
||||
|
@ -310,6 +305,7 @@ func NewBFInfoCmd(ctx context.Context, args ...interface{}) *BFInfoCmd {
|
|||
func (cmd *BFInfoCmd) SetVal(val BFInfo) {
|
||||
cmd.val = val
|
||||
}
|
||||
|
||||
func (cmd *BFInfoCmd) String() string {
|
||||
return cmdString(cmd, cmd.val)
|
||||
}
|
||||
|
@ -493,10 +489,10 @@ func (c cmdable) CFReserveMaxIterations(ctx context.Context, key string, capacit
|
|||
return cmd
|
||||
}
|
||||
|
||||
// CFReserveArgs creates an empty Cuckoo filter with the specified options.
|
||||
// CFReserveWithArgs creates an empty Cuckoo filter with the specified options.
|
||||
// This function allows for specifying additional options such as bucket size and maximum number of iterations.
|
||||
// For more information - https://redis.io/commands/cf.reserve/
|
||||
func (c cmdable) CFReserveArgs(ctx context.Context, key string, options *CFReserveOptions) *StatusCmd {
|
||||
func (c cmdable) CFReserveWithArgs(ctx context.Context, key string, options *CFReserveOptions) *StatusCmd {
|
||||
args := []interface{}{"CF.RESERVE", key, options.Capacity}
|
||||
if options.BucketSize != 0 {
|
||||
args = append(args, "BUCKETSIZE", options.BucketSize)
|
||||
|
@ -679,7 +675,7 @@ func (c cmdable) CFInfo(ctx context.Context, key string) *CFInfoCmd {
|
|||
// For more information - https://redis.io/commands/cf.insert/
|
||||
func (c cmdable) CFInsert(ctx context.Context, key string, options *CFInsertOptions, elements ...interface{}) *BoolSliceCmd {
|
||||
args := []interface{}{"CF.INSERT", key}
|
||||
args = c.getCfInsertArgs(args, options, elements...)
|
||||
args = c.getCfInsertWithArgs(args, options, elements...)
|
||||
|
||||
cmd := NewBoolSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
|
@ -693,14 +689,14 @@ func (c cmdable) CFInsert(ctx context.Context, key string, options *CFInsertOpti
|
|||
// For more information - https://redis.io/commands/cf.insertnx/
|
||||
func (c cmdable) CFInsertNX(ctx context.Context, key string, options *CFInsertOptions, elements ...interface{}) *IntSliceCmd {
|
||||
args := []interface{}{"CF.INSERTNX", key}
|
||||
args = c.getCfInsertArgs(args, options, elements...)
|
||||
args = c.getCfInsertWithArgs(args, options, elements...)
|
||||
|
||||
cmd := NewIntSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) getCfInsertArgs(args []interface{}, options *CFInsertOptions, elements ...interface{}) []interface{} {
|
||||
func (c cmdable) getCfInsertWithArgs(args []interface{}, options *CFInsertOptions, elements ...interface{}) []interface{} {
|
||||
if options != nil {
|
||||
if options.Capacity != 0 {
|
||||
args = append(args, "CAPACITY", options.Capacity)
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
|
||||
. "github.com/bsm/ginkgo/v2"
|
||||
. "github.com/bsm/gomega"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
|
@ -159,7 +160,6 @@ var _ = Describe("Probabilistic commands", Label("probabilistic"), func() {
|
|||
Expect(resultAdd2[0]).To(BeFalse())
|
||||
Expect(resultAdd2[1]).To(BeFalse())
|
||||
Expect(resultAdd2[2]).To(BeTrue())
|
||||
|
||||
})
|
||||
|
||||
It("should BFMExists", Label("bloom", "bfmexists"), func() {
|
||||
|
@ -227,14 +227,14 @@ var _ = Describe("Probabilistic commands", Label("probabilistic"), func() {
|
|||
Expect(infBefore).To(BeEquivalentTo(infAfter))
|
||||
})
|
||||
|
||||
It("should BFReserveArgs", Label("bloom", "bfreserveargs"), func() {
|
||||
It("should BFReserveWithArgs", Label("bloom", "bfreserveargs"), func() {
|
||||
options := &redis.BFReserveOptions{
|
||||
Capacity: 2000,
|
||||
Error: 0.001,
|
||||
Expansion: 3,
|
||||
NonScaling: false,
|
||||
}
|
||||
err := client.BFReserveArgs(ctx, "testbf", options).Err()
|
||||
err := client.BFReserveWithArgs(ctx, "testbf", options).Err()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
result, err := client.BFInfo(ctx, "testbf").Result()
|
||||
|
@ -352,7 +352,7 @@ var _ = Describe("Probabilistic commands", Label("probabilistic"), func() {
|
|||
Expect(infBefore).To(BeEquivalentTo(infAfter))
|
||||
})
|
||||
|
||||
It("should CFInfo and CFReserveArgs", Label("cuckoo", "cfinfo", "cfreserveargs"), func() {
|
||||
It("should CFInfo and CFReserveWithArgs", Label("cuckoo", "cfinfo", "cfreserveargs"), func() {
|
||||
args := &redis.CFReserveOptions{
|
||||
Capacity: 2048,
|
||||
BucketSize: 3,
|
||||
|
@ -360,7 +360,7 @@ var _ = Describe("Probabilistic commands", Label("probabilistic"), func() {
|
|||
Expansion: 2,
|
||||
}
|
||||
|
||||
err := client.CFReserveArgs(ctx, "testcf1", args).Err()
|
||||
err := client.CFReserveWithArgs(ctx, "testcf1", args).Err()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
result, err := client.CFInfo(ctx, "testcf1").Result()
|
||||
|
@ -424,7 +424,6 @@ var _ = Describe("Probabilistic commands", Label("probabilistic"), func() {
|
|||
Expect(result[2]).To(BeTrue())
|
||||
Expect(result[3]).To(BeFalse())
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
Describe("CMS", Label("cms"), func() {
|
||||
|
@ -438,7 +437,6 @@ var _ = Describe("Probabilistic commands", Label("probabilistic"), func() {
|
|||
Expect(result[0]).To(BeEquivalentTo(int64(1)))
|
||||
Expect(result[1]).To(BeEquivalentTo(int64(2)))
|
||||
Expect(result[2]).To(BeEquivalentTo(int64(3)))
|
||||
|
||||
})
|
||||
|
||||
It("should CMSInitByDim and CMSInfo", Label("cms", "cmsinitbydim", "cmsinfo"), func() {
|
||||
|
@ -512,9 +510,7 @@ var _ = Describe("Probabilistic commands", Label("probabilistic"), func() {
|
|||
Expect(result[0]).To(BeEquivalentTo(int64(1)))
|
||||
Expect(result[1]).To(BeEquivalentTo(int64(6)))
|
||||
Expect(result[2]).To(BeEquivalentTo(int64(6)))
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
Describe("TopK", Label("topk"), func() {
|
||||
|
@ -580,7 +576,6 @@ var _ = Describe("Probabilistic commands", Label("probabilistic"), func() {
|
|||
Expect(resultInfo.Depth).To(BeEquivalentTo(int64(8)))
|
||||
Expect(resultInfo.Decay).To(BeEquivalentTo(0.5))
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
Describe("t-digest", Label("tdigest"), func() {
|
||||
|
@ -691,7 +686,6 @@ var _ = Describe("Probabilistic commands", Label("probabilistic"), func() {
|
|||
reset, err := client.TDigestReset(ctx, "tdigest1").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(reset).To(BeEquivalentTo("OK"))
|
||||
|
||||
})
|
||||
|
||||
It("should TDigestCreateWithCompression", Label("tdigest", "tcreatewithcompression"), func() {
|
||||
|
|
|
@ -487,11 +487,11 @@ func (c *PubSub) getContext() context.Context {
|
|||
|
||||
// Channel returns a Go channel for concurrently receiving messages.
|
||||
// The channel is closed together with the PubSub. If the Go channel
|
||||
// is blocked full for 30 seconds the message is dropped.
|
||||
// is blocked full for 1 minute the message is dropped.
|
||||
// Receive* APIs can not be used after channel is created.
|
||||
//
|
||||
// go-redis periodically sends ping messages to test connection health
|
||||
// and re-subscribes if ping can not not received for 30 seconds.
|
||||
// and re-subscribes if ping can not not received for 1 minute.
|
||||
func (c *PubSub) Channel(opts ...ChannelOption) <-chan *Message {
|
||||
c.chOnce.Do(func() {
|
||||
c.msgCh = newChannel(c, opts...)
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
package redis
|
||||
|
||||
import "context"
|
||||
|
||||
type PubSubCmdable interface {
|
||||
Publish(ctx context.Context, channel string, message interface{}) *IntCmd
|
||||
SPublish(ctx context.Context, channel string, message interface{}) *IntCmd
|
||||
PubSubChannels(ctx context.Context, pattern string) *StringSliceCmd
|
||||
PubSubNumSub(ctx context.Context, channels ...string) *MapStringIntCmd
|
||||
PubSubNumPat(ctx context.Context) *IntCmd
|
||||
PubSubShardChannels(ctx context.Context, pattern string) *StringSliceCmd
|
||||
PubSubShardNumSub(ctx context.Context, channels ...string) *MapStringIntCmd
|
||||
}
|
||||
|
||||
// Publish posts the message to the channel.
|
||||
func (c cmdable) Publish(ctx context.Context, channel string, message interface{}) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "publish", channel, message)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) SPublish(ctx context.Context, channel string, message interface{}) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "spublish", channel, message)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) PubSubChannels(ctx context.Context, pattern string) *StringSliceCmd {
|
||||
args := []interface{}{"pubsub", "channels"}
|
||||
if pattern != "*" {
|
||||
args = append(args, pattern)
|
||||
}
|
||||
cmd := NewStringSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) PubSubNumSub(ctx context.Context, channels ...string) *MapStringIntCmd {
|
||||
args := make([]interface{}, 2+len(channels))
|
||||
args[0] = "pubsub"
|
||||
args[1] = "numsub"
|
||||
for i, channel := range channels {
|
||||
args[2+i] = channel
|
||||
}
|
||||
cmd := NewMapStringIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) PubSubShardChannels(ctx context.Context, pattern string) *StringSliceCmd {
|
||||
args := []interface{}{"pubsub", "shardchannels"}
|
||||
if pattern != "*" {
|
||||
args = append(args, pattern)
|
||||
}
|
||||
cmd := NewStringSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) PubSubShardNumSub(ctx context.Context, channels ...string) *MapStringIntCmd {
|
||||
args := make([]interface{}, 2+len(channels))
|
||||
args[0] = "pubsub"
|
||||
args[1] = "shardnumsub"
|
||||
for i, channel := range channels {
|
||||
args[2+i] = channel
|
||||
}
|
||||
cmd := NewMapStringIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) PubSubNumPat(ctx context.Context) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "pubsub", "numpat")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
9
redis.go
9
redis.go
|
@ -299,7 +299,14 @@ func (c *baseClient) initConn(ctx context.Context, cn *pool.Conn) error {
|
|||
// difficult to rely on error strings to determine all results.
|
||||
return err
|
||||
}
|
||||
|
||||
if !c.opt.DisableIndentity {
|
||||
libName := ""
|
||||
libVer := Version()
|
||||
libInfo := LibraryInfo{LibName: &libName}
|
||||
conn.ClientSetInfo(ctx, libInfo)
|
||||
libInfo = LibraryInfo{LibVer: &libVer}
|
||||
conn.ClientSetInfo(ctx, libInfo)
|
||||
}
|
||||
_, err := conn.Pipelined(ctx, func(pipe Pipeliner) error {
|
||||
if !auth && password != "" {
|
||||
if username != "" {
|
||||
|
|
|
@ -558,4 +558,24 @@ var _ = Describe("Hook", func() {
|
|||
"hook-1-process-end",
|
||||
}))
|
||||
})
|
||||
|
||||
It("wrapped error in a hook", func() {
|
||||
client.AddHook(&hook{
|
||||
processHook: func(hook redis.ProcessHook) redis.ProcessHook {
|
||||
return func(ctx context.Context, cmd redis.Cmder) error {
|
||||
if err := hook(ctx, cmd); err != nil {
|
||||
return fmt.Errorf("wrapped error: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
},
|
||||
})
|
||||
client.ScriptFlush(ctx)
|
||||
|
||||
script := redis.NewScript(`return 'Script and hook'`)
|
||||
|
||||
cmd := script.Run(ctx, client, nil)
|
||||
Expect(cmd.Err()).NotTo(HaveOccurred())
|
||||
Expect(cmd.Val()).To(Equal("Script and hook"))
|
||||
})
|
||||
})
|
||||
|
|
21
ring.go
21
ring.go
|
@ -79,9 +79,10 @@ type RingOptions struct {
|
|||
MinRetryBackoff time.Duration
|
||||
MaxRetryBackoff time.Duration
|
||||
|
||||
DialTimeout time.Duration
|
||||
ReadTimeout time.Duration
|
||||
WriteTimeout time.Duration
|
||||
DialTimeout time.Duration
|
||||
ReadTimeout time.Duration
|
||||
WriteTimeout time.Duration
|
||||
ContextTimeoutEnabled bool
|
||||
|
||||
// PoolFIFO uses FIFO mode for each node connection pool GET/PUT (default LIFO).
|
||||
PoolFIFO bool
|
||||
|
@ -90,11 +91,14 @@ type RingOptions struct {
|
|||
PoolTimeout time.Duration
|
||||
MinIdleConns int
|
||||
MaxIdleConns int
|
||||
MaxActiveConns int
|
||||
ConnMaxIdleTime time.Duration
|
||||
ConnMaxLifetime time.Duration
|
||||
|
||||
TLSConfig *tls.Config
|
||||
Limiter Limiter
|
||||
|
||||
DisableIndentity bool
|
||||
}
|
||||
|
||||
func (opt *RingOptions) init() {
|
||||
|
@ -144,20 +148,24 @@ func (opt *RingOptions) clientOptions() *Options {
|
|||
|
||||
MaxRetries: -1,
|
||||
|
||||
DialTimeout: opt.DialTimeout,
|
||||
ReadTimeout: opt.ReadTimeout,
|
||||
WriteTimeout: opt.WriteTimeout,
|
||||
DialTimeout: opt.DialTimeout,
|
||||
ReadTimeout: opt.ReadTimeout,
|
||||
WriteTimeout: opt.WriteTimeout,
|
||||
ContextTimeoutEnabled: opt.ContextTimeoutEnabled,
|
||||
|
||||
PoolFIFO: opt.PoolFIFO,
|
||||
PoolSize: opt.PoolSize,
|
||||
PoolTimeout: opt.PoolTimeout,
|
||||
MinIdleConns: opt.MinIdleConns,
|
||||
MaxIdleConns: opt.MaxIdleConns,
|
||||
MaxActiveConns: opt.MaxActiveConns,
|
||||
ConnMaxIdleTime: opt.ConnMaxIdleTime,
|
||||
ConnMaxLifetime: opt.ConnMaxLifetime,
|
||||
|
||||
TLSConfig: opt.TLSConfig,
|
||||
Limiter: opt.Limiter,
|
||||
|
||||
DisableIndentity: opt.DisableIndentity,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -292,7 +300,6 @@ func (c *ringSharding) SetAddrs(addrs map[string]string) {
|
|||
func (c *ringSharding) newRingShards(
|
||||
addrs map[string]string, existing *ringShards,
|
||||
) (shards *ringShards, created, unused map[string]*ringShard) {
|
||||
|
||||
shards = &ringShards{m: make(map[string]*ringShard, len(addrs))}
|
||||
created = make(map[string]*ringShard) // indexed by addr
|
||||
unused = make(map[string]*ringShard) // indexed by addr
|
||||
|
|
|
@ -0,0 +1,215 @@
|
|||
package redis
|
||||
|
||||
import "context"
|
||||
|
||||
type ScriptingFunctionsCmdable interface {
|
||||
Eval(ctx context.Context, script string, keys []string, args ...interface{}) *Cmd
|
||||
EvalSha(ctx context.Context, sha1 string, keys []string, args ...interface{}) *Cmd
|
||||
EvalRO(ctx context.Context, script string, keys []string, args ...interface{}) *Cmd
|
||||
EvalShaRO(ctx context.Context, sha1 string, keys []string, args ...interface{}) *Cmd
|
||||
ScriptExists(ctx context.Context, hashes ...string) *BoolSliceCmd
|
||||
ScriptFlush(ctx context.Context) *StatusCmd
|
||||
ScriptKill(ctx context.Context) *StatusCmd
|
||||
ScriptLoad(ctx context.Context, script string) *StringCmd
|
||||
|
||||
FunctionLoad(ctx context.Context, code string) *StringCmd
|
||||
FunctionLoadReplace(ctx context.Context, code string) *StringCmd
|
||||
FunctionDelete(ctx context.Context, libName string) *StringCmd
|
||||
FunctionFlush(ctx context.Context) *StringCmd
|
||||
FunctionKill(ctx context.Context) *StringCmd
|
||||
FunctionFlushAsync(ctx context.Context) *StringCmd
|
||||
FunctionList(ctx context.Context, q FunctionListQuery) *FunctionListCmd
|
||||
FunctionDump(ctx context.Context) *StringCmd
|
||||
FunctionRestore(ctx context.Context, libDump string) *StringCmd
|
||||
FunctionStats(ctx context.Context) *FunctionStatsCmd
|
||||
FCall(ctx context.Context, function string, keys []string, args ...interface{}) *Cmd
|
||||
FCallRo(ctx context.Context, function string, keys []string, args ...interface{}) *Cmd
|
||||
FCallRO(ctx context.Context, function string, keys []string, args ...interface{}) *Cmd
|
||||
}
|
||||
|
||||
func (c cmdable) Eval(ctx context.Context, script string, keys []string, args ...interface{}) *Cmd {
|
||||
return c.eval(ctx, "eval", script, keys, args...)
|
||||
}
|
||||
|
||||
func (c cmdable) EvalRO(ctx context.Context, script string, keys []string, args ...interface{}) *Cmd {
|
||||
return c.eval(ctx, "eval_ro", script, keys, args...)
|
||||
}
|
||||
|
||||
func (c cmdable) EvalSha(ctx context.Context, sha1 string, keys []string, args ...interface{}) *Cmd {
|
||||
return c.eval(ctx, "evalsha", sha1, keys, args...)
|
||||
}
|
||||
|
||||
func (c cmdable) EvalShaRO(ctx context.Context, sha1 string, keys []string, args ...interface{}) *Cmd {
|
||||
return c.eval(ctx, "evalsha_ro", sha1, keys, args...)
|
||||
}
|
||||
|
||||
func (c cmdable) eval(ctx context.Context, name, payload string, keys []string, args ...interface{}) *Cmd {
|
||||
cmdArgs := make([]interface{}, 3+len(keys), 3+len(keys)+len(args))
|
||||
cmdArgs[0] = name
|
||||
cmdArgs[1] = payload
|
||||
cmdArgs[2] = len(keys)
|
||||
for i, key := range keys {
|
||||
cmdArgs[3+i] = key
|
||||
}
|
||||
cmdArgs = appendArgs(cmdArgs, args)
|
||||
cmd := NewCmd(ctx, cmdArgs...)
|
||||
|
||||
// it is possible that only args exist without a key.
|
||||
// rdb.eval(ctx, eval, script, nil, arg1, arg2)
|
||||
if len(keys) > 0 {
|
||||
cmd.SetFirstKeyPos(3)
|
||||
}
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ScriptExists(ctx context.Context, hashes ...string) *BoolSliceCmd {
|
||||
args := make([]interface{}, 2+len(hashes))
|
||||
args[0] = "script"
|
||||
args[1] = "exists"
|
||||
for i, hash := range hashes {
|
||||
args[2+i] = hash
|
||||
}
|
||||
cmd := NewBoolSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ScriptFlush(ctx context.Context) *StatusCmd {
|
||||
cmd := NewStatusCmd(ctx, "script", "flush")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ScriptKill(ctx context.Context) *StatusCmd {
|
||||
cmd := NewStatusCmd(ctx, "script", "kill")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ScriptLoad(ctx context.Context, script string) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "script", "load", script)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------
|
||||
|
||||
// FunctionListQuery is used with FunctionList to query for Redis libraries
|
||||
//
|
||||
// LibraryNamePattern - Use an empty string to get all libraries.
|
||||
// - Use a glob-style pattern to match multiple libraries with a matching name
|
||||
// - Use a library's full name to match a single library
|
||||
// WithCode - If true, it will return the code of the library
|
||||
type FunctionListQuery struct {
|
||||
LibraryNamePattern string
|
||||
WithCode bool
|
||||
}
|
||||
|
||||
func (c cmdable) FunctionLoad(ctx context.Context, code string) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "function", "load", code)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) FunctionLoadReplace(ctx context.Context, code string) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "function", "load", "replace", code)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) FunctionDelete(ctx context.Context, libName string) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "function", "delete", libName)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) FunctionFlush(ctx context.Context) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "function", "flush")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) FunctionKill(ctx context.Context) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "function", "kill")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) FunctionFlushAsync(ctx context.Context) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "function", "flush", "async")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) FunctionList(ctx context.Context, q FunctionListQuery) *FunctionListCmd {
|
||||
args := make([]interface{}, 2, 5)
|
||||
args[0] = "function"
|
||||
args[1] = "list"
|
||||
if q.LibraryNamePattern != "" {
|
||||
args = append(args, "libraryname", q.LibraryNamePattern)
|
||||
}
|
||||
if q.WithCode {
|
||||
args = append(args, "withcode")
|
||||
}
|
||||
cmd := NewFunctionListCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) FunctionDump(ctx context.Context) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "function", "dump")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) FunctionRestore(ctx context.Context, libDump string) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "function", "restore", libDump)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) FunctionStats(ctx context.Context) *FunctionStatsCmd {
|
||||
cmd := NewFunctionStatsCmd(ctx, "function", "stats")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) FCall(ctx context.Context, function string, keys []string, args ...interface{}) *Cmd {
|
||||
cmdArgs := fcallArgs("fcall", function, keys, args...)
|
||||
cmd := NewCmd(ctx, cmdArgs...)
|
||||
if len(keys) > 0 {
|
||||
cmd.SetFirstKeyPos(3)
|
||||
}
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// FCallRo this function simply calls FCallRO,
|
||||
// Deprecated: to maintain convention FCallRO.
|
||||
func (c cmdable) FCallRo(ctx context.Context, function string, keys []string, args ...interface{}) *Cmd {
|
||||
return c.FCallRO(ctx, function, keys, args...)
|
||||
}
|
||||
|
||||
func (c cmdable) FCallRO(ctx context.Context, function string, keys []string, args ...interface{}) *Cmd {
|
||||
cmdArgs := fcallArgs("fcall_ro", function, keys, args...)
|
||||
cmd := NewCmd(ctx, cmdArgs...)
|
||||
if len(keys) > 0 {
|
||||
cmd.SetFirstKeyPos(3)
|
||||
}
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func fcallArgs(command string, function string, keys []string, args ...interface{}) []interface{} {
|
||||
cmdArgs := make([]interface{}, 3+len(keys), 3+len(keys)+len(args))
|
||||
cmdArgs[0] = command
|
||||
cmdArgs[1] = function
|
||||
cmdArgs[2] = len(keys)
|
||||
for i, key := range keys {
|
||||
cmdArgs[3+i] = key
|
||||
}
|
||||
|
||||
cmdArgs = append(cmdArgs, args...)
|
||||
return cmdArgs
|
||||
}
|
22
sentinel.go
22
sentinel.go
|
@ -74,10 +74,13 @@ type FailoverOptions struct {
|
|||
PoolTimeout time.Duration
|
||||
MinIdleConns int
|
||||
MaxIdleConns int
|
||||
MaxActiveConns int
|
||||
ConnMaxIdleTime time.Duration
|
||||
ConnMaxLifetime time.Duration
|
||||
|
||||
TLSConfig *tls.Config
|
||||
|
||||
DisableIndentity bool
|
||||
}
|
||||
|
||||
func (opt *FailoverOptions) clientOptions() *Options {
|
||||
|
@ -107,10 +110,13 @@ func (opt *FailoverOptions) clientOptions() *Options {
|
|||
PoolTimeout: opt.PoolTimeout,
|
||||
MinIdleConns: opt.MinIdleConns,
|
||||
MaxIdleConns: opt.MaxIdleConns,
|
||||
MaxActiveConns: opt.MaxActiveConns,
|
||||
ConnMaxIdleTime: opt.ConnMaxIdleTime,
|
||||
ConnMaxLifetime: opt.ConnMaxLifetime,
|
||||
|
||||
TLSConfig: opt.TLSConfig,
|
||||
|
||||
DisableIndentity: opt.DisableIndentity,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -130,15 +136,17 @@ func (opt *FailoverOptions) sentinelOptions(addr string) *Options {
|
|||
MinRetryBackoff: opt.MinRetryBackoff,
|
||||
MaxRetryBackoff: opt.MaxRetryBackoff,
|
||||
|
||||
DialTimeout: opt.DialTimeout,
|
||||
ReadTimeout: opt.ReadTimeout,
|
||||
WriteTimeout: opt.WriteTimeout,
|
||||
DialTimeout: opt.DialTimeout,
|
||||
ReadTimeout: opt.ReadTimeout,
|
||||
WriteTimeout: opt.WriteTimeout,
|
||||
ContextTimeoutEnabled: opt.ContextTimeoutEnabled,
|
||||
|
||||
PoolFIFO: opt.PoolFIFO,
|
||||
PoolSize: opt.PoolSize,
|
||||
PoolTimeout: opt.PoolTimeout,
|
||||
MinIdleConns: opt.MinIdleConns,
|
||||
MaxIdleConns: opt.MaxIdleConns,
|
||||
MaxActiveConns: opt.MaxActiveConns,
|
||||
ConnMaxIdleTime: opt.ConnMaxIdleTime,
|
||||
ConnMaxLifetime: opt.ConnMaxLifetime,
|
||||
|
||||
|
@ -165,15 +173,17 @@ func (opt *FailoverOptions) clusterOptions() *ClusterOptions {
|
|||
MinRetryBackoff: opt.MinRetryBackoff,
|
||||
MaxRetryBackoff: opt.MaxRetryBackoff,
|
||||
|
||||
DialTimeout: opt.DialTimeout,
|
||||
ReadTimeout: opt.ReadTimeout,
|
||||
WriteTimeout: opt.WriteTimeout,
|
||||
DialTimeout: opt.DialTimeout,
|
||||
ReadTimeout: opt.ReadTimeout,
|
||||
WriteTimeout: opt.WriteTimeout,
|
||||
ContextTimeoutEnabled: opt.ContextTimeoutEnabled,
|
||||
|
||||
PoolFIFO: opt.PoolFIFO,
|
||||
PoolSize: opt.PoolSize,
|
||||
PoolTimeout: opt.PoolTimeout,
|
||||
MinIdleConns: opt.MinIdleConns,
|
||||
MaxIdleConns: opt.MaxIdleConns,
|
||||
MaxActiveConns: opt.MaxActiveConns,
|
||||
ConnMaxIdleTime: opt.ConnMaxIdleTime,
|
||||
ConnMaxLifetime: opt.ConnMaxLifetime,
|
||||
|
||||
|
|
|
@ -0,0 +1,217 @@
|
|||
package redis
|
||||
|
||||
import "context"
|
||||
|
||||
type SetCmdable interface {
|
||||
SAdd(ctx context.Context, key string, members ...interface{}) *IntCmd
|
||||
SCard(ctx context.Context, key string) *IntCmd
|
||||
SDiff(ctx context.Context, keys ...string) *StringSliceCmd
|
||||
SDiffStore(ctx context.Context, destination string, keys ...string) *IntCmd
|
||||
SInter(ctx context.Context, keys ...string) *StringSliceCmd
|
||||
SInterCard(ctx context.Context, limit int64, keys ...string) *IntCmd
|
||||
SInterStore(ctx context.Context, destination string, keys ...string) *IntCmd
|
||||
SIsMember(ctx context.Context, key string, member interface{}) *BoolCmd
|
||||
SMIsMember(ctx context.Context, key string, members ...interface{}) *BoolSliceCmd
|
||||
SMembers(ctx context.Context, key string) *StringSliceCmd
|
||||
SMembersMap(ctx context.Context, key string) *StringStructMapCmd
|
||||
SMove(ctx context.Context, source, destination string, member interface{}) *BoolCmd
|
||||
SPop(ctx context.Context, key string) *StringCmd
|
||||
SPopN(ctx context.Context, key string, count int64) *StringSliceCmd
|
||||
SRandMember(ctx context.Context, key string) *StringCmd
|
||||
SRandMemberN(ctx context.Context, key string, count int64) *StringSliceCmd
|
||||
SRem(ctx context.Context, key string, members ...interface{}) *IntCmd
|
||||
SScan(ctx context.Context, key string, cursor uint64, match string, count int64) *ScanCmd
|
||||
SUnion(ctx context.Context, keys ...string) *StringSliceCmd
|
||||
SUnionStore(ctx context.Context, destination string, keys ...string) *IntCmd
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
func (c cmdable) SAdd(ctx context.Context, key string, members ...interface{}) *IntCmd {
|
||||
args := make([]interface{}, 2, 2+len(members))
|
||||
args[0] = "sadd"
|
||||
args[1] = key
|
||||
args = appendArgs(args, members)
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) SCard(ctx context.Context, key string) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "scard", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) SDiff(ctx context.Context, keys ...string) *StringSliceCmd {
|
||||
args := make([]interface{}, 1+len(keys))
|
||||
args[0] = "sdiff"
|
||||
for i, key := range keys {
|
||||
args[1+i] = key
|
||||
}
|
||||
cmd := NewStringSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) SDiffStore(ctx context.Context, destination string, keys ...string) *IntCmd {
|
||||
args := make([]interface{}, 2+len(keys))
|
||||
args[0] = "sdiffstore"
|
||||
args[1] = destination
|
||||
for i, key := range keys {
|
||||
args[2+i] = key
|
||||
}
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) SInter(ctx context.Context, keys ...string) *StringSliceCmd {
|
||||
args := make([]interface{}, 1+len(keys))
|
||||
args[0] = "sinter"
|
||||
for i, key := range keys {
|
||||
args[1+i] = key
|
||||
}
|
||||
cmd := NewStringSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) SInterCard(ctx context.Context, limit int64, keys ...string) *IntCmd {
|
||||
args := make([]interface{}, 4+len(keys))
|
||||
args[0] = "sintercard"
|
||||
numkeys := int64(0)
|
||||
for i, key := range keys {
|
||||
args[2+i] = key
|
||||
numkeys++
|
||||
}
|
||||
args[1] = numkeys
|
||||
args[2+numkeys] = "limit"
|
||||
args[3+numkeys] = limit
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) SInterStore(ctx context.Context, destination string, keys ...string) *IntCmd {
|
||||
args := make([]interface{}, 2+len(keys))
|
||||
args[0] = "sinterstore"
|
||||
args[1] = destination
|
||||
for i, key := range keys {
|
||||
args[2+i] = key
|
||||
}
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) SIsMember(ctx context.Context, key string, member interface{}) *BoolCmd {
|
||||
cmd := NewBoolCmd(ctx, "sismember", key, member)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// SMIsMember Redis `SMISMEMBER key member [member ...]` command.
|
||||
func (c cmdable) SMIsMember(ctx context.Context, key string, members ...interface{}) *BoolSliceCmd {
|
||||
args := make([]interface{}, 2, 2+len(members))
|
||||
args[0] = "smismember"
|
||||
args[1] = key
|
||||
args = appendArgs(args, members)
|
||||
cmd := NewBoolSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// SMembers Redis `SMEMBERS key` command output as a slice.
|
||||
func (c cmdable) SMembers(ctx context.Context, key string) *StringSliceCmd {
|
||||
cmd := NewStringSliceCmd(ctx, "smembers", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// SMembersMap Redis `SMEMBERS key` command output as a map.
|
||||
func (c cmdable) SMembersMap(ctx context.Context, key string) *StringStructMapCmd {
|
||||
cmd := NewStringStructMapCmd(ctx, "smembers", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) SMove(ctx context.Context, source, destination string, member interface{}) *BoolCmd {
|
||||
cmd := NewBoolCmd(ctx, "smove", source, destination, member)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// SPop Redis `SPOP key` command.
|
||||
func (c cmdable) SPop(ctx context.Context, key string) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "spop", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// SPopN Redis `SPOP key count` command.
|
||||
func (c cmdable) SPopN(ctx context.Context, key string, count int64) *StringSliceCmd {
|
||||
cmd := NewStringSliceCmd(ctx, "spop", key, count)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// SRandMember Redis `SRANDMEMBER key` command.
|
||||
func (c cmdable) SRandMember(ctx context.Context, key string) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "srandmember", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// SRandMemberN Redis `SRANDMEMBER key count` command.
|
||||
func (c cmdable) SRandMemberN(ctx context.Context, key string, count int64) *StringSliceCmd {
|
||||
cmd := NewStringSliceCmd(ctx, "srandmember", key, count)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) SRem(ctx context.Context, key string, members ...interface{}) *IntCmd {
|
||||
args := make([]interface{}, 2, 2+len(members))
|
||||
args[0] = "srem"
|
||||
args[1] = key
|
||||
args = appendArgs(args, members)
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) SUnion(ctx context.Context, keys ...string) *StringSliceCmd {
|
||||
args := make([]interface{}, 1+len(keys))
|
||||
args[0] = "sunion"
|
||||
for i, key := range keys {
|
||||
args[1+i] = key
|
||||
}
|
||||
cmd := NewStringSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) SUnionStore(ctx context.Context, destination string, keys ...string) *IntCmd {
|
||||
args := make([]interface{}, 2+len(keys))
|
||||
args[0] = "sunionstore"
|
||||
args[1] = destination
|
||||
for i, key := range keys {
|
||||
args[2+i] = key
|
||||
}
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) SScan(ctx context.Context, key string, cursor uint64, match string, count int64) *ScanCmd {
|
||||
args := []interface{}{"sscan", key, cursor}
|
||||
if match != "" {
|
||||
args = append(args, "match", match)
|
||||
}
|
||||
if count > 0 {
|
||||
args = append(args, "count", count)
|
||||
}
|
||||
cmd := NewScanCmd(ctx, c, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
|
@ -0,0 +1,772 @@
|
|||
package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type SortedSetCmdable interface {
|
||||
BZPopMax(ctx context.Context, timeout time.Duration, keys ...string) *ZWithKeyCmd
|
||||
BZPopMin(ctx context.Context, timeout time.Duration, keys ...string) *ZWithKeyCmd
|
||||
BZMPop(ctx context.Context, timeout time.Duration, order string, count int64, keys ...string) *ZSliceWithKeyCmd
|
||||
ZAdd(ctx context.Context, key string, members ...Z) *IntCmd
|
||||
ZAddLT(ctx context.Context, key string, members ...Z) *IntCmd
|
||||
ZAddGT(ctx context.Context, key string, members ...Z) *IntCmd
|
||||
ZAddNX(ctx context.Context, key string, members ...Z) *IntCmd
|
||||
ZAddXX(ctx context.Context, key string, members ...Z) *IntCmd
|
||||
ZAddArgs(ctx context.Context, key string, args ZAddArgs) *IntCmd
|
||||
ZAddArgsIncr(ctx context.Context, key string, args ZAddArgs) *FloatCmd
|
||||
ZCard(ctx context.Context, key string) *IntCmd
|
||||
ZCount(ctx context.Context, key, min, max string) *IntCmd
|
||||
ZLexCount(ctx context.Context, key, min, max string) *IntCmd
|
||||
ZIncrBy(ctx context.Context, key string, increment float64, member string) *FloatCmd
|
||||
ZInter(ctx context.Context, store *ZStore) *StringSliceCmd
|
||||
ZInterWithScores(ctx context.Context, store *ZStore) *ZSliceCmd
|
||||
ZInterCard(ctx context.Context, limit int64, keys ...string) *IntCmd
|
||||
ZInterStore(ctx context.Context, destination string, store *ZStore) *IntCmd
|
||||
ZMPop(ctx context.Context, order string, count int64, keys ...string) *ZSliceWithKeyCmd
|
||||
ZMScore(ctx context.Context, key string, members ...string) *FloatSliceCmd
|
||||
ZPopMax(ctx context.Context, key string, count ...int64) *ZSliceCmd
|
||||
ZPopMin(ctx context.Context, key string, count ...int64) *ZSliceCmd
|
||||
ZRange(ctx context.Context, key string, start, stop int64) *StringSliceCmd
|
||||
ZRangeWithScores(ctx context.Context, key string, start, stop int64) *ZSliceCmd
|
||||
ZRangeByScore(ctx context.Context, key string, opt *ZRangeBy) *StringSliceCmd
|
||||
ZRangeByLex(ctx context.Context, key string, opt *ZRangeBy) *StringSliceCmd
|
||||
ZRangeByScoreWithScores(ctx context.Context, key string, opt *ZRangeBy) *ZSliceCmd
|
||||
ZRangeArgs(ctx context.Context, z ZRangeArgs) *StringSliceCmd
|
||||
ZRangeArgsWithScores(ctx context.Context, z ZRangeArgs) *ZSliceCmd
|
||||
ZRangeStore(ctx context.Context, dst string, z ZRangeArgs) *IntCmd
|
||||
ZRank(ctx context.Context, key, member string) *IntCmd
|
||||
ZRankWithScore(ctx context.Context, key, member string) *RankWithScoreCmd
|
||||
ZRem(ctx context.Context, key string, members ...interface{}) *IntCmd
|
||||
ZRemRangeByRank(ctx context.Context, key string, start, stop int64) *IntCmd
|
||||
ZRemRangeByScore(ctx context.Context, key, min, max string) *IntCmd
|
||||
ZRemRangeByLex(ctx context.Context, key, min, max string) *IntCmd
|
||||
ZRevRange(ctx context.Context, key string, start, stop int64) *StringSliceCmd
|
||||
ZRevRangeWithScores(ctx context.Context, key string, start, stop int64) *ZSliceCmd
|
||||
ZRevRangeByScore(ctx context.Context, key string, opt *ZRangeBy) *StringSliceCmd
|
||||
ZRevRangeByLex(ctx context.Context, key string, opt *ZRangeBy) *StringSliceCmd
|
||||
ZRevRangeByScoreWithScores(ctx context.Context, key string, opt *ZRangeBy) *ZSliceCmd
|
||||
ZRevRank(ctx context.Context, key, member string) *IntCmd
|
||||
ZRevRankWithScore(ctx context.Context, key, member string) *RankWithScoreCmd
|
||||
ZScore(ctx context.Context, key, member string) *FloatCmd
|
||||
ZUnionStore(ctx context.Context, dest string, store *ZStore) *IntCmd
|
||||
ZRandMember(ctx context.Context, key string, count int) *StringSliceCmd
|
||||
ZRandMemberWithScores(ctx context.Context, key string, count int) *ZSliceCmd
|
||||
ZUnion(ctx context.Context, store ZStore) *StringSliceCmd
|
||||
ZUnionWithScores(ctx context.Context, store ZStore) *ZSliceCmd
|
||||
ZDiff(ctx context.Context, keys ...string) *StringSliceCmd
|
||||
ZDiffWithScores(ctx context.Context, keys ...string) *ZSliceCmd
|
||||
ZDiffStore(ctx context.Context, destination string, keys ...string) *IntCmd
|
||||
ZScan(ctx context.Context, key string, cursor uint64, match string, count int64) *ScanCmd
|
||||
}
|
||||
|
||||
// BZPopMax Redis `BZPOPMAX key [key ...] timeout` command.
|
||||
func (c cmdable) BZPopMax(ctx context.Context, timeout time.Duration, keys ...string) *ZWithKeyCmd {
|
||||
args := make([]interface{}, 1+len(keys)+1)
|
||||
args[0] = "bzpopmax"
|
||||
for i, key := range keys {
|
||||
args[1+i] = key
|
||||
}
|
||||
args[len(args)-1] = formatSec(ctx, timeout)
|
||||
cmd := NewZWithKeyCmd(ctx, args...)
|
||||
cmd.setReadTimeout(timeout)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// BZPopMin Redis `BZPOPMIN key [key ...] timeout` command.
|
||||
func (c cmdable) BZPopMin(ctx context.Context, timeout time.Duration, keys ...string) *ZWithKeyCmd {
|
||||
args := make([]interface{}, 1+len(keys)+1)
|
||||
args[0] = "bzpopmin"
|
||||
for i, key := range keys {
|
||||
args[1+i] = key
|
||||
}
|
||||
args[len(args)-1] = formatSec(ctx, timeout)
|
||||
cmd := NewZWithKeyCmd(ctx, args...)
|
||||
cmd.setReadTimeout(timeout)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// BZMPop is the blocking variant of ZMPOP.
|
||||
// When any of the sorted sets contains elements, this command behaves exactly like ZMPOP.
|
||||
// When all sorted sets are empty, Redis will block the connection until another client adds members to one of the keys or until the timeout elapses.
|
||||
// A timeout of zero can be used to block indefinitely.
|
||||
// example: client.BZMPop(ctx, 0,"max", 1, "set")
|
||||
func (c cmdable) BZMPop(ctx context.Context, timeout time.Duration, order string, count int64, keys ...string) *ZSliceWithKeyCmd {
|
||||
args := make([]interface{}, 3+len(keys), 6+len(keys))
|
||||
args[0] = "bzmpop"
|
||||
args[1] = formatSec(ctx, timeout)
|
||||
args[2] = len(keys)
|
||||
for i, key := range keys {
|
||||
args[3+i] = key
|
||||
}
|
||||
args = append(args, strings.ToLower(order), "count", count)
|
||||
cmd := NewZSliceWithKeyCmd(ctx, args...)
|
||||
cmd.setReadTimeout(timeout)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// ZAddArgs WARN: The GT, LT and NX options are mutually exclusive.
|
||||
type ZAddArgs struct {
|
||||
NX bool
|
||||
XX bool
|
||||
LT bool
|
||||
GT bool
|
||||
Ch bool
|
||||
Members []Z
|
||||
}
|
||||
|
||||
func (c cmdable) zAddArgs(key string, args ZAddArgs, incr bool) []interface{} {
|
||||
a := make([]interface{}, 0, 6+2*len(args.Members))
|
||||
a = append(a, "zadd", key)
|
||||
|
||||
// The GT, LT and NX options are mutually exclusive.
|
||||
if args.NX {
|
||||
a = append(a, "nx")
|
||||
} else {
|
||||
if args.XX {
|
||||
a = append(a, "xx")
|
||||
}
|
||||
if args.GT {
|
||||
a = append(a, "gt")
|
||||
} else if args.LT {
|
||||
a = append(a, "lt")
|
||||
}
|
||||
}
|
||||
if args.Ch {
|
||||
a = append(a, "ch")
|
||||
}
|
||||
if incr {
|
||||
a = append(a, "incr")
|
||||
}
|
||||
for _, m := range args.Members {
|
||||
a = append(a, m.Score)
|
||||
a = append(a, m.Member)
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func (c cmdable) ZAddArgs(ctx context.Context, key string, args ZAddArgs) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, c.zAddArgs(key, args, false)...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ZAddArgsIncr(ctx context.Context, key string, args ZAddArgs) *FloatCmd {
|
||||
cmd := NewFloatCmd(ctx, c.zAddArgs(key, args, true)...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// ZAdd Redis `ZADD key score member [score member ...]` command.
|
||||
func (c cmdable) ZAdd(ctx context.Context, key string, members ...Z) *IntCmd {
|
||||
return c.ZAddArgs(ctx, key, ZAddArgs{
|
||||
Members: members,
|
||||
})
|
||||
}
|
||||
|
||||
// ZAddLT Redis `ZADD key LT score member [score member ...]` command.
|
||||
func (c cmdable) ZAddLT(ctx context.Context, key string, members ...Z) *IntCmd {
|
||||
return c.ZAddArgs(ctx, key, ZAddArgs{
|
||||
LT: true,
|
||||
Members: members,
|
||||
})
|
||||
}
|
||||
|
||||
// ZAddGT Redis `ZADD key GT score member [score member ...]` command.
|
||||
func (c cmdable) ZAddGT(ctx context.Context, key string, members ...Z) *IntCmd {
|
||||
return c.ZAddArgs(ctx, key, ZAddArgs{
|
||||
GT: true,
|
||||
Members: members,
|
||||
})
|
||||
}
|
||||
|
||||
// ZAddNX Redis `ZADD key NX score member [score member ...]` command.
|
||||
func (c cmdable) ZAddNX(ctx context.Context, key string, members ...Z) *IntCmd {
|
||||
return c.ZAddArgs(ctx, key, ZAddArgs{
|
||||
NX: true,
|
||||
Members: members,
|
||||
})
|
||||
}
|
||||
|
||||
// ZAddXX Redis `ZADD key XX score member [score member ...]` command.
|
||||
func (c cmdable) ZAddXX(ctx context.Context, key string, members ...Z) *IntCmd {
|
||||
return c.ZAddArgs(ctx, key, ZAddArgs{
|
||||
XX: true,
|
||||
Members: members,
|
||||
})
|
||||
}
|
||||
|
||||
func (c cmdable) ZCard(ctx context.Context, key string) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "zcard", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ZCount(ctx context.Context, key, min, max string) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "zcount", key, min, max)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ZLexCount(ctx context.Context, key, min, max string) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "zlexcount", key, min, max)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ZIncrBy(ctx context.Context, key string, increment float64, member string) *FloatCmd {
|
||||
cmd := NewFloatCmd(ctx, "zincrby", key, increment, member)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ZInterStore(ctx context.Context, destination string, store *ZStore) *IntCmd {
|
||||
args := make([]interface{}, 0, 3+store.len())
|
||||
args = append(args, "zinterstore", destination, len(store.Keys))
|
||||
args = store.appendArgs(args)
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
cmd.SetFirstKeyPos(3)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ZInter(ctx context.Context, store *ZStore) *StringSliceCmd {
|
||||
args := make([]interface{}, 0, 2+store.len())
|
||||
args = append(args, "zinter", len(store.Keys))
|
||||
args = store.appendArgs(args)
|
||||
cmd := NewStringSliceCmd(ctx, args...)
|
||||
cmd.SetFirstKeyPos(2)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ZInterWithScores(ctx context.Context, store *ZStore) *ZSliceCmd {
|
||||
args := make([]interface{}, 0, 3+store.len())
|
||||
args = append(args, "zinter", len(store.Keys))
|
||||
args = store.appendArgs(args)
|
||||
args = append(args, "withscores")
|
||||
cmd := NewZSliceCmd(ctx, args...)
|
||||
cmd.SetFirstKeyPos(2)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ZInterCard(ctx context.Context, limit int64, keys ...string) *IntCmd {
|
||||
args := make([]interface{}, 4+len(keys))
|
||||
args[0] = "zintercard"
|
||||
numkeys := int64(0)
|
||||
for i, key := range keys {
|
||||
args[2+i] = key
|
||||
numkeys++
|
||||
}
|
||||
args[1] = numkeys
|
||||
args[2+numkeys] = "limit"
|
||||
args[3+numkeys] = limit
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// ZMPop Pops one or more elements with the highest or lowest score from the first non-empty sorted set key from the list of provided key names.
|
||||
// direction: "max" (highest score) or "min" (lowest score), count: > 0
|
||||
// example: client.ZMPop(ctx, "max", 5, "set1", "set2")
|
||||
func (c cmdable) ZMPop(ctx context.Context, order string, count int64, keys ...string) *ZSliceWithKeyCmd {
|
||||
args := make([]interface{}, 2+len(keys), 5+len(keys))
|
||||
args[0] = "zmpop"
|
||||
args[1] = len(keys)
|
||||
for i, key := range keys {
|
||||
args[2+i] = key
|
||||
}
|
||||
args = append(args, strings.ToLower(order), "count", count)
|
||||
cmd := NewZSliceWithKeyCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ZMScore(ctx context.Context, key string, members ...string) *FloatSliceCmd {
|
||||
args := make([]interface{}, 2+len(members))
|
||||
args[0] = "zmscore"
|
||||
args[1] = key
|
||||
for i, member := range members {
|
||||
args[2+i] = member
|
||||
}
|
||||
cmd := NewFloatSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ZPopMax(ctx context.Context, key string, count ...int64) *ZSliceCmd {
|
||||
args := []interface{}{
|
||||
"zpopmax",
|
||||
key,
|
||||
}
|
||||
|
||||
switch len(count) {
|
||||
case 0:
|
||||
break
|
||||
case 1:
|
||||
args = append(args, count[0])
|
||||
default:
|
||||
panic("too many arguments")
|
||||
}
|
||||
|
||||
cmd := NewZSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ZPopMin(ctx context.Context, key string, count ...int64) *ZSliceCmd {
|
||||
args := []interface{}{
|
||||
"zpopmin",
|
||||
key,
|
||||
}
|
||||
|
||||
switch len(count) {
|
||||
case 0:
|
||||
break
|
||||
case 1:
|
||||
args = append(args, count[0])
|
||||
default:
|
||||
panic("too many arguments")
|
||||
}
|
||||
|
||||
cmd := NewZSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// ZRangeArgs is all the options of the ZRange command.
|
||||
// In version> 6.2.0, you can replace the(cmd):
|
||||
//
|
||||
// ZREVRANGE,
|
||||
// ZRANGEBYSCORE,
|
||||
// ZREVRANGEBYSCORE,
|
||||
// ZRANGEBYLEX,
|
||||
// ZREVRANGEBYLEX.
|
||||
//
|
||||
// Please pay attention to your redis-server version.
|
||||
//
|
||||
// Rev, ByScore, ByLex and Offset+Count options require redis-server 6.2.0 and higher.
|
||||
type ZRangeArgs struct {
|
||||
Key string
|
||||
|
||||
// When the ByScore option is provided, the open interval(exclusive) can be set.
|
||||
// By default, the score intervals specified by <Start> and <Stop> are closed (inclusive).
|
||||
// It is similar to the deprecated(6.2.0+) ZRangeByScore command.
|
||||
// For example:
|
||||
// ZRangeArgs{
|
||||
// Key: "example-key",
|
||||
// Start: "(3",
|
||||
// Stop: 8,
|
||||
// ByScore: true,
|
||||
// }
|
||||
// cmd: "ZRange example-key (3 8 ByScore" (3 < score <= 8).
|
||||
//
|
||||
// For the ByLex option, it is similar to the deprecated(6.2.0+) ZRangeByLex command.
|
||||
// You can set the <Start> and <Stop> options as follows:
|
||||
// ZRangeArgs{
|
||||
// Key: "example-key",
|
||||
// Start: "[abc",
|
||||
// Stop: "(def",
|
||||
// ByLex: true,
|
||||
// }
|
||||
// cmd: "ZRange example-key [abc (def ByLex"
|
||||
//
|
||||
// For normal cases (ByScore==false && ByLex==false), <Start> and <Stop> should be set to the index range (int).
|
||||
// You can read the documentation for more information: https://redis.io/commands/zrange
|
||||
Start interface{}
|
||||
Stop interface{}
|
||||
|
||||
// The ByScore and ByLex options are mutually exclusive.
|
||||
ByScore bool
|
||||
ByLex bool
|
||||
|
||||
Rev bool
|
||||
|
||||
// limit offset count.
|
||||
Offset int64
|
||||
Count int64
|
||||
}
|
||||
|
||||
func (z ZRangeArgs) appendArgs(args []interface{}) []interface{} {
|
||||
// For Rev+ByScore/ByLex, we need to adjust the position of <Start> and <Stop>.
|
||||
if z.Rev && (z.ByScore || z.ByLex) {
|
||||
args = append(args, z.Key, z.Stop, z.Start)
|
||||
} else {
|
||||
args = append(args, z.Key, z.Start, z.Stop)
|
||||
}
|
||||
|
||||
if z.ByScore {
|
||||
args = append(args, "byscore")
|
||||
} else if z.ByLex {
|
||||
args = append(args, "bylex")
|
||||
}
|
||||
if z.Rev {
|
||||
args = append(args, "rev")
|
||||
}
|
||||
if z.Offset != 0 || z.Count != 0 {
|
||||
args = append(args, "limit", z.Offset, z.Count)
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
func (c cmdable) ZRangeArgs(ctx context.Context, z ZRangeArgs) *StringSliceCmd {
|
||||
args := make([]interface{}, 0, 9)
|
||||
args = append(args, "zrange")
|
||||
args = z.appendArgs(args)
|
||||
cmd := NewStringSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ZRangeArgsWithScores(ctx context.Context, z ZRangeArgs) *ZSliceCmd {
|
||||
args := make([]interface{}, 0, 10)
|
||||
args = append(args, "zrange")
|
||||
args = z.appendArgs(args)
|
||||
args = append(args, "withscores")
|
||||
cmd := NewZSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ZRange(ctx context.Context, key string, start, stop int64) *StringSliceCmd {
|
||||
return c.ZRangeArgs(ctx, ZRangeArgs{
|
||||
Key: key,
|
||||
Start: start,
|
||||
Stop: stop,
|
||||
})
|
||||
}
|
||||
|
||||
func (c cmdable) ZRangeWithScores(ctx context.Context, key string, start, stop int64) *ZSliceCmd {
|
||||
return c.ZRangeArgsWithScores(ctx, ZRangeArgs{
|
||||
Key: key,
|
||||
Start: start,
|
||||
Stop: stop,
|
||||
})
|
||||
}
|
||||
|
||||
type ZRangeBy struct {
|
||||
Min, Max string
|
||||
Offset, Count int64
|
||||
}
|
||||
|
||||
func (c cmdable) zRangeBy(ctx context.Context, zcmd, key string, opt *ZRangeBy, withScores bool) *StringSliceCmd {
|
||||
args := []interface{}{zcmd, key, opt.Min, opt.Max}
|
||||
if withScores {
|
||||
args = append(args, "withscores")
|
||||
}
|
||||
if opt.Offset != 0 || opt.Count != 0 {
|
||||
args = append(
|
||||
args,
|
||||
"limit",
|
||||
opt.Offset,
|
||||
opt.Count,
|
||||
)
|
||||
}
|
||||
cmd := NewStringSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ZRangeByScore(ctx context.Context, key string, opt *ZRangeBy) *StringSliceCmd {
|
||||
return c.zRangeBy(ctx, "zrangebyscore", key, opt, false)
|
||||
}
|
||||
|
||||
func (c cmdable) ZRangeByLex(ctx context.Context, key string, opt *ZRangeBy) *StringSliceCmd {
|
||||
return c.zRangeBy(ctx, "zrangebylex", key, opt, false)
|
||||
}
|
||||
|
||||
func (c cmdable) ZRangeByScoreWithScores(ctx context.Context, key string, opt *ZRangeBy) *ZSliceCmd {
|
||||
args := []interface{}{"zrangebyscore", key, opt.Min, opt.Max, "withscores"}
|
||||
if opt.Offset != 0 || opt.Count != 0 {
|
||||
args = append(
|
||||
args,
|
||||
"limit",
|
||||
opt.Offset,
|
||||
opt.Count,
|
||||
)
|
||||
}
|
||||
cmd := NewZSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ZRangeStore(ctx context.Context, dst string, z ZRangeArgs) *IntCmd {
|
||||
args := make([]interface{}, 0, 10)
|
||||
args = append(args, "zrangestore", dst)
|
||||
args = z.appendArgs(args)
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ZRank(ctx context.Context, key, member string) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "zrank", key, member)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// ZRankWithScore according to the Redis documentation, if member does not exist
|
||||
// in the sorted set or key does not exist, it will return a redis.Nil error.
|
||||
func (c cmdable) ZRankWithScore(ctx context.Context, key, member string) *RankWithScoreCmd {
|
||||
cmd := NewRankWithScoreCmd(ctx, "zrank", key, member, "withscore")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ZRem(ctx context.Context, key string, members ...interface{}) *IntCmd {
|
||||
args := make([]interface{}, 2, 2+len(members))
|
||||
args[0] = "zrem"
|
||||
args[1] = key
|
||||
args = appendArgs(args, members)
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ZRemRangeByRank(ctx context.Context, key string, start, stop int64) *IntCmd {
|
||||
cmd := NewIntCmd(
|
||||
ctx,
|
||||
"zremrangebyrank",
|
||||
key,
|
||||
start,
|
||||
stop,
|
||||
)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ZRemRangeByScore(ctx context.Context, key, min, max string) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "zremrangebyscore", key, min, max)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ZRemRangeByLex(ctx context.Context, key, min, max string) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "zremrangebylex", key, min, max)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ZRevRange(ctx context.Context, key string, start, stop int64) *StringSliceCmd {
|
||||
cmd := NewStringSliceCmd(ctx, "zrevrange", key, start, stop)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// ZRevRangeWithScores according to the Redis documentation, if member does not exist
|
||||
// in the sorted set or key does not exist, it will return a redis.Nil error.
|
||||
func (c cmdable) ZRevRangeWithScores(ctx context.Context, key string, start, stop int64) *ZSliceCmd {
|
||||
cmd := NewZSliceCmd(ctx, "zrevrange", key, start, stop, "withscores")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) zRevRangeBy(ctx context.Context, zcmd, key string, opt *ZRangeBy) *StringSliceCmd {
|
||||
args := []interface{}{zcmd, key, opt.Max, opt.Min}
|
||||
if opt.Offset != 0 || opt.Count != 0 {
|
||||
args = append(
|
||||
args,
|
||||
"limit",
|
||||
opt.Offset,
|
||||
opt.Count,
|
||||
)
|
||||
}
|
||||
cmd := NewStringSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ZRevRangeByScore(ctx context.Context, key string, opt *ZRangeBy) *StringSliceCmd {
|
||||
return c.zRevRangeBy(ctx, "zrevrangebyscore", key, opt)
|
||||
}
|
||||
|
||||
func (c cmdable) ZRevRangeByLex(ctx context.Context, key string, opt *ZRangeBy) *StringSliceCmd {
|
||||
return c.zRevRangeBy(ctx, "zrevrangebylex", key, opt)
|
||||
}
|
||||
|
||||
func (c cmdable) ZRevRangeByScoreWithScores(ctx context.Context, key string, opt *ZRangeBy) *ZSliceCmd {
|
||||
args := []interface{}{"zrevrangebyscore", key, opt.Max, opt.Min, "withscores"}
|
||||
if opt.Offset != 0 || opt.Count != 0 {
|
||||
args = append(
|
||||
args,
|
||||
"limit",
|
||||
opt.Offset,
|
||||
opt.Count,
|
||||
)
|
||||
}
|
||||
cmd := NewZSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ZRevRank(ctx context.Context, key, member string) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "zrevrank", key, member)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ZRevRankWithScore(ctx context.Context, key, member string) *RankWithScoreCmd {
|
||||
cmd := NewRankWithScoreCmd(ctx, "zrevrank", key, member, "withscore")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ZScore(ctx context.Context, key, member string) *FloatCmd {
|
||||
cmd := NewFloatCmd(ctx, "zscore", key, member)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ZUnion(ctx context.Context, store ZStore) *StringSliceCmd {
|
||||
args := make([]interface{}, 0, 2+store.len())
|
||||
args = append(args, "zunion", len(store.Keys))
|
||||
args = store.appendArgs(args)
|
||||
cmd := NewStringSliceCmd(ctx, args...)
|
||||
cmd.SetFirstKeyPos(2)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ZUnionWithScores(ctx context.Context, store ZStore) *ZSliceCmd {
|
||||
args := make([]interface{}, 0, 3+store.len())
|
||||
args = append(args, "zunion", len(store.Keys))
|
||||
args = store.appendArgs(args)
|
||||
args = append(args, "withscores")
|
||||
cmd := NewZSliceCmd(ctx, args...)
|
||||
cmd.SetFirstKeyPos(2)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ZUnionStore(ctx context.Context, dest string, store *ZStore) *IntCmd {
|
||||
args := make([]interface{}, 0, 3+store.len())
|
||||
args = append(args, "zunionstore", dest, len(store.Keys))
|
||||
args = store.appendArgs(args)
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
cmd.SetFirstKeyPos(3)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// ZRandMember redis-server version >= 6.2.0.
|
||||
func (c cmdable) ZRandMember(ctx context.Context, key string, count int) *StringSliceCmd {
|
||||
cmd := NewStringSliceCmd(ctx, "zrandmember", key, count)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// ZRandMemberWithScores redis-server version >= 6.2.0.
|
||||
func (c cmdable) ZRandMemberWithScores(ctx context.Context, key string, count int) *ZSliceCmd {
|
||||
cmd := NewZSliceCmd(ctx, "zrandmember", key, count, "withscores")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// ZDiff redis-server version >= 6.2.0.
|
||||
func (c cmdable) ZDiff(ctx context.Context, keys ...string) *StringSliceCmd {
|
||||
args := make([]interface{}, 2+len(keys))
|
||||
args[0] = "zdiff"
|
||||
args[1] = len(keys)
|
||||
for i, key := range keys {
|
||||
args[i+2] = key
|
||||
}
|
||||
|
||||
cmd := NewStringSliceCmd(ctx, args...)
|
||||
cmd.SetFirstKeyPos(2)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// ZDiffWithScores redis-server version >= 6.2.0.
|
||||
func (c cmdable) ZDiffWithScores(ctx context.Context, keys ...string) *ZSliceCmd {
|
||||
args := make([]interface{}, 3+len(keys))
|
||||
args[0] = "zdiff"
|
||||
args[1] = len(keys)
|
||||
for i, key := range keys {
|
||||
args[i+2] = key
|
||||
}
|
||||
args[len(keys)+2] = "withscores"
|
||||
|
||||
cmd := NewZSliceCmd(ctx, args...)
|
||||
cmd.SetFirstKeyPos(2)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// ZDiffStore redis-server version >=6.2.0.
|
||||
func (c cmdable) ZDiffStore(ctx context.Context, destination string, keys ...string) *IntCmd {
|
||||
args := make([]interface{}, 0, 3+len(keys))
|
||||
args = append(args, "zdiffstore", destination, len(keys))
|
||||
for _, key := range keys {
|
||||
args = append(args, key)
|
||||
}
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) ZScan(ctx context.Context, key string, cursor uint64, match string, count int64) *ScanCmd {
|
||||
args := []interface{}{"zscan", key, cursor}
|
||||
if match != "" {
|
||||
args = append(args, "match", match)
|
||||
}
|
||||
if count > 0 {
|
||||
args = append(args, "count", count)
|
||||
}
|
||||
cmd := NewScanCmd(ctx, c, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Z represents sorted set member.
|
||||
type Z struct {
|
||||
Score float64
|
||||
Member interface{}
|
||||
}
|
||||
|
||||
// ZWithKey represents sorted set member including the name of the key where it was popped.
|
||||
type ZWithKey struct {
|
||||
Z
|
||||
Key string
|
||||
}
|
||||
|
||||
// ZStore is used as an arg to ZInter/ZInterStore and ZUnion/ZUnionStore.
|
||||
type ZStore struct {
|
||||
Keys []string
|
||||
Weights []float64
|
||||
// Can be SUM, MIN or MAX.
|
||||
Aggregate string
|
||||
}
|
||||
|
||||
func (z ZStore) len() (n int) {
|
||||
n = len(z.Keys)
|
||||
if len(z.Weights) > 0 {
|
||||
n += 1 + len(z.Weights)
|
||||
}
|
||||
if z.Aggregate != "" {
|
||||
n += 2
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (z ZStore) appendArgs(args []interface{}) []interface{} {
|
||||
for _, key := range z.Keys {
|
||||
args = append(args, key)
|
||||
}
|
||||
if len(z.Weights) > 0 {
|
||||
args = append(args, "weights")
|
||||
for _, weights := range z.Weights {
|
||||
args = append(args, weights)
|
||||
}
|
||||
}
|
||||
if z.Aggregate != "" {
|
||||
args = append(args, "aggregate", z.Aggregate)
|
||||
}
|
||||
return args
|
||||
}
|
|
@ -0,0 +1,438 @@
|
|||
package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
type StreamCmdable interface {
|
||||
XAdd(ctx context.Context, a *XAddArgs) *StringCmd
|
||||
XDel(ctx context.Context, stream string, ids ...string) *IntCmd
|
||||
XLen(ctx context.Context, stream string) *IntCmd
|
||||
XRange(ctx context.Context, stream, start, stop string) *XMessageSliceCmd
|
||||
XRangeN(ctx context.Context, stream, start, stop string, count int64) *XMessageSliceCmd
|
||||
XRevRange(ctx context.Context, stream string, start, stop string) *XMessageSliceCmd
|
||||
XRevRangeN(ctx context.Context, stream string, start, stop string, count int64) *XMessageSliceCmd
|
||||
XRead(ctx context.Context, a *XReadArgs) *XStreamSliceCmd
|
||||
XReadStreams(ctx context.Context, streams ...string) *XStreamSliceCmd
|
||||
XGroupCreate(ctx context.Context, stream, group, start string) *StatusCmd
|
||||
XGroupCreateMkStream(ctx context.Context, stream, group, start string) *StatusCmd
|
||||
XGroupSetID(ctx context.Context, stream, group, start string) *StatusCmd
|
||||
XGroupDestroy(ctx context.Context, stream, group string) *IntCmd
|
||||
XGroupCreateConsumer(ctx context.Context, stream, group, consumer string) *IntCmd
|
||||
XGroupDelConsumer(ctx context.Context, stream, group, consumer string) *IntCmd
|
||||
XReadGroup(ctx context.Context, a *XReadGroupArgs) *XStreamSliceCmd
|
||||
XAck(ctx context.Context, stream, group string, ids ...string) *IntCmd
|
||||
XPending(ctx context.Context, stream, group string) *XPendingCmd
|
||||
XPendingExt(ctx context.Context, a *XPendingExtArgs) *XPendingExtCmd
|
||||
XClaim(ctx context.Context, a *XClaimArgs) *XMessageSliceCmd
|
||||
XClaimJustID(ctx context.Context, a *XClaimArgs) *StringSliceCmd
|
||||
XAutoClaim(ctx context.Context, a *XAutoClaimArgs) *XAutoClaimCmd
|
||||
XAutoClaimJustID(ctx context.Context, a *XAutoClaimArgs) *XAutoClaimJustIDCmd
|
||||
XTrimMaxLen(ctx context.Context, key string, maxLen int64) *IntCmd
|
||||
XTrimMaxLenApprox(ctx context.Context, key string, maxLen, limit int64) *IntCmd
|
||||
XTrimMinID(ctx context.Context, key string, minID string) *IntCmd
|
||||
XTrimMinIDApprox(ctx context.Context, key string, minID string, limit int64) *IntCmd
|
||||
XInfoGroups(ctx context.Context, key string) *XInfoGroupsCmd
|
||||
XInfoStream(ctx context.Context, key string) *XInfoStreamCmd
|
||||
XInfoStreamFull(ctx context.Context, key string, count int) *XInfoStreamFullCmd
|
||||
XInfoConsumers(ctx context.Context, key string, group string) *XInfoConsumersCmd
|
||||
}
|
||||
|
||||
// XAddArgs accepts values in the following formats:
|
||||
// - XAddArgs.Values = []interface{}{"key1", "value1", "key2", "value2"}
|
||||
// - XAddArgs.Values = []string("key1", "value1", "key2", "value2")
|
||||
// - XAddArgs.Values = map[string]interface{}{"key1": "value1", "key2": "value2"}
|
||||
//
|
||||
// Note that map will not preserve the order of key-value pairs.
|
||||
// MaxLen/MaxLenApprox and MinID are in conflict, only one of them can be used.
|
||||
type XAddArgs struct {
|
||||
Stream string
|
||||
NoMkStream bool
|
||||
MaxLen int64 // MAXLEN N
|
||||
MinID string
|
||||
// Approx causes MaxLen and MinID to use "~" matcher (instead of "=").
|
||||
Approx bool
|
||||
Limit int64
|
||||
ID string
|
||||
Values interface{}
|
||||
}
|
||||
|
||||
func (c cmdable) XAdd(ctx context.Context, a *XAddArgs) *StringCmd {
|
||||
args := make([]interface{}, 0, 11)
|
||||
args = append(args, "xadd", a.Stream)
|
||||
if a.NoMkStream {
|
||||
args = append(args, "nomkstream")
|
||||
}
|
||||
switch {
|
||||
case a.MaxLen > 0:
|
||||
if a.Approx {
|
||||
args = append(args, "maxlen", "~", a.MaxLen)
|
||||
} else {
|
||||
args = append(args, "maxlen", a.MaxLen)
|
||||
}
|
||||
case a.MinID != "":
|
||||
if a.Approx {
|
||||
args = append(args, "minid", "~", a.MinID)
|
||||
} else {
|
||||
args = append(args, "minid", a.MinID)
|
||||
}
|
||||
}
|
||||
if a.Limit > 0 {
|
||||
args = append(args, "limit", a.Limit)
|
||||
}
|
||||
if a.ID != "" {
|
||||
args = append(args, a.ID)
|
||||
} else {
|
||||
args = append(args, "*")
|
||||
}
|
||||
args = appendArg(args, a.Values)
|
||||
|
||||
cmd := NewStringCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) XDel(ctx context.Context, stream string, ids ...string) *IntCmd {
|
||||
args := []interface{}{"xdel", stream}
|
||||
for _, id := range ids {
|
||||
args = append(args, id)
|
||||
}
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) XLen(ctx context.Context, stream string) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "xlen", stream)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) XRange(ctx context.Context, stream, start, stop string) *XMessageSliceCmd {
|
||||
cmd := NewXMessageSliceCmd(ctx, "xrange", stream, start, stop)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) XRangeN(ctx context.Context, stream, start, stop string, count int64) *XMessageSliceCmd {
|
||||
cmd := NewXMessageSliceCmd(ctx, "xrange", stream, start, stop, "count", count)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) XRevRange(ctx context.Context, stream, start, stop string) *XMessageSliceCmd {
|
||||
cmd := NewXMessageSliceCmd(ctx, "xrevrange", stream, start, stop)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) XRevRangeN(ctx context.Context, stream, start, stop string, count int64) *XMessageSliceCmd {
|
||||
cmd := NewXMessageSliceCmd(ctx, "xrevrange", stream, start, stop, "count", count)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
type XReadArgs struct {
|
||||
Streams []string // list of streams and ids, e.g. stream1 stream2 id1 id2
|
||||
Count int64
|
||||
Block time.Duration
|
||||
}
|
||||
|
||||
func (c cmdable) XRead(ctx context.Context, a *XReadArgs) *XStreamSliceCmd {
|
||||
args := make([]interface{}, 0, 6+len(a.Streams))
|
||||
args = append(args, "xread")
|
||||
|
||||
keyPos := int8(1)
|
||||
if a.Count > 0 {
|
||||
args = append(args, "count")
|
||||
args = append(args, a.Count)
|
||||
keyPos += 2
|
||||
}
|
||||
if a.Block >= 0 {
|
||||
args = append(args, "block")
|
||||
args = append(args, int64(a.Block/time.Millisecond))
|
||||
keyPos += 2
|
||||
}
|
||||
args = append(args, "streams")
|
||||
keyPos++
|
||||
for _, s := range a.Streams {
|
||||
args = append(args, s)
|
||||
}
|
||||
|
||||
cmd := NewXStreamSliceCmd(ctx, args...)
|
||||
if a.Block >= 0 {
|
||||
cmd.setReadTimeout(a.Block)
|
||||
}
|
||||
cmd.SetFirstKeyPos(keyPos)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) XReadStreams(ctx context.Context, streams ...string) *XStreamSliceCmd {
|
||||
return c.XRead(ctx, &XReadArgs{
|
||||
Streams: streams,
|
||||
Block: -1,
|
||||
})
|
||||
}
|
||||
|
||||
func (c cmdable) XGroupCreate(ctx context.Context, stream, group, start string) *StatusCmd {
|
||||
cmd := NewStatusCmd(ctx, "xgroup", "create", stream, group, start)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) XGroupCreateMkStream(ctx context.Context, stream, group, start string) *StatusCmd {
|
||||
cmd := NewStatusCmd(ctx, "xgroup", "create", stream, group, start, "mkstream")
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) XGroupSetID(ctx context.Context, stream, group, start string) *StatusCmd {
|
||||
cmd := NewStatusCmd(ctx, "xgroup", "setid", stream, group, start)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) XGroupDestroy(ctx context.Context, stream, group string) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "xgroup", "destroy", stream, group)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) XGroupCreateConsumer(ctx context.Context, stream, group, consumer string) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "xgroup", "createconsumer", stream, group, consumer)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) XGroupDelConsumer(ctx context.Context, stream, group, consumer string) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "xgroup", "delconsumer", stream, group, consumer)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
type XReadGroupArgs struct {
|
||||
Group string
|
||||
Consumer string
|
||||
Streams []string // list of streams and ids, e.g. stream1 stream2 id1 id2
|
||||
Count int64
|
||||
Block time.Duration
|
||||
NoAck bool
|
||||
}
|
||||
|
||||
func (c cmdable) XReadGroup(ctx context.Context, a *XReadGroupArgs) *XStreamSliceCmd {
|
||||
args := make([]interface{}, 0, 10+len(a.Streams))
|
||||
args = append(args, "xreadgroup", "group", a.Group, a.Consumer)
|
||||
|
||||
keyPos := int8(4)
|
||||
if a.Count > 0 {
|
||||
args = append(args, "count", a.Count)
|
||||
keyPos += 2
|
||||
}
|
||||
if a.Block >= 0 {
|
||||
args = append(args, "block", int64(a.Block/time.Millisecond))
|
||||
keyPos += 2
|
||||
}
|
||||
if a.NoAck {
|
||||
args = append(args, "noack")
|
||||
keyPos++
|
||||
}
|
||||
args = append(args, "streams")
|
||||
keyPos++
|
||||
for _, s := range a.Streams {
|
||||
args = append(args, s)
|
||||
}
|
||||
|
||||
cmd := NewXStreamSliceCmd(ctx, args...)
|
||||
if a.Block >= 0 {
|
||||
cmd.setReadTimeout(a.Block)
|
||||
}
|
||||
cmd.SetFirstKeyPos(keyPos)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) XAck(ctx context.Context, stream, group string, ids ...string) *IntCmd {
|
||||
args := []interface{}{"xack", stream, group}
|
||||
for _, id := range ids {
|
||||
args = append(args, id)
|
||||
}
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) XPending(ctx context.Context, stream, group string) *XPendingCmd {
|
||||
cmd := NewXPendingCmd(ctx, "xpending", stream, group)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
type XPendingExtArgs struct {
|
||||
Stream string
|
||||
Group string
|
||||
Idle time.Duration
|
||||
Start string
|
||||
End string
|
||||
Count int64
|
||||
Consumer string
|
||||
}
|
||||
|
||||
func (c cmdable) XPendingExt(ctx context.Context, a *XPendingExtArgs) *XPendingExtCmd {
|
||||
args := make([]interface{}, 0, 9)
|
||||
args = append(args, "xpending", a.Stream, a.Group)
|
||||
if a.Idle != 0 {
|
||||
args = append(args, "idle", formatMs(ctx, a.Idle))
|
||||
}
|
||||
args = append(args, a.Start, a.End, a.Count)
|
||||
if a.Consumer != "" {
|
||||
args = append(args, a.Consumer)
|
||||
}
|
||||
cmd := NewXPendingExtCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
type XAutoClaimArgs struct {
|
||||
Stream string
|
||||
Group string
|
||||
MinIdle time.Duration
|
||||
Start string
|
||||
Count int64
|
||||
Consumer string
|
||||
}
|
||||
|
||||
func (c cmdable) XAutoClaim(ctx context.Context, a *XAutoClaimArgs) *XAutoClaimCmd {
|
||||
args := xAutoClaimArgs(ctx, a)
|
||||
cmd := NewXAutoClaimCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) XAutoClaimJustID(ctx context.Context, a *XAutoClaimArgs) *XAutoClaimJustIDCmd {
|
||||
args := xAutoClaimArgs(ctx, a)
|
||||
args = append(args, "justid")
|
||||
cmd := NewXAutoClaimJustIDCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func xAutoClaimArgs(ctx context.Context, a *XAutoClaimArgs) []interface{} {
|
||||
args := make([]interface{}, 0, 8)
|
||||
args = append(args, "xautoclaim", a.Stream, a.Group, a.Consumer, formatMs(ctx, a.MinIdle), a.Start)
|
||||
if a.Count > 0 {
|
||||
args = append(args, "count", a.Count)
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
type XClaimArgs struct {
|
||||
Stream string
|
||||
Group string
|
||||
Consumer string
|
||||
MinIdle time.Duration
|
||||
Messages []string
|
||||
}
|
||||
|
||||
func (c cmdable) XClaim(ctx context.Context, a *XClaimArgs) *XMessageSliceCmd {
|
||||
args := xClaimArgs(a)
|
||||
cmd := NewXMessageSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) XClaimJustID(ctx context.Context, a *XClaimArgs) *StringSliceCmd {
|
||||
args := xClaimArgs(a)
|
||||
args = append(args, "justid")
|
||||
cmd := NewStringSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func xClaimArgs(a *XClaimArgs) []interface{} {
|
||||
args := make([]interface{}, 0, 5+len(a.Messages))
|
||||
args = append(args,
|
||||
"xclaim",
|
||||
a.Stream,
|
||||
a.Group, a.Consumer,
|
||||
int64(a.MinIdle/time.Millisecond))
|
||||
for _, id := range a.Messages {
|
||||
args = append(args, id)
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
// xTrim If approx is true, add the "~" parameter, otherwise it is the default "=" (redis default).
|
||||
// example:
|
||||
//
|
||||
// XTRIM key MAXLEN/MINID threshold LIMIT limit.
|
||||
// XTRIM key MAXLEN/MINID ~ threshold LIMIT limit.
|
||||
//
|
||||
// The redis-server version is lower than 6.2, please set limit to 0.
|
||||
func (c cmdable) xTrim(
|
||||
ctx context.Context, key, strategy string,
|
||||
approx bool, threshold interface{}, limit int64,
|
||||
) *IntCmd {
|
||||
args := make([]interface{}, 0, 7)
|
||||
args = append(args, "xtrim", key, strategy)
|
||||
if approx {
|
||||
args = append(args, "~")
|
||||
}
|
||||
args = append(args, threshold)
|
||||
if limit > 0 {
|
||||
args = append(args, "limit", limit)
|
||||
}
|
||||
cmd := NewIntCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// XTrimMaxLen No `~` rules are used, `limit` cannot be used.
|
||||
// cmd: XTRIM key MAXLEN maxLen
|
||||
func (c cmdable) XTrimMaxLen(ctx context.Context, key string, maxLen int64) *IntCmd {
|
||||
return c.xTrim(ctx, key, "maxlen", false, maxLen, 0)
|
||||
}
|
||||
|
||||
func (c cmdable) XTrimMaxLenApprox(ctx context.Context, key string, maxLen, limit int64) *IntCmd {
|
||||
return c.xTrim(ctx, key, "maxlen", true, maxLen, limit)
|
||||
}
|
||||
|
||||
func (c cmdable) XTrimMinID(ctx context.Context, key string, minID string) *IntCmd {
|
||||
return c.xTrim(ctx, key, "minid", false, minID, 0)
|
||||
}
|
||||
|
||||
func (c cmdable) XTrimMinIDApprox(ctx context.Context, key string, minID string, limit int64) *IntCmd {
|
||||
return c.xTrim(ctx, key, "minid", true, minID, limit)
|
||||
}
|
||||
|
||||
func (c cmdable) XInfoConsumers(ctx context.Context, key string, group string) *XInfoConsumersCmd {
|
||||
cmd := NewXInfoConsumersCmd(ctx, key, group)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) XInfoGroups(ctx context.Context, key string) *XInfoGroupsCmd {
|
||||
cmd := NewXInfoGroupsCmd(ctx, key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) XInfoStream(ctx context.Context, key string) *XInfoStreamCmd {
|
||||
cmd := NewXInfoStreamCmd(ctx, key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// XInfoStreamFull XINFO STREAM FULL [COUNT count]
|
||||
// redis-server >= 6.0.
|
||||
func (c cmdable) XInfoStreamFull(ctx context.Context, key string, count int) *XInfoStreamFullCmd {
|
||||
args := make([]interface{}, 0, 6)
|
||||
args = append(args, "xinfo", "stream", key, "full")
|
||||
if count > 0 {
|
||||
args = append(args, "count", count)
|
||||
}
|
||||
cmd := NewXInfoStreamFullCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
|
@ -0,0 +1,303 @@
|
|||
package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
type StringCmdable interface {
|
||||
Append(ctx context.Context, key, value string) *IntCmd
|
||||
Decr(ctx context.Context, key string) *IntCmd
|
||||
DecrBy(ctx context.Context, key string, decrement int64) *IntCmd
|
||||
Get(ctx context.Context, key string) *StringCmd
|
||||
GetRange(ctx context.Context, key string, start, end int64) *StringCmd
|
||||
GetSet(ctx context.Context, key string, value interface{}) *StringCmd
|
||||
GetEx(ctx context.Context, key string, expiration time.Duration) *StringCmd
|
||||
GetDel(ctx context.Context, key string) *StringCmd
|
||||
Incr(ctx context.Context, key string) *IntCmd
|
||||
IncrBy(ctx context.Context, key string, value int64) *IntCmd
|
||||
IncrByFloat(ctx context.Context, key string, value float64) *FloatCmd
|
||||
LCS(ctx context.Context, q *LCSQuery) *LCSCmd
|
||||
MGet(ctx context.Context, keys ...string) *SliceCmd
|
||||
MSet(ctx context.Context, values ...interface{}) *StatusCmd
|
||||
MSetNX(ctx context.Context, values ...interface{}) *BoolCmd
|
||||
Set(ctx context.Context, key string, value interface{}, expiration time.Duration) *StatusCmd
|
||||
SetArgs(ctx context.Context, key string, value interface{}, a SetArgs) *StatusCmd
|
||||
SetEx(ctx context.Context, key string, value interface{}, expiration time.Duration) *StatusCmd
|
||||
SetNX(ctx context.Context, key string, value interface{}, expiration time.Duration) *BoolCmd
|
||||
SetXX(ctx context.Context, key string, value interface{}, expiration time.Duration) *BoolCmd
|
||||
SetRange(ctx context.Context, key string, offset int64, value string) *IntCmd
|
||||
StrLen(ctx context.Context, key string) *IntCmd
|
||||
}
|
||||
|
||||
func (c cmdable) Append(ctx context.Context, key, value string) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "append", key, value)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) Decr(ctx context.Context, key string) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "decr", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) DecrBy(ctx context.Context, key string, decrement int64) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "decrby", key, decrement)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Get Redis `GET key` command. It returns redis.Nil error when key does not exist.
|
||||
func (c cmdable) Get(ctx context.Context, key string) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "get", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) GetRange(ctx context.Context, key string, start, end int64) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "getrange", key, start, end)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) GetSet(ctx context.Context, key string, value interface{}) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "getset", key, value)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// GetEx An expiration of zero removes the TTL associated with the key (i.e. GETEX key persist).
|
||||
// Requires Redis >= 6.2.0.
|
||||
func (c cmdable) GetEx(ctx context.Context, key string, expiration time.Duration) *StringCmd {
|
||||
args := make([]interface{}, 0, 4)
|
||||
args = append(args, "getex", key)
|
||||
if expiration > 0 {
|
||||
if usePrecise(expiration) {
|
||||
args = append(args, "px", formatMs(ctx, expiration))
|
||||
} else {
|
||||
args = append(args, "ex", formatSec(ctx, expiration))
|
||||
}
|
||||
} else if expiration == 0 {
|
||||
args = append(args, "persist")
|
||||
}
|
||||
|
||||
cmd := NewStringCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// GetDel redis-server version >= 6.2.0.
|
||||
func (c cmdable) GetDel(ctx context.Context, key string) *StringCmd {
|
||||
cmd := NewStringCmd(ctx, "getdel", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) Incr(ctx context.Context, key string) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "incr", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) IncrBy(ctx context.Context, key string, value int64) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "incrby", key, value)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) IncrByFloat(ctx context.Context, key string, value float64) *FloatCmd {
|
||||
cmd := NewFloatCmd(ctx, "incrbyfloat", key, value)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) LCS(ctx context.Context, q *LCSQuery) *LCSCmd {
|
||||
cmd := NewLCSCmd(ctx, q)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) MGet(ctx context.Context, keys ...string) *SliceCmd {
|
||||
args := make([]interface{}, 1+len(keys))
|
||||
args[0] = "mget"
|
||||
for i, key := range keys {
|
||||
args[1+i] = key
|
||||
}
|
||||
cmd := NewSliceCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// MSet is like Set but accepts multiple values:
|
||||
// - MSet("key1", "value1", "key2", "value2")
|
||||
// - MSet([]string{"key1", "value1", "key2", "value2"})
|
||||
// - MSet(map[string]interface{}{"key1": "value1", "key2": "value2"})
|
||||
// - MSet(struct), For struct types, see HSet description.
|
||||
func (c cmdable) MSet(ctx context.Context, values ...interface{}) *StatusCmd {
|
||||
args := make([]interface{}, 1, 1+len(values))
|
||||
args[0] = "mset"
|
||||
args = appendArgs(args, values)
|
||||
cmd := NewStatusCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// MSetNX is like SetNX but accepts multiple values:
|
||||
// - MSetNX("key1", "value1", "key2", "value2")
|
||||
// - MSetNX([]string{"key1", "value1", "key2", "value2"})
|
||||
// - MSetNX(map[string]interface{}{"key1": "value1", "key2": "value2"})
|
||||
// - MSetNX(struct), For struct types, see HSet description.
|
||||
func (c cmdable) MSetNX(ctx context.Context, values ...interface{}) *BoolCmd {
|
||||
args := make([]interface{}, 1, 1+len(values))
|
||||
args[0] = "msetnx"
|
||||
args = appendArgs(args, values)
|
||||
cmd := NewBoolCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Set Redis `SET key value [expiration]` command.
|
||||
// Use expiration for `SETEx`-like behavior.
|
||||
//
|
||||
// Zero expiration means the key has no expiration time.
|
||||
// KeepTTL is a Redis KEEPTTL option to keep existing TTL, it requires your redis-server version >= 6.0,
|
||||
// otherwise you will receive an error: (error) ERR syntax error.
|
||||
func (c cmdable) Set(ctx context.Context, key string, value interface{}, expiration time.Duration) *StatusCmd {
|
||||
args := make([]interface{}, 3, 5)
|
||||
args[0] = "set"
|
||||
args[1] = key
|
||||
args[2] = value
|
||||
if expiration > 0 {
|
||||
if usePrecise(expiration) {
|
||||
args = append(args, "px", formatMs(ctx, expiration))
|
||||
} else {
|
||||
args = append(args, "ex", formatSec(ctx, expiration))
|
||||
}
|
||||
} else if expiration == KeepTTL {
|
||||
args = append(args, "keepttl")
|
||||
}
|
||||
|
||||
cmd := NewStatusCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// SetArgs provides arguments for the SetArgs function.
|
||||
type SetArgs struct {
|
||||
// Mode can be `NX` or `XX` or empty.
|
||||
Mode string
|
||||
|
||||
// Zero `TTL` or `Expiration` means that the key has no expiration time.
|
||||
TTL time.Duration
|
||||
ExpireAt time.Time
|
||||
|
||||
// When Get is true, the command returns the old value stored at key, or nil when key did not exist.
|
||||
Get bool
|
||||
|
||||
// KeepTTL is a Redis KEEPTTL option to keep existing TTL, it requires your redis-server version >= 6.0,
|
||||
// otherwise you will receive an error: (error) ERR syntax error.
|
||||
KeepTTL bool
|
||||
}
|
||||
|
||||
// SetArgs supports all the options that the SET command supports.
|
||||
// It is the alternative to the Set function when you want
|
||||
// to have more control over the options.
|
||||
func (c cmdable) SetArgs(ctx context.Context, key string, value interface{}, a SetArgs) *StatusCmd {
|
||||
args := []interface{}{"set", key, value}
|
||||
|
||||
if a.KeepTTL {
|
||||
args = append(args, "keepttl")
|
||||
}
|
||||
|
||||
if !a.ExpireAt.IsZero() {
|
||||
args = append(args, "exat", a.ExpireAt.Unix())
|
||||
}
|
||||
if a.TTL > 0 {
|
||||
if usePrecise(a.TTL) {
|
||||
args = append(args, "px", formatMs(ctx, a.TTL))
|
||||
} else {
|
||||
args = append(args, "ex", formatSec(ctx, a.TTL))
|
||||
}
|
||||
}
|
||||
|
||||
if a.Mode != "" {
|
||||
args = append(args, a.Mode)
|
||||
}
|
||||
|
||||
if a.Get {
|
||||
args = append(args, "get")
|
||||
}
|
||||
|
||||
cmd := NewStatusCmd(ctx, args...)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// SetEx Redis `SETEx key expiration value` command.
|
||||
func (c cmdable) SetEx(ctx context.Context, key string, value interface{}, expiration time.Duration) *StatusCmd {
|
||||
cmd := NewStatusCmd(ctx, "setex", key, formatSec(ctx, expiration), value)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// SetNX Redis `SET key value [expiration] NX` command.
|
||||
//
|
||||
// Zero expiration means the key has no expiration time.
|
||||
// KeepTTL is a Redis KEEPTTL option to keep existing TTL, it requires your redis-server version >= 6.0,
|
||||
// otherwise you will receive an error: (error) ERR syntax error.
|
||||
func (c cmdable) SetNX(ctx context.Context, key string, value interface{}, expiration time.Duration) *BoolCmd {
|
||||
var cmd *BoolCmd
|
||||
switch expiration {
|
||||
case 0:
|
||||
// Use old `SETNX` to support old Redis versions.
|
||||
cmd = NewBoolCmd(ctx, "setnx", key, value)
|
||||
case KeepTTL:
|
||||
cmd = NewBoolCmd(ctx, "set", key, value, "keepttl", "nx")
|
||||
default:
|
||||
if usePrecise(expiration) {
|
||||
cmd = NewBoolCmd(ctx, "set", key, value, "px", formatMs(ctx, expiration), "nx")
|
||||
} else {
|
||||
cmd = NewBoolCmd(ctx, "set", key, value, "ex", formatSec(ctx, expiration), "nx")
|
||||
}
|
||||
}
|
||||
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// SetXX Redis `SET key value [expiration] XX` command.
|
||||
//
|
||||
// Zero expiration means the key has no expiration time.
|
||||
// KeepTTL is a Redis KEEPTTL option to keep existing TTL, it requires your redis-server version >= 6.0,
|
||||
// otherwise you will receive an error: (error) ERR syntax error.
|
||||
func (c cmdable) SetXX(ctx context.Context, key string, value interface{}, expiration time.Duration) *BoolCmd {
|
||||
var cmd *BoolCmd
|
||||
switch expiration {
|
||||
case 0:
|
||||
cmd = NewBoolCmd(ctx, "set", key, value, "xx")
|
||||
case KeepTTL:
|
||||
cmd = NewBoolCmd(ctx, "set", key, value, "keepttl", "xx")
|
||||
default:
|
||||
if usePrecise(expiration) {
|
||||
cmd = NewBoolCmd(ctx, "set", key, value, "px", formatMs(ctx, expiration), "xx")
|
||||
} else {
|
||||
cmd = NewBoolCmd(ctx, "set", key, value, "ex", formatSec(ctx, expiration), "xx")
|
||||
}
|
||||
}
|
||||
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) SetRange(ctx context.Context, key string, offset int64, value string) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "setrange", key, offset, value)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c cmdable) StrLen(ctx context.Context, key string) *IntCmd {
|
||||
cmd := NewIntCmd(ctx, "strlen", key)
|
||||
_ = c(ctx, cmd)
|
||||
return cmd
|
||||
}
|
|
@ -209,7 +209,6 @@ func (c cmdable) TSAddWithArgs(ctx context.Context, key string, timestamp interf
|
|||
}
|
||||
if options.ChunkSize != 0 {
|
||||
args = append(args, "CHUNK_SIZE", options.ChunkSize)
|
||||
|
||||
}
|
||||
if options.Encoding != "" {
|
||||
args = append(args, "ENCODING", options.Encoding)
|
||||
|
@ -251,7 +250,6 @@ func (c cmdable) TSCreateWithArgs(ctx context.Context, key string, options *TSOp
|
|||
}
|
||||
if options.ChunkSize != 0 {
|
||||
args = append(args, "CHUNK_SIZE", options.ChunkSize)
|
||||
|
||||
}
|
||||
if options.Encoding != "" {
|
||||
args = append(args, "ENCODING", options.Encoding)
|
||||
|
@ -264,7 +262,6 @@ func (c cmdable) TSCreateWithArgs(ctx context.Context, key string, options *TSOp
|
|||
args = append(args, "LABELS")
|
||||
for label, value := range options.Labels {
|
||||
args = append(args, label, value)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -285,7 +282,6 @@ func (c cmdable) TSAlter(ctx context.Context, key string, options *TSAlterOption
|
|||
}
|
||||
if options.ChunkSize != 0 {
|
||||
args = append(args, "CHUNK_SIZE", options.ChunkSize)
|
||||
|
||||
}
|
||||
if options.DuplicatePolicy != "" {
|
||||
args = append(args, "DUPLICATE_POLICY", options.DuplicatePolicy)
|
||||
|
@ -294,7 +290,6 @@ func (c cmdable) TSAlter(ctx context.Context, key string, options *TSAlterOption
|
|||
args = append(args, "LABELS")
|
||||
for label, value := range options.Labels {
|
||||
args = append(args, label, value)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -352,7 +347,6 @@ func (c cmdable) TSIncrByWithArgs(ctx context.Context, key string, timestamp flo
|
|||
}
|
||||
if options.ChunkSize != 0 {
|
||||
args = append(args, "CHUNK_SIZE", options.ChunkSize)
|
||||
|
||||
}
|
||||
if options.Uncompressed {
|
||||
args = append(args, "UNCOMPRESSED")
|
||||
|
@ -361,7 +355,6 @@ func (c cmdable) TSIncrByWithArgs(ctx context.Context, key string, timestamp flo
|
|||
args = append(args, "LABELS")
|
||||
for label, value := range options.Labels {
|
||||
args = append(args, label, value)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -394,7 +387,6 @@ func (c cmdable) TSDecrByWithArgs(ctx context.Context, key string, timestamp flo
|
|||
}
|
||||
if options.ChunkSize != 0 {
|
||||
args = append(args, "CHUNK_SIZE", options.ChunkSize)
|
||||
|
||||
}
|
||||
if options.Uncompressed {
|
||||
args = append(args, "UNCOMPRESSED")
|
||||
|
@ -403,7 +395,6 @@ func (c cmdable) TSDecrByWithArgs(ctx context.Context, key string, timestamp flo
|
|||
args = append(args, "LABELS")
|
||||
for label, value := range options.Labels {
|
||||
args = append(args, label, value)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@ import (
|
|||
|
||||
. "github.com/bsm/ginkgo/v2"
|
||||
. "github.com/bsm/gomega"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
|
@ -137,7 +138,6 @@ var _ = Describe("RedisTimeseries commands", Label("timeseries"), func() {
|
|||
resultGet, err = client.TSGet(ctx, "tsami-1").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(resultGet.Value).To(BeEquivalentTo(5))
|
||||
|
||||
})
|
||||
|
||||
It("should TSAlter", Label("timeseries", "tsalter"), func() {
|
||||
|
@ -179,7 +179,6 @@ var _ = Describe("RedisTimeseries commands", Label("timeseries"), func() {
|
|||
resultInfo, err = client.TSInfo(ctx, "1").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(resultInfo["duplicatePolicy"]).To(BeEquivalentTo("min"))
|
||||
|
||||
})
|
||||
|
||||
It("should TSCreateRule and TSDeleteRule", Label("timeseries", "tscreaterule", "tsdeleterule"), func() {
|
||||
|
@ -215,7 +214,6 @@ var _ = Describe("RedisTimeseries commands", Label("timeseries"), func() {
|
|||
resultInfo, err := client.TSInfo(ctx, "1").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(resultInfo["rules"]).To(BeEquivalentTo(map[interface{}]interface{}{}))
|
||||
|
||||
})
|
||||
|
||||
It("should TSIncrBy, TSIncrByWithArgs, TSDecrBy and TSDecrByWithArgs", Label("timeseries", "tsincrby", "tsdecrby", "tsincrbyWithArgs", "tsdecrbyWithArgs"), func() {
|
||||
|
@ -290,7 +288,6 @@ var _ = Describe("RedisTimeseries commands", Label("timeseries"), func() {
|
|||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(result.Timestamp).To(BeEquivalentTo(2265985))
|
||||
Expect(result.Value).To(BeEquivalentTo(151))
|
||||
|
||||
})
|
||||
|
||||
It("should TSGet Latest", Label("timeseries", "tsgetlatest"), func() {
|
||||
|
@ -328,7 +325,6 @@ var _ = Describe("RedisTimeseries commands", Label("timeseries"), func() {
|
|||
result, err := client.TSInfo(ctx, "foo").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(result["firstTimestamp"]).To(BeEquivalentTo(2265985))
|
||||
|
||||
})
|
||||
|
||||
It("should TSMAdd", Label("timeseries", "tsmadd"), func() {
|
||||
|
@ -346,7 +342,6 @@ var _ = Describe("RedisTimeseries commands", Label("timeseries"), func() {
|
|||
result, err := client.TSMAdd(ctx, ktvSlices).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(result).To(BeEquivalentTo([]int64{1, 2, 3}))
|
||||
|
||||
})
|
||||
|
||||
It("should TSMGet and TSMGetWithArgs", Label("timeseries", "tsmget", "tsmgetWithArgs"), func() {
|
||||
|
@ -397,7 +392,6 @@ var _ = Describe("RedisTimeseries commands", Label("timeseries"), func() {
|
|||
result, err = client.TSMGetWithArgs(ctx, []string{"is_compaction=true"}, mgetOpt).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(result["d"][1]).To(BeEquivalentTo([]interface{}{int64(10), 8.0}))
|
||||
|
||||
})
|
||||
|
||||
It("should TSQueryIndex", Label("timeseries", "tsqueryindex"), func() {
|
||||
|
@ -415,7 +409,6 @@ var _ = Describe("RedisTimeseries commands", Label("timeseries"), func() {
|
|||
result, err = client.TSQueryIndex(ctx, []string{"Taste=That"}).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(result)).To(BeEquivalentTo(1))
|
||||
|
||||
})
|
||||
|
||||
It("should TSDel and TSRange", Label("timeseries", "tsdel", "tsrange"), func() {
|
||||
|
@ -674,7 +667,6 @@ var _ = Describe("RedisTimeseries commands", Label("timeseries"), func() {
|
|||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(resultRange[0]).To(BeEquivalentTo(redis.TSTimestampValue{Timestamp: 70, Value: 5}))
|
||||
Expect(len(resultRange)).To(BeEquivalentTo(7))
|
||||
|
||||
})
|
||||
|
||||
It("should TSMRange and TSMRangeWithArgs", Label("timeseries", "tsmrange", "tsmrangeWithArgs"), func() {
|
||||
|
@ -761,7 +753,6 @@ var _ = Describe("RedisTimeseries commands", Label("timeseries"), func() {
|
|||
result, err = client.TSMRangeWithArgs(ctx, 0, 10, []string{"team=ny"}, mrangeOpt).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(result["a"][2]).To(BeEquivalentTo([]interface{}{[]interface{}{int64(0), 5.0}, []interface{}{int64(5), 6.0}}))
|
||||
|
||||
})
|
||||
|
||||
It("should TSMRangeWithArgs Latest", Label("timeseries", "tsmrangeWithArgs", "tsmrangelatest"), func() {
|
||||
|
@ -810,7 +801,6 @@ var _ = Describe("RedisTimeseries commands", Label("timeseries"), func() {
|
|||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(result["b"][2]).To(BeEquivalentTo([]interface{}{[]interface{}{int64(0), 4.0}, []interface{}{int64(10), 8.0}}))
|
||||
Expect(result["d"][2]).To(BeEquivalentTo([]interface{}{[]interface{}{int64(0), 4.0}, []interface{}{int64(10), 8.0}}))
|
||||
|
||||
})
|
||||
It("should TSMRevRange and TSMRevRangeWithArgs", Label("timeseries", "tsmrevrange", "tsmrevrangeWithArgs"), func() {
|
||||
createOpt := &redis.TSOptions{Labels: map[string]string{"Test": "This", "team": "ny"}}
|
||||
|
@ -896,7 +886,6 @@ var _ = Describe("RedisTimeseries commands", Label("timeseries"), func() {
|
|||
result, err = client.TSMRevRangeWithArgs(ctx, 0, 10, []string{"team=ny"}, mrangeOpt).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(result["a"][2]).To(BeEquivalentTo([]interface{}{[]interface{}{int64(1), 10.0}, []interface{}{int64(0), 1.0}}))
|
||||
|
||||
})
|
||||
|
||||
It("should TSMRevRangeWithArgs Latest", Label("timeseries", "tsmrevrangeWithArgs", "tsmrevrangelatest"), func() {
|
||||
|
@ -945,7 +934,5 @@ var _ = Describe("RedisTimeseries commands", Label("timeseries"), func() {
|
|||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(result["b"][2]).To(BeEquivalentTo([]interface{}{[]interface{}{int64(10), 8.0}, []interface{}{int64(0), 4.0}}))
|
||||
Expect(result["d"][2]).To(BeEquivalentTo([]interface{}{[]interface{}{int64(10), 8.0}, []interface{}{int64(0), 4.0}}))
|
||||
|
||||
})
|
||||
|
||||
})
|
12
universal.go
12
universal.go
|
@ -48,6 +48,7 @@ type UniversalOptions struct {
|
|||
PoolTimeout time.Duration
|
||||
MinIdleConns int
|
||||
MaxIdleConns int
|
||||
MaxActiveConns int
|
||||
ConnMaxIdleTime time.Duration
|
||||
ConnMaxLifetime time.Duration
|
||||
|
||||
|
@ -64,6 +65,8 @@ type UniversalOptions struct {
|
|||
// Only failover clients.
|
||||
|
||||
MasterName string
|
||||
|
||||
DisableIndentity bool
|
||||
}
|
||||
|
||||
// Cluster returns cluster options created from the universal options.
|
||||
|
@ -102,10 +105,13 @@ func (o *UniversalOptions) Cluster() *ClusterOptions {
|
|||
PoolTimeout: o.PoolTimeout,
|
||||
MinIdleConns: o.MinIdleConns,
|
||||
MaxIdleConns: o.MaxIdleConns,
|
||||
MaxActiveConns: o.MaxActiveConns,
|
||||
ConnMaxIdleTime: o.ConnMaxIdleTime,
|
||||
ConnMaxLifetime: o.ConnMaxLifetime,
|
||||
|
||||
TLSConfig: o.TLSConfig,
|
||||
|
||||
DisableIndentity: o.DisableIndentity,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -144,10 +150,13 @@ func (o *UniversalOptions) Failover() *FailoverOptions {
|
|||
PoolTimeout: o.PoolTimeout,
|
||||
MinIdleConns: o.MinIdleConns,
|
||||
MaxIdleConns: o.MaxIdleConns,
|
||||
MaxActiveConns: o.MaxActiveConns,
|
||||
ConnMaxIdleTime: o.ConnMaxIdleTime,
|
||||
ConnMaxLifetime: o.ConnMaxLifetime,
|
||||
|
||||
TLSConfig: o.TLSConfig,
|
||||
|
||||
DisableIndentity: o.DisableIndentity,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -183,10 +192,13 @@ func (o *UniversalOptions) Simple() *Options {
|
|||
PoolTimeout: o.PoolTimeout,
|
||||
MinIdleConns: o.MinIdleConns,
|
||||
MaxIdleConns: o.MaxIdleConns,
|
||||
MaxActiveConns: o.MaxActiveConns,
|
||||
ConnMaxIdleTime: o.ConnMaxIdleTime,
|
||||
ConnMaxLifetime: o.ConnMaxLifetime,
|
||||
|
||||
TLSConfig: o.TLSConfig,
|
||||
|
||||
DisableIndentity: o.DisableIndentity,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,5 +2,5 @@ package redis
|
|||
|
||||
// Version is the current release version.
|
||||
func Version() string {
|
||||
return "9.1.0"
|
||||
return "9.3.0"
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue