From 0cc9cd6e0836055c40f15b59467dbc03899eaa55 Mon Sep 17 00:00:00 2001 From: Chayim Date: Wed, 26 Jul 2023 09:40:01 +0300 Subject: [PATCH 001/110] Updating the README with connection options (#2661) --- README.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/README.md b/README.md index 36d60fd4e..55dbec518 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,40 @@ func ExampleClient() { } ``` +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{ + Addr: "localhost:6379", + Password: "", // no password set + DB: 0, // use default DB + Protocol: 3, // specify 2 for RESP 2 or 3 for RESP 3 + }) + +``` + +### Connecting via a redis url + +go-redis also supports connecting via the [redis uri specification](https://github.com/redis/redis-specifications/tree/master/uri/redis.txt). The example below demonstrates how the connection can easily be configured using a string, adhering to this specification. + +```go +import ( + "context" + "github.com/redis/go-redis/v9" + "fmt" +) + +var ctx = context.Background() + +func ExampleClient() { + url := "redis://localhost:6379?password=hello&protocol=3" + opts, err := redis.ParseURL(url) + if err != nil { + panic(err) + } + rdb := redis.NewClient(opts) +``` + ## Look and feel Some corner cases: From b1103e3d436b6fe98813ecbbe1f99dc8d59b06c9 Mon Sep 17 00:00:00 2001 From: Chayim Date: Wed, 26 Jul 2023 14:58:49 +0300 Subject: [PATCH 002/110] Bumping redis versions (#2662) --- .github/workflows/build.yml | 2 +- Makefile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 30fb9e851..403e199d0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,7 +20,7 @@ jobs: services: redis: - image: redis:7.2-rc + image: redis/redis-stack-server:edge options: >- --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5 ports: diff --git a/Makefile b/Makefile index 285f65dd5..b59c39554 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-rc1.tar.gz | tar xvz --strip-components=1 -C $@ + wget -qO- https://download.redis.io/releases/redis-7.2-rc3.tar.gz | tar xvz --strip-components=1 -C $@ testdata/redis/src/redis-server: testdata/redis cd $< && make all From c7399b6a17d7d3e2a57654528af91349f2468529 Mon Sep 17 00:00:00 2001 From: Chayim Date: Sun, 13 Aug 2023 06:07:42 +0300 Subject: [PATCH 003/110] Spelling for CI (#2666) --- .github/spellcheck-settings.yml | 29 +++++++++++++++++ .github/wordlist.txt | 54 ++++++++++++++++++++++++++++++++ .github/workflows/spellcheck.yml | 14 +++++++++ 3 files changed, 97 insertions(+) create mode 100644 .github/spellcheck-settings.yml create mode 100644 .github/wordlist.txt create mode 100644 .github/workflows/spellcheck.yml diff --git a/.github/spellcheck-settings.yml b/.github/spellcheck-settings.yml new file mode 100644 index 000000000..b8ca6cca6 --- /dev/null +++ b/.github/spellcheck-settings.yml @@ -0,0 +1,29 @@ +matrix: +- name: Markdown + expect_match: false + apsell: + lang: en + d: en_US + ignore-case: true + dictionary: + wordlists: + - .github/wordlist.txt + output: wordlist.dic + pipeline: + - pyspelling.filters.markdown: + markdown_extensions: + - markdown.extensions.extra: + - pyspelling.filters.html: + comments: false + attributes: + - alt + ignores: + - ':matches(code, pre)' + - code + - pre + - blockquote + - img + sources: + - 'README.md' + - 'FAQ.md' + - 'docs/**' diff --git a/.github/wordlist.txt b/.github/wordlist.txt new file mode 100644 index 000000000..3045ae93f --- /dev/null +++ b/.github/wordlist.txt @@ -0,0 +1,54 @@ +ACLs +autoload +autoloader +autoloading +Autoloading +backend +backends +behaviour +CAS +ClickHouse +config +customizable +Customizable +dataset +de +ElastiCache +extensibility +FPM +Golang +IANA +keyspace +keyspaces +Kvrocks +localhost +Lua +MSSQL +namespace +NoSQL +ORM +Packagist +PhpRedis +pipelining +pluggable +Predis +PSR +Quickstart +README +rebalanced +rebalancing +redis +Redis +RocksDB +runtime +SHA +sharding +SSL +struct +stunnel +TCP +TLS +uri +URI +url +variadic diff --git a/.github/workflows/spellcheck.yml b/.github/workflows/spellcheck.yml new file mode 100644 index 000000000..e15284155 --- /dev/null +++ b/.github/workflows/spellcheck.yml @@ -0,0 +1,14 @@ +name: spellcheck +on: + pull_request: +jobs: + check-spelling: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Check Spelling + uses: rojopolis/spellcheck-github-actions@0.33.1 + with: + config_path: .github/spellcheck-settings.yml + task_name: Markdown From 558581eac6ee7527407045859542a118157efab4 Mon Sep 17 00:00:00 2001 From: ofekshenawa <104765379+ofekshenawa@users.noreply.github.com> Date: Tue, 15 Aug 2023 10:19:19 +0300 Subject: [PATCH 004/110] bloom support (#2673) --- .github/wordlist.txt | 1 + README.md | 1 + commands.go | 2 + probabilistic.go | 1433 +++++++++++++++++++++++++++++++++++++++++ probabilistic_test.go | 739 +++++++++++++++++++++ 5 files changed, 2176 insertions(+) create mode 100644 probabilistic.go create mode 100644 probabilistic_test.go diff --git a/.github/wordlist.txt b/.github/wordlist.txt index 3045ae93f..294988d52 100644 --- a/.github/wordlist.txt +++ b/.github/wordlist.txt @@ -52,3 +52,4 @@ uri URI url variadic +RedisStack \ No newline at end of file diff --git a/README.md b/README.md index 55dbec518..3486e8e5a 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,7 @@ key value NoSQL database that uses RocksDB as storage engine and is compatible w - [Redis Cluster](https://redis.uptrace.dev/guide/go-redis-cluster.html). - [Redis Ring](https://redis.uptrace.dev/guide/ring.html). - [Redis Performance Monitoring](https://redis.uptrace.dev/guide/redis-performance-monitoring.html). +- [Redis Probabilistic [RedisStack]](https://redis.io/docs/data-types/probabilistic/) ## Installation diff --git a/commands.go b/commands.go index 34f4d2c22..41f8d3ae3 100644 --- a/commands.go +++ b/commands.go @@ -504,6 +504,8 @@ type Cmdable interface { ACLLogReset(ctx context.Context) *StatusCmd ModuleLoadex(ctx context.Context, conf *ModuleLoadexConfig) *StringCmd + + probabilisticCmdable } type StatefulCmdable interface { diff --git a/probabilistic.go b/probabilistic.go new file mode 100644 index 000000000..8e32bca98 --- /dev/null +++ b/probabilistic.go @@ -0,0 +1,1433 @@ +package redis + +import ( + "context" + "fmt" + + "github.com/redis/go-redis/v9/internal/proto" +) + +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 + BFInfo(ctx context.Context, key string) *BFInfoCmd + BFInfoArg(ctx context.Context, key, option string) *BFInfoCmd + BFInfoCapacity(ctx context.Context, key string) *BFInfoCmd + BFInfoSize(ctx context.Context, key string) *BFInfoCmd + BFInfoFilters(ctx context.Context, key string) *BFInfoCmd + BFInfoItems(ctx context.Context, key string) *BFInfoCmd + BFInfoExpansion(ctx context.Context, key string) *BFInfoCmd + BFInsert(ctx context.Context, key string, options *BFInsertOptions, elements ...interface{}) *BoolSliceCmd + BFMAdd(ctx context.Context, key string, elements ...interface{}) *BoolSliceCmd + BFMExists(ctx context.Context, key string, elements ...interface{}) *BoolSliceCmd + 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 + BFScanDump(ctx context.Context, key string, iterator int64) *ScanDumpCmd + BFLoadChunk(ctx context.Context, key string, iterator int64, data interface{}) *StatusCmd + + CFAdd(ctx context.Context, key string, element interface{}) *BoolCmd + CFAddNX(ctx context.Context, key string, element interface{}) *BoolCmd + CFCount(ctx context.Context, key string, element interface{}) *IntCmd + CFDel(ctx context.Context, key string, element interface{}) *BoolCmd + CFExists(ctx context.Context, key string, element interface{}) *BoolCmd + CFInfo(ctx context.Context, key string) *CFInfoCmd + CFInsert(ctx context.Context, key string, options *CFInsertOptions, elements ...interface{}) *BoolSliceCmd + 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 + 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 + CFScanDump(ctx context.Context, key string, iterator int64) *ScanDumpCmd + CFLoadChunk(ctx context.Context, key string, iterator int64, data interface{}) *StatusCmd + + CMSIncrBy(ctx context.Context, key string, elements ...interface{}) *IntSliceCmd + CMSInfo(ctx context.Context, key string) *CMSInfoCmd + CMSInitByDim(ctx context.Context, key string, width, height int64) *StatusCmd + CMSInitByProb(ctx context.Context, key string, errorRate, probability float64) *StatusCmd + CMSMerge(ctx context.Context, destKey string, sourceKeys ...string) *StatusCmd + CMSMergeWithWeight(ctx context.Context, destKey string, sourceKeys map[string]int64) *StatusCmd + CMSQuery(ctx context.Context, key string, elements ...interface{}) *IntSliceCmd + + TopKAdd(ctx context.Context, key string, elements ...interface{}) *StringSliceCmd + TopKCount(ctx context.Context, key string, elements ...interface{}) *IntSliceCmd + TopKIncrBy(ctx context.Context, key string, elements ...interface{}) *StringSliceCmd + TopKInfo(ctx context.Context, key string) *TopKInfoCmd + TopKList(ctx context.Context, key string) *StringSliceCmd + TopKListWithCount(ctx context.Context, key string) *MapStringIntCmd + TopKQuery(ctx context.Context, key string, elements ...interface{}) *BoolSliceCmd + TopKReserve(ctx context.Context, key string, k int64) *StatusCmd + TopKReserveWithOptions(ctx context.Context, key string, k int64, width, depth int64, decay float64) *StatusCmd + + TDigestAdd(ctx context.Context, key string, elements ...float64) *StatusCmd + TDigestByRank(ctx context.Context, key string, rank ...uint64) *FloatSliceCmd + TDigestByRevRank(ctx context.Context, key string, rank ...uint64) *FloatSliceCmd + TDigestCDF(ctx context.Context, key string, elements ...float64) *FloatSliceCmd + TDigestCreate(ctx context.Context, key string) *StatusCmd + TDigestCreateWithCompression(ctx context.Context, key string, compression int64) *StatusCmd + TDigestInfo(ctx context.Context, key string) *TDigestInfoCmd + TDigestMax(ctx context.Context, key string) *FloatCmd + TDigestMin(ctx context.Context, key string) *FloatCmd + TDigestMerge(ctx context.Context, destKey string, options *TDigestMergeOptions, sourceKeys ...string) *StatusCmd + TDigestQuantile(ctx context.Context, key string, elements ...float64) *FloatSliceCmd + TDigestRank(ctx context.Context, key string, values ...float64) *IntSliceCmd + TDigestReset(ctx context.Context, key string) *StatusCmd + TDigestRevRank(ctx context.Context, key string, values ...float64) *IntSliceCmd + TDigestTrimmedMean(ctx context.Context, key string, lowCutQuantile, highCutQuantile float64) *FloatCmd +} + +type BFInsertOptions struct { + Capacity int64 + Error float64 + Expansion int64 + NonScaling bool + NoCreate bool +} + +type BFReserveOptions struct { + Capacity int64 + Error float64 + Expansion int64 + NonScaling bool +} + +type CFReserveOptions struct { + Capacity int64 + BucketSize int64 + MaxIterations int64 + Expansion int64 +} + +type CFInsertOptions struct { + Capacity int64 + NoCreate bool +} + +// ------------------------------------------- +// Bloom filter commands +//------------------------------------------- + +// BFReserve creates an empty Bloom filter with a single sub-filter +// for the initial specified capacity and with an upper bound error_rate. +// For more information - https://redis.io/commands/bf.reserve/ +func (c cmdable) BFReserve(ctx context.Context, key string, errorRate float64, capacity int64) *StatusCmd { + args := []interface{}{"BF.RESERVE", key, errorRate, capacity} + cmd := NewStatusCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// BFReserveExpansion 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 an expansion rate for the filter. +// For more information - https://redis.io/commands/bf.reserve/ +func (c cmdable) BFReserveExpansion(ctx context.Context, key string, errorRate float64, capacity, expansion int64) *StatusCmd { + args := []interface{}{"BF.RESERVE", key, errorRate, capacity, "EXPANSION", expansion} + cmd := NewStatusCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// BFReserveNonScaling 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 that the filter should not scale. +// For more information - https://redis.io/commands/bf.reserve/ +func (c cmdable) BFReserveNonScaling(ctx context.Context, key string, errorRate float64, capacity int64) *StatusCmd { + args := []interface{}{"BF.RESERVE", key, errorRate, capacity, "NONSCALING"} + cmd := NewStatusCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// BFReserveArgs 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 { + 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) + } + if options.Expansion != 0 { + args = append(args, "EXPANSION", options.Expansion) + } + if options.NonScaling { + args = append(args, "NONSCALING") + } + } + cmd := NewStatusCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// BFAdd adds an item to a Bloom filter. +// For more information - https://redis.io/commands/bf.add/ +func (c cmdable) BFAdd(ctx context.Context, key string, element interface{}) *BoolCmd { + args := []interface{}{"BF.ADD", key, element} + cmd := NewBoolCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// BFCard returns the cardinality of a Bloom filter - +// number of items that were added to a Bloom filter and detected as unique +// (items that caused at least one bit to be set in at least one sub-filter). +// For more information - https://redis.io/commands/bf.card/ +func (c cmdable) BFCard(ctx context.Context, key string) *IntCmd { + args := []interface{}{"BF.CARD", key} + cmd := NewIntCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// BFExists determines whether a given item was added to a Bloom filter. +// For more information - https://redis.io/commands/bf.exists/ +func (c cmdable) BFExists(ctx context.Context, key string, element interface{}) *BoolCmd { + args := []interface{}{"BF.EXISTS", key, element} + cmd := NewBoolCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// BFLoadChunk restores a Bloom filter previously saved using BF.SCANDUMP. +// For more information - https://redis.io/commands/bf.loadchunk/ +func (c cmdable) BFLoadChunk(ctx context.Context, key string, iterator int64, data interface{}) *StatusCmd { + args := []interface{}{"BF.LOADCHUNK", key, iterator, data} + cmd := NewStatusCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// Begins an incremental save of the Bloom filter. +// This command is useful for large Bloom filters that cannot fit into the DUMP and RESTORE model. +// For more information - https://redis.io/commands/bf.scandump/ +func (c cmdable) BFScanDump(ctx context.Context, key string, iterator int64) *ScanDumpCmd { + args := []interface{}{"BF.SCANDUMP", key, iterator} + cmd := newScanDumpCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +type ScanDump struct { + Iter int64 + Data string +} + +type ScanDumpCmd struct { + baseCmd + + val ScanDump +} + +func newScanDumpCmd(ctx context.Context, args ...interface{}) *ScanDumpCmd { + return &ScanDumpCmd{ + baseCmd: baseCmd{ + ctx: ctx, + args: args, + }, + } +} + +func (cmd *ScanDumpCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *ScanDumpCmd) SetVal(val ScanDump) { + cmd.val = val +} + +func (cmd *ScanDumpCmd) Result() (ScanDump, error) { + return cmd.val, cmd.err +} + +func (cmd *ScanDumpCmd) Val() ScanDump { + return cmd.val +} + +func (cmd *ScanDumpCmd) readReply(rd *proto.Reader) (err error) { + n, err := rd.ReadMapLen() + if err != nil { + return err + } + cmd.val = ScanDump{} + for i := 0; i < n; i++ { + iter, err := rd.ReadInt() + if err != nil { + return err + } + data, err := rd.ReadString() + if err != nil { + return err + } + cmd.val.Data = data + cmd.val.Iter = iter + + } + + return nil +} + +// Returns information about a Bloom filter. +// For more information - https://redis.io/commands/bf.info/ +func (c cmdable) BFInfo(ctx context.Context, key string) *BFInfoCmd { + args := []interface{}{"BF.INFO", key} + cmd := NewBFInfoCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +type BFInfo struct { + Capacity int64 + Size int64 + Filters int64 + ItemsInserted int64 + ExpansionRate int64 +} + +type BFInfoCmd struct { + baseCmd + + val BFInfo +} + +func NewBFInfoCmd(ctx context.Context, args ...interface{}) *BFInfoCmd { + return &BFInfoCmd{ + baseCmd: baseCmd{ + ctx: ctx, + args: args, + }, + } +} + +func (cmd *BFInfoCmd) SetVal(val BFInfo) { + cmd.val = val +} +func (cmd *BFInfoCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *BFInfoCmd) Val() BFInfo { + return cmd.val +} + +func (cmd *BFInfoCmd) Result() (BFInfo, error) { + return cmd.val, cmd.err +} + +func (cmd *BFInfoCmd) readReply(rd *proto.Reader) (err error) { + n, err := rd.ReadMapLen() + if err != nil { + return err + } + + var key string + var result BFInfo + for f := 0; f < n; f++ { + key, err = rd.ReadString() + if err != nil { + return err + } + + switch key { + case "Capacity": + result.Capacity, err = rd.ReadInt() + case "Size": + result.Size, err = rd.ReadInt() + case "Number of filters": + result.Filters, err = rd.ReadInt() + case "Number of items inserted": + result.ItemsInserted, err = rd.ReadInt() + case "Expansion rate": + result.ExpansionRate, err = rd.ReadInt() + default: + return fmt.Errorf("redis: BLOOM.INFO unexpected key %s", key) + } + + if err != nil { + return err + } + } + + cmd.val = result + return nil +} + +// BFInfoCapacity returns information about the capacity of a Bloom filter. +// For more information - https://redis.io/commands/bf.info/ +func (c cmdable) BFInfoCapacity(ctx context.Context, key string) *BFInfoCmd { + return c.BFInfoArg(ctx, key, "CAPACITY") +} + +// BFInfoSize returns information about the size of a Bloom filter. +// For more information - https://redis.io/commands/bf.info/ +func (c cmdable) BFInfoSize(ctx context.Context, key string) *BFInfoCmd { + return c.BFInfoArg(ctx, key, "SIZE") +} + +// BFInfoFilters returns information about the filters of a Bloom filter. +// For more information - https://redis.io/commands/bf.info/ +func (c cmdable) BFInfoFilters(ctx context.Context, key string) *BFInfoCmd { + return c.BFInfoArg(ctx, key, "FILTERS") +} + +// BFInfoItems returns information about the items of a Bloom filter. +// For more information - https://redis.io/commands/bf.info/ +func (c cmdable) BFInfoItems(ctx context.Context, key string) *BFInfoCmd { + return c.BFInfoArg(ctx, key, "ITEMS") +} + +// BFInfoExpansion returns information about the expansion rate of a Bloom filter. +// For more information - https://redis.io/commands/bf.info/ +func (c cmdable) BFInfoExpansion(ctx context.Context, key string) *BFInfoCmd { + return c.BFInfoArg(ctx, key, "EXPANSION") +} + +// BFInfoArg returns information about a specific option of a Bloom filter. +// For more information - https://redis.io/commands/bf.info/ +func (c cmdable) BFInfoArg(ctx context.Context, key, option string) *BFInfoCmd { + args := []interface{}{"BF.INFO", key, option} + cmd := NewBFInfoCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// BFInsert inserts elements into a Bloom filter. +// This function also allows for specifying additional options such as: +// capacity, error rate, expansion rate, and non-scaling behavior. +// For more information - https://redis.io/commands/bf.insert/ +func (c cmdable) BFInsert(ctx context.Context, key string, options *BFInsertOptions, elements ...interface{}) *BoolSliceCmd { + args := []interface{}{"BF.INSERT", key} + if options != nil { + if options.Capacity != 0 { + args = append(args, "CAPACITY", options.Capacity) + } + if options.Error != 0 { + args = append(args, "ERROR", options.Error) + } + if options.Expansion != 0 { + args = append(args, "EXPANSION", options.Expansion) + } + if options.NoCreate { + args = append(args, "NOCREATE") + } + if options.NonScaling { + args = append(args, "NONSCALING") + } + } + args = append(args, "ITEMS") + args = append(args, elements...) + + cmd := NewBoolSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// BFMAdd adds multiple elements to a Bloom filter. +// Returns an array of booleans indicating whether each element was added to the filter or not. +// For more information - https://redis.io/commands/bf.madd/ +func (c cmdable) BFMAdd(ctx context.Context, key string, elements ...interface{}) *BoolSliceCmd { + args := []interface{}{"BF.MADD", key} + args = append(args, elements...) + cmd := NewBoolSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// BFMExists check if multiple elements exist in a Bloom filter. +// Returns an array of booleans indicating whether each element exists in the filter or not. +// For more information - https://redis.io/commands/bf.mexists/ +func (c cmdable) BFMExists(ctx context.Context, key string, elements ...interface{}) *BoolSliceCmd { + args := []interface{}{"BF.MEXISTS", key} + args = append(args, elements...) + + cmd := NewBoolSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// ------------------------------------------- +// Cuckoo filter commands +//------------------------------------------- + +// CFReserve creates an empty Cuckoo filter with the specified capacity. +// For more information - https://redis.io/commands/cf.reserve/ +func (c cmdable) CFReserve(ctx context.Context, key string, capacity int64) *StatusCmd { + args := []interface{}{"CF.RESERVE", key, capacity} + cmd := NewStatusCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// CFReserveExpansion creates an empty Cuckoo filter with the specified capacity and expansion rate. +// For more information - https://redis.io/commands/cf.reserve/ +func (c cmdable) CFReserveExpansion(ctx context.Context, key string, capacity int64, expansion int64) *StatusCmd { + args := []interface{}{"CF.RESERVE", key, capacity, "EXPANSION", expansion} + cmd := NewStatusCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// CFReserveBucketSize creates an empty Cuckoo filter with the specified capacity and bucket size. +// For more information - https://redis.io/commands/cf.reserve/ +func (c cmdable) CFReserveBucketSize(ctx context.Context, key string, capacity int64, bucketsize int64) *StatusCmd { + args := []interface{}{"CF.RESERVE", key, capacity, "BUCKETSIZE", bucketsize} + cmd := NewStatusCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// CFReserveMaxIterations creates an empty Cuckoo filter with the specified capacity and maximum number of iterations. +// For more information - https://redis.io/commands/cf.reserve/ +func (c cmdable) CFReserveMaxIterations(ctx context.Context, key string, capacity int64, maxiterations int64) *StatusCmd { + args := []interface{}{"CF.RESERVE", key, capacity, "MAXITERATIONS", maxiterations} + cmd := NewStatusCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// CFReserveArgs 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 { + args := []interface{}{"CF.RESERVE", key, options.Capacity} + if options.BucketSize != 0 { + args = append(args, "BUCKETSIZE", options.BucketSize) + } + if options.MaxIterations != 0 { + args = append(args, "MAXITERATIONS", options.MaxIterations) + } + if options.Expansion != 0 { + args = append(args, "EXPANSION", options.Expansion) + } + cmd := NewStatusCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// CFAdd adds an element to a Cuckoo filter. +// Returns true if the element was added to the filter or false if it already exists in the filter. +// For more information - https://redis.io/commands/cf.add/ +func (c cmdable) CFAdd(ctx context.Context, key string, element interface{}) *BoolCmd { + args := []interface{}{"CF.ADD", key, element} + cmd := NewBoolCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// CFAddNX adds an element to a Cuckoo filter only if it does not already exist in the filter. +// Returns true if the element was added to the filter or false if it already exists in the filter. +// For more information - https://redis.io/commands/cf.addnx/ +func (c cmdable) CFAddNX(ctx context.Context, key string, element interface{}) *BoolCmd { + args := []interface{}{"CF.ADDNX", key, element} + cmd := NewBoolCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// CFCount returns an estimate of the number of times an element may be in a Cuckoo Filter. +// For more information - https://redis.io/commands/cf.count/ +func (c cmdable) CFCount(ctx context.Context, key string, element interface{}) *IntCmd { + args := []interface{}{"CF.COUNT", key, element} + cmd := NewIntCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// CFDel deletes an item once from the cuckoo filter. +// For more information - https://redis.io/commands/cf.del/ +func (c cmdable) CFDel(ctx context.Context, key string, element interface{}) *BoolCmd { + args := []interface{}{"CF.DEL", key, element} + cmd := NewBoolCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// CFExists determines whether an item may exist in the Cuckoo Filter or not. +// For more information - https://redis.io/commands/cf.exists/ +func (c cmdable) CFExists(ctx context.Context, key string, element interface{}) *BoolCmd { + args := []interface{}{"CF.EXISTS", key, element} + cmd := NewBoolCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// CFLoadChunk restores a filter previously saved using SCANDUMP. +// For more information - https://redis.io/commands/cf.loadchunk/ +func (c cmdable) CFLoadChunk(ctx context.Context, key string, iterator int64, data interface{}) *StatusCmd { + args := []interface{}{"CF.LOADCHUNK", key, iterator, data} + cmd := NewStatusCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// CFScanDump begins an incremental save of the cuckoo filter. +// For more information - https://redis.io/commands/cf.scandump/ +func (c cmdable) CFScanDump(ctx context.Context, key string, iterator int64) *ScanDumpCmd { + args := []interface{}{"CF.SCANDUMP", key, iterator} + cmd := newScanDumpCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +type CFInfo struct { + Size int64 + NumBuckets int64 + NumFilters int64 + NumItemsInserted int64 + NumItemsDeleted int64 + BucketSize int64 + ExpansionRate int64 + MaxIteration int64 +} + +type CFInfoCmd struct { + baseCmd + + val CFInfo +} + +func NewCFInfoCmd(ctx context.Context, args ...interface{}) *CFInfoCmd { + return &CFInfoCmd{ + baseCmd: baseCmd{ + ctx: ctx, + args: args, + }, + } +} + +func (cmd *CFInfoCmd) SetVal(val CFInfo) { + cmd.val = val +} + +func (cmd *CFInfoCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *CFInfoCmd) Val() CFInfo { + return cmd.val +} + +func (cmd *CFInfoCmd) Result() (CFInfo, error) { + return cmd.val, cmd.err +} + +func (cmd *CFInfoCmd) readReply(rd *proto.Reader) (err error) { + n, err := rd.ReadMapLen() + if err != nil { + return err + } + + var key string + var result CFInfo + for f := 0; f < n; f++ { + key, err = rd.ReadString() + if err != nil { + return err + } + + switch key { + case "Size": + result.Size, err = rd.ReadInt() + case "Number of buckets": + result.NumBuckets, err = rd.ReadInt() + case "Number of filters": + result.NumFilters, err = rd.ReadInt() + case "Number of items inserted": + result.NumItemsInserted, err = rd.ReadInt() + case "Number of items deleted": + result.NumItemsDeleted, err = rd.ReadInt() + case "Bucket size": + result.BucketSize, err = rd.ReadInt() + case "Expansion rate": + result.ExpansionRate, err = rd.ReadInt() + case "Max iterations": + result.MaxIteration, err = rd.ReadInt() + + default: + return fmt.Errorf("redis: CF.INFO unexpected key %s", key) + } + + if err != nil { + return err + } + } + + cmd.val = result + return nil +} + +// CFInfo returns information about a Cuckoo filter. +// For more information - https://redis.io/commands/cf.info/ +func (c cmdable) CFInfo(ctx context.Context, key string) *CFInfoCmd { + args := []interface{}{"CF.INFO", key} + cmd := NewCFInfoCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// CFInsert inserts elements into a Cuckoo filter. +// This function also allows for specifying additional options such as capacity, error rate, expansion rate, and non-scaling behavior. +// Returns an array of booleans indicating whether each element was added to the filter or not. +// 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...) + + cmd := NewBoolSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// CFInsertNX inserts elements into a Cuckoo filter only if they do not already exist in the filter. +// This function also allows for specifying additional options such as: +// capacity, error rate, expansion rate, and non-scaling behavior. +// Returns an array of integers indicating whether each element was added to the filter or not. +// 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...) + + cmd := NewIntSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) getCfInsertArgs(args []interface{}, options *CFInsertOptions, elements ...interface{}) []interface{} { + if options != nil { + if options.Capacity != 0 { + args = append(args, "CAPACITY", options.Capacity) + } + if options.NoCreate { + args = append(args, "NOCREATE") + } + } + args = append(args, "ITEMS") + args = append(args, elements...) + + return args +} + +// CFMExists check if multiple elements exist in a Cuckoo filter. +// Returns an array of booleans indicating whether each element exists in the filter or not. +// For more information - https://redis.io/commands/cf.mexists/ +func (c cmdable) CFMExists(ctx context.Context, key string, elements ...interface{}) *BoolSliceCmd { + args := []interface{}{"CF.MEXISTS", key} + args = append(args, elements...) + cmd := NewBoolSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// ------------------------------------------- +// CMS commands +//------------------------------------------- + +// CMSIncrBy increments the count of one or more items in a Count-Min Sketch filter. +// Returns an array of integers representing the updated count of each item. +// For more information - https://redis.io/commands/cms.incrby/ +func (c cmdable) CMSIncrBy(ctx context.Context, key string, elements ...interface{}) *IntSliceCmd { + args := make([]interface{}, 2, 2+len(elements)) + args[0] = "CMS.INCRBY" + args[1] = key + args = appendArgs(args, elements) + + cmd := NewIntSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +type CMSInfo struct { + Width int64 + Depth int64 + Count int64 +} + +type CMSInfoCmd struct { + baseCmd + + val CMSInfo +} + +func NewCMSInfoCmd(ctx context.Context, args ...interface{}) *CMSInfoCmd { + return &CMSInfoCmd{ + baseCmd: baseCmd{ + ctx: ctx, + args: args, + }, + } +} + +func (cmd *CMSInfoCmd) SetVal(val CMSInfo) { + cmd.val = val +} + +func (cmd *CMSInfoCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *CMSInfoCmd) Val() CMSInfo { + return cmd.val +} + +func (cmd *CMSInfoCmd) Result() (CMSInfo, error) { + return cmd.val, cmd.err +} + +func (cmd *CMSInfoCmd) readReply(rd *proto.Reader) (err error) { + n, err := rd.ReadMapLen() + if err != nil { + return err + } + + var key string + var result CMSInfo + for f := 0; f < n; f++ { + key, err = rd.ReadString() + if err != nil { + return err + } + + switch key { + case "width": + result.Width, err = rd.ReadInt() + case "depth": + result.Depth, err = rd.ReadInt() + case "count": + result.Count, err = rd.ReadInt() + default: + return fmt.Errorf("redis: CMS.INFO unexpected key %s", key) + } + + if err != nil { + return err + } + } + + cmd.val = result + return nil +} + +// CMSInfo returns information about a Count-Min Sketch filter. +// For more information - https://redis.io/commands/cms.info/ +func (c cmdable) CMSInfo(ctx context.Context, key string) *CMSInfoCmd { + args := []interface{}{"CMS.INFO", key} + cmd := NewCMSInfoCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// CMSInitByDim creates an empty Count-Min Sketch filter with the specified dimensions. +// For more information - https://redis.io/commands/cms.initbydim/ +func (c cmdable) CMSInitByDim(ctx context.Context, key string, width, depth int64) *StatusCmd { + args := []interface{}{"CMS.INITBYDIM", key, width, depth} + cmd := NewStatusCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// CMSInitByProb creates an empty Count-Min Sketch filter with the specified error rate and probability. +// For more information - https://redis.io/commands/cms.initbyprob/ +func (c cmdable) CMSInitByProb(ctx context.Context, key string, errorRate, probability float64) *StatusCmd { + args := []interface{}{"CMS.INITBYPROB", key, errorRate, probability} + cmd := NewStatusCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// CMSMerge merges multiple Count-Min Sketch filters into a single filter. +// The destination filter must not exist and will be created with the dimensions of the first source filter. +// The number of items in each source filter must be equal. +// Returns OK on success or an error if the filters could not be merged. +// For more information - https://redis.io/commands/cms.merge/ +func (c cmdable) CMSMerge(ctx context.Context, destKey string, sourceKeys ...string) *StatusCmd { + args := []interface{}{"CMS.MERGE", destKey, len(sourceKeys)} + for _, s := range sourceKeys { + args = append(args, s) + } + cmd := NewStatusCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// CMSMergeWithWeight merges multiple Count-Min Sketch filters into a single filter with weights for each source filter. +// The destination filter must not exist and will be created with the dimensions of the first source filter. +// The number of items in each source filter must be equal. +// Returns OK on success or an error if the filters could not be merged. +// For more information - https://redis.io/commands/cms.merge/ +func (c cmdable) CMSMergeWithWeight(ctx context.Context, destKey string, sourceKeys map[string]int64) *StatusCmd { + args := make([]interface{}, 0, 4+(len(sourceKeys)*2+1)) + args = append(args, "CMS.MERGE", destKey, len(sourceKeys)) + + if len(sourceKeys) > 0 { + sk := make([]interface{}, len(sourceKeys)) + sw := make([]interface{}, len(sourceKeys)) + + i := 0 + for k, w := range sourceKeys { + sk[i] = k + sw[i] = w + i++ + } + + args = append(args, sk...) + args = append(args, "WEIGHTS") + args = append(args, sw...) + } + + cmd := NewStatusCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// CMSQuery returns count for item(s). +// For more information - https://redis.io/commands/cms.query/ +func (c cmdable) CMSQuery(ctx context.Context, key string, elements ...interface{}) *IntSliceCmd { + args := []interface{}{"CMS.QUERY", key} + args = append(args, elements...) + cmd := NewIntSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// ------------------------------------------- +// TopK commands +//-------------------------------------------- + +// TopKAdd adds one or more elements to a Top-K filter. +// Returns an array of strings representing the items that were removed from the filter, if any. +// For more information - https://redis.io/commands/topk.add/ +func (c cmdable) TopKAdd(ctx context.Context, key string, elements ...interface{}) *StringSliceCmd { + args := make([]interface{}, 2, 2+len(elements)) + args[0] = "TOPK.ADD" + args[1] = key + args = appendArgs(args, elements) + + cmd := NewStringSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// TopKReserve creates an empty Top-K filter with the specified number of top items to keep. +// For more information - https://redis.io/commands/topk.reserve/ +func (c cmdable) TopKReserve(ctx context.Context, key string, k int64) *StatusCmd { + args := []interface{}{"TOPK.RESERVE", key, k} + + cmd := NewStatusCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// TopKReserveWithOptions creates an empty Top-K filter with the specified number of top items to keep and additional options. +// This function allows for specifying additional options such as width, depth and decay. +// For more information - https://redis.io/commands/topk.reserve/ +func (c cmdable) TopKReserveWithOptions(ctx context.Context, key string, k int64, width, depth int64, decay float64) *StatusCmd { + args := []interface{}{"TOPK.RESERVE", key, k, width, depth, decay} + + cmd := NewStatusCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +type TopKInfo struct { + K int64 + Width int64 + Depth int64 + Decay float64 +} + +type TopKInfoCmd struct { + baseCmd + + val TopKInfo +} + +func NewTopKInfoCmd(ctx context.Context, args ...interface{}) *TopKInfoCmd { + return &TopKInfoCmd{ + baseCmd: baseCmd{ + ctx: ctx, + args: args, + }, + } +} + +func (cmd *TopKInfoCmd) SetVal(val TopKInfo) { + cmd.val = val +} + +func (cmd *TopKInfoCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *TopKInfoCmd) Val() TopKInfo { + return cmd.val +} + +func (cmd *TopKInfoCmd) Result() (TopKInfo, error) { + return cmd.val, cmd.err +} + +func (cmd *TopKInfoCmd) readReply(rd *proto.Reader) (err error) { + n, err := rd.ReadMapLen() + if err != nil { + return err + } + + var key string + var result TopKInfo + for f := 0; f < n; f++ { + key, err = rd.ReadString() + if err != nil { + return err + } + + switch key { + case "k": + result.K, err = rd.ReadInt() + case "width": + result.Width, err = rd.ReadInt() + case "depth": + result.Depth, err = rd.ReadInt() + case "decay": + result.Decay, err = rd.ReadFloat() + default: + return fmt.Errorf("redis: topk.info unexpected key %s", key) + } + + if err != nil { + return err + } + } + + cmd.val = result + return nil +} + +// TopKInfo returns information about a Top-K filter. +// For more information - https://redis.io/commands/topk.info/ +func (c cmdable) TopKInfo(ctx context.Context, key string) *TopKInfoCmd { + args := []interface{}{"TOPK.INFO", key} + + cmd := NewTopKInfoCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// TopKQuery check if multiple elements exist in a Top-K filter. +// Returns an array of booleans indicating whether each element exists in the filter or not. +// For more information - https://redis.io/commands/topk.query/ +func (c cmdable) TopKQuery(ctx context.Context, key string, elements ...interface{}) *BoolSliceCmd { + args := make([]interface{}, 2, 2+len(elements)) + args[0] = "TOPK.QUERY" + args[1] = key + args = appendArgs(args, elements) + + cmd := NewBoolSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// TopKCount returns an estimate of the number of times an item may be in a Top-K filter. +// For more information - https://redis.io/commands/topk.count/ +func (c cmdable) TopKCount(ctx context.Context, key string, elements ...interface{}) *IntSliceCmd { + args := make([]interface{}, 2, 2+len(elements)) + args[0] = "TOPK.COUNT" + args[1] = key + args = appendArgs(args, elements) + + cmd := NewIntSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// TopKIncrBy increases the count of one or more items in a Top-K filter. +// For more information - https://redis.io/commands/topk.incrby/ +func (c cmdable) TopKIncrBy(ctx context.Context, key string, elements ...interface{}) *StringSliceCmd { + args := make([]interface{}, 2, 2+len(elements)) + args[0] = "TOPK.INCRBY" + args[1] = key + args = appendArgs(args, elements) + + cmd := NewStringSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// TopKList returns all items in Top-K list. +// For more information - https://redis.io/commands/topk.list/ +func (c cmdable) TopKList(ctx context.Context, key string) *StringSliceCmd { + args := []interface{}{"TOPK.LIST", key} + + cmd := NewStringSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// TopKListWithCount returns all items in Top-K list with their respective count. +// For more information - https://redis.io/commands/topk.list/ +func (c cmdable) TopKListWithCount(ctx context.Context, key string) *MapStringIntCmd { + args := []interface{}{"TOPK.LIST", key, "WITHCOUNT"} + + cmd := NewMapStringIntCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// ------------------------------------------- +// t-digest commands +// -------------------------------------------- + +// TDigestAdd adds one or more elements to a t-Digest data structure. +// Returns OK on success or an error if the operation could not be completed. +// For more information - https://redis.io/commands/tdigest.add/ +func (c cmdable) TDigestAdd(ctx context.Context, key string, elements ...float64) *StatusCmd { + args := make([]interface{}, 2, 2+len(elements)) + args[0] = "TDIGEST.ADD" + args[1] = key + + // Convert floatSlice to []interface{} + interfaceSlice := make([]interface{}, len(elements)) + for i, v := range elements { + interfaceSlice[i] = v + } + + args = append(args, interfaceSlice...) + + cmd := NewStatusCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// TDigestByRank returns an array of values from a t-Digest data structure based on their rank. +// The rank of an element is its position in the sorted list of all elements in the t-Digest. +// Returns an array of floats representing the values at the specified ranks or an error if the operation could not be completed. +// For more information - https://redis.io/commands/tdigest.byrank/ +func (c cmdable) TDigestByRank(ctx context.Context, key string, rank ...uint64) *FloatSliceCmd { + args := make([]interface{}, 2, 2+len(rank)) + args[0] = "TDIGEST.BYRANK" + args[1] = key + + // Convert uint slice to []interface{} + interfaceSlice := make([]interface{}, len(rank)) + for i, v := range rank { + interfaceSlice[i] = v + } + + args = append(args, interfaceSlice...) + + cmd := NewFloatSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// TDigestByRevRank returns an array of values from a t-Digest data structure based on their reverse rank. +// The reverse rank of an element is its position in the sorted list of all elements in the t-Digest when sorted in descending order. +// Returns an array of floats representing the values at the specified ranks or an error if the operation could not be completed. +// For more information - https://redis.io/commands/tdigest.byrevrank/ +func (c cmdable) TDigestByRevRank(ctx context.Context, key string, rank ...uint64) *FloatSliceCmd { + args := make([]interface{}, 2, 2+len(rank)) + args[0] = "TDIGEST.BYREVRANK" + args[1] = key + + // Convert uint slice to []interface{} + interfaceSlice := make([]interface{}, len(rank)) + for i, v := range rank { + interfaceSlice[i] = v + } + + args = append(args, interfaceSlice...) + + cmd := NewFloatSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// TDigestCDF returns an array of cumulative distribution function (CDF) values for one or more elements in a t-Digest data structure. +// The CDF value for an element is the fraction of all elements in the t-Digest that are less than or equal to it. +// Returns an array of floats representing the CDF values for each element or an error if the operation could not be completed. +// For more information - https://redis.io/commands/tdigest.cdf/ +func (c cmdable) TDigestCDF(ctx context.Context, key string, elements ...float64) *FloatSliceCmd { + args := make([]interface{}, 2, 2+len(elements)) + args[0] = "TDIGEST.CDF" + args[1] = key + + // Convert floatSlice to []interface{} + interfaceSlice := make([]interface{}, len(elements)) + for i, v := range elements { + interfaceSlice[i] = v + } + + args = append(args, interfaceSlice...) + + cmd := NewFloatSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// TDigestCreate creates an empty t-Digest data structure with default parameters. +// Returns OK on success or an error if the operation could not be completed. +// For more information - https://redis.io/commands/tdigest.create/ +func (c cmdable) TDigestCreate(ctx context.Context, key string) *StatusCmd { + args := []interface{}{"TDIGEST.CREATE", key} + + cmd := NewStatusCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// TDigestCreateWithCompression creates an empty t-Digest data structure with a specified compression parameter. +// The compression parameter controls the accuracy and memory usage of the t-Digest. +// Returns OK on success or an error if the operation could not be completed. +// For more information - https://redis.io/commands/tdigest.create/ +func (c cmdable) TDigestCreateWithCompression(ctx context.Context, key string, compression int64) *StatusCmd { + args := []interface{}{"TDIGEST.CREATE", key, "COMPRESSION", compression} + + cmd := NewStatusCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +type TDigestInfo struct { + Compression int64 + Capacity int64 + MergedNodes int64 + UnmergedNodes int64 + MergedWeight int64 + UnmergedWeight int64 + Observations int64 + TotalCompressions int64 + MemoryUsage int64 +} + +type TDigestInfoCmd struct { + baseCmd + + val TDigestInfo +} + +func NewTDigestInfoCmd(ctx context.Context, args ...interface{}) *TDigestInfoCmd { + return &TDigestInfoCmd{ + baseCmd: baseCmd{ + ctx: ctx, + args: args, + }, + } +} + +func (cmd *TDigestInfoCmd) SetVal(val TDigestInfo) { + cmd.val = val +} + +func (cmd *TDigestInfoCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *TDigestInfoCmd) Val() TDigestInfo { + return cmd.val +} + +func (cmd *TDigestInfoCmd) Result() (TDigestInfo, error) { + return cmd.val, cmd.err +} + +func (cmd *TDigestInfoCmd) readReply(rd *proto.Reader) (err error) { + n, err := rd.ReadMapLen() + if err != nil { + return err + } + + var key string + var result TDigestInfo + for f := 0; f < n; f++ { + key, err = rd.ReadString() + if err != nil { + return err + } + + switch key { + case "Compression": + result.Compression, err = rd.ReadInt() + case "Capacity": + result.Capacity, err = rd.ReadInt() + case "Merged nodes": + result.MergedNodes, err = rd.ReadInt() + case "Unmerged nodes": + result.UnmergedNodes, err = rd.ReadInt() + case "Merged weight": + result.MergedWeight, err = rd.ReadInt() + case "Unmerged weight": + result.UnmergedWeight, err = rd.ReadInt() + case "Observations": + result.Observations, err = rd.ReadInt() + case "Total compressions": + result.TotalCompressions, err = rd.ReadInt() + case "Memory usage": + result.MemoryUsage, err = rd.ReadInt() + default: + return fmt.Errorf("redis: tdigest.info unexpected key %s", key) + } + + if err != nil { + return err + } + } + + cmd.val = result + return nil +} + +// TDigestInfo returns information about a t-Digest data structure. +// For more information - https://redis.io/commands/tdigest.info/ +func (c cmdable) TDigestInfo(ctx context.Context, key string) *TDigestInfoCmd { + args := []interface{}{"TDIGEST.INFO", key} + + cmd := NewTDigestInfoCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// TDigestMax returns the maximum value from a t-Digest data structure. +// For more information - https://redis.io/commands/tdigest.max/ +func (c cmdable) TDigestMax(ctx context.Context, key string) *FloatCmd { + args := []interface{}{"TDIGEST.MAX", key} + + cmd := NewFloatCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +type TDigestMergeOptions struct { + Compression int64 + Override bool +} + +// TDigestMerge merges multiple t-Digest data structures into a single t-Digest. +// This function also allows for specifying additional options such as compression and override behavior. +// Returns OK on success or an error if the operation could not be completed. +// For more information - https://redis.io/commands/tdigest.merge/ +func (c cmdable) TDigestMerge(ctx context.Context, destKey string, options *TDigestMergeOptions, sourceKeys ...string) *StatusCmd { + args := []interface{}{"TDIGEST.MERGE", destKey, len(sourceKeys)} + + for _, sourceKey := range sourceKeys { + args = append(args, sourceKey) + } + + if options != nil { + if options.Compression != 0 { + args = append(args, "COMPRESSION", options.Compression) + } + if options.Override { + args = append(args, "OVERRIDE") + } + } + + cmd := NewStatusCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// TDigestMin returns the minimum value from a t-Digest data structure. +// For more information - https://redis.io/commands/tdigest.min/ +func (c cmdable) TDigestMin(ctx context.Context, key string) *FloatCmd { + args := []interface{}{"TDIGEST.MIN", key} + + cmd := NewFloatCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// TDigestQuantile returns an array of quantile values for one or more elements in a t-Digest data structure. +// The quantile value for an element is the fraction of all elements in the t-Digest that are less than or equal to it. +// Returns an array of floats representing the quantile values for each element or an error if the operation could not be completed. +// For more information - https://redis.io/commands/tdigest.quantile/ +func (c cmdable) TDigestQuantile(ctx context.Context, key string, elements ...float64) *FloatSliceCmd { + args := make([]interface{}, 2, 2+len(elements)) + args[0] = "TDIGEST.QUANTILE" + args[1] = key + + // Convert floatSlice to []interface{} + interfaceSlice := make([]interface{}, len(elements)) + for i, v := range elements { + interfaceSlice[i] = v + } + + args = append(args, interfaceSlice...) + + cmd := NewFloatSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// TDigestRank returns an array of rank values for one or more elements in a t-Digest data structure. +// The rank of an element is its position in the sorted list of all elements in the t-Digest. +// Returns an array of integers representing the rank values for each element or an error if the operation could not be completed. +// For more information - https://redis.io/commands/tdigest.rank/ +func (c cmdable) TDigestRank(ctx context.Context, key string, values ...float64) *IntSliceCmd { + args := make([]interface{}, 2, 2+len(values)) + args[0] = "TDIGEST.RANK" + args[1] = key + + // Convert floatSlice to []interface{} + interfaceSlice := make([]interface{}, len(values)) + for i, v := range values { + interfaceSlice[i] = v + } + + args = append(args, interfaceSlice...) + + cmd := NewIntSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// TDigestReset resets a t-Digest data structure to its initial state. +// Returns OK on success or an error if the operation could not be completed. +// For more information - https://redis.io/commands/tdigest.reset/ +func (c cmdable) TDigestReset(ctx context.Context, key string) *StatusCmd { + args := []interface{}{"TDIGEST.RESET", key} + + cmd := NewStatusCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// TDigestRevRank returns an array of reverse rank values for one or more elements in a t-Digest data structure. +// The reverse rank of an element is its position in the sorted list of all elements in the t-Digest when sorted in descending order. +// Returns an array of integers representing the reverse rank values for each element or an error if the operation could not be completed. +// For more information - https://redis.io/commands/tdigest.revrank/ +func (c cmdable) TDigestRevRank(ctx context.Context, key string, values ...float64) *IntSliceCmd { + args := make([]interface{}, 2, 2+len(values)) + args[0] = "TDIGEST.REVRANK" + args[1] = key + + // Convert floatSlice to []interface{} + interfaceSlice := make([]interface{}, len(values)) + for i, v := range values { + interfaceSlice[i] = v + } + + args = append(args, interfaceSlice...) + + cmd := NewIntSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// TDigestTrimmedMean returns the trimmed mean value from a t-Digest data structure. +// The trimmed mean is calculated by removing a specified fraction of the highest and lowest values from the t-Digest and then calculating the mean of the remaining values. +// Returns a float representing the trimmed mean value or an error if the operation could not be completed. +// For more information - https://redis.io/commands/tdigest.trimmed_mean/ +func (c cmdable) TDigestTrimmedMean(ctx context.Context, key string, lowCutQuantile, highCutQuantile float64) *FloatCmd { + args := []interface{}{"TDIGEST.TRIMMED_MEAN", key, lowCutQuantile, highCutQuantile} + + cmd := NewFloatCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} diff --git a/probabilistic_test.go b/probabilistic_test.go new file mode 100644 index 000000000..4b0dde646 --- /dev/null +++ b/probabilistic_test.go @@ -0,0 +1,739 @@ +package redis_test + +import ( + "context" + "fmt" + "math" + + . "github.com/bsm/ginkgo/v2" + . "github.com/bsm/gomega" + "github.com/redis/go-redis/v9" +) + +var _ = Describe("Probabilistic commands", Label("probabilistic"), 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("bloom", Label("bloom"), func() { + It("should BFAdd", Label("bloom", "bfadd"), func() { + resultAdd, err := client.BFAdd(ctx, "testbf1", 1).Result() + + Expect(err).NotTo(HaveOccurred()) + Expect(resultAdd).To(BeTrue()) + + resultInfo, err := client.BFInfo(ctx, "testbf1").Result() + + Expect(err).NotTo(HaveOccurred()) + Expect(resultInfo).To(BeAssignableToTypeOf(redis.BFInfo{})) + Expect(resultInfo.ItemsInserted).To(BeEquivalentTo(int64(1))) + }) + + It("should BFCard", Label("bloom", "bfcard"), func() { + // This is a probabilistic data structure, and it's not always guaranteed that we will get back + // the exact number of inserted items, during hash collisions + // But with such a low number of items (only 3), + // the probability of a collision is very low, so we can expect to get back the exact number of items + _, err := client.BFAdd(ctx, "testbf1", "item1").Result() + Expect(err).NotTo(HaveOccurred()) + _, err = client.BFAdd(ctx, "testbf1", "item2").Result() + Expect(err).NotTo(HaveOccurred()) + _, err = client.BFAdd(ctx, "testbf1", 3).Result() + Expect(err).NotTo(HaveOccurred()) + + result, err := client.BFCard(ctx, "testbf1").Result() + + Expect(err).NotTo(HaveOccurred()) + Expect(result).To(BeEquivalentTo(int64(3))) + }) + + It("should BFExists", Label("bloom", "bfexists"), func() { + exists, err := client.BFExists(ctx, "testbf1", "item1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(exists).To(BeFalse()) + + _, err = client.BFAdd(ctx, "testbf1", "item1").Result() + Expect(err).NotTo(HaveOccurred()) + + exists, err = client.BFExists(ctx, "testbf1", "item1").Result() + + Expect(err).NotTo(HaveOccurred()) + Expect(exists).To(BeTrue()) + }) + + It("should BFInfo and BFReserve", Label("bloom", "bfinfo", "bfreserve"), func() { + err := client.BFReserve(ctx, "testbf1", 0.001, 2000).Err() + Expect(err).NotTo(HaveOccurred()) + + result, err := client.BFInfo(ctx, "testbf1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result).To(BeAssignableToTypeOf(redis.BFInfo{})) + Expect(result.Capacity).To(BeEquivalentTo(int64(2000))) + }) + + It("should BFInfoCapacity, BFInfoSize, BFInfoFilters, BFInfoItems, BFInfoExpansion, ", Label("bloom", "bfinfocapacity", "bfinfosize", "bfinfofilters", "bfinfoitems", "bfinfoexpansion"), func() { + err := client.BFReserve(ctx, "testbf1", 0.001, 2000).Err() + Expect(err).NotTo(HaveOccurred()) + + result, err := client.BFInfoCapacity(ctx, "testbf1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result.Capacity).To(BeEquivalentTo(int64(2000))) + + result, err = client.BFInfoItems(ctx, "testbf1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result.ItemsInserted).To(BeEquivalentTo(int64(0))) + + result, err = client.BFInfoSize(ctx, "testbf1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result.Size).To(BeEquivalentTo(int64(4056))) + + err = client.BFReserveExpansion(ctx, "testbf2", 0.001, 2000, 3).Err() + Expect(err).NotTo(HaveOccurred()) + + result, err = client.BFInfoFilters(ctx, "testbf2").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result.Filters).To(BeEquivalentTo(int64(1))) + + result, err = client.BFInfoExpansion(ctx, "testbf2").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result.ExpansionRate).To(BeEquivalentTo(int64(3))) + }) + + It("should BFInsert", Label("bloom", "bfinsert"), func() { + options := &redis.BFInsertOptions{ + Capacity: 2000, + Error: 0.001, + Expansion: 3, + NonScaling: false, + NoCreate: true, + } + + resultInsert, err := client.BFInsert(ctx, "testbf1", options, "item1").Result() + Expect(err).To(HaveOccurred()) + Expect(err).To(MatchError("ERR not found")) + + options = &redis.BFInsertOptions{ + Capacity: 2000, + Error: 0.001, + Expansion: 3, + NonScaling: false, + NoCreate: false, + } + + resultInsert, err = client.BFInsert(ctx, "testbf1", options, "item1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(len(resultInsert)).To(BeEquivalentTo(1)) + + exists, err := client.BFExists(ctx, "testbf1", "item1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(exists).To(BeTrue()) + + result, err := client.BFInfo(ctx, "testbf1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result).To(BeAssignableToTypeOf(redis.BFInfo{})) + Expect(result.Capacity).To(BeEquivalentTo(int64(2000))) + Expect(result.ExpansionRate).To(BeEquivalentTo(int64(3))) + }) + + It("should BFMAdd", Label("bloom", "bfmadd"), func() { + resultAdd, err := client.BFMAdd(ctx, "testbf1", "item1", "item2", "item3").Result() + + Expect(err).NotTo(HaveOccurred()) + Expect(len(resultAdd)).To(Equal(3)) + + resultInfo, err := client.BFInfo(ctx, "testbf1").Result() + + Expect(err).NotTo(HaveOccurred()) + Expect(resultInfo).To(BeAssignableToTypeOf(redis.BFInfo{})) + Expect(resultInfo.ItemsInserted).To(BeEquivalentTo(int64(3))) + resultAdd2, err := client.BFMAdd(ctx, "testbf1", "item1", "item2", "item4").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultAdd2[0]).To(BeFalse()) + Expect(resultAdd2[1]).To(BeFalse()) + Expect(resultAdd2[2]).To(BeTrue()) + + }) + + It("should BFMExists", Label("bloom", "bfmexists"), func() { + exist, err := client.BFMExists(ctx, "testbf1", "item1", "item2", "item3").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(len(exist)).To(Equal(3)) + Expect(exist[0]).To(BeFalse()) + Expect(exist[1]).To(BeFalse()) + Expect(exist[2]).To(BeFalse()) + + _, err = client.BFMAdd(ctx, "testbf1", "item1", "item2", "item3").Result() + Expect(err).NotTo(HaveOccurred()) + + exist, err = client.BFMExists(ctx, "testbf1", "item1", "item2", "item3", "item4").Result() + + Expect(err).NotTo(HaveOccurred()) + Expect(len(exist)).To(Equal(4)) + Expect(exist[0]).To(BeTrue()) + Expect(exist[1]).To(BeTrue()) + Expect(exist[2]).To(BeTrue()) + Expect(exist[3]).To(BeFalse()) + }) + + It("should BFReserveExpansion", Label("bloom", "bfreserveexpansion"), func() { + err := client.BFReserveExpansion(ctx, "testbf1", 0.001, 2000, 3).Err() + Expect(err).NotTo(HaveOccurred()) + + result, err := client.BFInfo(ctx, "testbf1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result).To(BeAssignableToTypeOf(redis.BFInfo{})) + Expect(result.Capacity).To(BeEquivalentTo(int64(2000))) + Expect(result.ExpansionRate).To(BeEquivalentTo(int64(3))) + }) + + It("should BFReserveNonScaling", Label("bloom", "bfreservenonscaling"), func() { + err := client.BFReserveNonScaling(ctx, "testbfns1", 0.001, 1000).Err() + Expect(err).NotTo(HaveOccurred()) + + _, err = client.BFInfo(ctx, "testbfns1").Result() + Expect(err).To(HaveOccurred()) + }) + + It("should BFScanDump and BFLoadChunk", Label("bloom", "bfscandump", "bfloadchunk"), func() { + err := client.BFReserve(ctx, "testbfsd1", 0.001, 3000).Err() + Expect(err).NotTo(HaveOccurred()) + for i := 0; i < 1000; i++ { + client.BFAdd(ctx, "testbfsd1", i) + } + infBefore := client.BFInfoSize(ctx, "testbfsd1") + fd := []redis.ScanDump{} + sd, err := client.BFScanDump(ctx, "testbfsd1", 0).Result() + for { + if sd.Iter == 0 { + break + } + Expect(err).NotTo(HaveOccurred()) + fd = append(fd, sd) + sd, err = client.BFScanDump(ctx, "testbfsd1", sd.Iter).Result() + } + client.Del(ctx, "testbfsd1") + for _, e := range fd { + client.BFLoadChunk(ctx, "testbfsd1", e.Iter, e.Data) + } + infAfter := client.BFInfoSize(ctx, "testbfsd1") + Expect(infBefore).To(BeEquivalentTo(infAfter)) + }) + + It("should BFReserveArgs", Label("bloom", "bfreserveargs"), func() { + options := &redis.BFReserveOptions{ + Capacity: 2000, + Error: 0.001, + Expansion: 3, + NonScaling: false, + } + err := client.BFReserveArgs(ctx, "testbf", options).Err() + Expect(err).NotTo(HaveOccurred()) + + result, err := client.BFInfo(ctx, "testbf").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result).To(BeAssignableToTypeOf(redis.BFInfo{})) + Expect(result.Capacity).To(BeEquivalentTo(int64(2000))) + Expect(result.ExpansionRate).To(BeEquivalentTo(int64(3))) + }) + }) + + Describe("cuckoo", Label("cuckoo"), func() { + It("should CFAdd", Label("cuckoo", "cfadd"), func() { + add, err := client.CFAdd(ctx, "testcf1", "item1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(add).To(BeTrue()) + + exists, err := client.CFExists(ctx, "testcf1", "item1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(exists).To(BeTrue()) + + info, err := client.CFInfo(ctx, "testcf1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(info).To(BeAssignableToTypeOf(redis.CFInfo{})) + Expect(info.NumItemsInserted).To(BeEquivalentTo(int64(1))) + }) + + It("should CFAddNX", Label("cuckoo", "cfaddnx"), func() { + add, err := client.CFAddNX(ctx, "testcf1", "item1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(add).To(BeTrue()) + + exists, err := client.CFExists(ctx, "testcf1", "item1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(exists).To(BeTrue()) + + result, err := client.CFAddNX(ctx, "testcf1", "item1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result).To(BeFalse()) + + info, err := client.CFInfo(ctx, "testcf1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(info).To(BeAssignableToTypeOf(redis.CFInfo{})) + Expect(info.NumItemsInserted).To(BeEquivalentTo(int64(1))) + }) + + It("should CFCount", Label("cuckoo", "cfcount"), func() { + err := client.CFAdd(ctx, "testcf1", "item1").Err() + cnt, err := client.CFCount(ctx, "testcf1", "item1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cnt).To(BeEquivalentTo(int64(1))) + + err = client.CFAdd(ctx, "testcf1", "item1").Err() + Expect(err).NotTo(HaveOccurred()) + + cnt, err = client.CFCount(ctx, "testcf1", "item1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cnt).To(BeEquivalentTo(int64(2))) + }) + + It("should CFDel and CFExists", Label("cuckoo", "cfdel", "cfexists"), func() { + err := client.CFAdd(ctx, "testcf1", "item1").Err() + Expect(err).NotTo(HaveOccurred()) + + exists, err := client.CFExists(ctx, "testcf1", "item1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(exists).To(BeTrue()) + + del, err := client.CFDel(ctx, "testcf1", "item1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(del).To(BeTrue()) + + exists, err = client.CFExists(ctx, "testcf1", "item1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(exists).To(BeFalse()) + }) + + It("should CFInfo and CFReserve", Label("cuckoo", "cfinfo", "cfreserve"), func() { + err := client.CFReserve(ctx, "testcf1", 1000).Err() + Expect(err).NotTo(HaveOccurred()) + err = client.CFReserveExpansion(ctx, "testcfe1", 1000, 1).Err() + Expect(err).NotTo(HaveOccurred()) + err = client.CFReserveBucketSize(ctx, "testcfbs1", 1000, 4).Err() + Expect(err).NotTo(HaveOccurred()) + err = client.CFReserveMaxIterations(ctx, "testcfmi1", 1000, 10).Err() + Expect(err).NotTo(HaveOccurred()) + + result, err := client.CFInfo(ctx, "testcf1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result).To(BeAssignableToTypeOf(redis.CFInfo{})) + }) + + It("should CFScanDump and CFLoadChunk", Label("bloom", "cfscandump", "cfloadchunk"), func() { + err := client.CFReserve(ctx, "testcfsd1", 1000).Err() + Expect(err).NotTo(HaveOccurred()) + for i := 0; i < 1000; i++ { + Item := fmt.Sprintf("item%d", i) + client.CFAdd(ctx, "testcfsd1", Item) + } + infBefore := client.CFInfo(ctx, "testcfsd1") + fd := []redis.ScanDump{} + sd, err := client.CFScanDump(ctx, "testcfsd1", 0).Result() + for { + if sd.Iter == 0 { + break + } + Expect(err).NotTo(HaveOccurred()) + fd = append(fd, sd) + sd, err = client.CFScanDump(ctx, "testcfsd1", sd.Iter).Result() + } + client.Del(ctx, "testcfsd1") + for _, e := range fd { + client.CFLoadChunk(ctx, "testcfsd1", e.Iter, e.Data) + } + infAfter := client.CFInfo(ctx, "testcfsd1") + Expect(infBefore).To(BeEquivalentTo(infAfter)) + }) + + It("should CFInfo and CFReserveArgs", Label("cuckoo", "cfinfo", "cfreserveargs"), func() { + args := &redis.CFReserveOptions{ + Capacity: 2048, + BucketSize: 3, + MaxIterations: 15, + Expansion: 2, + } + + err := client.CFReserveArgs(ctx, "testcf1", args).Err() + Expect(err).NotTo(HaveOccurred()) + + result, err := client.CFInfo(ctx, "testcf1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result).To(BeAssignableToTypeOf(redis.CFInfo{})) + Expect(result.BucketSize).To(BeEquivalentTo(int64(3))) + Expect(result.MaxIteration).To(BeEquivalentTo(int64(15))) + Expect(result.ExpansionRate).To(BeEquivalentTo(int64(2))) + }) + + It("should CFInsert", Label("cuckoo", "cfinsert"), func() { + args := &redis.CFInsertOptions{ + Capacity: 3000, + NoCreate: true, + } + + result, err := client.CFInsert(ctx, "testcf1", args, "item1", "item2", "item3").Result() + Expect(err).To(HaveOccurred()) + + args = &redis.CFInsertOptions{ + Capacity: 3000, + NoCreate: false, + } + + result, err = client.CFInsert(ctx, "testcf1", args, "item1", "item2", "item3").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(len(result)).To(BeEquivalentTo(3)) + }) + + It("should CFInsertNX", Label("cuckoo", "cfinsertnx"), func() { + args := &redis.CFInsertOptions{ + Capacity: 3000, + NoCreate: true, + } + + result, err := client.CFInsertNX(ctx, "testcf1", args, "item1", "item2", "item2").Result() + Expect(err).To(HaveOccurred()) + + args = &redis.CFInsertOptions{ + Capacity: 3000, + NoCreate: false, + } + + result, err = client.CFInsertNX(ctx, "testcf2", args, "item1", "item2", "item2").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(len(result)).To(BeEquivalentTo(3)) + Expect(result[0]).To(BeEquivalentTo(int64(1))) + Expect(result[1]).To(BeEquivalentTo(int64(1))) + Expect(result[2]).To(BeEquivalentTo(int64(0))) + }) + + It("should CFMexists", Label("cuckoo", "cfmexists"), func() { + err := client.CFInsert(ctx, "testcf1", nil, "item1", "item2", "item3").Err() + Expect(err).NotTo(HaveOccurred()) + + result, err := client.CFMExists(ctx, "testcf1", "item1", "item2", "item3", "item4").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(len(result)).To(BeEquivalentTo(4)) + Expect(result[0]).To(BeTrue()) + Expect(result[1]).To(BeTrue()) + Expect(result[2]).To(BeTrue()) + Expect(result[3]).To(BeFalse()) + }) + + }) + + Describe("CMS", Label("cms"), func() { + It("should CMSIncrBy", Label("cms", "cmsincrby"), func() { + err := client.CMSInitByDim(ctx, "testcms1", 5, 10).Err() + Expect(err).NotTo(HaveOccurred()) + + result, err := client.CMSIncrBy(ctx, "testcms1", "item1", 1, "item2", 2, "item3", 3).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(len(result)).To(BeEquivalentTo(3)) + 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() { + err := client.CMSInitByDim(ctx, "testcms1", 5, 10).Err() + Expect(err).NotTo(HaveOccurred()) + + info, err := client.CMSInfo(ctx, "testcms1").Result() + Expect(err).NotTo(HaveOccurred()) + + Expect(info).To(BeAssignableToTypeOf(redis.CMSInfo{})) + Expect(info.Width).To(BeEquivalentTo(int64(5))) + Expect(info.Depth).To(BeEquivalentTo(int64(10))) + }) + + It("should CMSInitByProb", Label("cms", "cmsinitbyprob"), func() { + err := client.CMSInitByProb(ctx, "testcms1", 0.002, 0.01).Err() + Expect(err).NotTo(HaveOccurred()) + + info, err := client.CMSInfo(ctx, "testcms1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(info).To(BeAssignableToTypeOf(redis.CMSInfo{})) + }) + + It("should CMSMerge, CMSMergeWithWeight and CMSQuery", Label("cms", "cmsmerge", "cmsquery"), func() { + err := client.CMSMerge(ctx, "destCms1", "testcms2", "testcms3").Err() + Expect(err).To(HaveOccurred()) + Expect(err).To(MatchError("CMS: key does not exist")) + + err = client.CMSInitByDim(ctx, "destCms1", 5, 10).Err() + Expect(err).NotTo(HaveOccurred()) + err = client.CMSInitByDim(ctx, "destCms2", 5, 10).Err() + Expect(err).NotTo(HaveOccurred()) + err = client.CMSInitByDim(ctx, "cms1", 2, 20).Err() + Expect(err).NotTo(HaveOccurred()) + err = client.CMSInitByDim(ctx, "cms2", 3, 20).Err() + Expect(err).NotTo(HaveOccurred()) + + err = client.CMSMerge(ctx, "destCms1", "cms1", "cms2").Err() + Expect(err).To(MatchError("CMS: width/depth is not equal")) + + client.Del(ctx, "cms1", "cms2") + + err = client.CMSInitByDim(ctx, "cms1", 5, 10).Err() + Expect(err).NotTo(HaveOccurred()) + err = client.CMSInitByDim(ctx, "cms2", 5, 10).Err() + Expect(err).NotTo(HaveOccurred()) + + client.CMSIncrBy(ctx, "cms1", "item1", 1, "item2", 2) + client.CMSIncrBy(ctx, "cms2", "item2", 2, "item3", 3) + + err = client.CMSMerge(ctx, "destCms1", "cms1", "cms2").Err() + Expect(err).NotTo(HaveOccurred()) + + result, err := client.CMSQuery(ctx, "destCms1", "item1", "item2", "item3").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(len(result)).To(BeEquivalentTo(3)) + Expect(result[0]).To(BeEquivalentTo(int64(1))) + Expect(result[1]).To(BeEquivalentTo(int64(4))) + Expect(result[2]).To(BeEquivalentTo(int64(3))) + + sourceSketches := map[string]int64{ + "cms1": 1, + "cms2": 2, + } + err = client.CMSMergeWithWeight(ctx, "destCms2", sourceSketches).Err() + Expect(err).NotTo(HaveOccurred()) + + result, err = client.CMSQuery(ctx, "destCms2", "item1", "item2", "item3").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(len(result)).To(BeEquivalentTo(3)) + 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() { + It("should TopKReserve, TopKInfo, TopKAdd, TopKQuery, TopKCount, TopKIncrBy, TopKList, TopKListWithCount", Label("topk", "topkreserve", "topkinfo", "topkadd", "topkquery", "topkcount", "topkincrby", "topklist", "topklistwithcount"), func() { + err := client.TopKReserve(ctx, "topk1", 3).Err() + Expect(err).NotTo(HaveOccurred()) + + resultInfo, err := client.TopKInfo(ctx, "topk1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultInfo.K).To(BeEquivalentTo(int64(3))) + + resultAdd, err := client.TopKAdd(ctx, "topk1", "item1", "item2", 3, "item1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(len(resultAdd)).To(BeEquivalentTo(int64(4))) + + resultQuery, err := client.TopKQuery(ctx, "topk1", "item1", "item2", 4, 3).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(len(resultQuery)).To(BeEquivalentTo(4)) + Expect(resultQuery[0]).To(BeTrue()) + Expect(resultQuery[1]).To(BeTrue()) + Expect(resultQuery[2]).To(BeFalse()) + Expect(resultQuery[3]).To(BeTrue()) + + resultCount, err := client.TopKCount(ctx, "topk1", "item1", "item2", "item3").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(len(resultCount)).To(BeEquivalentTo(3)) + Expect(resultCount[0]).To(BeEquivalentTo(int64(2))) + Expect(resultCount[1]).To(BeEquivalentTo(int64(1))) + Expect(resultCount[2]).To(BeEquivalentTo(int64(0))) + + resultIncr, err := client.TopKIncrBy(ctx, "topk1", "item1", 5, "item2", 10).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(len(resultIncr)).To(BeEquivalentTo(2)) + + resultCount, err = client.TopKCount(ctx, "topk1", "item1", "item2", "item3").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(len(resultCount)).To(BeEquivalentTo(3)) + Expect(resultCount[0]).To(BeEquivalentTo(int64(7))) + Expect(resultCount[1]).To(BeEquivalentTo(int64(11))) + Expect(resultCount[2]).To(BeEquivalentTo(int64(0))) + + resultList, err := client.TopKList(ctx, "topk1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(len(resultList)).To(BeEquivalentTo(3)) + Expect(resultList).To(ContainElements("item2", "item1", "3")) + + resultListWithCount, err := client.TopKListWithCount(ctx, "topk1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(len(resultListWithCount)).To(BeEquivalentTo(3)) + Expect(resultListWithCount["3"]).To(BeEquivalentTo(int64(1))) + Expect(resultListWithCount["item1"]).To(BeEquivalentTo(int64(7))) + Expect(resultListWithCount["item2"]).To(BeEquivalentTo(int64(11))) + }) + + It("should TopKReserveWithOptions", Label("topk", "topkreservewithoptions"), func() { + err := client.TopKReserveWithOptions(ctx, "topk1", 3, 1500, 8, 0.5).Err() + Expect(err).NotTo(HaveOccurred()) + + resultInfo, err := client.TopKInfo(ctx, "topk1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultInfo.K).To(BeEquivalentTo(int64(3))) + Expect(resultInfo.Width).To(BeEquivalentTo(int64(1500))) + Expect(resultInfo.Depth).To(BeEquivalentTo(int64(8))) + Expect(resultInfo.Decay).To(BeEquivalentTo(0.5)) + }) + + }) + + Describe("t-digest", Label("tdigest"), func() { + It("should TDigestAdd, TDigestCreate, TDigestInfo, TDigestByRank, TDigestByRevRank, TDigestCDF, TDigestMax, TDigestMin, TDigestQuantile, TDigestRank, TDigestRevRank, TDigestTrimmedMean, TDigestReset, ", Label("tdigest", "tdigestadd", "tdigestcreate", "tdigestinfo", "tdigestbyrank", "tdigestbyrevrank", "tdigestcdf", "tdigestmax", "tdigestmin", "tdigestquantile", "tdigestrank", "tdigestrevrank", "tdigesttrimmedmean", "tdigestreset"), func() { + err := client.TDigestCreate(ctx, "tdigest1").Err() + Expect(err).NotTo(HaveOccurred()) + + info, err := client.TDigestInfo(ctx, "tdigest1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(info.Observations).To(BeEquivalentTo(int64(0))) + + // Test with empty sketch + byRank, err := client.TDigestByRank(ctx, "tdigest1", 0, 1, 2, 3).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(len(byRank)).To(BeEquivalentTo(4)) + + byRevRank, err := client.TDigestByRevRank(ctx, "tdigest1", 0, 1, 2).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(len(byRevRank)).To(BeEquivalentTo(3)) + + cdf, err := client.TDigestCDF(ctx, "tdigest1", 15, 35, 70).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(len(cdf)).To(BeEquivalentTo(3)) + + max, err := client.TDigestMax(ctx, "tdigest1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(math.IsNaN(max)).To(BeTrue()) + + min, err := client.TDigestMin(ctx, "tdigest1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(math.IsNaN(min)).To(BeTrue()) + + quantile, err := client.TDigestQuantile(ctx, "tdigest1", 0.1, 0.2).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(len(quantile)).To(BeEquivalentTo(2)) + + rank, err := client.TDigestRank(ctx, "tdigest1", 10, 20).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(len(rank)).To(BeEquivalentTo(2)) + + revRank, err := client.TDigestRevRank(ctx, "tdigest1", 10, 20).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(len(revRank)).To(BeEquivalentTo(2)) + + trimmedMean, err := client.TDigestTrimmedMean(ctx, "tdigest1", 0.1, 0.6).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(math.IsNaN(trimmedMean)).To(BeTrue()) + + // Add elements + err = client.TDigestAdd(ctx, "tdigest1", 10, 20, 30, 40, 50, 60, 70, 80, 90, 100).Err() + Expect(err).NotTo(HaveOccurred()) + + info, err = client.TDigestInfo(ctx, "tdigest1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(info.Observations).To(BeEquivalentTo(int64(10))) + + byRank, err = client.TDigestByRank(ctx, "tdigest1", 0, 1, 2).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(len(byRank)).To(BeEquivalentTo(3)) + Expect(byRank[0]).To(BeEquivalentTo(float64(10))) + Expect(byRank[1]).To(BeEquivalentTo(float64(20))) + Expect(byRank[2]).To(BeEquivalentTo(float64(30))) + + byRevRank, err = client.TDigestByRevRank(ctx, "tdigest1", 0, 1, 2).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(len(byRevRank)).To(BeEquivalentTo(3)) + Expect(byRevRank[0]).To(BeEquivalentTo(float64(100))) + Expect(byRevRank[1]).To(BeEquivalentTo(float64(90))) + Expect(byRevRank[2]).To(BeEquivalentTo(float64(80))) + + cdf, err = client.TDigestCDF(ctx, "tdigest1", 15, 35, 70).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(len(cdf)).To(BeEquivalentTo(3)) + Expect(cdf[0]).To(BeEquivalentTo(0.1)) + Expect(cdf[1]).To(BeEquivalentTo(0.3)) + Expect(cdf[2]).To(BeEquivalentTo(0.65)) + + max, err = client.TDigestMax(ctx, "tdigest1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(max).To(BeEquivalentTo(float64(100))) + + min, err = client.TDigestMin(ctx, "tdigest1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(min).To(BeEquivalentTo(float64(10))) + + quantile, err = client.TDigestQuantile(ctx, "tdigest1", 0.1, 0.2).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(len(quantile)).To(BeEquivalentTo(2)) + Expect(quantile[0]).To(BeEquivalentTo(float64(20))) + Expect(quantile[1]).To(BeEquivalentTo(float64(30))) + + rank, err = client.TDigestRank(ctx, "tdigest1", 10, 20).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(len(rank)).To(BeEquivalentTo(2)) + Expect(rank[0]).To(BeEquivalentTo(int64(0))) + Expect(rank[1]).To(BeEquivalentTo(int64(1))) + + revRank, err = client.TDigestRevRank(ctx, "tdigest1", 10, 20).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(len(revRank)).To(BeEquivalentTo(2)) + Expect(revRank[0]).To(BeEquivalentTo(int64(9))) + Expect(revRank[1]).To(BeEquivalentTo(int64(8))) + + trimmedMean, err = client.TDigestTrimmedMean(ctx, "tdigest1", 0.1, 0.6).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(trimmedMean).To(BeEquivalentTo(float64(40))) + + reset, err := client.TDigestReset(ctx, "tdigest1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(reset).To(BeEquivalentTo("OK")) + + }) + + It("should TDigestCreateWithCompression", Label("tdigest", "tcreatewithcompression"), func() { + err := client.TDigestCreateWithCompression(ctx, "tdigest1", 2000).Err() + Expect(err).NotTo(HaveOccurred()) + + info, err := client.TDigestInfo(ctx, "tdigest1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(info.Compression).To(BeEquivalentTo(int64(2000))) + }) + + It("should TDigestMerge", Label("tdigest", "tmerge"), func() { + err := client.TDigestCreate(ctx, "tdigest1").Err() + Expect(err).NotTo(HaveOccurred()) + err = client.TDigestAdd(ctx, "tdigest1", 10, 20, 30, 40, 50, 60, 70, 80, 90, 100).Err() + Expect(err).NotTo(HaveOccurred()) + + err = client.TDigestCreate(ctx, "tdigest2").Err() + Expect(err).NotTo(HaveOccurred()) + err = client.TDigestAdd(ctx, "tdigest2", 15, 25, 35, 45, 55, 65, 75, 85, 95, 105).Err() + Expect(err).NotTo(HaveOccurred()) + + err = client.TDigestCreate(ctx, "tdigest3").Err() + Expect(err).NotTo(HaveOccurred()) + err = client.TDigestAdd(ctx, "tdigest3", 50, 60, 70, 80, 90, 100, 110, 120, 130, 140).Err() + Expect(err).NotTo(HaveOccurred()) + + options := &redis.TDigestMergeOptions{ + Compression: 1000, + Override: false, + } + err = client.TDigestMerge(ctx, "tdigest1", options, "tdigest2", "tdigest3").Err() + Expect(err).NotTo(HaveOccurred()) + + info, err := client.TDigestInfo(ctx, "tdigest1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(info.Observations).To(BeEquivalentTo(int64(30))) + Expect(info.Compression).To(BeEquivalentTo(int64(1000))) + + max, err := client.TDigestMax(ctx, "tdigest1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(max).To(BeEquivalentTo(float64(140))) + }) + }) +}) From aba21be8cd68b5039bb658f611546cf49f24a008 Mon Sep 17 00:00:00 2001 From: ofekshenawa <104765379+ofekshenawa@users.noreply.github.com> Date: Wed, 16 Aug 2023 17:06:07 +0300 Subject: [PATCH 005/110] Add Redis Gears support (#2675) * Add gears commands and tests * Fix tfunctionlist and add docstrings * fixes * fixes * add tfcallasync to gearsCmdable --- .github/wordlist.txt | 3 +- command.go | 65 +++++++++++++++++ commands.go | 1 + redis_gears.go | 161 +++++++++++++++++++++++++++++++++++++++++++ redis_gears_test.go | 139 +++++++++++++++++++++++++++++++++++++ 5 files changed, 368 insertions(+), 1 deletion(-) create mode 100644 redis_gears.go create mode 100644 redis_gears_test.go diff --git a/.github/wordlist.txt b/.github/wordlist.txt index 294988d52..32aba8317 100644 --- a/.github/wordlist.txt +++ b/.github/wordlist.txt @@ -52,4 +52,5 @@ uri URI url variadic -RedisStack \ No newline at end of file +RedisStack +RedisGears \ No newline at end of file diff --git a/command.go b/command.go index b6df28fbe..1bd4d5db1 100644 --- a/command.go +++ b/command.go @@ -3713,6 +3713,71 @@ func (cmd *MapStringStringSliceCmd) readReply(rd *proto.Reader) error { return nil } +//----------------------------------------------------------------------- + +type MapStringInterfaceSliceCmd struct { + baseCmd + + val []map[string]interface{} +} + +var _ Cmder = (*MapStringInterfaceSliceCmd)(nil) + +func NewMapStringInterfaceSliceCmd(ctx context.Context, args ...interface{}) *MapStringInterfaceSliceCmd { + return &MapStringInterfaceSliceCmd{ + baseCmd: baseCmd{ + ctx: ctx, + args: args, + }, + } +} + +func (cmd *MapStringInterfaceSliceCmd) SetVal(val []map[string]interface{}) { + cmd.val = val +} + +func (cmd *MapStringInterfaceSliceCmd) Val() []map[string]interface{} { + return cmd.val +} + +func (cmd *MapStringInterfaceSliceCmd) Result() ([]map[string]interface{}, error) { + return cmd.Val(), cmd.Err() +} + +func (cmd *MapStringInterfaceSliceCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *MapStringInterfaceSliceCmd) readReply(rd *proto.Reader) error { + n, err := rd.ReadArrayLen() + if err != nil { + return err + } + + cmd.val = make([]map[string]interface{}, n) + for i := 0; i < n; i++ { + nn, err := rd.ReadMapLen() + if err != nil { + return err + } + cmd.val[i] = make(map[string]interface{}, nn) + for f := 0; f < nn; f++ { + k, err := rd.ReadString() + if err != nil { + return err + } + v, err := rd.ReadReply() + if err != nil { + if err != Nil { + return err + } + } + cmd.val[i][k] = v + } + } + return nil +} + //------------------------------------------------------------------------------ type KeyValuesCmd struct { diff --git a/commands.go b/commands.go index 41f8d3ae3..07c8e2c88 100644 --- a/commands.go +++ b/commands.go @@ -505,6 +505,7 @@ type Cmdable interface { ModuleLoadex(ctx context.Context, conf *ModuleLoadexConfig) *StringCmd + gearsCmdable probabilisticCmdable } diff --git a/redis_gears.go b/redis_gears.go new file mode 100644 index 000000000..5fafea403 --- /dev/null +++ b/redis_gears.go @@ -0,0 +1,161 @@ +package redis + +import ( + "context" + "fmt" + "strings" +) + +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 + TFunctionList(ctx context.Context) *MapStringInterfaceSliceCmd + TFunctionListArgs(ctx context.Context, options *TFunctionListOptions) *MapStringInterfaceSliceCmd + TFCall(ctx context.Context, libName string, funcName string, numKeys int) *Cmd + TFCallArgs(ctx context.Context, libName string, funcName string, numKeys int, options *TFCallOptions) *Cmd + 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 +} + +type TFunctionListOptions struct { + Withcode bool + Verbose int + Library string +} + +type TFCallOptions struct { + Keys []string + Arguments []string +} + +// TFunctionLoad - load a new JavaScript library into Redis. +// For more information - https://redis.io/commands/tfunction-load/ +func (c cmdable) TFunctionLoad(ctx context.Context, lib string) *StatusCmd { + args := []interface{}{"TFUNCTION", "LOAD", lib} + cmd := NewStatusCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) TFunctionLoadArgs(ctx context.Context, lib string, options *TFunctionLoadOptions) *StatusCmd { + args := []interface{}{"TFUNCTION", "LOAD"} + if options != nil { + if options.Replace { + args = append(args, "REPLACE") + } + if options.Config != "" { + args = append(args, "CONFIG", options.Config) + } + } + args = append(args, lib) + cmd := NewStatusCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// TFunctionDelete - delete a JavaScript library from Redis. +// For more information - https://redis.io/commands/tfunction-delete/ +func (c cmdable) TFunctionDelete(ctx context.Context, libName string) *StatusCmd { + args := []interface{}{"TFUNCTION", "DELETE", libName} + cmd := NewStatusCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// TFunctionList - list the functions with additional information about each function. +// For more information - https://redis.io/commands/tfunction-list/ +func (c cmdable) TFunctionList(ctx context.Context) *MapStringInterfaceSliceCmd { + args := []interface{}{"TFUNCTION", "LIST"} + cmd := NewMapStringInterfaceSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) TFunctionListArgs(ctx context.Context, options *TFunctionListOptions) *MapStringInterfaceSliceCmd { + args := []interface{}{"TFUNCTION", "LIST"} + if options != nil { + if options.Withcode { + args = append(args, "WITHCODE") + } + if options.Verbose != 0 { + v := strings.Repeat("v", options.Verbose) + args = append(args, v) + } + if options.Library != "" { + args = append(args, "LIBRARY", options.Library) + + } + } + cmd := NewMapStringInterfaceSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// TFCall - invoke a function. +// For more information - https://redis.io/commands/tfcall/ +func (c cmdable) TFCall(ctx context.Context, libName string, funcName string, numKeys int) *Cmd { + lf := libName + "." + funcName + args := []interface{}{"TFCALL", lf, numKeys} + cmd := NewCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) TFCallArgs(ctx context.Context, libName string, funcName string, numKeys int, options *TFCallOptions) *Cmd { + lf := libName + "." + funcName + args := []interface{}{"TFCALL", lf, numKeys} + 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) + } + } + } + cmd := NewCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// TFCallASYNC - invoke an asynchronous JavaScript function (coroutine). +// For more information - https://redis.io/commands/TFCallASYNC/ +func (c cmdable) TFCallASYNC(ctx context.Context, libName string, funcName string, numKeys int) *Cmd { + lf := fmt.Sprintf("%s.%s", libName, funcName) + args := []interface{}{"TFCALLASYNC", lf, numKeys} + cmd := NewCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) TFCallASYNCArgs(ctx context.Context, libName string, funcName string, numKeys int, options *TFCallOptions) *Cmd { + 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) + } + } + if options.Arguments != nil { + for _, key := range options.Arguments { + + args = append(args, key) + } + } + } + cmd := NewCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} diff --git a/redis_gears_test.go b/redis_gears_test.go new file mode 100644 index 000000000..d02487cb7 --- /dev/null +++ b/redis_gears_test.go @@ -0,0 +1,139 @@ +package redis_test + +import ( + "context" + "fmt" + + . "github.com/bsm/ginkgo/v2" + . "github.com/bsm/gomega" + "github.com/redis/go-redis/v9" +) + +func libCode(libName string) string { + return fmt.Sprintf("#!js api_version=1.0 name=%s\n redis.registerFunction('foo', ()=>{{return 'bar'}})", libName) +} + +func libCodeWithConfig(libName string) string { + lib := `#!js api_version=1.0 name=%s + + var last_update_field_name = "__last_update__" + + if (redis.config.last_update_field_name !== undefined) { + if (typeof redis.config.last_update_field_name != 'string') { + throw "last_update_field_name must be a string"; + } + last_update_field_name = redis.config.last_update_field_name + } + + redis.registerFunction("hset", function(client, key, field, val){ + // get the current time in ms + var curr_time = client.call("time")[0]; + return client.call('hset', key, field, val, last_update_field_name, curr_time); + });` + return fmt.Sprintf(lib, libName) +} + +var _ = Describe("RedisGears commands", Label("gears"), 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()) + }) + + It("should TFunctionLoad, TFunctionLoadArgs and TFunctionDelete ", Label("gears", "tfunctionload"), func() { + + resultAdd, err := client.TFunctionLoad(ctx, libCode("libtflo1")).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultAdd).To(BeEquivalentTo("OK")) + opt := &redis.TFunctionLoadOptions{Replace: true, Config: `{"last_update_field_name":"last_update"}`} + resultAdd, err = client.TFunctionLoadArgs(ctx, libCodeWithConfig("libtflo1"), opt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultAdd).To(BeEquivalentTo("OK")) + resultAdd, err = client.TFunctionDelete(ctx, "libtflo1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultAdd).To(BeEquivalentTo("OK")) + + }) + It("should TFunctionList", Label("gears", "tfunctionlist"), func() { + resultAdd, err := client.TFunctionLoad(ctx, libCode("libtfli1")).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultAdd).To(BeEquivalentTo("OK")) + resultAdd, err = client.TFunctionLoad(ctx, libCode("libtfli2")).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultAdd).To(BeEquivalentTo("OK")) + resultList, err := client.TFunctionList(ctx).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultList[0]["engine"]).To(BeEquivalentTo("js")) + opt := &redis.TFunctionListOptions{Withcode: true, Verbose: 2} + resultListArgs, err := client.TFunctionListArgs(ctx, opt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultListArgs[0]["code"]).To(BeEquivalentTo(libCode("libtfli1"))) + resultAdd, err = client.TFunctionDelete(ctx, "libtfli1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultAdd).To(BeEquivalentTo("OK")) + resultAdd, err = client.TFunctionDelete(ctx, "libtfli2").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultAdd).To(BeEquivalentTo("OK")) + }) + + It("should TFCall", Label("gears", "tfcall"), func() { + var resultAdd interface{} + resultAdd, err := client.TFunctionLoad(ctx, libCode("libtfc1")).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultAdd).To(BeEquivalentTo("OK")) + resultAdd, err = client.TFCall(ctx, "libtfc1", "foo", 0).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultAdd).To(BeEquivalentTo("bar")) + resultAdd, err = client.TFunctionDelete(ctx, "libtfc1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultAdd).To(BeEquivalentTo("OK")) + }) + + It("should TFCallArgs", Label("gears", "tfcallargs"), func() { + var resultAdd interface{} + resultAdd, err := client.TFunctionLoad(ctx, libCode("libtfca1")).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultAdd).To(BeEquivalentTo("OK")) + opt := &redis.TFCallOptions{Arguments: []string{"foo", "bar"}} + resultAdd, err = client.TFCallArgs(ctx, "libtfca1", "foo", 0, opt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultAdd).To(BeEquivalentTo("bar")) + resultAdd, err = client.TFunctionDelete(ctx, "libtfca1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultAdd).To(BeEquivalentTo("OK")) + }) + + It("should TFCallASYNC", Label("gears", "TFCallASYNC"), func() { + var resultAdd interface{} + resultAdd, err := client.TFunctionLoad(ctx, libCode("libtfc1")).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultAdd).To(BeEquivalentTo("OK")) + resultAdd, err = client.TFCallASYNC(ctx, "libtfc1", "foo", 0).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultAdd).To(BeEquivalentTo("bar")) + resultAdd, err = client.TFunctionDelete(ctx, "libtfc1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultAdd).To(BeEquivalentTo("OK")) + }) + + It("should TFCallASYNCArgs", Label("gears", "TFCallASYNCargs"), func() { + var resultAdd interface{} + resultAdd, err := client.TFunctionLoad(ctx, libCode("libtfca1")).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultAdd).To(BeEquivalentTo("OK")) + opt := &redis.TFCallOptions{Arguments: []string{"foo", "bar"}} + resultAdd, err = client.TFCallASYNCArgs(ctx, "libtfca1", "foo", 0, opt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultAdd).To(BeEquivalentTo("bar")) + resultAdd, err = client.TFunctionDelete(ctx, "libtfca1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultAdd).To(BeEquivalentTo("OK")) + }) + +}) From c3098d5f7eba88330638a8b2b3e23d42cd5c9338 Mon Sep 17 00:00:00 2001 From: ofekshenawa <104765379+ofekshenawa@users.noreply.github.com> Date: Wed, 16 Aug 2023 18:30:40 +0300 Subject: [PATCH 006/110] Bump to 9.1.0 (#2676) * Bump to 9.0.6 * Bump to 9.1.0 --- 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 c42bcec8a..4d0f416a6 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.0.5 + github.com/redis/go-redis/v9 v9.1.0 go.uber.org/zap v1.24.0 ) diff --git a/example/hll/go.mod b/example/hll/go.mod index 67fcec46b..6f24b5204 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.0.5 +require github.com/redis/go-redis/v9 v9.1.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 4a4f341cc..ad82d856f 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.0.5 +require github.com/redis/go-redis/v9 v9.1.0 require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect diff --git a/example/otel/go.mod b/example/otel/go.mod index 5f64d05e9..95aca7d1d 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.0.5 - github.com/redis/go-redis/v9 v9.0.5 + github.com/redis/go-redis/extra/redisotel/v9 v9.1.0 + github.com/redis/go-redis/v9 v9.1.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.0.5 // indirect + github.com/redis/go-redis/extra/rediscmd/v9 v9.1.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 4a4f341cc..ad82d856f 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.0.5 +require github.com/redis/go-redis/v9 v9.1.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 6eedff4e8..fd0fabe2a 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.0.5 + github.com/redis/go-redis/v9 v9.1.0 ) require ( diff --git a/extra/rediscensus/go.mod b/extra/rediscensus/go.mod index be64747c5..4a6c6f265 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.0.5 - github.com/redis/go-redis/v9 v9.0.5 + github.com/redis/go-redis/extra/rediscmd/v9 v9.1.0 + github.com/redis/go-redis/v9 v9.1.0 go.opencensus.io v0.24.0 ) diff --git a/extra/rediscmd/go.mod b/extra/rediscmd/go.mod index a31ccc489..324cbf919 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.0.5 + github.com/redis/go-redis/v9 v9.1.0 ) diff --git a/extra/redisotel/go.mod b/extra/redisotel/go.mod index f18d628c1..a03f28ecb 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.0.5 - github.com/redis/go-redis/v9 v9.0.5 + github.com/redis/go-redis/extra/rediscmd/v9 v9.1.0 + github.com/redis/go-redis/v9 v9.1.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 9788fa373..7a36a1a6f 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.0.5 + github.com/redis/go-redis/v9 v9.1.0 ) require ( diff --git a/package.json b/package.json index e26e29141..9fff597cb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "redis", - "version": "9.0.5", + "version": "9.1.0", "main": "index.js", "repository": "git@github.com:redis/go-redis.git", "author": "Vladimir Mihailenco ", diff --git a/version.go b/version.go index e81eb1ab9..d68ab6e08 100644 --- a/version.go +++ b/version.go @@ -2,5 +2,5 @@ package redis // Version is the current release version. func Version() string { - return "9.0.5" + return "9.1.0" } From 5bbd80d94362639106792987d8bb7cfcf9f1b4ee Mon Sep 17 00:00:00 2001 From: ofekshenawa <104765379+ofekshenawa@users.noreply.github.com> Date: Sun, 10 Sep 2023 15:43:22 +0300 Subject: [PATCH 007/110] Skip flaky tests (#2699) --- redis_gears_test.go | 49 ++++++++++++--------------------------------- sentinel_test.go | 3 +++ universal_test.go | 1 + 3 files changed, 17 insertions(+), 36 deletions(-) diff --git a/redis_gears_test.go b/redis_gears_test.go index d02487cb7..1318615e7 100644 --- a/redis_gears_test.go +++ b/redis_gears_test.go @@ -40,6 +40,7 @@ var _ = Describe("RedisGears commands", Label("gears"), func() { BeforeEach(func() { client = redis.NewClient(&redis.Options{Addr: ":6379"}) Expect(client.FlushDB(ctx).Err()).NotTo(HaveOccurred()) + client.TFunctionDelete(ctx, "lib1") }) AfterEach(func() { @@ -48,23 +49,17 @@ var _ = Describe("RedisGears commands", Label("gears"), func() { It("should TFunctionLoad, TFunctionLoadArgs and TFunctionDelete ", Label("gears", "tfunctionload"), func() { - resultAdd, err := client.TFunctionLoad(ctx, libCode("libtflo1")).Result() + resultAdd, err := client.TFunctionLoad(ctx, libCode("lib1")).Result() Expect(err).NotTo(HaveOccurred()) Expect(resultAdd).To(BeEquivalentTo("OK")) opt := &redis.TFunctionLoadOptions{Replace: true, Config: `{"last_update_field_name":"last_update"}`} - resultAdd, err = client.TFunctionLoadArgs(ctx, libCodeWithConfig("libtflo1"), opt).Result() - Expect(err).NotTo(HaveOccurred()) - Expect(resultAdd).To(BeEquivalentTo("OK")) - resultAdd, err = client.TFunctionDelete(ctx, "libtflo1").Result() + 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("libtfli1")).Result() - Expect(err).NotTo(HaveOccurred()) - Expect(resultAdd).To(BeEquivalentTo("OK")) - resultAdd, err = client.TFunctionLoad(ctx, libCode("libtfli2")).Result() + resultAdd, err := client.TFunctionLoad(ctx, libCode("lib1")).Result() Expect(err).NotTo(HaveOccurred()) Expect(resultAdd).To(BeEquivalentTo("OK")) resultList, err := client.TFunctionList(ctx).Result() @@ -73,67 +68,49 @@ var _ = Describe("RedisGears commands", Label("gears"), func() { opt := &redis.TFunctionListOptions{Withcode: true, Verbose: 2} resultListArgs, err := client.TFunctionListArgs(ctx, opt).Result() Expect(err).NotTo(HaveOccurred()) - Expect(resultListArgs[0]["code"]).To(BeEquivalentTo(libCode("libtfli1"))) - resultAdd, err = client.TFunctionDelete(ctx, "libtfli1").Result() - Expect(err).NotTo(HaveOccurred()) - Expect(resultAdd).To(BeEquivalentTo("OK")) - resultAdd, err = client.TFunctionDelete(ctx, "libtfli2").Result() - Expect(err).NotTo(HaveOccurred()) - Expect(resultAdd).To(BeEquivalentTo("OK")) + Expect(resultListArgs[0]["code"]).NotTo(BeEquivalentTo("")) }) It("should TFCall", Label("gears", "tfcall"), func() { var resultAdd interface{} - resultAdd, err := client.TFunctionLoad(ctx, libCode("libtfc1")).Result() + resultAdd, err := client.TFunctionLoad(ctx, libCode("lib1")).Result() Expect(err).NotTo(HaveOccurred()) Expect(resultAdd).To(BeEquivalentTo("OK")) - resultAdd, err = client.TFCall(ctx, "libtfc1", "foo", 0).Result() + resultAdd, err = client.TFCall(ctx, "lib1", "foo", 0).Result() Expect(err).NotTo(HaveOccurred()) Expect(resultAdd).To(BeEquivalentTo("bar")) - resultAdd, err = client.TFunctionDelete(ctx, "libtfc1").Result() - Expect(err).NotTo(HaveOccurred()) - Expect(resultAdd).To(BeEquivalentTo("OK")) }) It("should TFCallArgs", Label("gears", "tfcallargs"), func() { var resultAdd interface{} - resultAdd, err := client.TFunctionLoad(ctx, libCode("libtfca1")).Result() + resultAdd, err := client.TFunctionLoad(ctx, libCode("lib1")).Result() Expect(err).NotTo(HaveOccurred()) Expect(resultAdd).To(BeEquivalentTo("OK")) opt := &redis.TFCallOptions{Arguments: []string{"foo", "bar"}} - resultAdd, err = client.TFCallArgs(ctx, "libtfca1", "foo", 0, opt).Result() + resultAdd, err = client.TFCallArgs(ctx, "lib1", "foo", 0, opt).Result() Expect(err).NotTo(HaveOccurred()) Expect(resultAdd).To(BeEquivalentTo("bar")) - resultAdd, err = client.TFunctionDelete(ctx, "libtfca1").Result() - Expect(err).NotTo(HaveOccurred()) - Expect(resultAdd).To(BeEquivalentTo("OK")) }) It("should TFCallASYNC", Label("gears", "TFCallASYNC"), func() { var resultAdd interface{} - resultAdd, err := client.TFunctionLoad(ctx, libCode("libtfc1")).Result() + resultAdd, err := client.TFunctionLoad(ctx, libCode("lib1")).Result() Expect(err).NotTo(HaveOccurred()) Expect(resultAdd).To(BeEquivalentTo("OK")) - resultAdd, err = client.TFCallASYNC(ctx, "libtfc1", "foo", 0).Result() + resultAdd, err = client.TFCallASYNC(ctx, "lib1", "foo", 0).Result() Expect(err).NotTo(HaveOccurred()) Expect(resultAdd).To(BeEquivalentTo("bar")) - resultAdd, err = client.TFunctionDelete(ctx, "libtfc1").Result() - Expect(err).NotTo(HaveOccurred()) - Expect(resultAdd).To(BeEquivalentTo("OK")) }) It("should TFCallASYNCArgs", Label("gears", "TFCallASYNCargs"), func() { var resultAdd interface{} - resultAdd, err := client.TFunctionLoad(ctx, libCode("libtfca1")).Result() + resultAdd, err := client.TFunctionLoad(ctx, libCode("lib1")).Result() Expect(err).NotTo(HaveOccurred()) Expect(resultAdd).To(BeEquivalentTo("OK")) opt := &redis.TFCallOptions{Arguments: []string{"foo", "bar"}} - resultAdd, err = client.TFCallASYNCArgs(ctx, "libtfca1", "foo", 0, opt).Result() + resultAdd, err = client.TFCallASYNCArgs(ctx, "lib1", "foo", 0, opt).Result() Expect(err).NotTo(HaveOccurred()) Expect(resultAdd).To(BeEquivalentTo("bar")) - resultAdd, err = client.TFunctionDelete(ctx, "libtfca1").Result() - Expect(err).NotTo(HaveOccurred()) - Expect(resultAdd).To(BeEquivalentTo("OK")) }) }) diff --git a/sentinel_test.go b/sentinel_test.go index 705b6a094..4c013f05f 100644 --- a/sentinel_test.go +++ b/sentinel_test.go @@ -241,6 +241,7 @@ var _ = Describe("NewFailoverClusterClient", func() { }) It("should facilitate failover", func() { + Skip("Flaky Test") // Set value. err := client.Set(ctx, "foo", "master", 0).Err() Expect(err).NotTo(HaveOccurred()) @@ -283,6 +284,7 @@ var _ = Describe("NewFailoverClusterClient", func() { }) It("should sentinel cluster client setname", func() { + Skip("Flaky Test") err := client.ForEachShard(ctx, func(ctx context.Context, c *redis.Client) error { return c.Ping(ctx).Err() }) @@ -297,6 +299,7 @@ var _ = Describe("NewFailoverClusterClient", func() { }) It("should sentinel cluster PROTO 3", func() { + Skip("Flaky Test") _ = client.ForEachShard(ctx, func(ctx context.Context, c *redis.Client) error { val, err := client.Do(ctx, "HELLO").Result() Expect(err).NotTo(HaveOccurred()) diff --git a/universal_test.go b/universal_test.go index c8b5c7b92..cda0ce53d 100644 --- a/universal_test.go +++ b/universal_test.go @@ -17,6 +17,7 @@ var _ = Describe("UniversalClient", func() { }) It("should connect to failover servers", func() { + Skip("Flaky Test") client = redis.NewUniversalClient(&redis.UniversalOptions{ MasterName: sentinelName, Addrs: sentinelAddrs, From 017466b6cc46ae6878f0227e5c6b00e0be8b3eb8 Mon Sep 17 00:00:00 2001 From: ofekshenawa <104765379+ofekshenawa@users.noreply.github.com> Date: Mon, 11 Sep 2023 14:51:31 +0300 Subject: [PATCH 008/110] Add Redis Timeseries support (#2688) * Add Redis Timeseries support * Small fixes * Make timeseries interface public * remove bloom renaming --- .github/wordlist.txt | 3 +- command.go | 59 +++ commands.go | 1 + redis_timeseries.go | 929 ++++++++++++++++++++++++++++++++++++++ redis_timeseries_test.go | 951 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 1942 insertions(+), 1 deletion(-) create mode 100644 redis_timeseries.go create mode 100644 redis_timeseries_test.go diff --git a/.github/wordlist.txt b/.github/wordlist.txt index 32aba8317..48d3fc487 100644 --- a/.github/wordlist.txt +++ b/.github/wordlist.txt @@ -53,4 +53,5 @@ URI url variadic RedisStack -RedisGears \ No newline at end of file +RedisGears +RedisTimeseries \ No newline at end of file diff --git a/command.go b/command.go index 1bd4d5db1..549322a91 100644 --- a/command.go +++ b/command.go @@ -1351,6 +1351,65 @@ func (cmd *MapStringIntCmd) readReply(rd *proto.Reader) error { return nil } +// ------------------------------------------------------------------------------ +type MapStringSliceInterfaceCmd struct { + baseCmd + val map[string][]interface{} +} + +func NewMapStringSliceInterfaceCmd(ctx context.Context, args ...interface{}) *MapStringSliceInterfaceCmd { + return &MapStringSliceInterfaceCmd{ + baseCmd: baseCmd{ + ctx: ctx, + args: args, + }, + } +} + +func (cmd *MapStringSliceInterfaceCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *MapStringSliceInterfaceCmd) SetVal(val map[string][]interface{}) { + cmd.val = val +} + +func (cmd *MapStringSliceInterfaceCmd) Result() (map[string][]interface{}, error) { + return cmd.val, cmd.err +} + +func (cmd *MapStringSliceInterfaceCmd) Val() map[string][]interface{} { + return cmd.val +} + +func (cmd *MapStringSliceInterfaceCmd) readReply(rd *proto.Reader) (err error) { + n, err := rd.ReadMapLen() + if err != nil { + return err + } + cmd.val = make(map[string][]interface{}, n) + for i := 0; i < n; i++ { + k, err := rd.ReadString() + if err != nil { + return err + } + nn, err := rd.ReadArrayLen() + if err != nil { + return err + } + cmd.val[k] = make([]interface{}, nn) + for j := 0; j < nn; j++ { + value, err := rd.ReadReply() + if err != nil { + return err + } + cmd.val[k][j] = value + } + } + + return nil +} + //------------------------------------------------------------------------------ type StringStructMapCmd struct { diff --git a/commands.go b/commands.go index 07c8e2c88..ce383025e 100644 --- a/commands.go +++ b/commands.go @@ -507,6 +507,7 @@ type Cmdable interface { gearsCmdable probabilisticCmdable + TimeseriesCmdable } type StatefulCmdable interface { diff --git a/redis_timeseries.go b/redis_timeseries.go new file mode 100644 index 000000000..5ead2fa5f --- /dev/null +++ b/redis_timeseries.go @@ -0,0 +1,929 @@ +package redis + +import ( + "context" + "strconv" + + "github.com/redis/go-redis/v9/internal/proto" +) + +type TimeseriesCmdable interface { + TSAdd(ctx context.Context, key string, timestamp interface{}, value float64) *IntCmd + TSAddWithArgs(ctx context.Context, key string, timestamp interface{}, value float64, options *TSOptions) *IntCmd + TSCreate(ctx context.Context, key string) *StatusCmd + TSCreateWithArgs(ctx context.Context, key string, options *TSOptions) *StatusCmd + TSAlter(ctx context.Context, key string, options *TSAlterOptions) *StatusCmd + TSCreateRule(ctx context.Context, sourceKey string, destKey string, aggregator Aggregator, bucketDuration int) *StatusCmd + TSCreateRuleWithArgs(ctx context.Context, sourceKey string, destKey string, aggregator Aggregator, bucketDuration int, options *TSCreateRuleOptions) *StatusCmd + TSIncrBy(ctx context.Context, Key string, timestamp float64) *IntCmd + TSIncrByWithArgs(ctx context.Context, key string, timestamp float64, options *TSIncrDecrOptions) *IntCmd + TSDecrBy(ctx context.Context, Key string, timestamp float64) *IntCmd + TSDecrByWithArgs(ctx context.Context, key string, timestamp float64, options *TSIncrDecrOptions) *IntCmd + TSDel(ctx context.Context, Key string, fromTimestamp int, toTimestamp int) *IntCmd + TSDeleteRule(ctx context.Context, sourceKey string, destKey string) *StatusCmd + TSGet(ctx context.Context, key string) *TSTimestampValueCmd + TSGetWithArgs(ctx context.Context, key string, options *TSGetOptions) *TSTimestampValueCmd + TSInfo(ctx context.Context, key string) *MapStringInterfaceCmd + TSInfoWithArgs(ctx context.Context, key string, options *TSInfoOptions) *MapStringInterfaceCmd + TSMAdd(ctx context.Context, ktvSlices [][]interface{}) *IntSliceCmd + TSQueryIndex(ctx context.Context, filterExpr []string) *StringSliceCmd + TSRevRange(ctx context.Context, key string, fromTimestamp int, toTimestamp int) *TSTimestampValueSliceCmd + TSRevRangeWithArgs(ctx context.Context, key string, fromTimestamp int, toTimestamp int, options *TSRevRangeOptions) *TSTimestampValueSliceCmd + TSRange(ctx context.Context, key string, fromTimestamp int, toTimestamp int) *TSTimestampValueSliceCmd + TSRangeWithArgs(ctx context.Context, key string, fromTimestamp int, toTimestamp int, options *TSRangeOptions) *TSTimestampValueSliceCmd + TSMRange(ctx context.Context, fromTimestamp int, toTimestamp int, filterExpr []string) *MapStringSliceInterfaceCmd + TSMRangeWithArgs(ctx context.Context, fromTimestamp int, toTimestamp int, filterExpr []string, options *TSMRangeOptions) *MapStringSliceInterfaceCmd + TSMRevRange(ctx context.Context, fromTimestamp int, toTimestamp int, filterExpr []string) *MapStringSliceInterfaceCmd + TSMRevRangeWithArgs(ctx context.Context, fromTimestamp int, toTimestamp int, filterExpr []string, options *TSMRevRangeOptions) *MapStringSliceInterfaceCmd + TSMGet(ctx context.Context, filters []string) *MapStringSliceInterfaceCmd + TSMGetWithArgs(ctx context.Context, filters []string, options *TSMGetOptions) *MapStringSliceInterfaceCmd +} + +type TSOptions struct { + Retention int + ChunkSize int + Encoding string + DuplicatePolicy string + Labels map[string]string +} +type TSIncrDecrOptions struct { + Timestamp int64 + Retention int + ChunkSize int + Uncompressed bool + Labels map[string]string +} + +type TSAlterOptions struct { + Retention int + ChunkSize int + DuplicatePolicy string + Labels map[string]string +} + +type TSCreateRuleOptions struct { + alignTimestamp int64 +} + +type TSGetOptions struct { + Latest bool +} + +type TSInfoOptions struct { + Debug bool +} +type Aggregator int + +const ( + Invalid = Aggregator(iota) + Avg + Sum + Min + Max + Range + Count + First + Last + StdP + StdS + VarP + VarS + Twa +) + +func (a Aggregator) String() string { + switch a { + case Invalid: + return "" + case Avg: + return "AVG" + case Sum: + return "SUM" + case Min: + return "MIN" + case Max: + return "MAX" + case Range: + return "RANGE" + case Count: + return "COUNT" + case First: + return "FIRST" + case Last: + return "LAST" + case StdP: + return "STD.P" + case StdS: + return "STD.S" + case VarP: + return "VAR.P" + case VarS: + return "VAR.S" + case Twa: + return "TWA" + default: + return "" + } +} + +type TSRangeOptions struct { + Latest bool + FilterByTS []int + FilterByValue []int + Count int + Align interface{} + Aggregator Aggregator + BucketDuration int + BucketTimestamp interface{} + Empty bool +} + +type TSRevRangeOptions struct { + Latest bool + FilterByTS []int + FilterByValue []int + Count int + Align interface{} + Aggregator Aggregator + BucketDuration int + BucketTimestamp interface{} + Empty bool +} + +type TSMRangeOptions struct { + Latest bool + FilterByTS []int + FilterByValue []int + WithLabels bool + SelectedLabels []interface{} + Count int + Align interface{} + Aggregator Aggregator + BucketDuration int + BucketTimestamp interface{} + Empty bool + GroupByLabel interface{} + Reducer interface{} +} + +type TSMRevRangeOptions struct { + Latest bool + FilterByTS []int + FilterByValue []int + WithLabels bool + SelectedLabels []interface{} + Count int + Align interface{} + Aggregator Aggregator + BucketDuration int + BucketTimestamp interface{} + Empty bool + GroupByLabel interface{} + Reducer interface{} +} + +type TSMGetOptions struct { + Latest bool + WithLabels bool + SelectedLabels []interface{} +} + +// TSAdd - Adds one or more observations to a t-digest sketch. +// For more information - https://redis.io/commands/ts.add/ +func (c cmdable) TSAdd(ctx context.Context, key string, timestamp interface{}, value float64) *IntCmd { + args := []interface{}{"TS.ADD", key, timestamp, value} + cmd := NewIntCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// TSAddWithArgs - Adds one or more observations to a t-digest sketch. +// This function also allows for specifying additional options such as: +// Retention, ChunkSize, Encoding, DuplicatePolicy and Labels. +// For more information - https://redis.io/commands/ts.add/ +func (c cmdable) TSAddWithArgs(ctx context.Context, key string, timestamp interface{}, value float64, options *TSOptions) *IntCmd { + args := []interface{}{"TS.ADD", key, timestamp, value} + if options != nil { + if options.Retention != 0 { + args = append(args, "RETENTION", options.Retention) + } + if options.ChunkSize != 0 { + args = append(args, "CHUNK_SIZE", options.ChunkSize) + + } + if options.Encoding != "" { + args = append(args, "ENCODING", options.Encoding) + } + + if options.DuplicatePolicy != "" { + args = append(args, "DUPLICATE_POLICY", options.DuplicatePolicy) + } + if options.Labels != nil { + args = append(args, "LABELS") + for label, value := range options.Labels { + args = append(args, label, value) + } + } + } + cmd := NewIntCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// TSCreate - Creates a new time-series key. +// For more information - https://redis.io/commands/ts.create/ +func (c cmdable) TSCreate(ctx context.Context, key string) *StatusCmd { + args := []interface{}{"TS.CREATE", key} + cmd := NewStatusCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// TSCreateWithArgs - Creates a new time-series key with additional options. +// This function allows for specifying additional options such as: +// Retention, ChunkSize, Encoding, DuplicatePolicy and Labels. +// For more information - https://redis.io/commands/ts.create/ +func (c cmdable) TSCreateWithArgs(ctx context.Context, key string, options *TSOptions) *StatusCmd { + args := []interface{}{"TS.CREATE", key} + if options != nil { + if options.Retention != 0 { + args = append(args, "RETENTION", options.Retention) + } + if options.ChunkSize != 0 { + args = append(args, "CHUNK_SIZE", options.ChunkSize) + + } + if options.Encoding != "" { + args = append(args, "ENCODING", options.Encoding) + } + + if options.DuplicatePolicy != "" { + args = append(args, "DUPLICATE_POLICY", options.DuplicatePolicy) + } + if options.Labels != nil { + args = append(args, "LABELS") + for label, value := range options.Labels { + args = append(args, label, value) + + } + } + } + cmd := NewStatusCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// TSAlter - Alters an existing time-series key with additional options. +// This function allows for specifying additional options such as: +// Retention, ChunkSize and DuplicatePolicy. +// For more information - https://redis.io/commands/ts.alter/ +func (c cmdable) TSAlter(ctx context.Context, key string, options *TSAlterOptions) *StatusCmd { + args := []interface{}{"TS.ALTER", key} + if options != nil { + if options.Retention != 0 { + args = append(args, "RETENTION", options.Retention) + } + if options.ChunkSize != 0 { + args = append(args, "CHUNK_SIZE", options.ChunkSize) + + } + if options.DuplicatePolicy != "" { + args = append(args, "DUPLICATE_POLICY", options.DuplicatePolicy) + } + if options.Labels != nil { + args = append(args, "LABELS") + for label, value := range options.Labels { + args = append(args, label, value) + + } + } + } + cmd := NewStatusCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// TSCreateRule - Creates a compaction rule from sourceKey to destKey. +// For more information - https://redis.io/commands/ts.createrule/ +func (c cmdable) TSCreateRule(ctx context.Context, sourceKey string, destKey string, aggregator Aggregator, bucketDuration int) *StatusCmd { + args := []interface{}{"TS.CREATERULE", sourceKey, destKey, "AGGREGATION", aggregator.String(), bucketDuration} + cmd := NewStatusCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// TSCreateRuleWithArgs - Creates a compaction rule from sourceKey to destKey with additional option. +// This function allows for specifying additional option such as: +// alignTimestamp. +// For more information - https://redis.io/commands/ts.createrule/ +func (c cmdable) TSCreateRuleWithArgs(ctx context.Context, sourceKey string, destKey string, aggregator Aggregator, bucketDuration int, options *TSCreateRuleOptions) *StatusCmd { + args := []interface{}{"TS.CREATERULE", sourceKey, destKey, "AGGREGATION", aggregator.String(), bucketDuration} + if options != nil { + if options.alignTimestamp != 0 { + args = append(args, options.alignTimestamp) + } + } + cmd := NewStatusCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// TSIncrBy - Increments the value of a time-series key by the specified timestamp. +// For more information - https://redis.io/commands/ts.incrby/ +func (c cmdable) TSIncrBy(ctx context.Context, Key string, timestamp float64) *IntCmd { + args := []interface{}{"TS.INCRBY", Key, timestamp} + cmd := NewIntCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// TSIncrByWithArgs - Increments the value of a time-series key by the specified timestamp with additional options. +// This function allows for specifying additional options such as: +// Timestamp, Retention, ChunkSize, Uncompressed and Labels. +// For more information - https://redis.io/commands/ts.incrby/ +func (c cmdable) TSIncrByWithArgs(ctx context.Context, key string, timestamp float64, options *TSIncrDecrOptions) *IntCmd { + args := []interface{}{"TS.INCRBY", key, timestamp} + if options != nil { + if options.Timestamp != 0 { + args = append(args, "TIMESTAMP", options.Timestamp) + } + if options.Retention != 0 { + args = append(args, "RETENTION", options.Retention) + } + if options.ChunkSize != 0 { + args = append(args, "CHUNK_SIZE", options.ChunkSize) + + } + if options.Uncompressed { + args = append(args, "UNCOMPRESSED") + } + if options.Labels != nil { + args = append(args, "LABELS") + for label, value := range options.Labels { + args = append(args, label, value) + + } + } + } + cmd := NewIntCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// TSDecrBy - Decrements the value of a time-series key by the specified timestamp. +// For more information - https://redis.io/commands/ts.decrby/ +func (c cmdable) TSDecrBy(ctx context.Context, Key string, timestamp float64) *IntCmd { + args := []interface{}{"TS.DECRBY", Key, timestamp} + cmd := NewIntCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// TSDecrByWithArgs - Decrements the value of a time-series key by the specified timestamp with additional options. +// This function allows for specifying additional options such as: +// Timestamp, Retention, ChunkSize, Uncompressed and Labels. +// For more information - https://redis.io/commands/ts.decrby/ +func (c cmdable) TSDecrByWithArgs(ctx context.Context, key string, timestamp float64, options *TSIncrDecrOptions) *IntCmd { + args := []interface{}{"TS.DECRBY", key, timestamp} + if options != nil { + if options.Timestamp != 0 { + args = append(args, "TIMESTAMP", options.Timestamp) + } + if options.Retention != 0 { + args = append(args, "RETENTION", options.Retention) + } + if options.ChunkSize != 0 { + args = append(args, "CHUNK_SIZE", options.ChunkSize) + + } + if options.Uncompressed { + args = append(args, "UNCOMPRESSED") + } + if options.Labels != nil { + args = append(args, "LABELS") + for label, value := range options.Labels { + args = append(args, label, value) + + } + } + } + cmd := NewIntCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// TSDel - Deletes a range of samples from a time-series key. +// For more information - https://redis.io/commands/ts.del/ +func (c cmdable) TSDel(ctx context.Context, Key string, fromTimestamp int, toTimestamp int) *IntCmd { + args := []interface{}{"TS.DEL", Key, fromTimestamp, toTimestamp} + cmd := NewIntCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// TSDeleteRule - Deletes a compaction rule from sourceKey to destKey. +// For more information - https://redis.io/commands/ts.deleterule/ +func (c cmdable) TSDeleteRule(ctx context.Context, sourceKey string, destKey string) *StatusCmd { + args := []interface{}{"TS.DELETERULE", sourceKey, destKey} + cmd := NewStatusCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// TSGetWithArgs - Gets the last sample of a time-series key with additional option. +// This function allows for specifying additional option such as: +// Latest. +// For more information - https://redis.io/commands/ts.get/ +func (c cmdable) TSGetWithArgs(ctx context.Context, key string, options *TSGetOptions) *TSTimestampValueCmd { + args := []interface{}{"TS.GET", key} + if options != nil { + if options.Latest { + args = append(args, "LATEST") + } + } + cmd := newTSTimestampValueCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// TSGet - Gets the last sample of a time-series key. +// For more information - https://redis.io/commands/ts.get/ +func (c cmdable) TSGet(ctx context.Context, key string) *TSTimestampValueCmd { + args := []interface{}{"TS.GET", key} + cmd := newTSTimestampValueCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +type TSTimestampValue struct { + Timestamp int64 + Value float64 +} +type TSTimestampValueCmd struct { + baseCmd + val TSTimestampValue +} + +func newTSTimestampValueCmd(ctx context.Context, args ...interface{}) *TSTimestampValueCmd { + return &TSTimestampValueCmd{ + baseCmd: baseCmd{ + ctx: ctx, + args: args, + }, + } +} + +func (cmd *TSTimestampValueCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *TSTimestampValueCmd) SetVal(val TSTimestampValue) { + cmd.val = val +} + +func (cmd *TSTimestampValueCmd) Result() (TSTimestampValue, error) { + return cmd.val, cmd.err +} + +func (cmd *TSTimestampValueCmd) Val() TSTimestampValue { + return cmd.val +} + +func (cmd *TSTimestampValueCmd) readReply(rd *proto.Reader) (err error) { + n, err := rd.ReadMapLen() + if err != nil { + return err + } + cmd.val = TSTimestampValue{} + for i := 0; i < n; i++ { + timestamp, err := rd.ReadInt() + if err != nil { + return err + } + value, err := rd.ReadString() + if err != nil { + return err + } + cmd.val.Timestamp = timestamp + cmd.val.Value, err = strconv.ParseFloat(value, 64) + if err != nil { + return err + } + } + + return nil +} + +// TSInfo - Returns information about a time-series key. +// For more information - https://redis.io/commands/ts.info/ +func (c cmdable) TSInfo(ctx context.Context, key string) *MapStringInterfaceCmd { + args := []interface{}{"TS.INFO", key} + cmd := NewMapStringInterfaceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// TSInfoWithArgs - Returns information about a time-series key with additional option. +// This function allows for specifying additional option such as: +// Debug. +// For more information - https://redis.io/commands/ts.info/ +func (c cmdable) TSInfoWithArgs(ctx context.Context, key string, options *TSInfoOptions) *MapStringInterfaceCmd { + args := []interface{}{"TS.INFO", key} + if options != nil { + if options.Debug { + args = append(args, "DEBUG") + } + } + cmd := NewMapStringInterfaceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// TSMAdd - Adds multiple samples to multiple time-series keys. +// For more information - https://redis.io/commands/ts.madd/ +func (c cmdable) TSMAdd(ctx context.Context, ktvSlices [][]interface{}) *IntSliceCmd { + args := []interface{}{"TS.MADD"} + for _, ktv := range ktvSlices { + args = append(args, ktv...) + } + cmd := NewIntSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// TSQueryIndex - Returns all the keys matching the filter expression. +// For more information - https://redis.io/commands/ts.queryindex/ +func (c cmdable) TSQueryIndex(ctx context.Context, filterExpr []string) *StringSliceCmd { + args := []interface{}{"TS.QUERYINDEX"} + for _, f := range filterExpr { + args = append(args, f) + } + cmd := NewStringSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// TSRevRange - Returns a range of samples from a time-series key in reverse order. +// For more information - https://redis.io/commands/ts.revrange/ +func (c cmdable) TSRevRange(ctx context.Context, key string, fromTimestamp int, toTimestamp int) *TSTimestampValueSliceCmd { + args := []interface{}{"TS.REVRANGE", key, fromTimestamp, toTimestamp} + cmd := newTSTimestampValueSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// TSRevRangeWithArgs - Returns a range of samples from a time-series key in reverse order with additional options. +// This function allows for specifying additional options such as: +// Latest, FilterByTS, FilterByValue, Count, Align, Aggregator, +// BucketDuration, BucketTimestamp and Empty. +// For more information - https://redis.io/commands/ts.revrange/ +func (c cmdable) TSRevRangeWithArgs(ctx context.Context, key string, fromTimestamp int, toTimestamp int, options *TSRevRangeOptions) *TSTimestampValueSliceCmd { + args := []interface{}{"TS.REVRANGE", key, fromTimestamp, toTimestamp} + if options != nil { + if options.Latest { + args = append(args, "LATEST") + } + if options.FilterByTS != nil { + args = append(args, "FILTER_BY_TS") + for _, f := range options.FilterByTS { + args = append(args, f) + } + } + if options.FilterByValue != nil { + args = append(args, "FILTER_BY_VALUE") + for _, f := range options.FilterByValue { + args = append(args, f) + } + } + if options.Count != 0 { + args = append(args, "COUNT", options.Count) + } + if options.Align != nil { + args = append(args, "ALIGN", options.Align) + } + if options.Aggregator != 0 { + args = append(args, "AGGREGATION", options.Aggregator.String()) + } + if options.BucketDuration != 0 { + args = append(args, options.BucketDuration) + } + if options.BucketTimestamp != nil { + args = append(args, "BUCKETTIMESTAMP", options.BucketTimestamp) + } + if options.Empty { + args = append(args, "EMPTY") + } + } + cmd := newTSTimestampValueSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// TSRange - Returns a range of samples from a time-series key. +// For more information - https://redis.io/commands/ts.range/ +func (c cmdable) TSRange(ctx context.Context, key string, fromTimestamp int, toTimestamp int) *TSTimestampValueSliceCmd { + args := []interface{}{"TS.RANGE", key, fromTimestamp, toTimestamp} + cmd := newTSTimestampValueSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// TSRangeWithArgs - Returns a range of samples from a time-series key with additional options. +// This function allows for specifying additional options such as: +// Latest, FilterByTS, FilterByValue, Count, Align, Aggregator, +// BucketDuration, BucketTimestamp and Empty. +// For more information - https://redis.io/commands/ts.range/ +func (c cmdable) TSRangeWithArgs(ctx context.Context, key string, fromTimestamp int, toTimestamp int, options *TSRangeOptions) *TSTimestampValueSliceCmd { + args := []interface{}{"TS.RANGE", key, fromTimestamp, toTimestamp} + if options != nil { + if options.Latest { + args = append(args, "LATEST") + } + if options.FilterByTS != nil { + args = append(args, "FILTER_BY_TS") + for _, f := range options.FilterByTS { + args = append(args, f) + } + } + if options.FilterByValue != nil { + args = append(args, "FILTER_BY_VALUE") + for _, f := range options.FilterByValue { + args = append(args, f) + } + } + if options.Count != 0 { + args = append(args, "COUNT", options.Count) + } + if options.Align != nil { + args = append(args, "ALIGN", options.Align) + } + if options.Aggregator != 0 { + args = append(args, "AGGREGATION", options.Aggregator.String()) + } + if options.BucketDuration != 0 { + args = append(args, options.BucketDuration) + } + if options.BucketTimestamp != nil { + args = append(args, "BUCKETTIMESTAMP", options.BucketTimestamp) + } + if options.Empty { + args = append(args, "EMPTY") + } + } + cmd := newTSTimestampValueSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +type TSTimestampValueSliceCmd struct { + baseCmd + val []TSTimestampValue +} + +func newTSTimestampValueSliceCmd(ctx context.Context, args ...interface{}) *TSTimestampValueSliceCmd { + return &TSTimestampValueSliceCmd{ + baseCmd: baseCmd{ + ctx: ctx, + args: args, + }, + } +} + +func (cmd *TSTimestampValueSliceCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *TSTimestampValueSliceCmd) SetVal(val []TSTimestampValue) { + cmd.val = val +} + +func (cmd *TSTimestampValueSliceCmd) Result() ([]TSTimestampValue, error) { + return cmd.val, cmd.err +} + +func (cmd *TSTimestampValueSliceCmd) Val() []TSTimestampValue { + return cmd.val +} + +func (cmd *TSTimestampValueSliceCmd) readReply(rd *proto.Reader) (err error) { + n, err := rd.ReadArrayLen() + if err != nil { + return err + } + cmd.val = make([]TSTimestampValue, n) + for i := 0; i < n; i++ { + _, _ = rd.ReadArrayLen() + timestamp, err := rd.ReadInt() + if err != nil { + return err + } + value, err := rd.ReadString() + if err != nil { + return err + } + cmd.val[i].Timestamp = timestamp + cmd.val[i].Value, err = strconv.ParseFloat(value, 64) + if err != nil { + return err + } + } + + return nil +} + +// TSMRange - Returns a range of samples from multiple time-series keys. +// For more information - https://redis.io/commands/ts.mrange/ +func (c cmdable) TSMRange(ctx context.Context, fromTimestamp int, toTimestamp int, filterExpr []string) *MapStringSliceInterfaceCmd { + args := []interface{}{"TS.MRANGE", fromTimestamp, toTimestamp, "FILTER"} + for _, f := range filterExpr { + args = append(args, f) + } + cmd := NewMapStringSliceInterfaceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// TSMRangeWithArgs - Returns a range of samples from multiple time-series keys with additional options. +// This function allows for specifying additional options such as: +// Latest, FilterByTS, FilterByValue, WithLabels, SelectedLabels, +// Count, Align, Aggregator, BucketDuration, BucketTimestamp, +// Empty, GroupByLabel and Reducer. +// For more information - https://redis.io/commands/ts.mrange/ +func (c cmdable) TSMRangeWithArgs(ctx context.Context, fromTimestamp int, toTimestamp int, filterExpr []string, options *TSMRangeOptions) *MapStringSliceInterfaceCmd { + args := []interface{}{"TS.MRANGE", fromTimestamp, toTimestamp} + if options != nil { + if options.Latest { + args = append(args, "LATEST") + } + if options.FilterByTS != nil { + args = append(args, "FILTER_BY_TS") + for _, f := range options.FilterByTS { + args = append(args, f) + } + } + if options.FilterByValue != nil { + args = append(args, "FILTER_BY_VALUE") + for _, f := range options.FilterByValue { + args = append(args, f) + } + } + if options.WithLabels { + args = append(args, "WITHLABELS") + } + if options.SelectedLabels != nil { + args = append(args, "SELECTED_LABELS") + args = append(args, options.SelectedLabels...) + } + if options.Count != 0 { + args = append(args, "COUNT", options.Count) + } + if options.Align != nil { + args = append(args, "ALIGN", options.Align) + } + if options.Aggregator != 0 { + args = append(args, "AGGREGATION", options.Aggregator.String()) + } + if options.BucketDuration != 0 { + args = append(args, options.BucketDuration) + } + if options.BucketTimestamp != nil { + args = append(args, "BUCKETTIMESTAMP", options.BucketTimestamp) + } + if options.Empty { + args = append(args, "EMPTY") + } + } + args = append(args, "FILTER") + for _, f := range filterExpr { + args = append(args, f) + } + if options != nil { + if options.GroupByLabel != nil { + args = append(args, "GROUPBY", options.GroupByLabel) + } + if options.Reducer != nil { + args = append(args, "REDUCE", options.Reducer) + } + } + cmd := NewMapStringSliceInterfaceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// TSMRevRange - Returns a range of samples from multiple time-series keys in reverse order. +// For more information - https://redis.io/commands/ts.mrevrange/ +func (c cmdable) TSMRevRange(ctx context.Context, fromTimestamp int, toTimestamp int, filterExpr []string) *MapStringSliceInterfaceCmd { + args := []interface{}{"TS.MREVRANGE", fromTimestamp, toTimestamp, "FILTER"} + for _, f := range filterExpr { + args = append(args, f) + } + cmd := NewMapStringSliceInterfaceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// TSMRevRangeWithArgs - Returns a range of samples from multiple time-series keys in reverse order with additional options. +// This function allows for specifying additional options such as: +// Latest, FilterByTS, FilterByValue, WithLabels, SelectedLabels, +// Count, Align, Aggregator, BucketDuration, BucketTimestamp, +// Empty, GroupByLabel and Reducer. +// For more information - https://redis.io/commands/ts.mrevrange/ +func (c cmdable) TSMRevRangeWithArgs(ctx context.Context, fromTimestamp int, toTimestamp int, filterExpr []string, options *TSMRevRangeOptions) *MapStringSliceInterfaceCmd { + args := []interface{}{"TS.MREVRANGE", fromTimestamp, toTimestamp} + if options != nil { + if options.Latest { + args = append(args, "LATEST") + } + if options.FilterByTS != nil { + args = append(args, "FILTER_BY_TS") + for _, f := range options.FilterByTS { + args = append(args, f) + } + } + if options.FilterByValue != nil { + args = append(args, "FILTER_BY_VALUE") + for _, f := range options.FilterByValue { + args = append(args, f) + } + } + if options.WithLabels { + args = append(args, "WITHLABELS") + } + if options.SelectedLabels != nil { + args = append(args, "SELECTED_LABELS") + args = append(args, options.SelectedLabels...) + } + if options.Count != 0 { + args = append(args, "COUNT", options.Count) + } + if options.Align != nil { + args = append(args, "ALIGN", options.Align) + } + if options.Aggregator != 0 { + args = append(args, "AGGREGATION", options.Aggregator.String()) + } + if options.BucketDuration != 0 { + args = append(args, options.BucketDuration) + } + if options.BucketTimestamp != nil { + args = append(args, "BUCKETTIMESTAMP", options.BucketTimestamp) + } + if options.Empty { + args = append(args, "EMPTY") + } + } + args = append(args, "FILTER") + for _, f := range filterExpr { + args = append(args, f) + } + if options != nil { + if options.GroupByLabel != nil { + args = append(args, "GROUPBY", options.GroupByLabel) + } + if options.Reducer != nil { + args = append(args, "REDUCE", options.Reducer) + } + } + cmd := NewMapStringSliceInterfaceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// TSMGet - Returns the last sample of multiple time-series keys. +// For more information - https://redis.io/commands/ts.mget/ +func (c cmdable) TSMGet(ctx context.Context, filters []string) *MapStringSliceInterfaceCmd { + args := []interface{}{"TS.MGET", "FILTER"} + for _, f := range filters { + args = append(args, f) + } + cmd := NewMapStringSliceInterfaceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// TSMGetWithArgs - Returns the last sample of multiple time-series keys with additional options. +// This function allows for specifying additional options such as: +// Latest, WithLabels and SelectedLabels. +// For more information - https://redis.io/commands/ts.mget/ +func (c cmdable) TSMGetWithArgs(ctx context.Context, filters []string, options *TSMGetOptions) *MapStringSliceInterfaceCmd { + args := []interface{}{"TS.MGET"} + if options != nil { + if options.Latest { + args = append(args, "LATEST") + } + if options.WithLabels { + args = append(args, "WITHLABELS") + } + if options.SelectedLabels != nil { + args = append(args, "SELECTED_LABELS") + args = append(args, options.SelectedLabels...) + } + } + args = append(args, "FILTER") + for _, f := range filters { + args = append(args, f) + } + cmd := NewMapStringSliceInterfaceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} diff --git a/redis_timeseries_test.go b/redis_timeseries_test.go new file mode 100644 index 000000000..291274de3 --- /dev/null +++ b/redis_timeseries_test.go @@ -0,0 +1,951 @@ +package redis_test + +import ( + "context" + "strings" + + . "github.com/bsm/ginkgo/v2" + . "github.com/bsm/gomega" + "github.com/redis/go-redis/v9" +) + +var _ = Describe("RedisTimeseries commands", Label("timeseries"), 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()) + }) + + It("should TSCreate and TSCreateWithArgs", Label("timeseries", "tscreate", "tscreateWithArgs"), func() { + result, err := client.TSCreate(ctx, "1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result).To(BeEquivalentTo("OK")) + // Test TSCreateWithArgs + opt := &redis.TSOptions{Retention: 5} + result, err = client.TSCreateWithArgs(ctx, "2", opt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result).To(BeEquivalentTo("OK")) + opt = &redis.TSOptions{Labels: map[string]string{"Redis": "Labs"}} + result, err = client.TSCreateWithArgs(ctx, "3", opt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result).To(BeEquivalentTo("OK")) + opt = &redis.TSOptions{Labels: map[string]string{"Time": "Series"}, Retention: 20} + result, err = client.TSCreateWithArgs(ctx, "4", opt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result).To(BeEquivalentTo("OK")) + resultInfo, err := client.TSInfo(ctx, "4").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultInfo["labels"].(map[interface{}]interface{})["Time"]).To(BeEquivalentTo("Series")) + // Test chunk size + opt = &redis.TSOptions{ChunkSize: 128} + result, err = client.TSCreateWithArgs(ctx, "ts-cs-1", opt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result).To(BeEquivalentTo("OK")) + resultInfo, err = client.TSInfo(ctx, "ts-cs-1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultInfo["chunkSize"]).To(BeEquivalentTo(128)) + // Test duplicate policy + duplicate_policies := []string{"BLOCK", "LAST", "FIRST", "MIN", "MAX"} + for _, dup := range duplicate_policies { + keyName := "ts-dup-" + dup + opt = &redis.TSOptions{DuplicatePolicy: dup} + result, err = client.TSCreateWithArgs(ctx, keyName, opt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result).To(BeEquivalentTo("OK")) + resultInfo, err = client.TSInfo(ctx, keyName).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(strings.ToUpper(resultInfo["duplicatePolicy"].(string))).To(BeEquivalentTo(dup)) + + } + }) + It("should TSAdd and TSAddWithArgs", Label("timeseries", "tsadd", "tsaddWithArgs"), func() { + result, err := client.TSAdd(ctx, "1", 1, 1).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result).To(BeEquivalentTo(1)) + // Test TSAddWithArgs + opt := &redis.TSOptions{Retention: 10} + result, err = client.TSAddWithArgs(ctx, "2", 2, 3, opt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result).To(BeEquivalentTo(2)) + opt = &redis.TSOptions{Labels: map[string]string{"Redis": "Labs"}} + result, err = client.TSAddWithArgs(ctx, "3", 3, 2, opt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result).To(BeEquivalentTo(3)) + opt = &redis.TSOptions{Labels: map[string]string{"Redis": "Labs", "Time": "Series"}, Retention: 10} + result, err = client.TSAddWithArgs(ctx, "4", 4, 2, opt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result).To(BeEquivalentTo(4)) + resultInfo, err := client.TSInfo(ctx, "4").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultInfo["labels"].(map[interface{}]interface{})["Time"]).To(BeEquivalentTo("Series")) + // Test chunk size + opt = &redis.TSOptions{ChunkSize: 128} + result, err = client.TSAddWithArgs(ctx, "ts-cs-1", 1, 10, opt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result).To(BeEquivalentTo(1)) + resultInfo, err = client.TSInfo(ctx, "ts-cs-1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultInfo["chunkSize"]).To(BeEquivalentTo(128)) + // Test duplicate policy + // LAST + opt = &redis.TSOptions{DuplicatePolicy: "LAST"} + result, err = client.TSAddWithArgs(ctx, "tsal-1", 1, 5, opt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result).To(BeEquivalentTo(1)) + result, err = client.TSAddWithArgs(ctx, "tsal-1", 1, 10, opt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result).To(BeEquivalentTo(1)) + resultGet, err := client.TSGet(ctx, "tsal-1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultGet.Value).To(BeEquivalentTo(10)) + // FIRST + opt = &redis.TSOptions{DuplicatePolicy: "FIRST"} + result, err = client.TSAddWithArgs(ctx, "tsaf-1", 1, 5, opt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result).To(BeEquivalentTo(1)) + result, err = client.TSAddWithArgs(ctx, "tsaf-1", 1, 10, opt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result).To(BeEquivalentTo(1)) + resultGet, err = client.TSGet(ctx, "tsaf-1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultGet.Value).To(BeEquivalentTo(5)) + // MAX + opt = &redis.TSOptions{DuplicatePolicy: "MAX"} + result, err = client.TSAddWithArgs(ctx, "tsam-1", 1, 5, opt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result).To(BeEquivalentTo(1)) + result, err = client.TSAddWithArgs(ctx, "tsam-1", 1, 10, opt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result).To(BeEquivalentTo(1)) + resultGet, err = client.TSGet(ctx, "tsam-1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultGet.Value).To(BeEquivalentTo(10)) + // MIN + opt = &redis.TSOptions{DuplicatePolicy: "MIN"} + result, err = client.TSAddWithArgs(ctx, "tsami-1", 1, 5, opt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result).To(BeEquivalentTo(1)) + result, err = client.TSAddWithArgs(ctx, "tsami-1", 1, 10, opt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result).To(BeEquivalentTo(1)) + 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() { + result, err := client.TSCreate(ctx, "1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result).To(BeEquivalentTo("OK")) + resultInfo, err := client.TSInfo(ctx, "1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultInfo["retentionTime"]).To(BeEquivalentTo(0)) + + opt := &redis.TSAlterOptions{Retention: 10} + resultAlter, err := client.TSAlter(ctx, "1", opt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultAlter).To(BeEquivalentTo("OK")) + + resultInfo, err = client.TSInfo(ctx, "1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultInfo["retentionTime"]).To(BeEquivalentTo(10)) + + resultInfo, err = client.TSInfo(ctx, "1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultInfo["labels"]).To(BeEquivalentTo(map[interface{}]interface{}{})) + + opt = &redis.TSAlterOptions{Labels: map[string]string{"Time": "Series"}} + resultAlter, err = client.TSAlter(ctx, "1", opt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultAlter).To(BeEquivalentTo("OK")) + + resultInfo, err = client.TSInfo(ctx, "1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultInfo["labels"].(map[interface{}]interface{})["Time"]).To(BeEquivalentTo("Series")) + Expect(resultInfo["retentionTime"]).To(BeEquivalentTo(10)) + Expect(resultInfo["duplicatePolicy"]).To(BeEquivalentTo(redis.Nil)) + opt = &redis.TSAlterOptions{DuplicatePolicy: "min"} + resultAlter, err = client.TSAlter(ctx, "1", opt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultAlter).To(BeEquivalentTo("OK")) + + 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() { + result, err := client.TSCreate(ctx, "1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result).To(BeEquivalentTo("OK")) + result, err = client.TSCreate(ctx, "2").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result).To(BeEquivalentTo("OK")) + result, err = client.TSCreateRule(ctx, "1", "2", redis.Avg, 100).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result).To(BeEquivalentTo("OK")) + for i := 0; i < 50; i++ { + resultAdd, err := client.TSAdd(ctx, "1", 100+i*2, 1).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultAdd).To(BeEquivalentTo(100 + i*2)) + resultAdd, err = client.TSAdd(ctx, "1", 100+i*2+1, 2).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultAdd).To(BeEquivalentTo(100 + i*2 + 1)) + + } + resultAdd, err := client.TSAdd(ctx, "1", 100*2, 1.5).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultAdd).To(BeEquivalentTo(100 * 2)) + resultGet, err := client.TSGet(ctx, "2").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultGet.Value).To(BeEquivalentTo(1.5)) + Expect(resultGet.Timestamp).To(BeEquivalentTo(100)) + + resultDeleteRule, err := client.TSDeleteRule(ctx, "1", "2").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultDeleteRule).To(BeEquivalentTo("OK")) + 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() { + for i := 0; i < 100; i++ { + _, err := client.TSIncrBy(ctx, "1", 1).Result() + Expect(err).NotTo(HaveOccurred()) + } + result, err := client.TSGet(ctx, "1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result.Value).To(BeEquivalentTo(100)) + + for i := 0; i < 100; i++ { + _, err := client.TSDecrBy(ctx, "1", 1).Result() + Expect(err).NotTo(HaveOccurred()) + } + result, err = client.TSGet(ctx, "1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result.Value).To(BeEquivalentTo(0)) + + opt := &redis.TSIncrDecrOptions{Timestamp: 5} + _, err = client.TSIncrByWithArgs(ctx, "2", 1.5, opt).Result() + Expect(err).NotTo(HaveOccurred()) + + result, err = client.TSGet(ctx, "2").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result.Timestamp).To(BeEquivalentTo(5)) + Expect(result.Value).To(BeEquivalentTo(1.5)) + + opt = &redis.TSIncrDecrOptions{Timestamp: 7} + _, err = client.TSIncrByWithArgs(ctx, "2", 2.25, opt).Result() + Expect(err).NotTo(HaveOccurred()) + + result, err = client.TSGet(ctx, "2").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result.Timestamp).To(BeEquivalentTo(7)) + Expect(result.Value).To(BeEquivalentTo(3.75)) + + opt = &redis.TSIncrDecrOptions{Timestamp: 15} + _, err = client.TSDecrByWithArgs(ctx, "2", 1.5, opt).Result() + Expect(err).NotTo(HaveOccurred()) + + result, err = client.TSGet(ctx, "2").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result.Timestamp).To(BeEquivalentTo(15)) + Expect(result.Value).To(BeEquivalentTo(2.25)) + + // Test chunk size INCRBY + opt = &redis.TSIncrDecrOptions{ChunkSize: 128} + _, err = client.TSIncrByWithArgs(ctx, "3", 10, opt).Result() + Expect(err).NotTo(HaveOccurred()) + + resultInfo, err := client.TSInfo(ctx, "3").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultInfo["chunkSize"]).To(BeEquivalentTo(128)) + + // Test chunk size DECRBY + opt = &redis.TSIncrDecrOptions{ChunkSize: 128} + _, err = client.TSDecrByWithArgs(ctx, "4", 10, opt).Result() + Expect(err).NotTo(HaveOccurred()) + + resultInfo, err = client.TSInfo(ctx, "4").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultInfo["chunkSize"]).To(BeEquivalentTo(128)) + }) + + It("should TSGet", Label("timeseries", "tsget"), func() { + opt := &redis.TSOptions{DuplicatePolicy: "max"} + resultGet, err := client.TSAddWithArgs(ctx, "foo", 2265985, 151, opt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultGet).To(BeEquivalentTo(2265985)) + result, err := client.TSGet(ctx, "foo").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result.Timestamp).To(BeEquivalentTo(2265985)) + Expect(result.Value).To(BeEquivalentTo(151)) + + }) + + It("should TSGet Latest", Label("timeseries", "tsgetlatest"), func() { + resultGet, err := client.TSCreate(ctx, "tsgl-1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultGet).To(BeEquivalentTo("OK")) + resultGet, err = client.TSCreate(ctx, "tsgl-2").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultGet).To(BeEquivalentTo("OK")) + resultGet, err = client.TSCreateRule(ctx, "tsgl-1", "tsgl-2", redis.Sum, 10).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultGet).To(BeEquivalentTo("OK")) + _, err = client.TSAdd(ctx, "tsgl-1", 1, 1).Result() + Expect(err).NotTo(HaveOccurred()) + _, err = client.TSAdd(ctx, "tsgl-1", 2, 3).Result() + Expect(err).NotTo(HaveOccurred()) + _, err = client.TSAdd(ctx, "tsgl-1", 11, 7).Result() + Expect(err).NotTo(HaveOccurred()) + _, err = client.TSAdd(ctx, "tsgl-1", 13, 1).Result() + Expect(err).NotTo(HaveOccurred()) + result, errGet := client.TSGet(ctx, "tsgl-2").Result() + Expect(errGet).NotTo(HaveOccurred()) + Expect(result.Timestamp).To(BeEquivalentTo(0)) + Expect(result.Value).To(BeEquivalentTo(4)) + result, errGet = client.TSGetWithArgs(ctx, "tsgl-2", &redis.TSGetOptions{Latest: true}).Result() + Expect(errGet).NotTo(HaveOccurred()) + Expect(result.Timestamp).To(BeEquivalentTo(10)) + Expect(result.Value).To(BeEquivalentTo(8)) + }) + + It("should TSInfo", Label("timeseries", "tsinfo"), func() { + resultGet, err := client.TSAdd(ctx, "foo", 2265985, 151).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultGet).To(BeEquivalentTo(2265985)) + result, err := client.TSInfo(ctx, "foo").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result["firstTimestamp"]).To(BeEquivalentTo(2265985)) + + }) + + It("should TSMAdd", Label("timeseries", "tsmadd"), func() { + resultGet, err := client.TSCreate(ctx, "a").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultGet).To(BeEquivalentTo("OK")) + ktvSlices := make([][]interface{}, 3) + for i := 0; i < 3; i++ { + ktvSlices[i] = make([]interface{}, 3) + ktvSlices[i][0] = "a" + for j := 1; j < 3; j++ { + ktvSlices[i][j] = (i + j) * j + } + } + 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() { + opt := &redis.TSOptions{Labels: map[string]string{"Test": "This"}} + resultCreate, err := client.TSCreateWithArgs(ctx, "a", opt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultCreate).To(BeEquivalentTo("OK")) + opt = &redis.TSOptions{Labels: map[string]string{"Test": "This", "Taste": "That"}} + resultCreate, err = client.TSCreateWithArgs(ctx, "b", opt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultCreate).To(BeEquivalentTo("OK")) + _, err = client.TSAdd(ctx, "a", "*", 15).Result() + Expect(err).NotTo(HaveOccurred()) + _, err = client.TSAdd(ctx, "b", "*", 25).Result() + Expect(err).NotTo(HaveOccurred()) + + result, err := client.TSMGet(ctx, []string{"Test=This"}).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result["a"][1].([]interface{})[1]).To(BeEquivalentTo(15)) + Expect(result["b"][1].([]interface{})[1]).To(BeEquivalentTo(25)) + mgetOpt := &redis.TSMGetOptions{WithLabels: true} + result, err = client.TSMGetWithArgs(ctx, []string{"Test=This"}, mgetOpt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result["b"][0]).To(BeEquivalentTo(map[interface{}]interface{}{"Test": "This", "Taste": "That"})) + + resultCreate, err = client.TSCreate(ctx, "c").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultCreate).To(BeEquivalentTo("OK")) + opt = &redis.TSOptions{Labels: map[string]string{"is_compaction": "true"}} + resultCreate, err = client.TSCreateWithArgs(ctx, "d", opt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultCreate).To(BeEquivalentTo("OK")) + resultCreateRule, err := client.TSCreateRule(ctx, "c", "d", redis.Sum, 10).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultCreateRule).To(BeEquivalentTo("OK")) + _, err = client.TSAdd(ctx, "c", 1, 1).Result() + Expect(err).NotTo(HaveOccurred()) + _, err = client.TSAdd(ctx, "c", 2, 3).Result() + Expect(err).NotTo(HaveOccurred()) + _, err = client.TSAdd(ctx, "c", 11, 7).Result() + Expect(err).NotTo(HaveOccurred()) + _, err = client.TSAdd(ctx, "c", 13, 1).Result() + Expect(err).NotTo(HaveOccurred()) + result, err = client.TSMGet(ctx, []string{"is_compaction=true"}).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result["d"][1]).To(BeEquivalentTo([]interface{}{int64(0), 4.0})) + mgetOpt = &redis.TSMGetOptions{Latest: true} + 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() { + opt := &redis.TSOptions{Labels: map[string]string{"Test": "This"}} + resultCreate, err := client.TSCreateWithArgs(ctx, "a", opt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultCreate).To(BeEquivalentTo("OK")) + opt = &redis.TSOptions{Labels: map[string]string{"Test": "This", "Taste": "That"}} + resultCreate, err = client.TSCreateWithArgs(ctx, "b", opt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultCreate).To(BeEquivalentTo("OK")) + result, err := client.TSQueryIndex(ctx, []string{"Test=This"}).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(len(result)).To(BeEquivalentTo(2)) + 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() { + for i := 0; i < 100; i++ { + _, err := client.TSAdd(ctx, "a", i, float64(i%7)).Result() + Expect(err).NotTo(HaveOccurred()) + } + resultDelete, err := client.TSDel(ctx, "a", 0, 21).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultDelete).To(BeEquivalentTo(22)) + + resultRange, err := client.TSRange(ctx, "a", 0, 21).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultRange).To(BeEquivalentTo([]redis.TSTimestampValue{})) + + resultRange, err = client.TSRange(ctx, "a", 22, 22).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultRange[0]).To(BeEquivalentTo(redis.TSTimestampValue{Timestamp: 22, Value: 1})) + }) + + It("should TSRange, TSRangeWithArgs", Label("timeseries", "tsrange", "tsrangeWithArgs"), func() { + for i := 0; i < 100; i++ { + _, err := client.TSAdd(ctx, "a", i, float64(i%7)).Result() + Expect(err).NotTo(HaveOccurred()) + + } + result, err := client.TSRange(ctx, "a", 0, 200).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(len(result)).To(BeEquivalentTo(100)) + for i := 0; i < 100; i++ { + client.TSAdd(ctx, "a", i+200, float64(i%7)) + } + result, err = client.TSRange(ctx, "a", 0, 500).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(len(result)).To(BeEquivalentTo(200)) + fts := make([]int, 0) + for i := 10; i < 20; i++ { + fts = append(fts, i) + } + opt := &redis.TSRangeOptions{FilterByTS: fts, FilterByValue: []int{1, 2}} + result, err = client.TSRangeWithArgs(ctx, "a", 0, 500, opt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(len(result)).To(BeEquivalentTo(2)) + opt = &redis.TSRangeOptions{Aggregator: redis.Count, BucketDuration: 10, Align: "+"} + result, err = client.TSRangeWithArgs(ctx, "a", 0, 10, opt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result).To(BeEquivalentTo([]redis.TSTimestampValue{{Timestamp: 0, Value: 10}, {Timestamp: 10, Value: 1}})) + opt = &redis.TSRangeOptions{Aggregator: redis.Count, BucketDuration: 10, Align: "5"} + result, err = client.TSRangeWithArgs(ctx, "a", 0, 10, opt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result).To(BeEquivalentTo([]redis.TSTimestampValue{{Timestamp: 0, Value: 5}, {Timestamp: 5, Value: 6}})) + opt = &redis.TSRangeOptions{Aggregator: redis.Twa, BucketDuration: 10} + result, err = client.TSRangeWithArgs(ctx, "a", 0, 10, opt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result).To(BeEquivalentTo([]redis.TSTimestampValue{{Timestamp: 0, Value: 2.55}, {Timestamp: 10, Value: 3}})) + // Test Range Latest + resultCreate, err := client.TSCreate(ctx, "t1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultCreate).To(BeEquivalentTo("OK")) + resultCreate, err = client.TSCreate(ctx, "t2").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultCreate).To(BeEquivalentTo("OK")) + resultRule, err := client.TSCreateRule(ctx, "t1", "t2", redis.Sum, 10).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultRule).To(BeEquivalentTo("OK")) + _, errAdd := client.TSAdd(ctx, "t1", 1, 1).Result() + Expect(errAdd).NotTo(HaveOccurred()) + _, errAdd = client.TSAdd(ctx, "t1", 2, 3).Result() + Expect(errAdd).NotTo(HaveOccurred()) + _, errAdd = client.TSAdd(ctx, "t1", 11, 7).Result() + Expect(errAdd).NotTo(HaveOccurred()) + _, errAdd = client.TSAdd(ctx, "t1", 13, 1).Result() + Expect(errAdd).NotTo(HaveOccurred()) + resultRange, err := client.TSRange(ctx, "t1", 0, 20).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultRange[0]).To(BeEquivalentTo(redis.TSTimestampValue{Timestamp: 1, Value: 1})) + + opt = &redis.TSRangeOptions{Latest: true} + resultRange, err = client.TSRangeWithArgs(ctx, "t2", 0, 10, opt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultRange[0]).To(BeEquivalentTo(redis.TSTimestampValue{Timestamp: 0, Value: 4})) + // Test Bucket Timestamp + resultCreate, err = client.TSCreate(ctx, "t3").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultCreate).To(BeEquivalentTo("OK")) + _, errAdd = client.TSAdd(ctx, "t3", 15, 1).Result() + Expect(errAdd).NotTo(HaveOccurred()) + _, errAdd = client.TSAdd(ctx, "t3", 17, 4).Result() + Expect(errAdd).NotTo(HaveOccurred()) + _, errAdd = client.TSAdd(ctx, "t3", 51, 3).Result() + Expect(errAdd).NotTo(HaveOccurred()) + _, errAdd = client.TSAdd(ctx, "t3", 73, 5).Result() + Expect(errAdd).NotTo(HaveOccurred()) + _, errAdd = client.TSAdd(ctx, "t3", 75, 3).Result() + Expect(errAdd).NotTo(HaveOccurred()) + + opt = &redis.TSRangeOptions{Aggregator: redis.Max, Align: 0, BucketDuration: 10} + resultRange, err = client.TSRangeWithArgs(ctx, "t3", 0, 100, opt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultRange[0]).To(BeEquivalentTo(redis.TSTimestampValue{Timestamp: 10, Value: 4})) + Expect(len(resultRange)).To(BeEquivalentTo(3)) + + opt = &redis.TSRangeOptions{Aggregator: redis.Max, Align: 0, BucketDuration: 10, BucketTimestamp: "+"} + resultRange, err = client.TSRangeWithArgs(ctx, "t3", 0, 100, opt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultRange[0]).To(BeEquivalentTo(redis.TSTimestampValue{Timestamp: 20, Value: 4})) + Expect(len(resultRange)).To(BeEquivalentTo(3)) + // Test Empty + _, errAdd = client.TSAdd(ctx, "t4", 15, 1).Result() + Expect(errAdd).NotTo(HaveOccurred()) + _, errAdd = client.TSAdd(ctx, "t4", 17, 4).Result() + Expect(errAdd).NotTo(HaveOccurred()) + _, errAdd = client.TSAdd(ctx, "t4", 51, 3).Result() + Expect(errAdd).NotTo(HaveOccurred()) + _, errAdd = client.TSAdd(ctx, "t4", 73, 5).Result() + Expect(errAdd).NotTo(HaveOccurred()) + _, errAdd = client.TSAdd(ctx, "t4", 75, 3).Result() + Expect(errAdd).NotTo(HaveOccurred()) + + opt = &redis.TSRangeOptions{Aggregator: redis.Max, Align: 0, BucketDuration: 10} + resultRange, err = client.TSRangeWithArgs(ctx, "t4", 0, 100, opt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultRange[0]).To(BeEquivalentTo(redis.TSTimestampValue{Timestamp: 10, Value: 4})) + Expect(len(resultRange)).To(BeEquivalentTo(3)) + + opt = &redis.TSRangeOptions{Aggregator: redis.Max, Align: 0, BucketDuration: 10, Empty: true} + resultRange, err = client.TSRangeWithArgs(ctx, "t4", 0, 100, opt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultRange[0]).To(BeEquivalentTo(redis.TSTimestampValue{Timestamp: 10, Value: 4})) + Expect(len(resultRange)).To(BeEquivalentTo(7)) + }) + + It("should TSRevRange, TSRevRangeWithArgs", Label("timeseries", "tsrevrange", "tsrevrangeWithArgs"), func() { + for i := 0; i < 100; i++ { + _, err := client.TSAdd(ctx, "a", i, float64(i%7)).Result() + Expect(err).NotTo(HaveOccurred()) + + } + result, err := client.TSRange(ctx, "a", 0, 200).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(len(result)).To(BeEquivalentTo(100)) + for i := 0; i < 100; i++ { + client.TSAdd(ctx, "a", i+200, float64(i%7)) + } + result, err = client.TSRange(ctx, "a", 0, 500).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(len(result)).To(BeEquivalentTo(200)) + + opt := &redis.TSRevRangeOptions{Aggregator: redis.Avg, BucketDuration: 10} + result, err = client.TSRevRangeWithArgs(ctx, "a", 0, 500, opt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(len(result)).To(BeEquivalentTo(20)) + + opt = &redis.TSRevRangeOptions{Count: 10} + result, err = client.TSRevRangeWithArgs(ctx, "a", 0, 500, opt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(len(result)).To(BeEquivalentTo(10)) + + fts := make([]int, 0) + for i := 10; i < 20; i++ { + fts = append(fts, i) + } + opt = &redis.TSRevRangeOptions{FilterByTS: fts, FilterByValue: []int{1, 2}} + result, err = client.TSRevRangeWithArgs(ctx, "a", 0, 500, opt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(len(result)).To(BeEquivalentTo(2)) + + opt = &redis.TSRevRangeOptions{Aggregator: redis.Count, BucketDuration: 10, Align: "+"} + result, err = client.TSRevRangeWithArgs(ctx, "a", 0, 10, opt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result).To(BeEquivalentTo([]redis.TSTimestampValue{{Timestamp: 10, Value: 1}, {Timestamp: 0, Value: 10}})) + + opt = &redis.TSRevRangeOptions{Aggregator: redis.Count, BucketDuration: 10, Align: "1"} + result, err = client.TSRevRangeWithArgs(ctx, "a", 0, 10, opt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result).To(BeEquivalentTo([]redis.TSTimestampValue{{Timestamp: 1, Value: 10}, {Timestamp: 0, Value: 1}})) + + opt = &redis.TSRevRangeOptions{Aggregator: redis.Twa, BucketDuration: 10} + result, err = client.TSRevRangeWithArgs(ctx, "a", 0, 10, opt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result).To(BeEquivalentTo([]redis.TSTimestampValue{{Timestamp: 10, Value: 3}, {Timestamp: 0, Value: 2.55}})) + // Test Range Latest + resultCreate, err := client.TSCreate(ctx, "t1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultCreate).To(BeEquivalentTo("OK")) + resultCreate, err = client.TSCreate(ctx, "t2").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultCreate).To(BeEquivalentTo("OK")) + resultRule, err := client.TSCreateRule(ctx, "t1", "t2", redis.Sum, 10).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultRule).To(BeEquivalentTo("OK")) + _, errAdd := client.TSAdd(ctx, "t1", 1, 1).Result() + Expect(errAdd).NotTo(HaveOccurred()) + _, errAdd = client.TSAdd(ctx, "t1", 2, 3).Result() + Expect(errAdd).NotTo(HaveOccurred()) + _, errAdd = client.TSAdd(ctx, "t1", 11, 7).Result() + Expect(errAdd).NotTo(HaveOccurred()) + _, errAdd = client.TSAdd(ctx, "t1", 13, 1).Result() + Expect(errAdd).NotTo(HaveOccurred()) + resultRange, err := client.TSRange(ctx, "t2", 0, 10).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultRange[0]).To(BeEquivalentTo(redis.TSTimestampValue{Timestamp: 0, Value: 4})) + opt = &redis.TSRevRangeOptions{Latest: true} + resultRange, err = client.TSRevRangeWithArgs(ctx, "t2", 0, 10, opt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultRange[0]).To(BeEquivalentTo(redis.TSTimestampValue{Timestamp: 10, Value: 8})) + resultRange, err = client.TSRevRangeWithArgs(ctx, "t2", 0, 9, opt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultRange[0]).To(BeEquivalentTo(redis.TSTimestampValue{Timestamp: 0, Value: 4})) + // Test Bucket Timestamp + resultCreate, err = client.TSCreate(ctx, "t3").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultCreate).To(BeEquivalentTo("OK")) + _, errAdd = client.TSAdd(ctx, "t3", 15, 1).Result() + Expect(errAdd).NotTo(HaveOccurred()) + _, errAdd = client.TSAdd(ctx, "t3", 17, 4).Result() + Expect(errAdd).NotTo(HaveOccurred()) + _, errAdd = client.TSAdd(ctx, "t3", 51, 3).Result() + Expect(errAdd).NotTo(HaveOccurred()) + _, errAdd = client.TSAdd(ctx, "t3", 73, 5).Result() + Expect(errAdd).NotTo(HaveOccurred()) + _, errAdd = client.TSAdd(ctx, "t3", 75, 3).Result() + Expect(errAdd).NotTo(HaveOccurred()) + + opt = &redis.TSRevRangeOptions{Aggregator: redis.Max, Align: 0, BucketDuration: 10} + resultRange, err = client.TSRevRangeWithArgs(ctx, "t3", 0, 100, opt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultRange[0]).To(BeEquivalentTo(redis.TSTimestampValue{Timestamp: 70, Value: 5})) + Expect(len(resultRange)).To(BeEquivalentTo(3)) + + opt = &redis.TSRevRangeOptions{Aggregator: redis.Max, Align: 0, BucketDuration: 10, BucketTimestamp: "+"} + resultRange, err = client.TSRevRangeWithArgs(ctx, "t3", 0, 100, opt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultRange[0]).To(BeEquivalentTo(redis.TSTimestampValue{Timestamp: 80, Value: 5})) + Expect(len(resultRange)).To(BeEquivalentTo(3)) + // Test Empty + _, errAdd = client.TSAdd(ctx, "t4", 15, 1).Result() + Expect(errAdd).NotTo(HaveOccurred()) + _, errAdd = client.TSAdd(ctx, "t4", 17, 4).Result() + Expect(errAdd).NotTo(HaveOccurred()) + _, errAdd = client.TSAdd(ctx, "t4", 51, 3).Result() + Expect(errAdd).NotTo(HaveOccurred()) + _, errAdd = client.TSAdd(ctx, "t4", 73, 5).Result() + Expect(errAdd).NotTo(HaveOccurred()) + _, errAdd = client.TSAdd(ctx, "t4", 75, 3).Result() + Expect(errAdd).NotTo(HaveOccurred()) + + opt = &redis.TSRevRangeOptions{Aggregator: redis.Max, Align: 0, BucketDuration: 10} + resultRange, err = client.TSRevRangeWithArgs(ctx, "t4", 0, 100, opt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultRange[0]).To(BeEquivalentTo(redis.TSTimestampValue{Timestamp: 70, Value: 5})) + Expect(len(resultRange)).To(BeEquivalentTo(3)) + + opt = &redis.TSRevRangeOptions{Aggregator: redis.Max, Align: 0, BucketDuration: 10, Empty: true} + resultRange, err = client.TSRevRangeWithArgs(ctx, "t4", 0, 100, opt).Result() + 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() { + createOpt := &redis.TSOptions{Labels: map[string]string{"Test": "This", "team": "ny"}} + resultCreate, err := client.TSCreateWithArgs(ctx, "a", createOpt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultCreate).To(BeEquivalentTo("OK")) + createOpt = &redis.TSOptions{Labels: map[string]string{"Test": "This", "Taste": "That", "team": "sf"}} + resultCreate, err = client.TSCreateWithArgs(ctx, "b", createOpt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultCreate).To(BeEquivalentTo("OK")) + + for i := 0; i < 100; i++ { + _, err := client.TSAdd(ctx, "a", i, float64(i%7)).Result() + Expect(err).NotTo(HaveOccurred()) + _, err = client.TSAdd(ctx, "b", i, float64(i%11)).Result() + Expect(err).NotTo(HaveOccurred()) + } + + result, err := client.TSMRange(ctx, 0, 200, []string{"Test=This"}).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(len(result)).To(BeEquivalentTo(2)) + Expect(len(result["a"][2].([]interface{}))).To(BeEquivalentTo(100)) + // Test Count + mrangeOpt := &redis.TSMRangeOptions{Count: 10} + result, err = client.TSMRangeWithArgs(ctx, 0, 200, []string{"Test=This"}, mrangeOpt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(len(result["a"][2].([]interface{}))).To(BeEquivalentTo(10)) + // Test Aggregation and BucketDuration + for i := 0; i < 100; i++ { + _, err := client.TSAdd(ctx, "a", i+200, float64(i%7)).Result() + Expect(err).NotTo(HaveOccurred()) + } + mrangeOpt = &redis.TSMRangeOptions{Aggregator: redis.Avg, BucketDuration: 10} + result, err = client.TSMRangeWithArgs(ctx, 0, 500, []string{"Test=This"}, mrangeOpt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(len(result)).To(BeEquivalentTo(2)) + Expect(len(result["a"][2].([]interface{}))).To(BeEquivalentTo(20)) + // Test WithLabels + Expect(result["a"][0]).To(BeEquivalentTo(map[interface{}]interface{}{})) + mrangeOpt = &redis.TSMRangeOptions{WithLabels: true} + result, err = client.TSMRangeWithArgs(ctx, 0, 200, []string{"Test=This"}, mrangeOpt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result["a"][0]).To(BeEquivalentTo(map[interface{}]interface{}{"Test": "This", "team": "ny"})) + // Test SelectedLabels + mrangeOpt = &redis.TSMRangeOptions{SelectedLabels: []interface{}{"team"}} + result, err = client.TSMRangeWithArgs(ctx, 0, 200, []string{"Test=This"}, mrangeOpt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result["a"][0]).To(BeEquivalentTo(map[interface{}]interface{}{"team": "ny"})) + Expect(result["b"][0]).To(BeEquivalentTo(map[interface{}]interface{}{"team": "sf"})) + // Test FilterBy + fts := make([]int, 0) + for i := 10; i < 20; i++ { + fts = append(fts, i) + } + mrangeOpt = &redis.TSMRangeOptions{FilterByTS: fts, FilterByValue: []int{1, 2}} + result, err = client.TSMRangeWithArgs(ctx, 0, 200, []string{"Test=This"}, mrangeOpt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result["a"][2]).To(BeEquivalentTo([]interface{}{[]interface{}{int64(15), 1.0}, []interface{}{int64(16), 2.0}})) + // Test GroupBy + mrangeOpt = &redis.TSMRangeOptions{GroupByLabel: "Test", Reducer: "sum"} + result, err = client.TSMRangeWithArgs(ctx, 0, 3, []string{"Test=This"}, mrangeOpt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result["Test=This"][3]).To(BeEquivalentTo([]interface{}{[]interface{}{int64(0), 0.0}, []interface{}{int64(1), 2.0}, []interface{}{int64(2), 4.0}, []interface{}{int64(3), 6.0}})) + + mrangeOpt = &redis.TSMRangeOptions{GroupByLabel: "Test", Reducer: "max"} + result, err = client.TSMRangeWithArgs(ctx, 0, 3, []string{"Test=This"}, mrangeOpt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result["Test=This"][3]).To(BeEquivalentTo([]interface{}{[]interface{}{int64(0), 0.0}, []interface{}{int64(1), 1.0}, []interface{}{int64(2), 2.0}, []interface{}{int64(3), 3.0}})) + + mrangeOpt = &redis.TSMRangeOptions{GroupByLabel: "team", Reducer: "min"} + result, err = client.TSMRangeWithArgs(ctx, 0, 3, []string{"Test=This"}, mrangeOpt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(len(result)).To(BeEquivalentTo(2)) + Expect(result["team=ny"][3]).To(BeEquivalentTo([]interface{}{[]interface{}{int64(0), 0.0}, []interface{}{int64(1), 1.0}, []interface{}{int64(2), 2.0}, []interface{}{int64(3), 3.0}})) + Expect(result["team=sf"][3]).To(BeEquivalentTo([]interface{}{[]interface{}{int64(0), 0.0}, []interface{}{int64(1), 1.0}, []interface{}{int64(2), 2.0}, []interface{}{int64(3), 3.0}})) + // Test Align + mrangeOpt = &redis.TSMRangeOptions{Aggregator: redis.Count, BucketDuration: 10, Align: "-"} + 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), 10.0}, []interface{}{int64(10), 1.0}})) + + mrangeOpt = &redis.TSMRangeOptions{Aggregator: redis.Count, BucketDuration: 10, Align: 5} + 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() { + resultCreate, err := client.TSCreate(ctx, "a").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultCreate).To(BeEquivalentTo("OK")) + opt := &redis.TSOptions{Labels: map[string]string{"is_compaction": "true"}} + resultCreate, err = client.TSCreateWithArgs(ctx, "b", opt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultCreate).To(BeEquivalentTo("OK")) + + resultCreate, err = client.TSCreate(ctx, "c").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultCreate).To(BeEquivalentTo("OK")) + opt = &redis.TSOptions{Labels: map[string]string{"is_compaction": "true"}} + resultCreate, err = client.TSCreateWithArgs(ctx, "d", opt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultCreate).To(BeEquivalentTo("OK")) + + resultCreateRule, err := client.TSCreateRule(ctx, "a", "b", redis.Sum, 10).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultCreateRule).To(BeEquivalentTo("OK")) + resultCreateRule, err = client.TSCreateRule(ctx, "c", "d", redis.Sum, 10).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultCreateRule).To(BeEquivalentTo("OK")) + + _, err = client.TSAdd(ctx, "a", 1, 1).Result() + Expect(err).NotTo(HaveOccurred()) + _, err = client.TSAdd(ctx, "a", 2, 3).Result() + Expect(err).NotTo(HaveOccurred()) + _, err = client.TSAdd(ctx, "a", 11, 7).Result() + Expect(err).NotTo(HaveOccurred()) + _, err = client.TSAdd(ctx, "a", 13, 1).Result() + Expect(err).NotTo(HaveOccurred()) + + _, err = client.TSAdd(ctx, "c", 1, 1).Result() + Expect(err).NotTo(HaveOccurred()) + _, err = client.TSAdd(ctx, "c", 2, 3).Result() + Expect(err).NotTo(HaveOccurred()) + _, err = client.TSAdd(ctx, "c", 11, 7).Result() + Expect(err).NotTo(HaveOccurred()) + _, err = client.TSAdd(ctx, "c", 13, 1).Result() + Expect(err).NotTo(HaveOccurred()) + mrangeOpt := &redis.TSMRangeOptions{Latest: true} + result, err := client.TSMRangeWithArgs(ctx, 0, 10, []string{"is_compaction=true"}, mrangeOpt).Result() + 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"}} + resultCreate, err := client.TSCreateWithArgs(ctx, "a", createOpt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultCreate).To(BeEquivalentTo("OK")) + createOpt = &redis.TSOptions{Labels: map[string]string{"Test": "This", "Taste": "That", "team": "sf"}} + resultCreate, err = client.TSCreateWithArgs(ctx, "b", createOpt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultCreate).To(BeEquivalentTo("OK")) + + for i := 0; i < 100; i++ { + _, err := client.TSAdd(ctx, "a", i, float64(i%7)).Result() + Expect(err).NotTo(HaveOccurred()) + _, err = client.TSAdd(ctx, "b", i, float64(i%11)).Result() + Expect(err).NotTo(HaveOccurred()) + } + result, err := client.TSMRevRange(ctx, 0, 200, []string{"Test=This"}).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(len(result)).To(BeEquivalentTo(2)) + Expect(len(result["a"][2].([]interface{}))).To(BeEquivalentTo(100)) + // Test Count + mrangeOpt := &redis.TSMRevRangeOptions{Count: 10} + result, err = client.TSMRevRangeWithArgs(ctx, 0, 200, []string{"Test=This"}, mrangeOpt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(len(result["a"][2].([]interface{}))).To(BeEquivalentTo(10)) + // Test Aggregation and BucketDuration + for i := 0; i < 100; i++ { + _, err := client.TSAdd(ctx, "a", i+200, float64(i%7)).Result() + Expect(err).NotTo(HaveOccurred()) + } + mrangeOpt = &redis.TSMRevRangeOptions{Aggregator: redis.Avg, BucketDuration: 10} + result, err = client.TSMRevRangeWithArgs(ctx, 0, 500, []string{"Test=This"}, mrangeOpt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(len(result)).To(BeEquivalentTo(2)) + Expect(len(result["a"][2].([]interface{}))).To(BeEquivalentTo(20)) + Expect(result["a"][0]).To(BeEquivalentTo(map[interface{}]interface{}{})) + // Test WithLabels + Expect(result["a"][0]).To(BeEquivalentTo(map[interface{}]interface{}{})) + mrangeOpt = &redis.TSMRevRangeOptions{WithLabels: true} + result, err = client.TSMRevRangeWithArgs(ctx, 0, 200, []string{"Test=This"}, mrangeOpt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result["a"][0]).To(BeEquivalentTo(map[interface{}]interface{}{"Test": "This", "team": "ny"})) + // Test SelectedLabels + mrangeOpt = &redis.TSMRevRangeOptions{SelectedLabels: []interface{}{"team"}} + result, err = client.TSMRevRangeWithArgs(ctx, 0, 200, []string{"Test=This"}, mrangeOpt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result["a"][0]).To(BeEquivalentTo(map[interface{}]interface{}{"team": "ny"})) + Expect(result["b"][0]).To(BeEquivalentTo(map[interface{}]interface{}{"team": "sf"})) + // Test FilterBy + fts := make([]int, 0) + for i := 10; i < 20; i++ { + fts = append(fts, i) + } + mrangeOpt = &redis.TSMRevRangeOptions{FilterByTS: fts, FilterByValue: []int{1, 2}} + result, err = client.TSMRevRangeWithArgs(ctx, 0, 200, []string{"Test=This"}, mrangeOpt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result["a"][2]).To(BeEquivalentTo([]interface{}{[]interface{}{int64(16), 2.0}, []interface{}{int64(15), 1.0}})) + // Test GroupBy + mrangeOpt = &redis.TSMRevRangeOptions{GroupByLabel: "Test", Reducer: "sum"} + result, err = client.TSMRevRangeWithArgs(ctx, 0, 3, []string{"Test=This"}, mrangeOpt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result["Test=This"][3]).To(BeEquivalentTo([]interface{}{[]interface{}{int64(3), 6.0}, []interface{}{int64(2), 4.0}, []interface{}{int64(1), 2.0}, []interface{}{int64(0), 0.0}})) + + mrangeOpt = &redis.TSMRevRangeOptions{GroupByLabel: "Test", Reducer: "max"} + result, err = client.TSMRevRangeWithArgs(ctx, 0, 3, []string{"Test=This"}, mrangeOpt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(result["Test=This"][3]).To(BeEquivalentTo([]interface{}{[]interface{}{int64(3), 3.0}, []interface{}{int64(2), 2.0}, []interface{}{int64(1), 1.0}, []interface{}{int64(0), 0.0}})) + + mrangeOpt = &redis.TSMRevRangeOptions{GroupByLabel: "team", Reducer: "min"} + result, err = client.TSMRevRangeWithArgs(ctx, 0, 3, []string{"Test=This"}, mrangeOpt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(len(result)).To(BeEquivalentTo(2)) + Expect(result["team=ny"][3]).To(BeEquivalentTo([]interface{}{[]interface{}{int64(3), 3.0}, []interface{}{int64(2), 2.0}, []interface{}{int64(1), 1.0}, []interface{}{int64(0), 0.0}})) + Expect(result["team=sf"][3]).To(BeEquivalentTo([]interface{}{[]interface{}{int64(3), 3.0}, []interface{}{int64(2), 2.0}, []interface{}{int64(1), 1.0}, []interface{}{int64(0), 0.0}})) + // Test Align + mrangeOpt = &redis.TSMRevRangeOptions{Aggregator: redis.Count, BucketDuration: 10, Align: "-"} + 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(10), 1.0}, []interface{}{int64(0), 10.0}})) + + mrangeOpt = &redis.TSMRevRangeOptions{Aggregator: redis.Count, BucketDuration: 10, Align: 1} + 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() { + resultCreate, err := client.TSCreate(ctx, "a").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultCreate).To(BeEquivalentTo("OK")) + opt := &redis.TSOptions{Labels: map[string]string{"is_compaction": "true"}} + resultCreate, err = client.TSCreateWithArgs(ctx, "b", opt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultCreate).To(BeEquivalentTo("OK")) + + resultCreate, err = client.TSCreate(ctx, "c").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultCreate).To(BeEquivalentTo("OK")) + opt = &redis.TSOptions{Labels: map[string]string{"is_compaction": "true"}} + resultCreate, err = client.TSCreateWithArgs(ctx, "d", opt).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultCreate).To(BeEquivalentTo("OK")) + + resultCreateRule, err := client.TSCreateRule(ctx, "a", "b", redis.Sum, 10).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultCreateRule).To(BeEquivalentTo("OK")) + resultCreateRule, err = client.TSCreateRule(ctx, "c", "d", redis.Sum, 10).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resultCreateRule).To(BeEquivalentTo("OK")) + + _, err = client.TSAdd(ctx, "a", 1, 1).Result() + Expect(err).NotTo(HaveOccurred()) + _, err = client.TSAdd(ctx, "a", 2, 3).Result() + Expect(err).NotTo(HaveOccurred()) + _, err = client.TSAdd(ctx, "a", 11, 7).Result() + Expect(err).NotTo(HaveOccurred()) + _, err = client.TSAdd(ctx, "a", 13, 1).Result() + Expect(err).NotTo(HaveOccurred()) + + _, err = client.TSAdd(ctx, "c", 1, 1).Result() + Expect(err).NotTo(HaveOccurred()) + _, err = client.TSAdd(ctx, "c", 2, 3).Result() + Expect(err).NotTo(HaveOccurred()) + _, err = client.TSAdd(ctx, "c", 11, 7).Result() + Expect(err).NotTo(HaveOccurred()) + _, err = client.TSAdd(ctx, "c", 13, 1).Result() + Expect(err).NotTo(HaveOccurred()) + mrangeOpt := &redis.TSMRevRangeOptions{Latest: true} + result, err := client.TSMRevRangeWithArgs(ctx, 0, 10, []string{"is_compaction=true"}, mrangeOpt).Result() + 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}})) + + }) + +}) From 3b0d10b4ed8c453383170b54891daa3fa1d396ed Mon Sep 17 00:00:00 2001 From: Anurag Bandyopadhyay Date: Thu, 14 Sep 2023 18:54:23 +0530 Subject: [PATCH 009/110] 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 549322a91..1700b328c 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 ce383025e..4a3cd9ace 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 f88cb21e8..c8ea0f7bd 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 010/110] 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 b59c39554..cf1f6428d 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 011/110] 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 000000000..32fd9e817 --- /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 012/110] 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 de53064f2..50e339086 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 941838dd0..b30e22637 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 4a3cd9ace..2b2abfb75 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 c8ea0f7bd..b77447682 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 bb4816b27..f10bad38b 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 c7fbd0de8..9430eb75c 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 013/110] 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 403e199d0..4dacf2a13 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 00b0063bf..dac223363 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 d3232ecbd..e35ee85c0 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 e15284155..3bd776cf1 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 014/110] 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 8e32bca98..969ce8281 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 4b0dde646..b493abd46 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 015/110] 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 4dacf2a13..231233f79 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 dac223363..708dfcd3f 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 016/110] 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 000000000..90030b89f --- /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 3486e8e5a..1005868c0 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 017/110] 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 2b2abfb75..96d909911 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 b77447682..9b1c63e81 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 018/110] 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 cf1f6428d..6fe9c33ed 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 1005868c0..18095eeba 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 d3b4474a2..3d2f80711 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 1700b328c..00e356bbd 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 96d909911..dbbad083f 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 9b1c63e81..812d682d6 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 64020c915..1e69f4b0a 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 792bd4190..ab3a93603 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 549d78e21..d2f85d4e8 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 695c7ee3e..fc44a14ca 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 b726ec43a..77c424e89 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 e629f5f7a..924a9bfc8 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 e75647a3d..d93ec45fe 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 a97a49c9d..6a06ee217 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 f04dc1c3b..83190d394 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 9408446b5..295da1268 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 e5acb9f8f..2ade8a0b9 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 21307110b..8178f86df 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 daa8d7692..cbcd2cc09 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 8e87d76f8..6aaaf1c08 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 fa9ac6c9e..1db36fdb4 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 969ce8281..61a3460a0 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 b493abd46..61829c527 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 5fafea403..8e6ad874a 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 1318615e7..b1117a4dc 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 5ead2fa5f..61cc3a5b6 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 291274de3..29897b54c 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 0572ba346..7cf09111b 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 019/110] 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 fc44a14ca..915838f34 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 020/110] 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 dbbad083f..2537c0ac5 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 812d682d6..8e00c8368 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 021/110] 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 2537c0ac5..8cff2cb47 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 61a3460a0..d397b490a 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 8e6ad874a..0c67cdf2e 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 022/110] 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 bb9b14beb..f391cedae 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 f10bad38b..ba65defd2 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 023/110] 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 c13deb880..bb0c1e9d2 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 be26cfe3e..b10bcec26 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 024/110] 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 bb0c1e9d2..6c65f094f 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 b10bcec26..21b4f64ee 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 025/110] 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 | 285 +- commands.go | 3588 +---------------- 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, 3650 insertions(+), 3559 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 64a7cb512..6f868895b 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 6fe9c33ed..dc2fe780a 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 000000000..06847be2e --- /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 000000000..1436b02af --- /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 b13f8e7e9..0caf0977a 100644 --- a/cluster_commands.go +++ b/cluster_commands.go @@ -1,109 +1,192 @@ package redis -import ( - "context" - "sync" - "sync/atomic" -) - -func (c *ClusterClient) DBSize(ctx context.Context) *IntCmd { - cmd := NewIntCmd(ctx, "dbsize") - _ = c.withProcessHook(ctx, cmd, func(ctx context.Context, _ Cmder) error { - var size int64 - err := c.ForEachMaster(ctx, func(ctx context.Context, master *Client) error { - n, err := master.DBSize(ctx).Result() - if err != nil { - return err - } - atomic.AddInt64(&size, n) - return nil - }) - if err != nil { - cmd.SetErr(err) - } else { - cmd.val = size - } - return nil - }) - return cmd -} - -func (c *ClusterClient) ScriptLoad(ctx context.Context, script string) *StringCmd { - cmd := NewStringCmd(ctx, "script", "load", script) - _ = c.withProcessHook(ctx, cmd, func(ctx context.Context, _ Cmder) error { - var mu sync.Mutex - err := c.ForEachShard(ctx, func(ctx context.Context, shard *Client) error { - val, err := shard.ScriptLoad(ctx, script).Result() - if err != nil { - return err - } - - mu.Lock() - if cmd.Val() == "" { - cmd.val = val - } - mu.Unlock() - - return nil - }) - if err != nil { - cmd.SetErr(err) - } - return nil - }) - return cmd -} - -func (c *ClusterClient) ScriptFlush(ctx context.Context) *StatusCmd { - cmd := NewStatusCmd(ctx, "script", "flush") - _ = c.withProcessHook(ctx, cmd, func(ctx context.Context, _ Cmder) error { - err := c.ForEachShard(ctx, func(ctx context.Context, shard *Client) error { - return shard.ScriptFlush(ctx).Err() - }) - if err != nil { - cmd.SetErr(err) - } - return nil - }) - return cmd -} - -func (c *ClusterClient) ScriptExists(ctx context.Context, hashes ...string) *BoolSliceCmd { - args := make([]interface{}, 2+len(hashes)) - args[0] = "script" - args[1] = "exists" - for i, hash := range hashes { - args[2+i] = hash +import "context" + +type ClusterCmdable interface { + ClusterMyShardID(ctx context.Context) *StringCmd + ClusterSlots(ctx context.Context) *ClusterSlotsCmd + ClusterShards(ctx context.Context) *ClusterShardsCmd + ClusterLinks(ctx context.Context) *ClusterLinksCmd + ClusterNodes(ctx context.Context) *StringCmd + ClusterMeet(ctx context.Context, host, port string) *StatusCmd + ClusterForget(ctx context.Context, nodeID string) *StatusCmd + ClusterReplicate(ctx context.Context, nodeID string) *StatusCmd + ClusterResetSoft(ctx context.Context) *StatusCmd + ClusterResetHard(ctx context.Context) *StatusCmd + ClusterInfo(ctx context.Context) *StringCmd + ClusterKeySlot(ctx context.Context, key string) *IntCmd + ClusterGetKeysInSlot(ctx context.Context, slot int, count int) *StringSliceCmd + ClusterCountFailureReports(ctx context.Context, nodeID string) *IntCmd + ClusterCountKeysInSlot(ctx context.Context, slot int) *IntCmd + ClusterDelSlots(ctx context.Context, slots ...int) *StatusCmd + ClusterDelSlotsRange(ctx context.Context, min, max int) *StatusCmd + ClusterSaveConfig(ctx context.Context) *StatusCmd + ClusterSlaves(ctx context.Context, nodeID string) *StringSliceCmd + ClusterFailover(ctx context.Context) *StatusCmd + ClusterAddSlots(ctx context.Context, slots ...int) *StatusCmd + ClusterAddSlotsRange(ctx context.Context, min, max int) *StatusCmd + ReadOnly(ctx context.Context) *StatusCmd + ReadWrite(ctx context.Context) *StatusCmd +} + +func (c cmdable) ClusterMyShardID(ctx context.Context) *StringCmd { + cmd := NewStringCmd(ctx, "cluster", "myshardid") + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) 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 := NewBoolSliceCmd(ctx, args...) + cmd := NewStatusCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} - result := make([]bool, len(hashes)) - for i := range result { - result[i] = true +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 +} - _ = c.withProcessHook(ctx, cmd, func(ctx context.Context, _ Cmder) error { - var mu sync.Mutex - err := c.ForEachShard(ctx, func(ctx context.Context, shard *Client) error { - val, err := shard.ScriptExists(ctx, hashes...).Result() - if err != nil { - return err - } - - mu.Lock() - for i, v := range val { - result[i] = result[i] && v - } - mu.Unlock() - - return nil - }) - if err != nil { - cmd.SetErr(err) - } else { - cmd.val = result - } - return nil - }) +func (c cmdable) ReadWrite(ctx context.Context) *StatusCmd { + cmd := NewStatusCmd(ctx, "readwrite") + _ = c(ctx, cmd) return cmd } diff --git a/commands.go b/commands.go index 8cff2cb47..fffea441e 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 + MemoryUsage(ctx context.Context, key string, samples ...int) *IntCmd ModuleLoadex(ctx context.Context, conf *ModuleLoadexConfig) *StringCmd + ACLCmdable + HashCmdable + HyperLogLogCmdable + GeoCmdable + GenericCmdable + ListCmdable + SetCmdable + SortedSetCmdable + ClusterCmdable + ScriptingFunctionsCmdable + StringCmdable + PubSubCmdable GearsCmdable ProbabilisticCmdable TimeseriesCmdable @@ -710,3285 +424,260 @@ func (c cmdable) Quit(_ context.Context) *StatusCmd { panic("not implemented") } -func (c cmdable) Del(ctx context.Context, keys ...string) *IntCmd { - args := make([]interface{}, 1+len(keys)) - args[0] = "del" - for i, key := range keys { - args[1+i] = key - } - cmd := NewIntCmd(ctx, args...) +//------------------------------------------------------------------------------ + +func (c cmdable) BgRewriteAOF(ctx context.Context) *StatusCmd { + cmd := NewStatusCmd(ctx, "bgrewriteaof") _ = 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...) +func (c cmdable) BgSave(ctx context.Context) *StatusCmd { + cmd := NewStatusCmd(ctx, "bgsave") _ = c(ctx, cmd) return cmd } -func (c cmdable) Dump(ctx context.Context, key string) *StringCmd { - cmd := NewStringCmd(ctx, "dump", key) +func (c cmdable) ClientKill(ctx context.Context, ipPort string) *StatusCmd { + cmd := NewStatusCmd(ctx, "client", "kill", ipPort) _ = c(ctx, cmd) return cmd } -func (c cmdable) Exists(ctx context.Context, keys ...string) *IntCmd { - args := make([]interface{}, 1+len(keys)) - args[0] = "exists" +// ClientKillByFilter is new style syntax, while the ClientKill is old +// +// CLIENT KILL