From 3b0d10b4ed8c453383170b54891daa3fa1d396ed Mon Sep 17 00:00:00 2001 From: Anurag Bandyopadhyay Date: Thu, 14 Sep 2023 18:54:23 +0530 Subject: [PATCH 01/42] Support for CLIENT SETINFO (#2659) * feat: merge master * feat: revert ring.go * feat: add ClientSetInfo command * fix: test and cmd: * fix: test and cmd * fix: test and cmd * fix: test and cmd * feat: redesigning the API * fix: panic test * fix: panic test --------- Co-authored-by: Anuragkillswitch <70265851+Anuragkillswitch@users.noreply.github.com> Co-authored-by: ofekshenawa <104765379+ofekshenawa@users.noreply.github.com> --- command.go | 6 ++++++ commands.go | 30 ++++++++++++++++++++++++++++ commands_test.go | 51 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+) diff --git a/command.go b/command.go index 549322a9..1700b328 100644 --- a/command.go +++ b/command.go @@ -5292,3 +5292,9 @@ func (cmd *ACLLogCmd) readReply(rd *proto.Reader) error { return nil } + +// LibraryInfo holds the library info. +type LibraryInfo struct { + LibName *string + LibVer *string +} diff --git a/commands.go b/commands.go index ce383025..4a3cd9ac 100644 --- a/commands.go +++ b/commands.go @@ -517,6 +517,7 @@ type StatefulCmdable interface { Select(ctx context.Context, index int) *StatusCmd SwapDB(ctx context.Context, index1, index2 int) *StatusCmd ClientSetName(ctx context.Context, name string) *BoolCmd + ClientSetInfo(ctx context.Context, info LibraryInfo) *StatusCmd Hello(ctx context.Context, ver int, username, password, clientName string) *MapStringInterfaceCmd } @@ -574,6 +575,35 @@ func (c statefulCmdable) ClientSetName(ctx context.Context, name string) *BoolCm return cmd } +// ClientSetInfo sends a CLIENT SETINFO command with the provided info. +func (c statefulCmdable) ClientSetInfo(ctx context.Context, info LibraryInfo) *StatusCmd { + err := info.Validate() + if err != nil { + panic(err.Error()) + } + + var cmd *StatusCmd + if info.LibName != nil { + cmd = NewStatusCmd(ctx, "client", "setinfo", "LIB-NAME", *info.LibName) + } else { + cmd = NewStatusCmd(ctx, "client", "setinfo", "LIB-VER", *info.LibVer) + } + + _ = c(ctx, cmd) + return cmd +} + +// Validate checks if only one field in the struct is non-nil. +func (info LibraryInfo) Validate() error { + if info.LibName != nil && info.LibVer != nil { + return errors.New("both LibName and LibVer cannot be set at the same time") + } + if info.LibName == nil && info.LibVer == nil { + return errors.New("at least one of LibName and LibVer should be set") + } + return nil +} + // Hello Set the resp protocol used. func (c statefulCmdable) Hello(ctx context.Context, ver int, username, password, clientName string) *MapStringInterfaceCmd { diff --git a/commands_test.go b/commands_test.go index f88cb21e..c8ea0f7b 100644 --- a/commands_test.go +++ b/commands_test.go @@ -231,6 +231,57 @@ 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()) From 678bf21b488419750f213e429b9fb84b5844d1dc Mon Sep 17 00:00:00 2001 From: Chayim Date: Tue, 19 Sep 2023 18:53:47 +0300 Subject: [PATCH 02/42] Updating redis binary for makefile to 7.2.1 (#2693) * updating redis binary from makefile to 7.2.0 * redis 7.2.1 --------- Co-authored-by: ofekshenawa <104765379+ofekshenawa@users.noreply.github.com> --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index b59c3955..cf1f6428 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,7 @@ bench: testdeps 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 From 54a106ee19668505a9bdf04246401fa3c10d5293 Mon Sep 17 00:00:00 2001 From: Chayim Date: Wed, 20 Sep 2023 09:28:28 +0300 Subject: [PATCH 03/42] Adding stale issues workflow (#2700) --- .github/workflows/stale-issues.yml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 .github/workflows/stale-issues.yml diff --git a/.github/workflows/stale-issues.yml b/.github/workflows/stale-issues.yml new file mode 100644 index 00000000..32fd9e81 --- /dev/null +++ b/.github/workflows/stale-issues.yml @@ -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@v3 + 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 From 0b6be62b7175a3bf958962bcf39936af3bc90869 Mon Sep 17 00:00:00 2001 From: ofekshenawa <104765379+ofekshenawa@users.noreply.github.com> Date: Wed, 20 Sep 2023 10:33:09 +0300 Subject: [PATCH 04/42] Identify client on connect (#2708) * Update CLIENT-SETINFO to support suffixes * Update CLIENT-SETINFO * fix acl log test * add setinfo option to cluster * change to DisableIndentity * change to DisableIndentity --- bench_decode_test.go | 3 +++ cluster.go | 21 +++++++++++---------- commands.go | 5 ++++- commands_test.go | 3 +-- options.go | 3 +++ redis.go | 9 ++++++++- 6 files changed, 30 insertions(+), 14 deletions(-) diff --git a/bench_decode_test.go b/bench_decode_test.go index de53064f..50e33908 100644 --- a/bench_decode_test.go +++ b/bench_decode_test.go @@ -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{ { diff --git a/cluster.go b/cluster.go index 941838dd..b30e2263 100644 --- a/cluster.go +++ b/cluster.go @@ -83,7 +83,8 @@ type ClusterOptions struct { 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() { @@ -277,15 +278,15 @@ func (opt *ClusterOptions) clientOptions() *Options { ReadTimeout: opt.ReadTimeout, WriteTimeout: opt.WriteTimeout, - 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, + 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 diff --git a/commands.go b/commands.go index 4a3cd9ac..2b2abfb7 100644 --- a/commands.go +++ b/commands.go @@ -4,9 +4,11 @@ import ( "context" "encoding" "errors" + "fmt" "io" "net" "reflect" + "runtime" "strings" "time" @@ -584,7 +586,8 @@ func (c statefulCmdable) ClientSetInfo(ctx context.Context, info LibraryInfo) *S var cmd *StatusCmd if info.LibName != nil { - cmd = NewStatusCmd(ctx, "client", "setinfo", "LIB-NAME", *info.LibName) + libName := fmt.Sprintf("go-redis(%s,%s)", *info.LibName, runtime.Version()) + cmd = NewStatusCmd(ctx, "client", "setinfo", "LIB-NAME", libName) } else { cmd = NewStatusCmd(ctx, "client", "setinfo", "LIB-VER", *info.LibVer) } diff --git a/commands_test.go b/commands_test.go index c8ea0f7b..b7744768 100644 --- a/commands_test.go +++ b/commands_test.go @@ -2052,10 +2052,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()) diff --git a/options.go b/options.go index bb4816b2..f10bad38 100644 --- a/options.go +++ b/options.go @@ -136,6 +136,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() { diff --git a/redis.go b/redis.go index c7fbd0de..9430eb75 100644 --- a/redis.go +++ b/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 != "" { From 736351c7cf80e56102cb05bb7029bdc8579f0352 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Sep 2023 10:41:10 +0300 Subject: [PATCH 05/42] chore(deps): bump actions/checkout from 3 to 4 (#2702) Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: ofekshenawa <104765379+ofekshenawa@users.noreply.github.com> --- .github/workflows/build.yml | 2 +- .github/workflows/doctests.yaml | 2 +- .github/workflows/golangci-lint.yml | 2 +- .github/workflows/spellcheck.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 403e199d..4dacf2a1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -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 diff --git a/.github/workflows/doctests.yaml b/.github/workflows/doctests.yaml index 00b0063b..dac22336 100644 --- a/.github/workflows/doctests.yaml +++ b/.github/workflows/doctests.yaml @@ -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 diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index d3232ecb..e35ee85c 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -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 diff --git a/.github/workflows/spellcheck.yml b/.github/workflows/spellcheck.yml index e1528415..3bd776cf 100644 --- a/.github/workflows/spellcheck.yml +++ b/.github/workflows/spellcheck.yml @@ -6,7 +6,7 @@ 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 with: From 33edd3de6858f9c3312d1536249f4a987f1583bd Mon Sep 17 00:00:00 2001 From: ofekshenawa <104765379+ofekshenawa@users.noreply.github.com> Date: Wed, 20 Sep 2023 10:42:09 +0300 Subject: [PATCH 06/42] Rename probablistic commands with args (#2701) Co-authored-by: Chayim --- probabilistic.go | 18 +++++++++--------- probabilistic_test.go | 8 ++++---- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/probabilistic.go b/probabilistic.go index 8e32bca9..969ce828 100644 --- a/probabilistic.go +++ b/probabilistic.go @@ -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,11 +143,11 @@ 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 { @@ -493,10 +493,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 +679,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 +693,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) diff --git a/probabilistic_test.go b/probabilistic_test.go index 4b0dde64..b493abd4 100644 --- a/probabilistic_test.go +++ b/probabilistic_test.go @@ -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() From e502cdc7501bbc9f27243feedabe875d0c9456af Mon Sep 17 00:00:00 2001 From: Tiago Peczenyj Date: Wed, 20 Sep 2023 09:42:31 +0200 Subject: [PATCH 07/42] Adding Go 1.21.x for CI coverage (#2697) * Update build.yml Add support to go 1.21.x * Update doctests.yaml --------- Co-authored-by: ofekshenawa <104765379+ofekshenawa@users.noreply.github.com> --- .github/workflows/build.yml | 2 +- .github/workflows/doctests.yaml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4dacf2a1..231233f7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -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: diff --git a/.github/workflows/doctests.yaml b/.github/workflows/doctests.yaml index dac22336..708dfcd3 100644 --- a/.github/workflows/doctests.yaml +++ b/.github/workflows/doctests.yaml @@ -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 }} @@ -38,4 +38,4 @@ jobs: - name: Test doc examples working-directory: ./doctests - run: go test \ No newline at end of file + run: go test From 71dd81cded1241a47f5196d5e581c59cb6cb4e8a Mon Sep 17 00:00:00 2001 From: Chayim Date: Wed, 20 Sep 2023 10:42:45 +0300 Subject: [PATCH 08/42] CONTRIBUTING guideliens (#2718) --- CONTRIBUTING.md | 101 ++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 4 ++ 2 files changed, 105 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..90030b89 --- /dev/null +++ b/CONTRIBUTING.md @@ -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: + +- +- + +## 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 ()](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. diff --git a/README.md b/README.md index 3486e8e5..1005868c 100644 --- a/README.md +++ b/README.md @@ -140,6 +140,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: From 1a7d2f4ad4e91b9fac784aa1b94254f838863e3b Mon Sep 17 00:00:00 2001 From: loveY <35476126+wzlove@users.noreply.github.com> Date: Wed, 20 Sep 2023 15:49:46 +0800 Subject: [PATCH 09/42] upgrade bitfield cmd to `add multiple values` (#2648) Co-authored-by: wangzheng1 Co-authored-by: ofekshenawa <104765379+ofekshenawa@users.noreply.github.com> --- commands.go | 18 +++++++++++------- commands_test.go | 4 ++++ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/commands.go b/commands.go index 2b2abfb7..96d90991 100644 --- a/commands.go +++ b/commands.go @@ -238,7 +238,7 @@ type Cmdable interface { BitOpNot(ctx context.Context, destKey string, key string) *IntCmd BitPos(ctx context.Context, key string, bit int64, pos ...int64) *IntCmd BitPosSpan(ctx context.Context, key string, bit int8, start, end int64, span string) *IntCmd - BitField(ctx context.Context, key string, args ...interface{}) *IntSliceCmd + BitField(ctx context.Context, key string, values ...interface{}) *IntSliceCmd Scan(ctx context.Context, cursor uint64, match string, count int64) *ScanCmd ScanType(ctx context.Context, cursor uint64, match string, count int64, keyType string) *ScanCmd @@ -1369,12 +1369,16 @@ func (c cmdable) BitPosSpan(ctx context.Context, key string, bit int8, start, en return cmd } -func (c cmdable) BitField(ctx context.Context, key string, args ...interface{}) *IntSliceCmd { - a := make([]interface{}, 0, 2+len(args)) - a = append(a, "bitfield") - a = append(a, key) - a = append(a, args...) - cmd := NewIntSliceCmd(ctx, a...) +// 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 } diff --git a/commands_test.go b/commands_test.go index b7744768..9b1c63e8 100644 --- a/commands_test.go +++ b/commands_test.go @@ -1253,6 +1253,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() { From e8ad794e965a1b4e2ba63fe9a6975a14ebf289b9 Mon Sep 17 00:00:00 2001 From: Tiago Peczenyj Date: Wed, 20 Sep 2023 13:03:44 +0200 Subject: [PATCH 10/42] Format code and fix go vet (#2696) * run go fix ./... Signed-off-by: Tiago Peczenyj * run make fmt Signed-off-by: Tiago Peczenyj * fix go vet ./... issues * Update README.md Reorder imports with the rules defined in the Makefile as if we run `make fmt` * run gofumpt -w . * update Makefile to use gofumpt instead gofmt * increment makefile * format test * format tests Signed-off-by: Tiago Peczenyj --------- Signed-off-by: Tiago Peczenyj Co-authored-by: ofekshenawa <104765379+ofekshenawa@users.noreply.github.com> --- Makefile | 4 ++-- README.md | 6 +++-- cluster_test.go | 3 ++- command.go | 2 +- commands.go | 3 ++- commands_test.go | 23 ++++--------------- doctests/lpush_lrange_test.go | 3 ++- doctests/set_get_test.go | 3 ++- example/del-keys-without-ttl/main.go | 3 ++- extra/redisotel/metrics.go | 3 ++- extra/redisprometheus/collector.go | 12 +++++----- internal/customvet/checks/setval/setval.go | 1 + .../customvet/checks/setval/setval_test.go | 3 ++- internal/customvet/main.go | 3 ++- internal/pool/conn_check.go | 1 - internal/pool/conn_check_dummy.go | 1 - internal/pool/conn_check_test.go | 1 - internal/util/safe.go | 1 - internal/util/unsafe.go | 1 - main_test.go | 6 +++-- options_test.go | 1 - probabilistic.go | 1 + probabilistic_test.go | 8 +------ redis_gears.go | 5 ---- redis_gears_test.go | 4 +--- redis_timeseries.go | 9 -------- redis_timeseries_test.go | 15 +----------- ring.go | 1 - 28 files changed, 42 insertions(+), 85 deletions(-) diff --git a/Makefile b/Makefile index cf1f6428..6fe9c33e 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ 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 testdata/redis: mkdir -p $@ @@ -29,7 +29,7 @@ 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: diff --git a/README.md b/README.md index 1005868c..18095eeb 100644 --- a/README.md +++ b/README.md @@ -69,8 +69,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() @@ -125,8 +126,9 @@ go-redis also supports connecting via the [redis uri specification](https://gith ```go import ( "context" - "github.com/redis/go-redis/v9" "fmt" + + "github.com/redis/go-redis/v9" ) var ctx = context.Background() diff --git a/cluster_test.go b/cluster_test.go index d3b4474a..3d2f8071 100644 --- a/cluster_test.go +++ b/cluster_test.go @@ -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 diff --git a/command.go b/command.go index 1700b328..00e356bb 100644 --- a/command.go +++ b/command.go @@ -4324,7 +4324,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 +4340,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 { diff --git a/commands.go b/commands.go index 96d90991..dbbad083 100644 --- a/commands.go +++ b/commands.go @@ -609,7 +609,8 @@ func (info LibraryInfo) Validate() error { // Hello Set the resp protocol used. func (c statefulCmdable) Hello(ctx context.Context, - ver int, username, password, clientName string) *MapStringInterfaceCmd { + ver int, username, password, clientName string, +) *MapStringInterfaceCmd { args := make([]interface{}, 0, 7) args = append(args, "hello", ver) if password != "" { diff --git a/commands_test.go b/commands_test.go index 9b1c63e8..812d682d 100644 --- a/commands_test.go +++ b/commands_test.go @@ -232,7 +232,6 @@ var _ = Describe("Commands", func() { }) It("should ClientSetInfo", func() { - pipe := client.Pipeline() // Test setting the libName @@ -413,7 +412,6 @@ var _ = Describe("Commands", func() { }) It("should filter commands by ACL category", func() { - filter := &redis.FilterBy{ ACLCat: "admin", } @@ -580,7 +578,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() { @@ -727,7 +724,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") @@ -966,7 +962,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") @@ -988,7 +983,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") @@ -2042,7 +2036,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()) @@ -2073,7 +2066,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() { @@ -2087,7 +2079,6 @@ var _ = Describe("Commands", func() { Expect(err).NotTo(HaveOccurred()) Expect(len(logEntries)).To(Equal(0)) }) - }) Describe("hashes", func() { @@ -2699,7 +2690,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() { @@ -2721,7 +2711,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() @@ -2729,7 +2719,7 @@ var _ = Describe("Commands", func() { select { case <-done: - //ok + // ok case <-time.After(time.Second): Fail("BLMPop is still blocked") } @@ -4184,7 +4174,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() @@ -4256,11 +4245,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() @@ -4360,7 +4347,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() @@ -4368,7 +4355,7 @@ var _ = Describe("Commands", func() { select { case <-done: - //ok + // ok case <-time.After(time.Second): Fail("BZMPop is still blocked") } @@ -6928,7 +6915,6 @@ var _ = Describe("Commands", func() { close(started) }) - }) Describe("SlowLogGet", func() { @@ -6949,7 +6935,6 @@ var _ = Describe("Commands", func() { Expect(len(result)).NotTo(BeZero()) }) }) - }) type numberStruct struct { diff --git a/doctests/lpush_lrange_test.go b/doctests/lpush_lrange_test.go index 64020c91..1e69f4b0 100644 --- a/doctests/lpush_lrange_test.go +++ b/doctests/lpush_lrange_test.go @@ -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{ diff --git a/doctests/set_get_test.go b/doctests/set_get_test.go index 792bd419..ab3a9360 100644 --- a/doctests/set_get_test.go +++ b/doctests/set_get_test.go @@ -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{ diff --git a/example/del-keys-without-ttl/main.go b/example/del-keys-without-ttl/main.go index 549d78e2..d2f85d4e 100644 --- a/example/del-keys-without-ttl/main.go +++ b/example/del-keys-without-ttl/main.go @@ -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() { diff --git a/extra/redisotel/metrics.go b/extra/redisotel/metrics.go index 695c7ee3..fc44a14c 100644 --- a/extra/redisotel/metrics.go +++ b/extra/redisotel/metrics.go @@ -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. diff --git a/extra/redisprometheus/collector.go b/extra/redisprometheus/collector.go index b726ec43..77c424e8 100644 --- a/extra/redisprometheus/collector.go +++ b/extra/redisprometheus/collector.go @@ -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, diff --git a/internal/customvet/checks/setval/setval.go b/internal/customvet/checks/setval/setval.go index e629f5f7..924a9bfc 100644 --- a/internal/customvet/checks/setval/setval.go +++ b/internal/customvet/checks/setval/setval.go @@ -4,6 +4,7 @@ import ( "go/ast" "go/token" "go/types" + "golang.org/x/tools/go/analysis" ) diff --git a/internal/customvet/checks/setval/setval_test.go b/internal/customvet/checks/setval/setval_test.go index e75647a3..d93ec45f 100644 --- a/internal/customvet/checks/setval/setval_test.go +++ b/internal/customvet/checks/setval/setval_test.go @@ -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) { diff --git a/internal/customvet/main.go b/internal/customvet/main.go index a97a49c9..6a06ee21 100644 --- a/internal/customvet/main.go +++ b/internal/customvet/main.go @@ -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() { diff --git a/internal/pool/conn_check.go b/internal/pool/conn_check.go index f04dc1c3..83190d39 100644 --- a/internal/pool/conn_check.go +++ b/internal/pool/conn_check.go @@ -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 diff --git a/internal/pool/conn_check_dummy.go b/internal/pool/conn_check_dummy.go index 9408446b..295da126 100644 --- a/internal/pool/conn_check_dummy.go +++ b/internal/pool/conn_check_dummy.go @@ -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 diff --git a/internal/pool/conn_check_test.go b/internal/pool/conn_check_test.go index e5acb9f8..2ade8a0b 100644 --- a/internal/pool/conn_check_test.go +++ b/internal/pool/conn_check_test.go @@ -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 diff --git a/internal/util/safe.go b/internal/util/safe.go index 21307110..8178f86d 100644 --- a/internal/util/safe.go +++ b/internal/util/safe.go @@ -1,5 +1,4 @@ //go:build appengine -// +build appengine package util diff --git a/internal/util/unsafe.go b/internal/util/unsafe.go index daa8d769..cbcd2cc0 100644 --- a/internal/util/unsafe.go +++ b/internal/util/unsafe.go @@ -1,5 +1,4 @@ //go:build !appengine -// +build !appengine package util diff --git a/main_test.go b/main_test.go index 8e87d76f..6aaaf1c0 100644 --- a/main_test.go +++ b/main_test.go @@ -36,8 +36,10 @@ const ( sentinelPort3 = "9128" ) -var redisPort = "6380" -var redisAddr = ":" + redisPort +var ( + redisPort = "6380" + redisAddr = ":" + redisPort +) var ( sentinelAddrs = []string{":" + sentinelPort1, ":" + sentinelPort2, ":" + sentinelPort3} diff --git a/options_test.go b/options_test.go index fa9ac6c9..1db36fdb 100644 --- a/options_test.go +++ b/options_test.go @@ -1,5 +1,4 @@ //go:build go1.7 -// +build go1.7 package redis diff --git a/probabilistic.go b/probabilistic.go index 969ce828..61a3460a 100644 --- a/probabilistic.go +++ b/probabilistic.go @@ -310,6 +310,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) } diff --git a/probabilistic_test.go b/probabilistic_test.go index b493abd4..61829c52 100644 --- a/probabilistic_test.go +++ b/probabilistic_test.go @@ -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() { @@ -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() { diff --git a/redis_gears.go b/redis_gears.go index 5fafea40..8e6ad874 100644 --- a/redis_gears.go +++ b/redis_gears.go @@ -88,7 +88,6 @@ func (c cmdable) TFunctionListArgs(ctx context.Context, options *TFunctionListOp } if options.Library != "" { args = append(args, "LIBRARY", options.Library) - } } cmd := NewMapStringInterfaceSliceCmd(ctx, args...) @@ -112,13 +111,11 @@ func (c cmdable) TFCallArgs(ctx context.Context, libName string, funcName string if options != nil { if options.Keys != nil { for _, key := range options.Keys { - args = append(args, key) } } if options.Arguments != nil { for _, key := range options.Arguments { - args = append(args, key) } } @@ -144,13 +141,11 @@ func (c cmdable) TFCallASYNCArgs(ctx context.Context, libName string, funcName s if options != nil { if options.Keys != nil { for _, key := range options.Keys { - args = append(args, key) } } if options.Arguments != nil { for _, key := range options.Arguments { - args = append(args, key) } } diff --git a/redis_gears_test.go b/redis_gears_test.go index 1318615e..b1117a4d 100644 --- a/redis_gears_test.go +++ b/redis_gears_test.go @@ -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")) }) - }) diff --git a/redis_timeseries.go b/redis_timeseries.go index 5ead2fa5..61cc3a5b 100644 --- a/redis_timeseries.go +++ b/redis_timeseries.go @@ -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) - } } } diff --git a/redis_timeseries_test.go b/redis_timeseries_test.go index 291274de..29897b54 100644 --- a/redis_timeseries_test.go +++ b/redis_timeseries_test.go @@ -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}})) - }) - }) diff --git a/ring.go b/ring.go index 0572ba34..7cf09111 100644 --- a/ring.go +++ b/ring.go @@ -292,7 +292,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 From 7acc0cd2546899c179b5952710cba486d3200ee3 Mon Sep 17 00:00:00 2001 From: taytzehao Date: Wed, 20 Sep 2023 19:08:08 +0800 Subject: [PATCH 11/42] useTime duration calculation (#2651) Co-authored-by: tzehaoo Co-authored-by: ofekshenawa <104765379+ofekshenawa@users.noreply.github.com> --- extra/redisotel/metrics.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/extra/redisotel/metrics.go b/extra/redisotel/metrics.go index fc44a14c..915838f3 100644 --- a/extra/redisotel/metrics.go +++ b/extra/redisotel/metrics.go @@ -193,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 } } From 0637c53f103b127f77bce430a7ac5b566a61aa30 Mon Sep 17 00:00:00 2001 From: Nikan Vasei <94530582+NikanV@users.noreply.github.com> Date: Wed, 20 Sep 2023 14:39:23 +0330 Subject: [PATCH 12/42] Added the support for WAITAOF which is a new command in redis ver7.2.0 (#2629) * implemented WaitAOF command for the redis ver7.2.0 * updated the test corresponding to WaitAOF --------- Co-authored-by: Chayim Co-authored-by: ofekshenawa <104765379+ofekshenawa@users.noreply.github.com> --- commands.go | 7 +++++++ commands_test.go | 12 ++++++++++++ 2 files changed, 19 insertions(+) diff --git a/commands.go b/commands.go index dbbad083..2537c0ac 100644 --- a/commands.go +++ b/commands.go @@ -558,6 +558,13 @@ func (c cmdable) Wait(ctx context.Context, numSlaves int, timeout time.Duration) return cmd } +func (c cmdable) WaitAOF(ctx context.Context, numLocal, numSlaves int, timeout time.Duration) *IntCmd { + cmd := NewIntCmd(ctx, "waitAOF", numLocal, numSlaves, int(timeout/time.Millisecond)) + cmd.setReadTimeout(timeout) + _ = c(ctx, cmd) + return cmd +} + func (c statefulCmdable) Select(ctx context.Context, index int) *StatusCmd { cmd := NewStatusCmd(ctx, "select", index) _ = c(ctx, cmd) diff --git a/commands_test.go b/commands_test.go index 812d682d..8e00c836 100644 --- a/commands_test.go +++ b/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) From 934c6a3fe0073b3afcca6c293a35486f2971c034 Mon Sep 17 00:00:00 2001 From: Tiago Peczenyj Date: Wed, 20 Sep 2023 13:54:50 +0200 Subject: [PATCH 13/42] make public probabilistic and redis gears interfaces (#2695) Co-authored-by: ofekshenawa <104765379+ofekshenawa@users.noreply.github.com> --- commands.go | 4 ++-- probabilistic.go | 2 +- redis_gears.go | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/commands.go b/commands.go index 2537c0ac..8cff2cb4 100644 --- a/commands.go +++ b/commands.go @@ -507,8 +507,8 @@ type Cmdable interface { ModuleLoadex(ctx context.Context, conf *ModuleLoadexConfig) *StringCmd - gearsCmdable - probabilisticCmdable + GearsCmdable + ProbabilisticCmdable TimeseriesCmdable } diff --git a/probabilistic.go b/probabilistic.go index 61a3460a..d397b490 100644 --- a/probabilistic.go +++ b/probabilistic.go @@ -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 diff --git a/redis_gears.go b/redis_gears.go index 8e6ad874..0c67cdf2 100644 --- a/redis_gears.go +++ b/redis_gears.go @@ -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 From e23ea028bd95ae0f8a76a59ea53b7557e0c19fe5 Mon Sep 17 00:00:00 2001 From: Nikolay Vorobev Date: Wed, 20 Sep 2023 14:55:23 +0300 Subject: [PATCH 14/42] Added MaxActiveConns (#2646) * Added the ability to set a connection growth limit when there are not enough connections in the pool using MaxActiveConns * fix comment * fix * fix --------- Co-authored-by: ofekshenawa <104765379+ofekshenawa@users.noreply.github.com> --- internal/pool/pool.go | 19 +++++++++++++------ options.go | 8 +++++++- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/internal/pool/pool.go b/internal/pool/pool.go index bb9b14be..f391ceda 100644 --- a/internal/pool/pool.go +++ b/internal/pool/pool.go @@ -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. diff --git a/options.go b/options.go index f10bad38..ba65defd 100644 --- a/options.go +++ b/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. // @@ -502,6 +507,7 @@ func newConnPool( PoolTimeout: opt.PoolTimeout, MinIdleConns: opt.MinIdleConns, MaxIdleConns: opt.MaxIdleConns, + MaxActiveConns: opt.MaxActiveConns, ConnMaxIdleTime: opt.ConnMaxIdleTime, ConnMaxLifetime: opt.ConnMaxLifetime, }) From 7ecd7ac1c7b84f19fe723539d27089686ade1925 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Sep 2023 14:56:07 +0300 Subject: [PATCH 15/42] chore(deps): bump github.com/bsm/gomega from 1.26.0 to 1.27.10 (#2689) Bumps [github.com/bsm/gomega](https://github.com/bsm/gomega) from 1.26.0 to 1.27.10. - [Commits](https://github.com/bsm/gomega/compare/v1.26.0...v1.27.10) --- updated-dependencies: - dependency-name: github.com/bsm/gomega dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: ofekshenawa <104765379+ofekshenawa@users.noreply.github.com> Co-authored-by: Chayim --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index c13deb88..bb0c1e9d 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.18 require ( github.com/bsm/ginkgo/v2 v2.9.5 - github.com/bsm/gomega v1.26.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 ) diff --git a/go.sum b/go.sum index be26cfe3..b10bcec2 100644 --- a/go.sum +++ b/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/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= From e07f7e62b8197a8f47edeeef230dfbf53ef05aa5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Sep 2023 14:57:47 +0300 Subject: [PATCH 16/42] chore(deps): bump github.com/bsm/ginkgo/v2 from 2.9.5 to 2.12.0 (#2690) Bumps [github.com/bsm/ginkgo/v2](https://github.com/bsm/ginkgo) from 2.9.5 to 2.12.0. - [Commits](https://github.com/bsm/ginkgo/compare/v2.9.5...v2.12.0) --- updated-dependencies: - dependency-name: github.com/bsm/ginkgo/v2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: ofekshenawa <104765379+ofekshenawa@users.noreply.github.com> Co-authored-by: Chayim --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index bb0c1e9d..6c65f094 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/redis/go-redis/v9 go 1.18 require ( - github.com/bsm/ginkgo/v2 v2.9.5 + 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 diff --git a/go.sum b/go.sum index b10bcec2..21b4f64e 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -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/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= From 6199a2af2c59d85ab536408a4c8b92f8cba07f2f Mon Sep 17 00:00:00 2001 From: Chayim Date: Wed, 20 Sep 2023 16:08:24 +0300 Subject: [PATCH 17/42] Making command structs digestable (#2716) * intial move * adding stringcmdable * moving module commands to align with other changes --------- Co-authored-by: ofekshenawa <104765379+ofekshenawa@users.noreply.github.com> Co-authored-by: ofekshenawa --- .gitignore | 2 + Makefile | 3 + acl_commands.go | 35 + bitmap_commands.go | 129 + cluster_commands.go | 271 +- commands.go | 3354 +---------------- redis_gears.go => gears_commands.go | 0 redis_gears_test.go => gears_commands_test.go | 0 generic_commands.go | 377 ++ geo_commands.go | 155 + hash_commands.go | 174 + hyperloglog_commands.go | 42 + list_commands.go | 289 ++ cluster.go => osscluster.go | 0 osscluster_commands.go | 109 + cluster_test.go => osscluster_test.go | 0 pubsub_commands.go | 76 + scripting_commands.go | 215 ++ set_commands.go | 217 ++ sortedset_commands.go | 772 ++++ stream_commands.go | 438 +++ string_commands.go | 303 ++ redis_timeseries.go => timeseries_commands.go | 0 ...ies_test.go => timeseries_commands_test.go | 0 24 files changed, 3526 insertions(+), 3435 deletions(-) create mode 100644 acl_commands.go create mode 100644 bitmap_commands.go rename redis_gears.go => gears_commands.go (100%) rename redis_gears_test.go => gears_commands_test.go (100%) create mode 100644 generic_commands.go create mode 100644 geo_commands.go create mode 100644 hash_commands.go create mode 100644 hyperloglog_commands.go create mode 100644 list_commands.go rename cluster.go => osscluster.go (100%) create mode 100644 osscluster_commands.go rename cluster_test.go => osscluster_test.go (100%) create mode 100644 pubsub_commands.go create mode 100644 scripting_commands.go create mode 100644 set_commands.go create mode 100644 sortedset_commands.go create mode 100644 stream_commands.go create mode 100644 string_commands.go rename redis_timeseries.go => timeseries_commands.go (100%) rename redis_timeseries_test.go => timeseries_commands_test.go (100%) diff --git a/.gitignore b/.gitignore index 64a7cb51..6f868895 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ testdata/* .idea/ .DS_Store +*.tar.gz +*.dic \ No newline at end of file diff --git a/Makefile b/Makefile index 6fe9c33e..dc2fe780 100644 --- a/Makefile +++ b/Makefile @@ -21,6 +21,9 @@ bench: testdeps .PHONY: all test testdeps bench fmt +build: + go build . + testdata/redis: mkdir -p $@ wget -qO- https://download.redis.io/releases/redis-7.2.1.tar.gz | tar xvz --strip-components=1 -C $@ diff --git a/acl_commands.go b/acl_commands.go new file mode 100644 index 00000000..06847be2 --- /dev/null +++ b/acl_commands.go @@ -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 +} diff --git a/bitmap_commands.go b/bitmap_commands.go new file mode 100644 index 00000000..1436b02a --- /dev/null +++ b/bitmap_commands.go @@ -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 +} diff --git a/cluster_commands.go b/cluster_commands.go index b13f8e7e..0caf0977 100644 --- a/cluster_commands.go +++ b/cluster_commands.go @@ -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 } diff --git a/commands.go b/commands.go index 8cff2cb4..fffea441 100644 --- a/commands.go +++ b/commands.go @@ -172,234 +172,7 @@ type Cmdable interface { Echo(ctx context.Context, message interface{}) *StringCmd Ping(ctx context.Context) *StatusCmd Quit(ctx context.Context) *StatusCmd - Del(ctx context.Context, keys ...string) *IntCmd Unlink(ctx context.Context, keys ...string) *IntCmd - Dump(ctx context.Context, key string) *StringCmd - Exists(ctx context.Context, keys ...string) *IntCmd - Expire(ctx context.Context, key string, expiration time.Duration) *BoolCmd - ExpireAt(ctx context.Context, key string, tm time.Time) *BoolCmd - ExpireTime(ctx context.Context, key string) *DurationCmd - ExpireNX(ctx context.Context, key string, expiration time.Duration) *BoolCmd - ExpireXX(ctx context.Context, key string, expiration time.Duration) *BoolCmd - ExpireGT(ctx context.Context, key string, expiration time.Duration) *BoolCmd - ExpireLT(ctx context.Context, key string, expiration time.Duration) *BoolCmd - Keys(ctx context.Context, pattern string) *StringSliceCmd - Migrate(ctx context.Context, host, port, key string, db int, timeout time.Duration) *StatusCmd - Move(ctx context.Context, key string, db int) *BoolCmd - ObjectRefCount(ctx context.Context, key string) *IntCmd - ObjectEncoding(ctx context.Context, key string) *StringCmd - ObjectIdleTime(ctx context.Context, key string) *DurationCmd - Persist(ctx context.Context, key string) *BoolCmd - PExpire(ctx context.Context, key string, expiration time.Duration) *BoolCmd - PExpireAt(ctx context.Context, key string, tm time.Time) *BoolCmd - PExpireTime(ctx context.Context, key string) *DurationCmd - PTTL(ctx context.Context, key string) *DurationCmd - RandomKey(ctx context.Context) *StringCmd - Rename(ctx context.Context, key, newkey string) *StatusCmd - RenameNX(ctx context.Context, key, newkey string) *BoolCmd - Restore(ctx context.Context, key string, ttl time.Duration, value string) *StatusCmd - RestoreReplace(ctx context.Context, key string, ttl time.Duration, value string) *StatusCmd - Sort(ctx context.Context, key string, sort *Sort) *StringSliceCmd - SortRO(ctx context.Context, key string, sort *Sort) *StringSliceCmd - SortStore(ctx context.Context, key, store string, sort *Sort) *IntCmd - SortInterfaces(ctx context.Context, key string, sort *Sort) *SliceCmd - Touch(ctx context.Context, keys ...string) *IntCmd - TTL(ctx context.Context, key string) *DurationCmd - Type(ctx context.Context, key string) *StatusCmd - Append(ctx context.Context, key, value string) *IntCmd - Decr(ctx context.Context, key string) *IntCmd - DecrBy(ctx context.Context, key string, decrement int64) *IntCmd - Get(ctx context.Context, key string) *StringCmd - GetRange(ctx context.Context, key string, start, end int64) *StringCmd - GetSet(ctx context.Context, key string, value interface{}) *StringCmd - GetEx(ctx context.Context, key string, expiration time.Duration) *StringCmd - GetDel(ctx context.Context, key string) *StringCmd - Incr(ctx context.Context, key string) *IntCmd - IncrBy(ctx context.Context, key string, value int64) *IntCmd - IncrByFloat(ctx context.Context, key string, value float64) *FloatCmd - MGet(ctx context.Context, keys ...string) *SliceCmd - MSet(ctx context.Context, values ...interface{}) *StatusCmd - MSetNX(ctx context.Context, values ...interface{}) *BoolCmd - Set(ctx context.Context, key string, value interface{}, expiration time.Duration) *StatusCmd - SetArgs(ctx context.Context, key string, value interface{}, a SetArgs) *StatusCmd - SetEx(ctx context.Context, key string, value interface{}, expiration time.Duration) *StatusCmd - SetNX(ctx context.Context, key string, value interface{}, expiration time.Duration) *BoolCmd - SetXX(ctx context.Context, key string, value interface{}, expiration time.Duration) *BoolCmd - SetRange(ctx context.Context, key string, offset int64, value string) *IntCmd - StrLen(ctx context.Context, key string) *IntCmd - Copy(ctx context.Context, sourceKey string, destKey string, db int, replace bool) *IntCmd - - GetBit(ctx context.Context, key string, offset int64) *IntCmd - SetBit(ctx context.Context, key string, offset int64, value int) *IntCmd - BitCount(ctx context.Context, key string, bitCount *BitCount) *IntCmd - BitOpAnd(ctx context.Context, destKey string, keys ...string) *IntCmd - BitOpOr(ctx context.Context, destKey string, keys ...string) *IntCmd - BitOpXor(ctx context.Context, destKey string, keys ...string) *IntCmd - BitOpNot(ctx context.Context, destKey string, key string) *IntCmd - BitPos(ctx context.Context, key string, bit int64, pos ...int64) *IntCmd - BitPosSpan(ctx context.Context, key string, bit int8, start, end int64, span string) *IntCmd - BitField(ctx context.Context, key string, values ...interface{}) *IntSliceCmd - - Scan(ctx context.Context, cursor uint64, match string, count int64) *ScanCmd - ScanType(ctx context.Context, cursor uint64, match string, count int64, keyType string) *ScanCmd - SScan(ctx context.Context, key string, cursor uint64, match string, count int64) *ScanCmd - HScan(ctx context.Context, key string, cursor uint64, match string, count int64) *ScanCmd - ZScan(ctx context.Context, key string, cursor uint64, match string, count int64) *ScanCmd - - HDel(ctx context.Context, key string, fields ...string) *IntCmd - HExists(ctx context.Context, key, field string) *BoolCmd - HGet(ctx context.Context, key, field string) *StringCmd - HGetAll(ctx context.Context, key string) *MapStringStringCmd - HIncrBy(ctx context.Context, key, field string, incr int64) *IntCmd - HIncrByFloat(ctx context.Context, key, field string, incr float64) *FloatCmd - HKeys(ctx context.Context, key string) *StringSliceCmd - HLen(ctx context.Context, key string) *IntCmd - HMGet(ctx context.Context, key string, fields ...string) *SliceCmd - HSet(ctx context.Context, key string, values ...interface{}) *IntCmd - HMSet(ctx context.Context, key string, values ...interface{}) *BoolCmd - HSetNX(ctx context.Context, key, field string, value interface{}) *BoolCmd - HVals(ctx context.Context, key string) *StringSliceCmd - HRandField(ctx context.Context, key string, count int) *StringSliceCmd - HRandFieldWithValues(ctx context.Context, key string, count int) *KeyValueSliceCmd - - BLPop(ctx context.Context, timeout time.Duration, keys ...string) *StringSliceCmd - BLMPop(ctx context.Context, timeout time.Duration, direction string, count int64, keys ...string) *KeyValuesCmd - BRPop(ctx context.Context, timeout time.Duration, keys ...string) *StringSliceCmd - BRPopLPush(ctx context.Context, source, destination string, timeout time.Duration) *StringCmd - LCS(ctx context.Context, q *LCSQuery) *LCSCmd - LIndex(ctx context.Context, key string, index int64) *StringCmd - LInsert(ctx context.Context, key, op string, pivot, value interface{}) *IntCmd - LInsertBefore(ctx context.Context, key string, pivot, value interface{}) *IntCmd - LInsertAfter(ctx context.Context, key string, pivot, value interface{}) *IntCmd - LLen(ctx context.Context, key string) *IntCmd - LMPop(ctx context.Context, direction string, count int64, keys ...string) *KeyValuesCmd - LPop(ctx context.Context, key string) *StringCmd - LPopCount(ctx context.Context, key string, count int) *StringSliceCmd - LPos(ctx context.Context, key string, value string, args LPosArgs) *IntCmd - LPosCount(ctx context.Context, key string, value string, count int64, args LPosArgs) *IntSliceCmd - LPush(ctx context.Context, key string, values ...interface{}) *IntCmd - LPushX(ctx context.Context, key string, values ...interface{}) *IntCmd - LRange(ctx context.Context, key string, start, stop int64) *StringSliceCmd - LRem(ctx context.Context, key string, count int64, value interface{}) *IntCmd - LSet(ctx context.Context, key string, index int64, value interface{}) *StatusCmd - LTrim(ctx context.Context, key string, start, stop int64) *StatusCmd - RPop(ctx context.Context, key string) *StringCmd - RPopCount(ctx context.Context, key string, count int) *StringSliceCmd - RPopLPush(ctx context.Context, source, destination string) *StringCmd - RPush(ctx context.Context, key string, values ...interface{}) *IntCmd - RPushX(ctx context.Context, key string, values ...interface{}) *IntCmd - LMove(ctx context.Context, source, destination, srcpos, destpos string) *StringCmd - BLMove(ctx context.Context, source, destination, srcpos, destpos string, timeout time.Duration) *StringCmd - - SAdd(ctx context.Context, key string, members ...interface{}) *IntCmd - SCard(ctx context.Context, key string) *IntCmd - SDiff(ctx context.Context, keys ...string) *StringSliceCmd - SDiffStore(ctx context.Context, destination string, keys ...string) *IntCmd - SInter(ctx context.Context, keys ...string) *StringSliceCmd - SInterCard(ctx context.Context, limit int64, keys ...string) *IntCmd - SInterStore(ctx context.Context, destination string, keys ...string) *IntCmd - SIsMember(ctx context.Context, key string, member interface{}) *BoolCmd - SMIsMember(ctx context.Context, key string, members ...interface{}) *BoolSliceCmd - SMembers(ctx context.Context, key string) *StringSliceCmd - SMembersMap(ctx context.Context, key string) *StringStructMapCmd - SMove(ctx context.Context, source, destination string, member interface{}) *BoolCmd - SPop(ctx context.Context, key string) *StringCmd - SPopN(ctx context.Context, key string, count int64) *StringSliceCmd - SRandMember(ctx context.Context, key string) *StringCmd - SRandMemberN(ctx context.Context, key string, count int64) *StringSliceCmd - SRem(ctx context.Context, key string, members ...interface{}) *IntCmd - SUnion(ctx context.Context, keys ...string) *StringSliceCmd - SUnionStore(ctx context.Context, destination string, keys ...string) *IntCmd - - XAdd(ctx context.Context, a *XAddArgs) *StringCmd - XDel(ctx context.Context, stream string, ids ...string) *IntCmd - XLen(ctx context.Context, stream string) *IntCmd - XRange(ctx context.Context, stream, start, stop string) *XMessageSliceCmd - XRangeN(ctx context.Context, stream, start, stop string, count int64) *XMessageSliceCmd - XRevRange(ctx context.Context, stream string, start, stop string) *XMessageSliceCmd - XRevRangeN(ctx context.Context, stream string, start, stop string, count int64) *XMessageSliceCmd - XRead(ctx context.Context, a *XReadArgs) *XStreamSliceCmd - XReadStreams(ctx context.Context, streams ...string) *XStreamSliceCmd - XGroupCreate(ctx context.Context, stream, group, start string) *StatusCmd - XGroupCreateMkStream(ctx context.Context, stream, group, start string) *StatusCmd - XGroupSetID(ctx context.Context, stream, group, start string) *StatusCmd - XGroupDestroy(ctx context.Context, stream, group string) *IntCmd - XGroupCreateConsumer(ctx context.Context, stream, group, consumer string) *IntCmd - XGroupDelConsumer(ctx context.Context, stream, group, consumer string) *IntCmd - XReadGroup(ctx context.Context, a *XReadGroupArgs) *XStreamSliceCmd - XAck(ctx context.Context, stream, group string, ids ...string) *IntCmd - XPending(ctx context.Context, stream, group string) *XPendingCmd - XPendingExt(ctx context.Context, a *XPendingExtArgs) *XPendingExtCmd - XClaim(ctx context.Context, a *XClaimArgs) *XMessageSliceCmd - XClaimJustID(ctx context.Context, a *XClaimArgs) *StringSliceCmd - XAutoClaim(ctx context.Context, a *XAutoClaimArgs) *XAutoClaimCmd - XAutoClaimJustID(ctx context.Context, a *XAutoClaimArgs) *XAutoClaimJustIDCmd - XTrimMaxLen(ctx context.Context, key string, maxLen int64) *IntCmd - XTrimMaxLenApprox(ctx context.Context, key string, maxLen, limit int64) *IntCmd - XTrimMinID(ctx context.Context, key string, minID string) *IntCmd - XTrimMinIDApprox(ctx context.Context, key string, minID string, limit int64) *IntCmd - XInfoGroups(ctx context.Context, key string) *XInfoGroupsCmd - XInfoStream(ctx context.Context, key string) *XInfoStreamCmd - XInfoStreamFull(ctx context.Context, key string, count int) *XInfoStreamFullCmd - XInfoConsumers(ctx context.Context, key string, group string) *XInfoConsumersCmd - - BZPopMax(ctx context.Context, timeout time.Duration, keys ...string) *ZWithKeyCmd - BZPopMin(ctx context.Context, timeout time.Duration, keys ...string) *ZWithKeyCmd - BZMPop(ctx context.Context, timeout time.Duration, order string, count int64, keys ...string) *ZSliceWithKeyCmd - - ZAdd(ctx context.Context, key string, members ...Z) *IntCmd - ZAddLT(ctx context.Context, key string, members ...Z) *IntCmd - ZAddGT(ctx context.Context, key string, members ...Z) *IntCmd - ZAddNX(ctx context.Context, key string, members ...Z) *IntCmd - ZAddXX(ctx context.Context, key string, members ...Z) *IntCmd - ZAddArgs(ctx context.Context, key string, args ZAddArgs) *IntCmd - ZAddArgsIncr(ctx context.Context, key string, args ZAddArgs) *FloatCmd - ZCard(ctx context.Context, key string) *IntCmd - ZCount(ctx context.Context, key, min, max string) *IntCmd - ZLexCount(ctx context.Context, key, min, max string) *IntCmd - ZIncrBy(ctx context.Context, key string, increment float64, member string) *FloatCmd - ZInter(ctx context.Context, store *ZStore) *StringSliceCmd - ZInterWithScores(ctx context.Context, store *ZStore) *ZSliceCmd - ZInterCard(ctx context.Context, limit int64, keys ...string) *IntCmd - ZInterStore(ctx context.Context, destination string, store *ZStore) *IntCmd - ZMPop(ctx context.Context, order string, count int64, keys ...string) *ZSliceWithKeyCmd - ZMScore(ctx context.Context, key string, members ...string) *FloatSliceCmd - ZPopMax(ctx context.Context, key string, count ...int64) *ZSliceCmd - ZPopMin(ctx context.Context, key string, count ...int64) *ZSliceCmd - ZRange(ctx context.Context, key string, start, stop int64) *StringSliceCmd - ZRangeWithScores(ctx context.Context, key string, start, stop int64) *ZSliceCmd - ZRangeByScore(ctx context.Context, key string, opt *ZRangeBy) *StringSliceCmd - ZRangeByLex(ctx context.Context, key string, opt *ZRangeBy) *StringSliceCmd - ZRangeByScoreWithScores(ctx context.Context, key string, opt *ZRangeBy) *ZSliceCmd - ZRangeArgs(ctx context.Context, z ZRangeArgs) *StringSliceCmd - ZRangeArgsWithScores(ctx context.Context, z ZRangeArgs) *ZSliceCmd - ZRangeStore(ctx context.Context, dst string, z ZRangeArgs) *IntCmd - ZRank(ctx context.Context, key, member string) *IntCmd - ZRankWithScore(ctx context.Context, key, member string) *RankWithScoreCmd - ZRem(ctx context.Context, key string, members ...interface{}) *IntCmd - ZRemRangeByRank(ctx context.Context, key string, start, stop int64) *IntCmd - ZRemRangeByScore(ctx context.Context, key, min, max string) *IntCmd - ZRemRangeByLex(ctx context.Context, key, min, max string) *IntCmd - ZRevRange(ctx context.Context, key string, start, stop int64) *StringSliceCmd - ZRevRangeWithScores(ctx context.Context, key string, start, stop int64) *ZSliceCmd - ZRevRangeByScore(ctx context.Context, key string, opt *ZRangeBy) *StringSliceCmd - ZRevRangeByLex(ctx context.Context, key string, opt *ZRangeBy) *StringSliceCmd - ZRevRangeByScoreWithScores(ctx context.Context, key string, opt *ZRangeBy) *ZSliceCmd - ZRevRank(ctx context.Context, key, member string) *IntCmd - ZRevRankWithScore(ctx context.Context, key, member string) *RankWithScoreCmd - ZScore(ctx context.Context, key, member string) *FloatCmd - ZUnionStore(ctx context.Context, dest string, store *ZStore) *IntCmd - ZRandMember(ctx context.Context, key string, count int) *StringSliceCmd - ZRandMemberWithScores(ctx context.Context, key string, count int) *ZSliceCmd - ZUnion(ctx context.Context, store ZStore) *StringSliceCmd - ZUnionWithScores(ctx context.Context, store ZStore) *ZSliceCmd - ZDiff(ctx context.Context, keys ...string) *StringSliceCmd - ZDiffWithScores(ctx context.Context, keys ...string) *ZSliceCmd - ZDiffStore(ctx context.Context, destination string, keys ...string) *IntCmd - - PFAdd(ctx context.Context, key string, els ...interface{}) *IntCmd - PFCount(ctx context.Context, keys ...string) *IntCmd - PFMerge(ctx context.Context, dest string, keys ...string) *StatusCmd BgRewriteAOF(ctx context.Context) *StatusCmd BgSave(ctx context.Context) *StatusCmd @@ -431,82 +204,23 @@ type Cmdable interface { SlowLogGet(ctx context.Context, num int64) *SlowLogCmd Time(ctx context.Context) *TimeCmd DebugObject(ctx context.Context, key string) *StringCmd - ReadOnly(ctx context.Context) *StatusCmd - ReadWrite(ctx context.Context) *StatusCmd + MemoryUsage(ctx context.Context, key string, samples ...int) *IntCmd - Eval(ctx context.Context, script string, keys []string, args ...interface{}) *Cmd - EvalSha(ctx context.Context, sha1 string, keys []string, args ...interface{}) *Cmd - EvalRO(ctx context.Context, script string, keys []string, args ...interface{}) *Cmd - EvalShaRO(ctx context.Context, sha1 string, keys []string, args ...interface{}) *Cmd - ScriptExists(ctx context.Context, hashes ...string) *BoolSliceCmd - ScriptFlush(ctx context.Context) *StatusCmd - ScriptKill(ctx context.Context) *StatusCmd - ScriptLoad(ctx context.Context, script string) *StringCmd - - FunctionLoad(ctx context.Context, code string) *StringCmd - FunctionLoadReplace(ctx context.Context, code string) *StringCmd - FunctionDelete(ctx context.Context, libName string) *StringCmd - FunctionFlush(ctx context.Context) *StringCmd - FunctionKill(ctx context.Context) *StringCmd - FunctionFlushAsync(ctx context.Context) *StringCmd - FunctionList(ctx context.Context, q FunctionListQuery) *FunctionListCmd - FunctionDump(ctx context.Context) *StringCmd - FunctionRestore(ctx context.Context, libDump string) *StringCmd - FunctionStats(ctx context.Context) *FunctionStatsCmd - FCall(ctx context.Context, function string, keys []string, args ...interface{}) *Cmd - FCallRo(ctx context.Context, function string, keys []string, args ...interface{}) *Cmd - FCallRO(ctx context.Context, function string, keys []string, args ...interface{}) *Cmd - - Publish(ctx context.Context, channel string, message interface{}) *IntCmd - SPublish(ctx context.Context, channel string, message interface{}) *IntCmd - PubSubChannels(ctx context.Context, pattern string) *StringSliceCmd - PubSubNumSub(ctx context.Context, channels ...string) *MapStringIntCmd - PubSubNumPat(ctx context.Context) *IntCmd - PubSubShardChannels(ctx context.Context, pattern string) *StringSliceCmd - PubSubShardNumSub(ctx context.Context, channels ...string) *MapStringIntCmd - - ClusterMyShardID(ctx context.Context) *StringCmd - ClusterSlots(ctx context.Context) *ClusterSlotsCmd - ClusterShards(ctx context.Context) *ClusterShardsCmd - ClusterLinks(ctx context.Context) *ClusterLinksCmd - ClusterNodes(ctx context.Context) *StringCmd - ClusterMeet(ctx context.Context, host, port string) *StatusCmd - ClusterForget(ctx context.Context, nodeID string) *StatusCmd - ClusterReplicate(ctx context.Context, nodeID string) *StatusCmd - ClusterResetSoft(ctx context.Context) *StatusCmd - ClusterResetHard(ctx context.Context) *StatusCmd - ClusterInfo(ctx context.Context) *StringCmd - ClusterKeySlot(ctx context.Context, key string) *IntCmd - ClusterGetKeysInSlot(ctx context.Context, slot int, count int) *StringSliceCmd - ClusterCountFailureReports(ctx context.Context, nodeID string) *IntCmd - ClusterCountKeysInSlot(ctx context.Context, slot int) *IntCmd - ClusterDelSlots(ctx context.Context, slots ...int) *StatusCmd - ClusterDelSlotsRange(ctx context.Context, min, max int) *StatusCmd - ClusterSaveConfig(ctx context.Context) *StatusCmd - ClusterSlaves(ctx context.Context, nodeID string) *StringSliceCmd - ClusterFailover(ctx context.Context) *StatusCmd - ClusterAddSlots(ctx context.Context, slots ...int) *StatusCmd - ClusterAddSlotsRange(ctx context.Context, min, max int) *StatusCmd - - GeoAdd(ctx context.Context, key string, geoLocation ...*GeoLocation) *IntCmd - GeoPos(ctx context.Context, key string, members ...string) *GeoPosCmd - GeoRadius(ctx context.Context, key string, longitude, latitude float64, query *GeoRadiusQuery) *GeoLocationCmd - GeoRadiusStore(ctx context.Context, key string, longitude, latitude float64, query *GeoRadiusQuery) *IntCmd - GeoRadiusByMember(ctx context.Context, key, member string, query *GeoRadiusQuery) *GeoLocationCmd - GeoRadiusByMemberStore(ctx context.Context, key, member string, query *GeoRadiusQuery) *IntCmd - GeoSearch(ctx context.Context, key string, q *GeoSearchQuery) *StringSliceCmd - GeoSearchLocation(ctx context.Context, key string, q *GeoSearchLocationQuery) *GeoSearchLocationCmd - GeoSearchStore(ctx context.Context, key, store string, q *GeoSearchStoreQuery) *IntCmd - GeoDist(ctx context.Context, key string, member1, member2, unit string) *FloatCmd - GeoHash(ctx context.Context, key string, members ...string) *StringSliceCmd - - ACLDryRun(ctx context.Context, username string, command ...interface{}) *StringCmd - ACLLog(ctx context.Context, count int64) *ACLLogCmd - ACLLogReset(ctx context.Context) *StatusCmd - ModuleLoadex(ctx context.Context, conf *ModuleLoadexConfig) *StringCmd + ACLCmdable + HashCmdable + HyperLogLogCmdable + GeoCmdable + GenericCmdable + ListCmdable + SetCmdable + SortedSetCmdable + ClusterCmdable + ScriptingFunctionsCmdable + StringCmdable + PubSubCmdable GearsCmdable ProbabilisticCmdable TimeseriesCmdable @@ -710,2469 +424,6 @@ func (c cmdable) Quit(_ context.Context) *StatusCmd { panic("not implemented") } -func (c cmdable) Del(ctx context.Context, keys ...string) *IntCmd { - args := make([]interface{}, 1+len(keys)) - args[0] = "del" - for i, key := range keys { - args[1+i] = key - } - cmd := NewIntCmd(ctx, args...) - _ = 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) 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) 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 -} - -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) 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 -} - -//------------------------------------------------------------------------------ - -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 -} - -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 -} - -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 -} - -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 -} - -//------------------------------------------------------------------------------ - -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) 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) LCS(ctx context.Context, q *LCSQuery) *LCSCmd { - cmd := NewLCSCmd(ctx, q) - _ = 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 -} - -//------------------------------------------------------------------------------ - -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 -} - -//------------------------------------------------------------------------------ - -// 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 -} - -//------------------------------------------------------------------------------ - -// 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 -} - -// 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 and 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 and 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), and 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 and . - 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) 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 -} - //------------------------------------------------------------------------------ func (c cmdable) BgRewriteAOF(ctx context.Context) *StatusCmd { @@ -3391,18 +642,6 @@ func (c cmdable) DebugObject(ctx context.Context, key string) *StringCmd { return cmd } -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 -} - func (c cmdable) MemoryUsage(ctx context.Context, key string, samples ...int) *IntCmd { args := []interface{}{"memory", "usage", key} if len(samples) > 0 { @@ -3419,556 +658,6 @@ func (c cmdable) MemoryUsage(ctx context.Context, key string, samples ...int) *I //------------------------------------------------------------------------------ -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 -} - -//------------------------------------------------------------------------------ - -// 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 -} - -//------------------------------------------------------------------------------ - -func (c cmdable) ClusterMyShardID(ctx context.Context) *StringCmd { - cmd := NewStringCmd(ctx, "cluster", "myshardid") - _ = c(ctx, cmd) - return cmd -} - -func (c cmdable) ClusterSlots(ctx context.Context) *ClusterSlotsCmd { - cmd := NewClusterSlotsCmd(ctx, "cluster", "slots") - _ = c(ctx, cmd) - return cmd -} - -func (c cmdable) ClusterShards(ctx context.Context) *ClusterShardsCmd { - cmd := NewClusterShardsCmd(ctx, "cluster", "shards") - _ = c(ctx, cmd) - return cmd -} - -func (c cmdable) ClusterLinks(ctx context.Context) *ClusterLinksCmd { - cmd := NewClusterLinksCmd(ctx, "cluster", "links") - _ = c(ctx, cmd) - return cmd -} - -func (c cmdable) ClusterNodes(ctx context.Context) *StringCmd { - cmd := NewStringCmd(ctx, "cluster", "nodes") - _ = c(ctx, cmd) - return cmd -} - -func (c cmdable) ClusterMeet(ctx context.Context, host, port string) *StatusCmd { - cmd := NewStatusCmd(ctx, "cluster", "meet", host, port) - _ = c(ctx, cmd) - return cmd -} - -func (c cmdable) ClusterForget(ctx context.Context, nodeID string) *StatusCmd { - cmd := NewStatusCmd(ctx, "cluster", "forget", nodeID) - _ = c(ctx, cmd) - return cmd -} - -func (c cmdable) ClusterReplicate(ctx context.Context, nodeID string) *StatusCmd { - cmd := NewStatusCmd(ctx, "cluster", "replicate", nodeID) - _ = c(ctx, cmd) - return cmd -} - -func (c cmdable) ClusterResetSoft(ctx context.Context) *StatusCmd { - cmd := NewStatusCmd(ctx, "cluster", "reset", "soft") - _ = c(ctx, cmd) - return cmd -} - -func (c cmdable) ClusterResetHard(ctx context.Context) *StatusCmd { - cmd := NewStatusCmd(ctx, "cluster", "reset", "hard") - _ = c(ctx, cmd) - return cmd -} - -func (c cmdable) ClusterInfo(ctx context.Context) *StringCmd { - cmd := NewStringCmd(ctx, "cluster", "info") - _ = c(ctx, cmd) - return cmd -} - -func (c cmdable) ClusterKeySlot(ctx context.Context, key string) *IntCmd { - cmd := NewIntCmd(ctx, "cluster", "keyslot", key) - _ = c(ctx, cmd) - return cmd -} - -func (c cmdable) ClusterGetKeysInSlot(ctx context.Context, slot int, count int) *StringSliceCmd { - cmd := NewStringSliceCmd(ctx, "cluster", "getkeysinslot", slot, count) - _ = c(ctx, cmd) - return cmd -} - -func (c cmdable) ClusterCountFailureReports(ctx context.Context, nodeID string) *IntCmd { - cmd := NewIntCmd(ctx, "cluster", "count-failure-reports", nodeID) - _ = c(ctx, cmd) - return cmd -} - -func (c cmdable) ClusterCountKeysInSlot(ctx context.Context, slot int) *IntCmd { - cmd := NewIntCmd(ctx, "cluster", "countkeysinslot", slot) - _ = c(ctx, cmd) - return cmd -} - -func (c cmdable) ClusterDelSlots(ctx context.Context, slots ...int) *StatusCmd { - args := make([]interface{}, 2+len(slots)) - args[0] = "cluster" - args[1] = "delslots" - for i, slot := range slots { - args[2+i] = slot - } - cmd := NewStatusCmd(ctx, args...) - _ = c(ctx, cmd) - return cmd -} - -func (c cmdable) ClusterDelSlotsRange(ctx context.Context, min, max int) *StatusCmd { - size := max - min + 1 - slots := make([]int, size) - for i := 0; i < size; i++ { - slots[i] = min + i - } - return c.ClusterDelSlots(ctx, slots...) -} - -func (c cmdable) ClusterSaveConfig(ctx context.Context) *StatusCmd { - cmd := NewStatusCmd(ctx, "cluster", "saveconfig") - _ = c(ctx, cmd) - return cmd -} - -func (c cmdable) ClusterSlaves(ctx context.Context, nodeID string) *StringSliceCmd { - cmd := NewStringSliceCmd(ctx, "cluster", "slaves", nodeID) - _ = c(ctx, cmd) - return cmd -} - -func (c cmdable) ClusterFailover(ctx context.Context) *StatusCmd { - cmd := NewStatusCmd(ctx, "cluster", "failover") - _ = c(ctx, cmd) - return cmd -} - -func (c cmdable) ClusterAddSlots(ctx context.Context, slots ...int) *StatusCmd { - args := make([]interface{}, 2+len(slots)) - args[0] = "cluster" - args[1] = "addslots" - for i, num := range slots { - args[2+i] = num - } - cmd := NewStatusCmd(ctx, args...) - _ = c(ctx, cmd) - return cmd -} - -func (c cmdable) ClusterAddSlotsRange(ctx context.Context, min, max int) *StatusCmd { - size := max - min + 1 - slots := make([]int, size) - for i := 0; i < size; i++ { - slots[i] = min + i - } - return c.ClusterAddSlots(ctx, slots...) -} - -//------------------------------------------------------------------------------ - -func (c cmdable) 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 -} - -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 -} - // ModuleLoadexConfig struct is used to specify the arguments for the MODULE LOADEX command of redis. // `MODULE LOADEX path [CONFIG name value [CONFIG name value ...]] [ARGS args [args ...]]` type ModuleLoadexConfig struct { @@ -3997,20 +686,3 @@ func (c cmdable) ModuleLoadex(ctx context.Context, conf *ModuleLoadexConfig) *St _ = 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 -} diff --git a/redis_gears.go b/gears_commands.go similarity index 100% rename from redis_gears.go rename to gears_commands.go diff --git a/redis_gears_test.go b/gears_commands_test.go similarity index 100% rename from redis_gears_test.go rename to gears_commands_test.go diff --git a/generic_commands.go b/generic_commands.go new file mode 100644 index 00000000..bf1fb47d --- /dev/null +++ b/generic_commands.go @@ -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 +} diff --git a/geo_commands.go b/geo_commands.go new file mode 100644 index 00000000..f047b98a --- /dev/null +++ b/geo_commands.go @@ -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 +} diff --git a/hash_commands.go b/hash_commands.go new file mode 100644 index 00000000..2c62a75a --- /dev/null +++ b/hash_commands.go @@ -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 +} diff --git a/hyperloglog_commands.go b/hyperloglog_commands.go new file mode 100644 index 00000000..5a608fae --- /dev/null +++ b/hyperloglog_commands.go @@ -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 +} diff --git a/list_commands.go b/list_commands.go new file mode 100644 index 00000000..24a0de08 --- /dev/null +++ b/list_commands.go @@ -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 +} diff --git a/cluster.go b/osscluster.go similarity index 100% rename from cluster.go rename to osscluster.go diff --git a/osscluster_commands.go b/osscluster_commands.go new file mode 100644 index 00000000..b13f8e7e --- /dev/null +++ b/osscluster_commands.go @@ -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 +} diff --git a/cluster_test.go b/osscluster_test.go similarity index 100% rename from cluster_test.go rename to osscluster_test.go diff --git a/pubsub_commands.go b/pubsub_commands.go new file mode 100644 index 00000000..28622aa6 --- /dev/null +++ b/pubsub_commands.go @@ -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 +} diff --git a/scripting_commands.go b/scripting_commands.go new file mode 100644 index 00000000..af9c3397 --- /dev/null +++ b/scripting_commands.go @@ -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 +} diff --git a/set_commands.go b/set_commands.go new file mode 100644 index 00000000..cef8ad6d --- /dev/null +++ b/set_commands.go @@ -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 +} diff --git a/sortedset_commands.go b/sortedset_commands.go new file mode 100644 index 00000000..67014027 --- /dev/null +++ b/sortedset_commands.go @@ -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 and 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 and 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), and 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 and . + 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 +} diff --git a/stream_commands.go b/stream_commands.go new file mode 100644 index 00000000..0a986920 --- /dev/null +++ b/stream_commands.go @@ -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 +} diff --git a/string_commands.go b/string_commands.go new file mode 100644 index 00000000..eff5880d --- /dev/null +++ b/string_commands.go @@ -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 +} diff --git a/redis_timeseries.go b/timeseries_commands.go similarity index 100% rename from redis_timeseries.go rename to timeseries_commands.go diff --git a/redis_timeseries_test.go b/timeseries_commands_test.go similarity index 100% rename from redis_timeseries_test.go rename to timeseries_commands_test.go From dac3314bc640e4a4c3c8bca03bae9a4ec67b0de8 Mon Sep 17 00:00:00 2001 From: ofekshenawa <104765379+ofekshenawa@users.noreply.github.com> Date: Wed, 20 Sep 2023 16:38:58 +0300 Subject: [PATCH 18/42] Bump version to 9.2.0 (#2722) --- example/del-keys-without-ttl/go.mod | 2 +- example/hll/go.mod | 2 +- example/lua-scripting/go.mod | 2 +- example/otel/go.mod | 6 +++--- example/redis-bloom/go.mod | 2 +- example/scan-struct/go.mod | 2 +- extra/rediscensus/go.mod | 4 ++-- extra/rediscmd/go.mod | 2 +- extra/redisotel/go.mod | 4 ++-- extra/redisprometheus/go.mod | 2 +- package.json | 2 +- version.go | 2 +- 12 files changed, 16 insertions(+), 16 deletions(-) diff --git a/example/del-keys-without-ttl/go.mod b/example/del-keys-without-ttl/go.mod index 4d0f416a..4a880c56 100644 --- a/example/del-keys-without-ttl/go.mod +++ b/example/del-keys-without-ttl/go.mod @@ -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.2.0 go.uber.org/zap v1.24.0 ) diff --git a/example/hll/go.mod b/example/hll/go.mod index 6f24b520..2c27b769 100644 --- a/example/hll/go.mod +++ b/example/hll/go.mod @@ -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.2.0 require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect diff --git a/example/lua-scripting/go.mod b/example/lua-scripting/go.mod index ad82d856..34ed15b5 100644 --- a/example/lua-scripting/go.mod +++ b/example/lua-scripting/go.mod @@ -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.2.0 require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect diff --git a/example/otel/go.mod b/example/otel/go.mod index 95aca7d1..5fbb675d 100644 --- a/example/otel/go.mod +++ b/example/otel/go.mod @@ -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.2.0 + github.com/redis/go-redis/v9 v9.2.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.2.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 diff --git a/example/redis-bloom/go.mod b/example/redis-bloom/go.mod index ad82d856..34ed15b5 100644 --- a/example/redis-bloom/go.mod +++ b/example/redis-bloom/go.mod @@ -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.2.0 require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect diff --git a/example/scan-struct/go.mod b/example/scan-struct/go.mod index fd0fabe2..d117b119 100644 --- a/example/scan-struct/go.mod +++ b/example/scan-struct/go.mod @@ -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.2.0 ) require ( diff --git a/extra/rediscensus/go.mod b/extra/rediscensus/go.mod index 4a6c6f26..54b8547f 100644 --- a/extra/rediscensus/go.mod +++ b/extra/rediscensus/go.mod @@ -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.2.0 + github.com/redis/go-redis/v9 v9.2.0 go.opencensus.io v0.24.0 ) diff --git a/extra/rediscmd/go.mod b/extra/rediscmd/go.mod index 324cbf91..8765c4c8 100644 --- a/extra/rediscmd/go.mod +++ b/extra/rediscmd/go.mod @@ -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.2.0 ) diff --git a/extra/redisotel/go.mod b/extra/redisotel/go.mod index a03f28ec..58b5a1ad 100644 --- a/extra/redisotel/go.mod +++ b/extra/redisotel/go.mod @@ -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.2.0 + github.com/redis/go-redis/v9 v9.2.0 go.opentelemetry.io/otel v1.16.0 go.opentelemetry.io/otel/metric v1.16.0 go.opentelemetry.io/otel/sdk v1.16.0 diff --git a/extra/redisprometheus/go.mod b/extra/redisprometheus/go.mod index 7a36a1a6..bf65e1ed 100644 --- a/extra/redisprometheus/go.mod +++ b/extra/redisprometheus/go.mod @@ -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.2.0 ) require ( diff --git a/package.json b/package.json index 9fff597c..c51342e2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "redis", - "version": "9.1.0", + "version": "9.2.0", "main": "index.js", "repository": "git@github.com:redis/go-redis.git", "author": "Vladimir Mihailenco ", diff --git a/version.go b/version.go index d68ab6e0..75518a8a 100644 --- a/version.go +++ b/version.go @@ -2,5 +2,5 @@ package redis // Version is the current release version. func Version() string { - return "9.1.0" + return "9.2.0" } From 275af739718caca7af07dde35c234517d70b6b07 Mon Sep 17 00:00:00 2001 From: Eng Zer Jun Date: Wed, 27 Sep 2023 15:50:05 +0800 Subject: [PATCH 19/42] refactor(gears): remove redundant nil check (#2728) From the Go specification: "1. For a nil slice, the number of iterations is 0." [1] Therefore, an additional nil check for before the loop is unnecessary. [1]: https://go.dev/ref/spec#For_range Signed-off-by: Eng Zer Jun --- gears_commands.go | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/gears_commands.go b/gears_commands.go index 0c67cdf2..e0d49a6b 100644 --- a/gears_commands.go +++ b/gears_commands.go @@ -110,15 +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...) @@ -140,15 +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...) From c6fe509f4a0c6746544a6320f6d1237e74424a7a Mon Sep 17 00:00:00 2001 From: Gabriel Date: Wed, 27 Sep 2023 09:50:47 +0200 Subject: [PATCH 20/42] Add stream interface back to `Cmdable` (#2725) Without it we can't call stream functions from `Cmdable` and alikes. Co-authored-by: ofekshenawa <104765379+ofekshenawa@users.noreply.github.com> --- commands.go | 1 + 1 file changed, 1 insertion(+) diff --git a/commands.go b/commands.go index fffea441..d791753d 100644 --- a/commands.go +++ b/commands.go @@ -224,6 +224,7 @@ type Cmdable interface { GearsCmdable ProbabilisticCmdable TimeseriesCmdable + StreamCmdable } type StatefulCmdable interface { From 0b5e1866b1b37cfafd7e5d403446207c0143b6ec Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 27 Sep 2023 11:24:47 +0300 Subject: [PATCH 21/42] chore(deps): bump actions/stale from 3 to 8 (#2732) Bumps [actions/stale](https://github.com/actions/stale) from 3 to 8. - [Release notes](https://github.com/actions/stale/releases) - [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/stale/compare/v3...v8) --- updated-dependencies: - dependency-name: actions/stale dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: ofekshenawa <104765379+ofekshenawa@users.noreply.github.com> --- .github/workflows/stale-issues.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale-issues.yml b/.github/workflows/stale-issues.yml index 32fd9e81..b3776d70 100644 --- a/.github/workflows/stale-issues.yml +++ b/.github/workflows/stale-issues.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/stale@v3 + - 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.' From f994ff1cd96299a5c8029ae3403af7b17ef06e8a Mon Sep 17 00:00:00 2001 From: ofekshenawa <104765379+ofekshenawa@users.noreply.github.com> Date: Wed, 27 Sep 2023 11:25:09 +0300 Subject: [PATCH 22/42] Bump version to 9.2.1 (#2735) --- example/del-keys-without-ttl/go.mod | 2 +- example/hll/go.mod | 2 +- example/lua-scripting/go.mod | 2 +- example/otel/go.mod | 6 +++--- example/redis-bloom/go.mod | 2 +- example/scan-struct/go.mod | 2 +- extra/rediscensus/go.mod | 4 ++-- extra/rediscmd/go.mod | 2 +- extra/redisotel/go.mod | 4 ++-- extra/redisprometheus/go.mod | 2 +- package.json | 2 +- version.go | 2 +- 12 files changed, 16 insertions(+), 16 deletions(-) diff --git a/example/del-keys-without-ttl/go.mod b/example/del-keys-without-ttl/go.mod index 4a880c56..fb481ca0 100644 --- a/example/del-keys-without-ttl/go.mod +++ b/example/del-keys-without-ttl/go.mod @@ -5,7 +5,7 @@ go 1.18 replace github.com/redis/go-redis/v9 => ../.. require ( - github.com/redis/go-redis/v9 v9.2.0 + github.com/redis/go-redis/v9 v9.2.1 go.uber.org/zap v1.24.0 ) diff --git a/example/hll/go.mod b/example/hll/go.mod index 2c27b769..ebf14f43 100644 --- a/example/hll/go.mod +++ b/example/hll/go.mod @@ -4,7 +4,7 @@ go 1.18 replace github.com/redis/go-redis/v9 => ../.. -require github.com/redis/go-redis/v9 v9.2.0 +require github.com/redis/go-redis/v9 v9.2.1 require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect diff --git a/example/lua-scripting/go.mod b/example/lua-scripting/go.mod index 34ed15b5..7f0bb702 100644 --- a/example/lua-scripting/go.mod +++ b/example/lua-scripting/go.mod @@ -4,7 +4,7 @@ go 1.18 replace github.com/redis/go-redis/v9 => ../.. -require github.com/redis/go-redis/v9 v9.2.0 +require github.com/redis/go-redis/v9 v9.2.1 require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect diff --git a/example/otel/go.mod b/example/otel/go.mod index 5fbb675d..c2227b9e 100644 --- a/example/otel/go.mod +++ b/example/otel/go.mod @@ -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.2.0 - github.com/redis/go-redis/v9 v9.2.0 + github.com/redis/go-redis/extra/redisotel/v9 v9.2.1 + github.com/redis/go-redis/v9 v9.2.1 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.2.0 // indirect + github.com/redis/go-redis/extra/rediscmd/v9 v9.2.1 // 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 diff --git a/example/redis-bloom/go.mod b/example/redis-bloom/go.mod index 34ed15b5..7f0bb702 100644 --- a/example/redis-bloom/go.mod +++ b/example/redis-bloom/go.mod @@ -4,7 +4,7 @@ go 1.18 replace github.com/redis/go-redis/v9 => ../.. -require github.com/redis/go-redis/v9 v9.2.0 +require github.com/redis/go-redis/v9 v9.2.1 require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect diff --git a/example/scan-struct/go.mod b/example/scan-struct/go.mod index d117b119..eec4e6ec 100644 --- a/example/scan-struct/go.mod +++ b/example/scan-struct/go.mod @@ -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.2.0 + github.com/redis/go-redis/v9 v9.2.1 ) require ( diff --git a/extra/rediscensus/go.mod b/extra/rediscensus/go.mod index 54b8547f..9d117239 100644 --- a/extra/rediscensus/go.mod +++ b/extra/rediscensus/go.mod @@ -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.2.0 - github.com/redis/go-redis/v9 v9.2.0 + github.com/redis/go-redis/extra/rediscmd/v9 v9.2.1 + github.com/redis/go-redis/v9 v9.2.1 go.opencensus.io v0.24.0 ) diff --git a/extra/rediscmd/go.mod b/extra/rediscmd/go.mod index 8765c4c8..58de4a6f 100644 --- a/extra/rediscmd/go.mod +++ b/extra/rediscmd/go.mod @@ -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.2.0 + github.com/redis/go-redis/v9 v9.2.1 ) diff --git a/extra/redisotel/go.mod b/extra/redisotel/go.mod index 58b5a1ad..382ee5bb 100644 --- a/extra/redisotel/go.mod +++ b/extra/redisotel/go.mod @@ -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.2.0 - github.com/redis/go-redis/v9 v9.2.0 + github.com/redis/go-redis/extra/rediscmd/v9 v9.2.1 + github.com/redis/go-redis/v9 v9.2.1 go.opentelemetry.io/otel v1.16.0 go.opentelemetry.io/otel/metric v1.16.0 go.opentelemetry.io/otel/sdk v1.16.0 diff --git a/extra/redisprometheus/go.mod b/extra/redisprometheus/go.mod index bf65e1ed..f831d485 100644 --- a/extra/redisprometheus/go.mod +++ b/extra/redisprometheus/go.mod @@ -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.2.0 + github.com/redis/go-redis/v9 v9.2.1 ) require ( diff --git a/package.json b/package.json index c51342e2..ffc2ab9e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "redis", - "version": "9.2.0", + "version": "9.2.1", "main": "index.js", "repository": "git@github.com:redis/go-redis.git", "author": "Vladimir Mihailenco ", diff --git a/version.go b/version.go index 75518a8a..ddcecfb1 100644 --- a/version.go +++ b/version.go @@ -2,5 +2,5 @@ package redis // Version is the current release version. func Version() string { - return "9.2.0" + return "9.2.1" } From 45d28c136feb75d7e670282a840ffea7960588a9 Mon Sep 17 00:00:00 2001 From: Jason Parraga Date: Sat, 14 Oct 2023 02:37:19 -0700 Subject: [PATCH 23/42] Fix OpenTelemetry link in README (#2755) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 18095eeb..efae6da4 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ > 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 +> See [OpenTelemetry](master/example/otel) example which demonstrates how you can use Uptrace to monitor > go-redis. ## Documentation From 86bae21dce00924cbf4bc1ab22beba29ed474c5f Mon Sep 17 00:00:00 2001 From: Vladimir Mihailenco Date: Fri, 20 Oct 2023 08:39:54 +0300 Subject: [PATCH 24/42] chore: fix link --- README.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index efae6da4..7668eddc 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,8 @@ > use it to monitor applications and set up automatic alerts to receive notifications via email, > Slack, Telegram, and others. > -> See [OpenTelemetry](master/example/otel) example which demonstrates how you can use Uptrace to monitor -> go-redis. +> See [OpenTelemetry](example/otel/README.md) example which demonstrates how you can use Uptrace to +> monitor go-redis. ## Documentation @@ -107,7 +107,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{ @@ -121,7 +122,10 @@ 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 ( @@ -208,7 +212,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 From af4872cbd0de349855ce3f0978929c2f56eb995f Mon Sep 17 00:00:00 2001 From: Vladimir Mihailenco Date: Fri, 20 Oct 2023 08:41:48 +0300 Subject: [PATCH 25/42] chore: use abs link --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7668eddc..2aedf5f1 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,8 @@ > use it to monitor applications and set up automatic alerts to receive notifications via email, > Slack, Telegram, and others. > -> See [OpenTelemetry](example/otel/README.md) 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. ## Documentation From 0d7a013e5d74abeb2e2342baf1837108f75f14b6 Mon Sep 17 00:00:00 2001 From: ofekshenawa <104765379+ofekshenawa@users.noreply.github.com> Date: Mon, 30 Oct 2023 14:09:25 +0200 Subject: [PATCH 26/42] Json support (#2769) * Add support for RedisJSON * Add optional args and tests * Add more tests * Add more tests * Add more tests and cleanups * Add docstring * update JSONArrIndex and matching texts Rename JSONArrIndexWithArgs to JSONArrIndexArgs change name of args struct to match function name for consistency change arg types to int (for required arg) and *int (for optional) * update JSONArrTrim and matching texts Rename JSONArrTrimWithArgs to JSONArrTrimArgs change name of args struct to match function name for consistency change arg types to int (for required arg) and *int (for optional) * update JSONGetWithArgs and matching texts Rename JSONGetWithArgs to JSONGetArgs Removed Paths & Path from args - redundant and ambiguous Renamed mispelled "Indention" Changed args param from pointer to value Updated and added additional tests for formatting params * Update JSONSetMode changed mode parameter back to string, added value checking for that string * Updated JSONMSet Changed name of param struct to ...Args for consistency Updated arg list to use an array of structs not pointers as all args are mandatory (we never want to pass a nil parameter) * Updated tests for JSONMSet * Added stubbed (panicking) implementations of JSON.RESP AND JSON.DEBUG * Pre-pull request tidy up Renamed xArgs() to xWithArgs to match other Redis modules Modified params to xWithArgs functions to use a pointer for the arguments struct to match other Redis modules. Modified JSONMSet to JSONMSetArgs and added a version that takes a vararray of args to match the semantics of the native redis call Updated tests to match above changes. Renamed some variables in the json tests for consistency * Testing fixes Fixed error in JSONSetWithArgs found during tests Fixed tests for indentation * fix JSONCmdable typo (#5) * Remove deprecated command: JSONNumMultBy (#6) * Lowercase newJSONCmd (#7) --------- Co-authored-by: Nic Gibson Co-authored-by: Nic Gibson --- commands.go | 3 +- json.go | 606 ++++++++++++++++++++++++++++++++++++++++++++++ json_test.go | 669 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1277 insertions(+), 1 deletion(-) create mode 100644 json.go create mode 100644 json_test.go diff --git a/commands.go b/commands.go index d791753d..b3ee715c 100644 --- a/commands.go +++ b/commands.go @@ -221,10 +221,11 @@ type Cmdable interface { ScriptingFunctionsCmdable StringCmdable PubSubCmdable + StreamCmdable GearsCmdable ProbabilisticCmdable TimeseriesCmdable - StreamCmdable + JSONCmdable } type StatefulCmdable interface { diff --git a/json.go b/json.go new file mode 100644 index 00000000..f9425241 --- /dev/null +++ b/json.go @@ -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 +} diff --git a/json_test.go b/json_test.go new file mode 100644 index 00000000..d527133a --- /dev/null +++ b/json_test.go @@ -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"))) + }) + }) +}) From d6a3f02666fe5f45a00bb7c1469979312f1ddd81 Mon Sep 17 00:00:00 2001 From: Seth Rylan Gainey Date: Mon, 30 Oct 2023 08:32:13 -0400 Subject: [PATCH 27/42] Add BitMapCmdable to Cmdable. (#2737) Co-authored-by: ofekshenawa <104765379+ofekshenawa@users.noreply.github.com> --- commands.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/commands.go b/commands.go index b3ee715c..a9149953 100644 --- a/commands.go +++ b/commands.go @@ -210,20 +210,21 @@ type Cmdable interface { ModuleLoadex(ctx context.Context, conf *ModuleLoadexConfig) *StringCmd ACLCmdable + BitMapCmdable + ClusterCmdable + GearsCmdable + GenericCmdable + GeoCmdable HashCmdable HyperLogLogCmdable - GeoCmdable - GenericCmdable ListCmdable + ProbabilisticCmdable + PubSubCmdable + ScriptingFunctionsCmdable SetCmdable SortedSetCmdable - ClusterCmdable - ScriptingFunctionsCmdable StringCmdable - PubSubCmdable StreamCmdable - GearsCmdable - ProbabilisticCmdable TimeseriesCmdable JSONCmdable } From ea3f5a0b33a58ad8155a1101d5f01256caf93bd1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Oct 2023 14:32:44 +0200 Subject: [PATCH 28/42] chore(deps): bump rojopolis/spellcheck-github-actions (#2740) Bumps [rojopolis/spellcheck-github-actions](https://github.com/rojopolis/spellcheck-github-actions) from 0.33.1 to 0.34.0. - [Release notes](https://github.com/rojopolis/spellcheck-github-actions/releases) - [Changelog](https://github.com/rojopolis/spellcheck-github-actions/blob/master/CHANGELOG.md) - [Commits](https://github.com/rojopolis/spellcheck-github-actions/compare/0.33.1...0.34.0) --- updated-dependencies: - dependency-name: rojopolis/spellcheck-github-actions dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: ofekshenawa <104765379+ofekshenawa@users.noreply.github.com> --- .github/workflows/spellcheck.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/spellcheck.yml b/.github/workflows/spellcheck.yml index 3bd776cf..46c629b2 100644 --- a/.github/workflows/spellcheck.yml +++ b/.github/workflows/spellcheck.yml @@ -8,7 +8,7 @@ jobs: - name: Checkout 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 From d466bd1dee7fbb1f10d9413ba53667ed6635e6af Mon Sep 17 00:00:00 2001 From: Chayim Date: Mon, 30 Oct 2023 14:33:18 +0200 Subject: [PATCH 29/42] Creating CODEOWNERS for the examples (#2749) Co-authored-by: ofekshenawa <104765379+ofekshenawa@users.noreply.github.com> --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..1af2323f --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +doctests/* @dmaier-redislabs From ed202936d71a75363fed11312bf551741db84433 Mon Sep 17 00:00:00 2001 From: Chayim Date: Mon, 30 Oct 2023 14:33:59 +0200 Subject: [PATCH 30/42] Linking to Redis resources (#2759) Co-authored-by: ofekshenawa <104765379+ofekshenawa@users.noreply.github.com> --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index 2aedf5f1..8965b915 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,20 @@ > 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 - [English](https://redis.uptrace.dev) From e053f7eaf34dd1b1abb7a3c8cdefca1e004eaf56 Mon Sep 17 00:00:00 2001 From: Sergey Kozlov Date: Mon, 30 Oct 2023 16:35:02 +0400 Subject: [PATCH 31/42] update PubSub.Channel documentation (#2761) Co-authored-by: ofekshenawa <104765379+ofekshenawa@users.noreply.github.com> --- pubsub.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pubsub.go b/pubsub.go index 16c0f567..5df537c4 100644 --- a/pubsub.go +++ b/pubsub.go @@ -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...) From fd13da4fead0f0a1d95f5c494b9167f85b31c529 Mon Sep 17 00:00:00 2001 From: chenjie199234 Date: Mon, 30 Oct 2023 20:35:51 +0800 Subject: [PATCH 32/42] fix missing fields in different Options (#2757) * fix missing fields in different Options * fix missing fields in different Options --------- Co-authored-by: chenjie199234 Co-authored-by: ofekshenawa <104765379+ofekshenawa@users.noreply.github.com> --- options.go | 1 + osscluster.go | 11 ++++++++--- ring.go | 16 ++++++++++------ sentinel.go | 18 ++++++++++++------ universal.go | 4 ++++ 5 files changed, 35 insertions(+), 15 deletions(-) diff --git a/options.go b/options.go index ba65defd..a8c67ae3 100644 --- a/options.go +++ b/options.go @@ -461,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 { diff --git a/osscluster.go b/osscluster.go index b30e2263..93e0eef1 100644 --- a/osscluster.go +++ b/osscluster.go @@ -80,6 +80,7 @@ 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 @@ -233,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") @@ -274,15 +277,17 @@ 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, + MaxActiveConns: opt.MaxActiveConns, ConnMaxIdleTime: opt.ConnMaxIdleTime, ConnMaxLifetime: opt.ConnMaxLifetime, DisableIndentity: opt.DisableIndentity, diff --git a/ring.go b/ring.go index 7cf09111..c83dcaee 100644 --- a/ring.go +++ b/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,6 +91,7 @@ type RingOptions struct { PoolTimeout time.Duration MinIdleConns int MaxIdleConns int + MaxActiveConns int ConnMaxIdleTime time.Duration ConnMaxLifetime time.Duration @@ -144,15 +146,17 @@ 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, diff --git a/sentinel.go b/sentinel.go index dbff4060..542293ab 100644 --- a/sentinel.go +++ b/sentinel.go @@ -74,6 +74,7 @@ type FailoverOptions struct { PoolTimeout time.Duration MinIdleConns int MaxIdleConns int + MaxActiveConns int ConnMaxIdleTime time.Duration ConnMaxLifetime time.Duration @@ -107,6 +108,7 @@ func (opt *FailoverOptions) clientOptions() *Options { PoolTimeout: opt.PoolTimeout, MinIdleConns: opt.MinIdleConns, MaxIdleConns: opt.MaxIdleConns, + MaxActiveConns: opt.MaxActiveConns, ConnMaxIdleTime: opt.ConnMaxIdleTime, ConnMaxLifetime: opt.ConnMaxLifetime, @@ -130,15 +132,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 +169,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, diff --git a/universal.go b/universal.go index 53ece185..cb3f2e14 100644 --- a/universal.go +++ b/universal.go @@ -48,6 +48,7 @@ type UniversalOptions struct { PoolTimeout time.Duration MinIdleConns int MaxIdleConns int + MaxActiveConns int ConnMaxIdleTime time.Duration ConnMaxLifetime time.Duration @@ -102,6 +103,7 @@ func (o *UniversalOptions) Cluster() *ClusterOptions { PoolTimeout: o.PoolTimeout, MinIdleConns: o.MinIdleConns, MaxIdleConns: o.MaxIdleConns, + MaxActiveConns: o.MaxActiveConns, ConnMaxIdleTime: o.ConnMaxIdleTime, ConnMaxLifetime: o.ConnMaxLifetime, @@ -144,6 +146,7 @@ func (o *UniversalOptions) Failover() *FailoverOptions { PoolTimeout: o.PoolTimeout, MinIdleConns: o.MinIdleConns, MaxIdleConns: o.MaxIdleConns, + MaxActiveConns: o.MaxActiveConns, ConnMaxIdleTime: o.ConnMaxIdleTime, ConnMaxLifetime: o.ConnMaxLifetime, @@ -183,6 +186,7 @@ func (o *UniversalOptions) Simple() *Options { PoolTimeout: o.PoolTimeout, MinIdleConns: o.MinIdleConns, MaxIdleConns: o.MaxIdleConns, + MaxActiveConns: o.MaxActiveConns, ConnMaxIdleTime: o.ConnMaxIdleTime, ConnMaxLifetime: o.ConnMaxLifetime, From a5fe17472addc919cab2495559586540813e1864 Mon Sep 17 00:00:00 2001 From: Tiago Peczenyj Date: Mon, 30 Oct 2023 13:36:44 +0100 Subject: [PATCH 33/42] Option types must propagage missing fields (#2726) * must propagage missing fields Signed-off-by: Tiago Peczenyj * remove credentials provider from ring --------- Signed-off-by: Tiago Peczenyj Co-authored-by: ofekshenawa <104765379+ofekshenawa@users.noreply.github.com> --- options.go | 2 +- ring.go | 8 ++++++++ sentinel.go | 4 ++++ universal.go | 8 ++++++++ 4 files changed, 21 insertions(+), 1 deletion(-) diff --git a/options.go b/options.go index a8c67ae3..449a9252 100644 --- a/options.go +++ b/options.go @@ -142,7 +142,7 @@ type Options struct { // Enables read only queries on slave/follower nodes. readOnly bool - // // Disable set-lib on connect. Default is false. + // Disable set-lib on connect. Default is false. DisableIndentity bool } diff --git a/ring.go b/ring.go index c83dcaee..d39856bc 100644 --- a/ring.go +++ b/ring.go @@ -84,6 +84,8 @@ type RingOptions struct { WriteTimeout time.Duration ContextTimeoutEnabled bool + ContextTimeoutEnabled bool + // PoolFIFO uses FIFO mode for each node connection pool GET/PUT (default LIFO). PoolFIFO bool @@ -97,6 +99,8 @@ type RingOptions struct { TLSConfig *tls.Config Limiter Limiter + + DisableIndentity bool } func (opt *RingOptions) init() { @@ -151,6 +155,8 @@ func (opt *RingOptions) clientOptions() *Options { WriteTimeout: opt.WriteTimeout, ContextTimeoutEnabled: opt.ContextTimeoutEnabled, + ContextTimeoutEnabled: opt.ContextTimeoutEnabled, + PoolFIFO: opt.PoolFIFO, PoolSize: opt.PoolSize, PoolTimeout: opt.PoolTimeout, @@ -162,6 +168,8 @@ func (opt *RingOptions) clientOptions() *Options { TLSConfig: opt.TLSConfig, Limiter: opt.Limiter, + + DisableIndentity: opt.DisableIndentity, } } diff --git a/sentinel.go b/sentinel.go index 542293ab..31ea3c77 100644 --- a/sentinel.go +++ b/sentinel.go @@ -79,6 +79,8 @@ type FailoverOptions struct { ConnMaxLifetime time.Duration TLSConfig *tls.Config + + DisableIndentity bool } func (opt *FailoverOptions) clientOptions() *Options { @@ -113,6 +115,8 @@ func (opt *FailoverOptions) clientOptions() *Options { ConnMaxLifetime: opt.ConnMaxLifetime, TLSConfig: opt.TLSConfig, + + DisableIndentity: opt.DisableIndentity, } } diff --git a/universal.go b/universal.go index cb3f2e14..1e48d7b9 100644 --- a/universal.go +++ b/universal.go @@ -65,6 +65,8 @@ type UniversalOptions struct { // Only failover clients. MasterName string + + DisableIndentity bool } // Cluster returns cluster options created from the universal options. @@ -108,6 +110,8 @@ func (o *UniversalOptions) Cluster() *ClusterOptions { ConnMaxLifetime: o.ConnMaxLifetime, TLSConfig: o.TLSConfig, + + DisableIndentity: o.DisableIndentity, } } @@ -151,6 +155,8 @@ func (o *UniversalOptions) Failover() *FailoverOptions { ConnMaxLifetime: o.ConnMaxLifetime, TLSConfig: o.TLSConfig, + + DisableIndentity: o.DisableIndentity, } } @@ -191,6 +197,8 @@ func (o *UniversalOptions) Simple() *Options { ConnMaxLifetime: o.ConnMaxLifetime, TLSConfig: o.TLSConfig, + + DisableIndentity: o.DisableIndentity, } } From 7ebb537c804cf74322fe4910969e8af4f938683b Mon Sep 17 00:00:00 2001 From: ofekshenawa <104765379+ofekshenawa@users.noreply.github.com> Date: Mon, 30 Oct 2023 17:01:41 +0200 Subject: [PATCH 34/42] remove duplicate declaration (#2773) --- ring.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/ring.go b/ring.go index d39856bc..367a542e 100644 --- a/ring.go +++ b/ring.go @@ -84,8 +84,6 @@ type RingOptions struct { WriteTimeout time.Duration ContextTimeoutEnabled bool - ContextTimeoutEnabled bool - // PoolFIFO uses FIFO mode for each node connection pool GET/PUT (default LIFO). PoolFIFO bool @@ -155,8 +153,6 @@ func (opt *RingOptions) clientOptions() *Options { WriteTimeout: opt.WriteTimeout, ContextTimeoutEnabled: opt.ContextTimeoutEnabled, - ContextTimeoutEnabled: opt.ContextTimeoutEnabled, - PoolFIFO: opt.PoolFIFO, PoolSize: opt.PoolSize, PoolTimeout: opt.PoolTimeout, From 4408f8cfb2d4b3c49fbb5d17e6dfb85f04ce96c2 Mon Sep 17 00:00:00 2001 From: cyningsun Date: Mon, 30 Oct 2023 23:42:26 +0800 Subject: [PATCH 35/42] free turn when leave with error (#2658) Co-authored-by: ofekshenawa <104765379+ofekshenawa@users.noreply.github.com> --- internal/pool/pool.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/pool/pool.go b/internal/pool/pool.go index f391ceda..986c05d0 100644 --- a/internal/pool/pool.go +++ b/internal/pool/pool.go @@ -263,6 +263,7 @@ func (p *ConnPool) Get(ctx context.Context) (*Conn, error) { p.connsMu.Unlock() if err != nil { + p.freeTurn() return nil, err } From 343016bf72212bb22c07d3e56393ef78f252c397 Mon Sep 17 00:00:00 2001 From: Nic Gibson Date: Mon, 30 Oct 2023 16:07:42 +0000 Subject: [PATCH 36/42] add InfoMap command (#2665) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add an extended version of Info() to parse the results from a call to redis.Info so that it’s simpler to operate on any given item in the result. Signed-off-by: Nic Gibson --- command.go | 83 ++++++++++++++++++++++++++++++++++++++++++++++++ commands.go | 11 +++++++ commands_test.go | 14 ++++++++ 3 files changed, 108 insertions(+) diff --git a/command.go b/command.go index 00e356bb..c641a3fa 100644 --- a/command.go +++ b/command.go @@ -1,9 +1,11 @@ package redis import ( + "bufio" "context" "fmt" "net" + "regexp" "strconv" "strings" "time" @@ -5298,3 +5300,84 @@ 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] + } +} diff --git a/commands.go b/commands.go index a9149953..842cc23a 100644 --- a/commands.go +++ b/commands.go @@ -571,6 +571,17 @@ func (c cmdable) Info(ctx context.Context, sections ...string) *StringCmd { return cmd } +func (c cmdable) InfoMap(ctx context.Context, sections ...string) *InfoCmd { + args := make([]interface{}, 1+len(sections)) + args[0] = "info" + for i, section := range sections { + args[i+1] = section + } + cmd := NewInfoCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + func (c cmdable) LastSave(ctx context.Context) *IntCmd { cmd := NewIntCmd(ctx, "lastsave") _ = c(ctx, cmd) diff --git a/commands_test.go b/commands_test.go index 8e00c836..fdc41c5a 100644 --- a/commands_test.go +++ b/commands_test.go @@ -335,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()) From 84f46c330142e8ac08d12d9d68aef316cd272f73 Mon Sep 17 00:00:00 2001 From: Nic Gibson Date: Mon, 30 Oct 2023 16:08:06 +0000 Subject: [PATCH 37/42] BUG: BFReserveArgs - error_rate & capacity (#2763) the error_rate and capacity parameters should not be optional - corrected --- probabilistic.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/probabilistic.go b/probabilistic.go index d397b490..5d5cd1a6 100644 --- a/probabilistic.go +++ b/probabilistic.go @@ -150,12 +150,7 @@ func (c cmdable) BFReserveNonScaling(ctx context.Context, key string, errorRate 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) } From 81947daa8d6b2bea7e3a12c210b022f0a88d4006 Mon Sep 17 00:00:00 2001 From: Sergey Galkin Date: Mon, 30 Oct 2023 20:08:18 +0400 Subject: [PATCH 38/42] Handle wrapped errors in scripter.Run (#2674) * Handle wrapped errors in script * test * remove accidentially committed changes --------- Co-authored-by: ofekshenawa <104765379+ofekshenawa@users.noreply.github.com> --- error.go | 7 ++++--- redis_test.go | 20 ++++++++++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/error.go b/error.go index d5ebad60..8a59913b 100644 --- a/error.go +++ b/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) } diff --git a/redis_test.go b/redis_test.go index c9d8df6b..5870fad7 100644 --- a/redis_test.go +++ b/redis_test.go @@ -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")) + }) }) From 15682e3227597d12d7f76862b712a75fbb3d02d3 Mon Sep 17 00:00:00 2001 From: Tong Zhaoqi <1661610563@qq.com> Date: Tue, 31 Oct 2023 13:17:22 +0800 Subject: [PATCH 39/42] feat: support write the types of pointer of simple data types (#2745) (#2753) Co-authored-by: ofekshenawa <104765379+ofekshenawa@users.noreply.github.com> --- internal/proto/writer.go | 31 +++++++++++++++++++++ internal/proto/writer_test.go | 51 +++++++++++++++++++++++++++++++++++ internal/util/type.go | 5 ++++ 3 files changed, 87 insertions(+) create mode 100644 internal/util/type.go diff --git a/internal/proto/writer.go b/internal/proto/writer.go index 8eaada19..78595cc4 100644 --- a/internal/proto/writer.go +++ b/internal/proto/writer.go @@ -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) diff --git a/internal/proto/writer_test.go b/internal/proto/writer_test.go index c801f87d..55f3d663 100644 --- a/internal/proto/writer_test.go +++ b/internal/proto/writer_test.go @@ -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)) + }) + } +}) diff --git a/internal/util/type.go b/internal/util/type.go new file mode 100644 index 00000000..a7ea712e --- /dev/null +++ b/internal/util/type.go @@ -0,0 +1,5 @@ +package util + +func ToPtr[T any](v T) *T { + return &v +} From 898bd9aa5155d4f0c83b7a7260d51d47affcd2e2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 31 Oct 2023 10:06:16 +0200 Subject: [PATCH 40/42] chore(deps): bump golang.org/x/net in /example/otel (#2776) --- example/otel/go.mod | 6 +++--- example/otel/go.sum | 18 ++++++++++-------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/example/otel/go.mod b/example/otel/go.mod index c2227b9e..b453017c 100644 --- a/example/otel/go.mod +++ b/example/otel/go.mod @@ -36,9 +36,9 @@ 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/protobuf v1.30.0 // indirect diff --git a/example/otel/go.sum b/example/otel/go.sum index 4ba59e44..2d599b5b 100644 --- a/example/otel/go.sum +++ b/example/otel/go.sum @@ -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= From 828fd2d3b83b813739ba56625fb637fe84f9c200 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 31 Oct 2023 10:49:29 +0200 Subject: [PATCH 41/42] chore(deps): bump google.golang.org/grpc in /example/otel (#2775) Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.55.0 to 1.56.3. - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.55.0...v1.56.3) --- updated-dependencies: - dependency-name: google.golang.org/grpc dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: ofekshenawa <104765379+ofekshenawa@users.noreply.github.com> --- example/otel/go.mod | 2 +- example/otel/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/example/otel/go.mod b/example/otel/go.mod index b453017c..84545666 100644 --- a/example/otel/go.mod +++ b/example/otel/go.mod @@ -40,6 +40,6 @@ require ( 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 ) diff --git a/example/otel/go.sum b/example/otel/go.sum index 2d599b5b..c40f81d0 100644 --- a/example/otel/go.sum +++ b/example/otel/go.sum @@ -432,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= From 21bd40a47e56e61c0598ea1bdf8e02e67d1aa651 Mon Sep 17 00:00:00 2001 From: ofekshenawa <104765379+ofekshenawa@users.noreply.github.com> Date: Tue, 31 Oct 2023 15:40:45 +0200 Subject: [PATCH 42/42] Version 9.3.0 (#2774) --- example/del-keys-without-ttl/go.mod | 2 +- example/hll/go.mod | 2 +- example/lua-scripting/go.mod | 2 +- example/otel/go.mod | 6 +++--- example/redis-bloom/go.mod | 2 +- example/scan-struct/go.mod | 2 +- extra/rediscensus/go.mod | 4 ++-- extra/rediscmd/go.mod | 2 +- extra/redisotel/go.mod | 4 ++-- extra/redisprometheus/go.mod | 2 +- package.json | 2 +- version.go | 2 +- 12 files changed, 16 insertions(+), 16 deletions(-) diff --git a/example/del-keys-without-ttl/go.mod b/example/del-keys-without-ttl/go.mod index fb481ca0..2fc1b8b4 100644 --- a/example/del-keys-without-ttl/go.mod +++ b/example/del-keys-without-ttl/go.mod @@ -5,7 +5,7 @@ go 1.18 replace github.com/redis/go-redis/v9 => ../.. require ( - github.com/redis/go-redis/v9 v9.2.1 + github.com/redis/go-redis/v9 v9.3.0 go.uber.org/zap v1.24.0 ) diff --git a/example/hll/go.mod b/example/hll/go.mod index ebf14f43..0048b1df 100644 --- a/example/hll/go.mod +++ b/example/hll/go.mod @@ -4,7 +4,7 @@ go 1.18 replace github.com/redis/go-redis/v9 => ../.. -require github.com/redis/go-redis/v9 v9.2.1 +require github.com/redis/go-redis/v9 v9.3.0 require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect diff --git a/example/lua-scripting/go.mod b/example/lua-scripting/go.mod index 7f0bb702..97416d41 100644 --- a/example/lua-scripting/go.mod +++ b/example/lua-scripting/go.mod @@ -4,7 +4,7 @@ go 1.18 replace github.com/redis/go-redis/v9 => ../.. -require github.com/redis/go-redis/v9 v9.2.1 +require github.com/redis/go-redis/v9 v9.3.0 require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect diff --git a/example/otel/go.mod b/example/otel/go.mod index 84545666..4ba72d81 100644 --- a/example/otel/go.mod +++ b/example/otel/go.mod @@ -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.2.1 - github.com/redis/go-redis/v9 v9.2.1 + 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.2.1 // 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 diff --git a/example/redis-bloom/go.mod b/example/redis-bloom/go.mod index 7f0bb702..97416d41 100644 --- a/example/redis-bloom/go.mod +++ b/example/redis-bloom/go.mod @@ -4,7 +4,7 @@ go 1.18 replace github.com/redis/go-redis/v9 => ../.. -require github.com/redis/go-redis/v9 v9.2.1 +require github.com/redis/go-redis/v9 v9.3.0 require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect diff --git a/example/scan-struct/go.mod b/example/scan-struct/go.mod index eec4e6ec..be3ce43c 100644 --- a/example/scan-struct/go.mod +++ b/example/scan-struct/go.mod @@ -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.2.1 + github.com/redis/go-redis/v9 v9.3.0 ) require ( diff --git a/extra/rediscensus/go.mod b/extra/rediscensus/go.mod index 9d117239..d3e84b78 100644 --- a/extra/rediscensus/go.mod +++ b/extra/rediscensus/go.mod @@ -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.2.1 - github.com/redis/go-redis/v9 v9.2.1 + 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 ) diff --git a/extra/rediscmd/go.mod b/extra/rediscmd/go.mod index 58de4a6f..e4472fb7 100644 --- a/extra/rediscmd/go.mod +++ b/extra/rediscmd/go.mod @@ -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.2.1 + github.com/redis/go-redis/v9 v9.3.0 ) diff --git a/extra/redisotel/go.mod b/extra/redisotel/go.mod index 382ee5bb..e7caabf4 100644 --- a/extra/redisotel/go.mod +++ b/extra/redisotel/go.mod @@ -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.2.1 - github.com/redis/go-redis/v9 v9.2.1 + 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 diff --git a/extra/redisprometheus/go.mod b/extra/redisprometheus/go.mod index f831d485..8b6090c3 100644 --- a/extra/redisprometheus/go.mod +++ b/extra/redisprometheus/go.mod @@ -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.2.1 + github.com/redis/go-redis/v9 v9.3.0 ) require ( diff --git a/package.json b/package.json index ffc2ab9e..73063efd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "redis", - "version": "9.2.1", + "version": "9.3.0", "main": "index.js", "repository": "git@github.com:redis/go-redis.git", "author": "Vladimir Mihailenco ", diff --git a/version.go b/version.go index ddcecfb1..b9ca0641 100644 --- a/version.go +++ b/version.go @@ -2,5 +2,5 @@ package redis // Version is the current release version. func Version() string { - return "9.2.1" + return "9.3.0" }