diff --git a/CHANGELOG.md b/CHANGELOG.md index d87d6dc246..d3432f4ae2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,12 +15,14 @@ * Go: Add `ZPopMin` and `ZPopMax` ([#2850](https://github.com/valkey-io/valkey-glide/pull/2850)) * Java: Add binary version of `ZRANK WITHSCORE` ([#2896](https://github.com/valkey-io/valkey-glide/pull/2896)) * Go: Add `ZCARD` ([#2838](https://github.com/valkey-io/valkey-glide/pull/2838)) +* Java, Node, Python: Update documentation for CONFIG SET and CONFIG GET ([#2919](https://github.com/valkey-io/valkey-glide/pull/2919)) * Go: Add `BZPopMin` ([#2849](https://github.com/valkey-io/valkey-glide/pull/2849)) #### Breaking Changes #### Fixes +* Node: Fix `zrangeWithScores` (disallow `RangeByLex` as it is not supported) ([#2926](https://github.com/valkey-io/valkey-glide/pull/2926)) * Core: improve fix in #2381 ([#2929](https://github.com/valkey-io/valkey-glide/pull/2929)) #### Operational Enhancements diff --git a/go/api/base_client.go b/go/api/base_client.go index a3c10587e2..d65228dbb6 100644 --- a/go/api/base_client.go +++ b/go/api/base_client.go @@ -1476,6 +1476,101 @@ func (client *baseClient) BZPopMin(keys []string, timeoutSecs float64) (Result[K return handleKeyWithMemberAndScoreResponse(result) } +// Returns the specified range of elements in the sorted set stored at `key`. +// `ZRANGE` can perform different types of range queries: by index (rank), by the score, or by lexicographical order. +// +// To get the elements with their scores, see [ZRangeWithScores]. +// +// See [valkey.io] for more details. +// +// Parameters: +// +// key - The key of the sorted set. +// rangeQuery - The range query object representing the type of range query to perform. +// - For range queries by index (rank), use [RangeByIndex]. +// - For range queries by lexicographical order, use [RangeByLex]. +// - For range queries by score, use [RangeByScore]. +// +// Return value: +// +// An array of elements within the specified range. +// If `key` does not exist, it is treated as an empty sorted set, and the command returns an empty array. +// +// Example: +// +// // Retrieve all members of a sorted set in ascending order +// result, err := client.ZRange("my_sorted_set", options.NewRangeByIndexQuery(0, -1)) +// +// // Retrieve members within a score range in descending order +// +// query := options.NewRangeByScoreQuery(options.NewScoreBoundary(3, false), +// options.NewInfiniteScoreBoundary(options.NegativeInfinity)). +// +// .SetReverse() +// result, err := client.ZRange("my_sorted_set", query) +// // `result` contains members which have scores within the range of negative infinity to 3, in descending order +// +// [valkey.io]: https://valkey.io/commands/zrange/ +func (client *baseClient) ZRange(key string, rangeQuery options.ZRangeQuery) ([]Result[string], error) { + args := make([]string, 0, 10) + args = append(args, key) + args = append(args, rangeQuery.ToArgs()...) + result, err := client.executeCommand(C.ZRange, args) + if err != nil { + return nil, err + } + + return handleStringArrayResponse(result) +} + +// Returns the specified range of elements with their scores in the sorted set stored at `key`. +// `ZRANGE` can perform different types of range queries: by index (rank), by the score, or by lexicographical order. +// +// See [valkey.io] for more details. +// +// Parameters: +// +// key - The key of the sorted set. +// rangeQuery - The range query object representing the type of range query to perform. +// - For range queries by index (rank), use [RangeByIndex]. +// - For range queries by score, use [RangeByScore]. +// +// Return value: +// +// A map of elements and their scores within the specified range. +// If `key` does not exist, it is treated as an empty sorted set, and the command returns an empty map. +// +// Example: +// +// // Retrieve all members of a sorted set in ascending order +// result, err := client.ZRangeWithScores("my_sorted_set", options.NewRangeByIndexQuery(0, -1)) +// +// // Retrieve members within a score range in descending order +// +// query := options.NewRangeByScoreQuery(options.NewScoreBoundary(3, false), +// options.NewInfiniteScoreBoundary(options.NegativeInfinity)). +// +// SetReverse() +// result, err := client.ZRangeWithScores("my_sorted_set", query) +// // `result` contains members with scores within the range of negative infinity to 3, in descending order +// +// [valkey.io]: https://valkey.io/commands/zrange/ +func (client *baseClient) ZRangeWithScores( + key string, + rangeQuery options.ZRangeQueryWithScores, +) (map[Result[string]]Result[float64], error) { + args := make([]string, 0, 10) + args = append(args, key) + args = append(args, rangeQuery.ToArgs()...) + args = append(args, "WITHSCORES") + result, err := client.executeCommand(C.ZRange, args) + if err != nil { + return nil, err + } + + return handleStringDoubleMapResponse(result) +} + func (client *baseClient) Persist(key string) (Result[bool], error) { result, err := client.executeCommand(C.Persist, []string{key}) if err != nil { @@ -1490,6 +1585,54 @@ func (client *baseClient) ZCount(key string, rangeOptions *options.ZCountRange) return CreateNilInt64Result(), err } result, err := client.executeCommand(C.ZCount, append([]string{key}, zCountRangeArgs...)) +} + +func (client *baseClient) ZRank(key string, member string) (Result[int64], error) { + result, err := client.executeCommand(C.ZRank, []string{key, member}) + if err != nil { + return CreateNilInt64Result(), err + } + return handleLongOrNullResponse(result) +} + +func (client *baseClient) ZRankWithScore(key string, member string) (Result[int64], Result[float64], error) { + result, err := client.executeCommand(C.ZRank, []string{key, member, options.WithScore}) + if err != nil { + return CreateNilInt64Result(), CreateNilFloat64Result(), err + } + return handleLongAndDoubleOrNullResponse(result) +} + +func (client *baseClient) ZRevRank(key string, member string) (Result[int64], error) { + result, err := client.executeCommand(C.ZRevRank, []string{key, member}) + if err != nil { + return CreateNilInt64Result(), err + } + return handleLongOrNullResponse(result) +} + +func (client *baseClient) ZRevRankWithScore(key string, member string) (Result[int64], Result[float64], error) { + result, err := client.executeCommand(C.ZRevRank, []string{key, member, options.WithScore}) + if err != nil { + return CreateNilInt64Result(), CreateNilFloat64Result(), err + } + return handleLongAndDoubleOrNullResponse(result) +} + +func (client *baseClient) XTrim(key string, options *options.XTrimOptions) (Result[int64], error) { + xTrimArgs, err := options.ToArgs() + if err != nil { + return CreateNilInt64Result(), err + } + result, err := client.executeCommand(C.XTrim, append([]string{key}, xTrimArgs...)) + if err != nil { + return CreateNilInt64Result(), err + } + return handleLongResponse(result) +} + +func (client *baseClient) XLen(key string) (Result[int64], error) { + result, err := client.executeCommand(C.XLen, []string{key}) if err != nil { return CreateNilInt64Result(), err } diff --git a/go/api/options/constants.go b/go/api/options/constants.go index f38b0f4541..83b0b3f0b8 100644 --- a/go/api/options/constants.go +++ b/go/api/options/constants.go @@ -3,7 +3,8 @@ package options const ( - CountKeyword string = "COUNT" // Valkey API keyword used to extract specific number of matching indices from a list. - MatchKeyword string = "MATCH" // Valkey API keyword used to indicate the match filter. - NoValue string = "NOVALUE" // Valkey API keyword for the no value option for hcsan command. + CountKeyword string = "COUNT" // Valkey API keyword used to extract specific number of matching indices from a list. + MatchKeyword string = "MATCH" // Valkey API keyword used to indicate the match filter. + NoValue string = "NOVALUE" // Valkey API keyword for the no value option for hcsan command. + WithScore string = "WITHSCORE" // Valkey API keyword for the with score option for zrank and zrevrank commands. ) diff --git a/go/api/options/stream_options.go b/go/api/options/stream_options.go index 2a07c0ad2c..95a8c69d33 100644 --- a/go/api/options/stream_options.go +++ b/go/api/options/stream_options.go @@ -85,36 +85,34 @@ func NewXTrimOptionsWithMaxLen(threshold int64) *XTrimOptions { } // Match exactly on the threshold. -func (xto *XTrimOptions) SetExactTrimming() *XTrimOptions { - xto.exact = triStateBoolTrue - return xto +func (xTrimOptions *XTrimOptions) SetExactTrimming() *XTrimOptions { + xTrimOptions.exact = triStateBoolTrue + return xTrimOptions } // Trim in a near-exact manner, which is more efficient. -func (xto *XTrimOptions) SetNearlyExactTrimming() *XTrimOptions { - xto.exact = triStateBoolFalse - return xto +func (xTrimOptions *XTrimOptions) SetNearlyExactTrimming() *XTrimOptions { + xTrimOptions.exact = triStateBoolFalse + return xTrimOptions } // Max number of stream entries to be trimmed for non-exact match. -func (xto *XTrimOptions) SetNearlyExactTrimmingAndLimit(limit int64) *XTrimOptions { - xto.exact = triStateBoolFalse - xto.limit = limit - return xto +func (xTrimOptions *XTrimOptions) SetNearlyExactTrimmingAndLimit(limit int64) *XTrimOptions { + xTrimOptions.exact = triStateBoolFalse + xTrimOptions.limit = limit + return xTrimOptions } -func (xto *XTrimOptions) ToArgs() ([]string, error) { - args := []string{} - args = append(args, xto.method) - if xto.exact == triStateBoolTrue { +func (xTrimOptions *XTrimOptions) ToArgs() ([]string, error) { + args := []string{xTrimOptions.method} + if xTrimOptions.exact == triStateBoolTrue { args = append(args, "=") - } else if xto.exact == triStateBoolFalse { + } else if xTrimOptions.exact == triStateBoolFalse { args = append(args, "~") } - args = append(args, xto.threshold) - if xto.limit > 0 { - args = append(args, "LIMIT", utils.IntToString(xto.limit)) + args = append(args, xTrimOptions.threshold) + if xTrimOptions.limit > 0 { + args = append(args, "LIMIT", utils.IntToString(xTrimOptions.limit)) } - var err error - return args, err + return args, nil } diff --git a/go/api/options/zrange_options.go b/go/api/options/zrange_options.go new file mode 100644 index 0000000000..002dc38e24 --- /dev/null +++ b/go/api/options/zrange_options.go @@ -0,0 +1,200 @@ +// Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 + +package options + +import ( + "github.com/valkey-io/valkey-glide/go/glide/utils" +) + +// Query for `ZRange` in [SortedSetCommands] +// - For range queries by index (rank), use `RangeByIndex`. +// - For range queries by lexicographical order, use `RangeByLex`. +// - For range queries by score, use `RangeByScore`. +type ZRangeQuery interface { + ToArgs() []string +} + +// Queries a range of elements from a sorted set by theirs index. +type RangeByIndex struct { + start, end int64 + reverse bool +} + +// Queries a range of elements from a sorted set by theirs score. +type RangeByScore struct { + start, end scoreBoundary + reverse bool + Limit *Limit +} + +// Queries a range of elements from a sorted set by theirs lexicographical order. +type RangeByLex struct { + start, end lexBoundary + reverse bool + Limit *Limit +} + +type ( + InfBoundary string + scoreBoundary string + lexBoundary string +) + +const ( + // The highest bound in the sorted set + PositiveInfinity InfBoundary = "+" + // The lowest bound in the sorted set + NegativeInfinity InfBoundary = "-" +) + +// Create a new inclusive score boundary. +func NewInclusiveScoreBoundary(bound float64) scoreBoundary { + return scoreBoundary(utils.FloatToString(bound)) +} + +// Create a new score boundary. +func NewScoreBoundary(bound float64, isInclusive bool) scoreBoundary { + if !isInclusive { + return scoreBoundary("(" + utils.FloatToString(bound)) + } + return scoreBoundary(utils.FloatToString(bound)) +} + +// Create a new score boundary defined by an infinity. +func NewInfiniteScoreBoundary(bound InfBoundary) scoreBoundary { + return scoreBoundary(string(bound) + "inf") +} + +// Create a new lex boundary. +func NewLexBoundary(bound string, isInclusive bool) lexBoundary { + if !isInclusive { + return lexBoundary("(" + bound) + } + return lexBoundary("[" + bound) +} + +// Create a new lex boundary defined by an infinity. +func NewInfiniteLexBoundary(bound InfBoundary) lexBoundary { + return lexBoundary(string(bound)) +} + +// TODO re-use limit from `SORT` https://github.com/valkey-io/valkey-glide/pull/2888 +// Limit struct represents the range of elements to retrieve +// The LIMIT argument is commonly used to specify a subset of results from the matching elements, similar to the +// LIMIT clause in SQL (e.g., `SELECT LIMIT offset, count`). +type Limit struct { + // The starting position of the range, zero based. + offset int64 + // The maximum number of elements to include in the range. A negative count returns all elementsnfrom the offset. + count int64 +} + +func (limit *Limit) toArgs() []string { + return []string{"LIMIT", utils.IntToString(limit.offset), utils.IntToString(limit.count)} +} + +// Queries a range of elements from a sorted set by theirs index. +// +// Parameters: +// +// start - The start index of the range. +// end - The end index of the range. +func NewRangeByIndexQuery(start int64, end int64) *RangeByIndex { + return &RangeByIndex{start, end, false} +} + +// Reverses the sorted set, with index `0` as the element with the highest score. +func (rbi *RangeByIndex) SetReverse() *RangeByIndex { + rbi.reverse = true + return rbi +} + +func (rbi *RangeByIndex) ToArgs() []string { + args := make([]string, 0, 3) + args = append(args, utils.IntToString(rbi.start), utils.IntToString(rbi.end)) + if rbi.reverse { + args = append(args, "REV") + } + return args +} + +// Queries a range of elements from a sorted set by theirs score. +// +// Parameters: +// +// start - The start score of the range. +// end - The end score of the range. +func NewRangeByScoreQuery(start scoreBoundary, end scoreBoundary) *RangeByScore { + return &RangeByScore{start, end, false, nil} +} + +// Reverses the sorted set, with index `0` as the element with the highest score. +func (rbs *RangeByScore) SetReverse() *RangeByScore { + rbs.reverse = true + return rbs +} + +// The limit argument for a range query, unset by default. See [Limit] for more information. +func (rbs *RangeByScore) SetLimit(offset, count int64) *RangeByScore { + rbs.Limit = &Limit{offset, count} + return rbs +} + +func (rbs *RangeByScore) ToArgs() []string { + args := make([]string, 0, 7) + args = append(args, string(rbs.start), string(rbs.end), "BYSCORE") + if rbs.reverse { + args = append(args, "REV") + } + if rbs.Limit != nil { + args = append(args, rbs.Limit.toArgs()...) + } + return args +} + +// Queries a range of elements from a sorted set by theirs lexicographical order. +// +// Parameters: +// +// start - The start lex of the range. +// end - The end lex of the range. +func NewRangeByLexQuery(start lexBoundary, end lexBoundary) *RangeByLex { + return &RangeByLex{start, end, false, nil} +} + +// Reverses the sorted set, with index `0` as the element with the highest score. +func (rbl *RangeByLex) SetReverse() *RangeByLex { + rbl.reverse = true + return rbl +} + +// The limit argument for a range query, unset by default. See [Limit] for more information. +func (rbl *RangeByLex) SetLimit(offset, count int64) *RangeByLex { + rbl.Limit = &Limit{offset, count} + return rbl +} + +func (rbl *RangeByLex) ToArgs() []string { + args := make([]string, 0, 7) + args = append(args, string(rbl.start), string(rbl.end), "BYLEX") + if rbl.reverse { + args = append(args, "REV") + } + if rbl.Limit != nil { + args = append(args, rbl.Limit.toArgs()...) + } + return args +} + +// Query for `ZRangeWithScores` in [SortedSetCommands] +// - For range queries by index (rank), use `RangeByIndex`. +// - For range queries by score, use `RangeByScore`. +type ZRangeQueryWithScores interface { + // A dummy interface to distinguish queries for `ZRange` and `ZRangeWithScores` + // `ZRangeWithScores` does not support BYLEX + dummy() + ToArgs() []string +} + +func (q *RangeByIndex) dummy() {} +func (q *RangeByScore) dummy() {} diff --git a/go/api/response_handlers.go b/go/api/response_handlers.go index 9f788f507d..fe2ecde613 100644 --- a/go/api/response_handlers.go +++ b/go/api/response_handlers.go @@ -267,6 +267,32 @@ func handleDoubleResponse(response *C.struct_CommandResponse) (Result[float64], return CreateFloat64Result(float64(response.float_value)), nil } +func handleLongAndDoubleOrNullResponse(response *C.struct_CommandResponse) (Result[int64], Result[float64], error) { + defer C.free_command_response(response) + + typeErr := checkResponseType(response, C.Array, true) + if typeErr != nil { + return CreateNilInt64Result(), CreateNilFloat64Result(), typeErr + } + + if response.response_type == C.Null { + return CreateNilInt64Result(), CreateNilFloat64Result(), nil + } + + rank := CreateNilInt64Result() + score := CreateNilFloat64Result() + for _, v := range unsafe.Slice(response.array_value, response.array_value_len) { + if v.response_type == C.Int { + rank = CreateInt64Result(int64(v.int_value)) + } + if v.response_type == C.Float { + score = CreateFloat64Result(float64(v.float_value)) + } + } + + return rank, score, nil +} + func handleBooleanResponse(response *C.struct_CommandResponse) (Result[bool], error) { defer C.free_command_response(response) diff --git a/go/api/sorted_set_commands.go b/go/api/sorted_set_commands.go index 032c794436..ee270c80d9 100644 --- a/go/api/sorted_set_commands.go +++ b/go/api/sorted_set_commands.go @@ -253,7 +253,7 @@ type SortedSetCommands interface { // A `KeyWithMemberAndScore` struct containing the key where the member was popped out, the member // itself, and the member score. If no member could be popped and the `timeout` expired, returns `nil`. // - // example + // Example: // zaddResult1, err := client.ZAdd(key1, map[string]float64{"a": 1.0, "b": 1.5}) // zaddResult2, err := client.ZAdd(key2, map[string]float64{"c": 2.0}) // result, err := client.BZPopMin([]string{key1, key2}, float64(.5)) @@ -263,7 +263,121 @@ type SortedSetCommands interface { // [blocking commands]: https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#blocking-commands BZPopMin(keys []string, timeoutSecs float64) (Result[KeyWithMemberAndScore], error) - // Returns the number of members in the sorted set stored at `key` with scores between `min` and `max` score. + ZRange(key string, rangeQuery options.ZRangeQuery) ([]Result[string], error) + + ZRangeWithScores(key string, rangeQuery options.ZRangeQueryWithScores) (map[Result[string]]Result[float64], error) + + // Returns the rank of `member` in the sorted set stored at `key`, with + // scores ordered from low to high, starting from `0`. + // To get the rank of `member` with its score, see [ZRankWithScore]. + // + // See [valkey.io] for details. + // + // Parameters: + // key - The key of the sorted set. + // member - The member to get the rank of. + // + // Return value: + // The rank of `member` in the sorted set. + // If `key` doesn't exist, or if `member` is not present in the set, + // `nil` will be returned. + // + // Example: + // res, err := client.ZRank("mySortedSet", "member1") + // fmt.Println(res.Value()) // Output: 3 + // + // res2, err := client.ZRank("mySortedSet", "non-existing-member") + // if res2.IsNil() { + // fmt.Println("Member not found") + // } + // + // [valkey.io]: https://valkey.io/commands/zrank/ + ZRank(key string, member string) (Result[int64], error) + + // Returns the rank of `member` in the sorted set stored at `key` with its + // score, where scores are ordered from the lowest to highest, starting from `0`. + // + // See [valkey.io] for details. + // + // Parameters: + // key - The key of the sorted set. + // member - The member to get the rank of. + // + // Return value: + // A tuple containing the rank of `member` and its score. + // If `key` doesn't exist, or if `member` is not present in the set, + // `nil` will be returned. + // + // Example: + // resRank, resScore, err := client.ZRankWithScore("mySortedSet", "member1") + // fmt.Println(resRank.Value()) // Output: 3 + // fmt.Println(resScore.Value()) // Output: 5.0 + // + // res2Rank, res2Score, err := client.ZRankWithScore("mySortedSet", "non-existing-member") + // if res2Rank.IsNil() { + // fmt.Println("Member not found") + // } + // + // [valkey.io]: https://valkey.io/commands/zrank/ + ZRankWithScore(key string, member string) (Result[int64], Result[float64], error) + + // Returns the rank of `member` in the sorted set stored at `key`, where + // scores are ordered from the highest to lowest, starting from `0`. + // To get the rank of `member` with its score, see [ZRevRankWithScore]. + // + // See [valkey.io] for details. + // + // Parameters: + // key - The key of the sorted set. + // member - The member to get the rank of. + // + // Return value: + // The rank of `member` in the sorted set, where ranks are ordered from high to + // low based on scores. + // If `key` doesn't exist, or if `member` is not present in the set, + // `nil` will be returned. + // + // Example: + // res, err := client.ZRevRank("mySortedSet", "member2") + // fmt.Println(res.Value()) // Output: 1 + // + // res2, err := client.ZRevRank("mySortedSet", "non-existing-member") + // if res2.IsNil() { + // fmt.Println("Member not found") + // } + // + // [valkey.io]: https://valkey.io/commands/zrevrank/ + ZRevRank(key string, member string) (Result[int64], error) + + // Returns the rank of `member` in the sorted set stored at `key`, where + // scores are ordered from the highest to lowest, starting from `0`. + // To get the rank of `member` with its score, see [ZRevRankWithScore]. + // + // See [valkey.io] for details. + // + // Parameters: + // key - The key of the sorted set. + // member - The member to get the rank of. + // + // Return value: + // A tuple containing the rank of `member` and its score. + // If `key` doesn't exist, or if `member` is not present in the set, + // `nil` will be returned.s + // + // Example: + // resRank, resScore, err := client.ZRevRankWithScore("mySortedSet", "member2") + // fmt.Println(resRank.Value()) // Output: 1 + // fmt.Println(resScore.Value()) // Output: 6.0 + // + // res2Rank, res2Score, err := client.ZRevRankWithScore("mySortedSet", "non-existing-member") + // if res2Rank.IsNil() { + // fmt.Println("Member not found") + // } + // + // [valkey.io]: https://valkey.io/commands/zrevrank/ + ZRevRankWithScore(key string, member string) (Result[int64], Result[float64], error) + + // Returns the number of members in the sorted set stored at `key` with scores between `min` and `max` score. // // See [valkey.io] for details. // diff --git a/go/api/stream_commands.go b/go/api/stream_commands.go index 1696a168c2..5bc1f20856 100644 --- a/go/api/stream_commands.go +++ b/go/api/stream_commands.go @@ -49,4 +49,56 @@ type StreamCommands interface { // // [valkey.io]: https://valkey.io/commands/xadd/ XAddWithOptions(key string, values [][]string, options *options.XAddOptions) (Result[string], error) + + // Trims the stream by evicting older entries. + // + // See [valkey.io] for details. + // + // Parameters: + // key - The key of the stream. + // options - Stream trim options + // + // Return value: + // Result[int64] - The number of entries deleted from the stream. + // + // For example: + // xAddResult, err = client.XAddWithOptions( + // "key1", + // [][]string{{field1, "foo4"}, {field2, "bar4"}}, + // options.NewXAddOptions().SetTrimOptions( + // options.NewXTrimOptionsWithMinId(id).SetExactTrimming(), + // ), + // ) + // xTrimResult, err := client.XTrim( + // "key1", + // options.NewXTrimOptionsWithMaxLen(1).SetExactTrimming(), + // ) + // fmt.Println(xTrimResult.Value()) // Output: 1 + // + // [valkey.io]: https://valkey.io/commands/xtrim/ + XTrim(key string, options *options.XTrimOptions) (Result[int64], error) + + // Returns the number of entries in the stream stored at `key`. + // + // See [valkey.io] for details. + // + // Parameters: + // key - The key of the stream. + // + // Return value: + // Result[int64] - The number of entries in the stream. If `key` does not exist, return 0. + // + // For example: + // xAddResult, err = client.XAddWithOptions( + // "key1", + // [][]string{{field1, "foo4"}, {field2, "bar4"}}, + // options.NewXAddOptions().SetTrimOptions( + // options.NewXTrimOptionsWithMinId(id).SetExactTrimming(), + // ), + // ) + // xLenResult, err = client.XLen("key1") + // fmt.Println(xLenResult.Value()) // Output: 2 + // + // [valkey.io]: https://valkey.io/commands/xlen/ + XLen(key string) (Result[int64], error) } diff --git a/go/integTest/glide_test_suite_test.go b/go/integTest/glide_test_suite_test.go index 46752041ce..fc6a5c8ff7 100644 --- a/go/integTest/glide_test_suite_test.go +++ b/go/integTest/glide_test_suite_test.go @@ -115,7 +115,7 @@ func extractAddresses(suite *GlideTestSuite, output string) []api.NodeAddress { func runClusterManager(suite *GlideTestSuite, args []string, ignoreExitCode bool) string { pythonArgs := append([]string{"../../utils/cluster_manager.py"}, args...) output, err := exec.Command("python3", pythonArgs...).CombinedOutput() - if len(output) > 0 { + if len(output) > 0 && !ignoreExitCode { suite.T().Logf("cluster_manager.py output:\n====\n%s\n====\n", string(output)) } diff --git a/go/integTest/shared_commands_test.go b/go/integTest/shared_commands_test.go index cd8c0e12d1..2ce4503361 100644 --- a/go/integTest/shared_commands_test.go +++ b/go/integTest/shared_commands_test.go @@ -4457,6 +4457,243 @@ func (suite *GlideTestSuite) TestZRem() { }) } +func (suite *GlideTestSuite) TestZRange() { + suite.runWithDefaultClients(func(client api.BaseClient) { + t := suite.T() + key := uuid.New().String() + memberScoreMap := map[string]float64{ + "a": 1.0, + "b": 2.0, + "c": 3.0, + } + _, err := client.ZAdd(key, memberScoreMap) + assert.NoError(t, err) + // index [0:1] + res, err := client.ZRange(key, options.NewRangeByIndexQuery(0, 1)) + expected := []api.Result[string]{ + api.CreateStringResult("a"), + api.CreateStringResult("b"), + } + assert.NoError(t, err) + assert.Equal(t, expected, res) + // index [0:-1] (all) + res, err = client.ZRange(key, options.NewRangeByIndexQuery(0, -1)) + expected = []api.Result[string]{ + api.CreateStringResult("a"), + api.CreateStringResult("b"), + api.CreateStringResult("c"), + } + assert.NoError(t, err) + assert.Equal(t, expected, res) + // index [3:1] (none) + res, err = client.ZRange(key, options.NewRangeByIndexQuery(3, 1)) + assert.NoError(t, err) + assert.Equal(t, 0, len(res)) + // score [-inf:3] + var query options.ZRangeQuery + query = options.NewRangeByScoreQuery( + options.NewInfiniteScoreBoundary(options.NegativeInfinity), + options.NewScoreBoundary(3, true)) + res, err = client.ZRange(key, query) + expected = []api.Result[string]{ + api.CreateStringResult("a"), + api.CreateStringResult("b"), + api.CreateStringResult("c"), + } + assert.NoError(t, err) + assert.Equal(t, expected, res) + // score [-inf:3) + query = options.NewRangeByScoreQuery( + options.NewInfiniteScoreBoundary(options.NegativeInfinity), + options.NewScoreBoundary(3, false)) + res, err = client.ZRange(key, query) + expected = []api.Result[string]{ + api.CreateStringResult("a"), + api.CreateStringResult("b"), + } + assert.NoError(t, err) + assert.Equal(t, expected, res) + // score (3:-inf] reverse + query = options.NewRangeByScoreQuery( + options.NewScoreBoundary(3, false), + options.NewInfiniteScoreBoundary(options.NegativeInfinity)). + SetReverse() + res, err = client.ZRange(key, query) + expected = []api.Result[string]{ + api.CreateStringResult("b"), + api.CreateStringResult("a"), + } + assert.NoError(t, err) + assert.Equal(t, expected, res) + // score [-inf:+inf] limit 1 2 + query = options.NewRangeByScoreQuery( + options.NewInfiniteScoreBoundary(options.NegativeInfinity), + options.NewInfiniteScoreBoundary(options.PositiveInfinity)). + SetLimit(1, 2) + res, err = client.ZRange(key, query) + expected = []api.Result[string]{ + api.CreateStringResult("b"), + api.CreateStringResult("c"), + } + assert.NoError(t, err) + assert.Equal(t, expected, res) + // score [-inf:3) reverse (none) + query = options.NewRangeByScoreQuery( + options.NewInfiniteScoreBoundary(options.NegativeInfinity), + options.NewScoreBoundary(3, true)). + SetReverse() + res, err = client.ZRange(key, query) + assert.NoError(t, err) + assert.Equal(t, 0, len(res)) + // score [+inf:3) (none) + query = options.NewRangeByScoreQuery( + options.NewInfiniteScoreBoundary(options.PositiveInfinity), + options.NewScoreBoundary(3, false)) + res, err = client.ZRange(key, query) + assert.NoError(t, err) + assert.Equal(t, 0, len(res)) + // lex [-:c) + query = options.NewRangeByLexQuery( + options.NewInfiniteLexBoundary(options.NegativeInfinity), + options.NewLexBoundary("c", false)) + res, err = client.ZRange(key, query) + expected = []api.Result[string]{ + api.CreateStringResult("a"), + api.CreateStringResult("b"), + } + assert.NoError(t, err) + assert.Equal(t, expected, res) + // lex [+:-] reverse limit 1 2 + query = options.NewRangeByLexQuery( + options.NewInfiniteLexBoundary(options.PositiveInfinity), + options.NewInfiniteLexBoundary(options.NegativeInfinity)). + SetReverse().SetLimit(1, 2) + res, err = client.ZRange(key, query) + expected = []api.Result[string]{ + api.CreateStringResult("b"), + api.CreateStringResult("a"), + } + assert.NoError(t, err) + assert.Equal(t, expected, res) + // lex (c:-] reverse + query = options.NewRangeByLexQuery( + options.NewLexBoundary("c", false), + options.NewInfiniteLexBoundary(options.NegativeInfinity)). + SetReverse() + res, err = client.ZRange(key, query) + expected = []api.Result[string]{ + api.CreateStringResult("b"), + api.CreateStringResult("a"), + } + assert.NoError(t, err) + assert.Equal(t, expected, res) + // lex [+:c] (none) + query = options.NewRangeByLexQuery( + options.NewInfiniteLexBoundary(options.PositiveInfinity), + options.NewLexBoundary("c", true)) + res, err = client.ZRange(key, query) + assert.NoError(t, err) + assert.Equal(t, 0, len(res)) + }) +} + +func (suite *GlideTestSuite) TestZRangeWithScores() { + suite.runWithDefaultClients(func(client api.BaseClient) { + t := suite.T() + key := uuid.New().String() + memberScoreMap := map[string]float64{ + "a": 1.0, + "b": 2.0, + "c": 3.0, + } + _, err := client.ZAdd(key, memberScoreMap) + assert.NoError(t, err) + // index [0:1] + res, err := client.ZRangeWithScores(key, options.NewRangeByIndexQuery(0, 1)) + expected := map[api.Result[string]]api.Result[float64]{ + api.CreateStringResult("a"): api.CreateFloat64Result(1.0), + api.CreateStringResult("b"): api.CreateFloat64Result(2.0), + } + assert.NoError(t, err) + assert.Equal(t, expected, res) + // index [0:-1] (all) + res, err = client.ZRangeWithScores(key, options.NewRangeByIndexQuery(0, -1)) + expected = map[api.Result[string]]api.Result[float64]{ + api.CreateStringResult("a"): api.CreateFloat64Result(1.0), + api.CreateStringResult("b"): api.CreateFloat64Result(2.0), + api.CreateStringResult("c"): api.CreateFloat64Result(3.0), + } + assert.NoError(t, err) + assert.Equal(t, expected, res) + // index [3:1] (none) + res, err = client.ZRangeWithScores(key, options.NewRangeByIndexQuery(3, 1)) + assert.NoError(t, err) + assert.Equal(t, 0, len(res)) + // score [-inf:3] + query := options.NewRangeByScoreQuery( + options.NewInfiniteScoreBoundary(options.NegativeInfinity), + options.NewScoreBoundary(3, true)) + res, err = client.ZRangeWithScores(key, query) + expected = map[api.Result[string]]api.Result[float64]{ + api.CreateStringResult("a"): api.CreateFloat64Result(1.0), + api.CreateStringResult("b"): api.CreateFloat64Result(2.0), + api.CreateStringResult("c"): api.CreateFloat64Result(3.0), + } + assert.NoError(t, err) + assert.Equal(t, expected, res) + // score [-inf:3) + query = options.NewRangeByScoreQuery( + options.NewInfiniteScoreBoundary(options.NegativeInfinity), + options.NewScoreBoundary(3, false)) + res, err = client.ZRangeWithScores(key, query) + expected = map[api.Result[string]]api.Result[float64]{ + api.CreateStringResult("a"): api.CreateFloat64Result(1.0), + api.CreateStringResult("b"): api.CreateFloat64Result(2.0), + } + assert.NoError(t, err) + assert.Equal(t, expected, res) + // score (3:-inf] reverse + query = options.NewRangeByScoreQuery( + options.NewScoreBoundary(3, false), + options.NewInfiniteScoreBoundary(options.NegativeInfinity)). + SetReverse() + res, err = client.ZRangeWithScores(key, query) + expected = map[api.Result[string]]api.Result[float64]{ + api.CreateStringResult("b"): api.CreateFloat64Result(2.0), + api.CreateStringResult("a"): api.CreateFloat64Result(1.0), + } + assert.NoError(t, err) + assert.Equal(t, expected, res) + // score [-inf:+inf] limit 1 2 + query = options.NewRangeByScoreQuery( + options.NewInfiniteScoreBoundary(options.NegativeInfinity), + options.NewInfiniteScoreBoundary(options.PositiveInfinity)). + SetLimit(1, 2) + res, err = client.ZRangeWithScores(key, query) + expected = map[api.Result[string]]api.Result[float64]{ + api.CreateStringResult("b"): api.CreateFloat64Result(2.0), + api.CreateStringResult("c"): api.CreateFloat64Result(3.0), + } + assert.NoError(t, err) + assert.Equal(t, expected, res) + // score [-inf:3) reverse (none) + query = options.NewRangeByScoreQuery( + options.NewInfiniteScoreBoundary(options.NegativeInfinity), + options.NewScoreBoundary(3, true)). + SetReverse() + res, err = client.ZRangeWithScores(key, query) + assert.NoError(t, err) + assert.Equal(t, 0, len(res)) + // score [+inf:3) (none) + query = options.NewRangeByScoreQuery( + options.NewInfiniteScoreBoundary(options.PositiveInfinity), + options.NewScoreBoundary(3, false)) + res, err = client.ZRangeWithScores(key, query) + assert.NoError(t, err) + assert.Equal(t, 0, len(res)) + }) +} + func (suite *GlideTestSuite) TestPersist() { suite.runWithDefaultClients(func(client api.BaseClient) { // Test 1: Check if persist command removes the expiration time of a key. @@ -4485,6 +4722,174 @@ func (suite *GlideTestSuite) TestPersist() { }) } +func (suite *GlideTestSuite) TestZRank() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.New().String() + stringKey := uuid.New().String() + client.ZAdd(key, map[string]float64{"one": 1.5, "two": 2.0, "three": 3.0}) + res, err := client.ZRank(key, "two") + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(1), res.Value()) + + if suite.serverVersion >= "7.2.0" { + res2Rank, res2Score, err := client.ZRankWithScore(key, "one") + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(0), res2Rank.Value()) + assert.Equal(suite.T(), float64(1.5), res2Score.Value()) + res4Rank, res4Score, err := client.ZRankWithScore(key, "non-existing-member") + assert.Nil(suite.T(), err) + assert.True(suite.T(), res4Rank.IsNil()) + assert.True(suite.T(), res4Score.IsNil()) + } + + res3, err := client.ZRank(key, "non-existing-member") + assert.Nil(suite.T(), err) + assert.True(suite.T(), res3.IsNil()) + + // key exists, but it is not a set + setRes, err := client.Set(stringKey, "value") + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), "OK", setRes.Value()) + + _, err = client.ZRank(stringKey, "value") + assert.NotNil(suite.T(), err) + assert.IsType(suite.T(), &api.RequestError{}, err) + }) +} + +func (suite *GlideTestSuite) TestZRevRank() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.New().String() + stringKey := uuid.New().String() + client.ZAdd(key, map[string]float64{"one": 1.5, "two": 2.0, "three": 3.0}) + res, err := client.ZRevRank(key, "two") + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(1), res.Value()) + + if suite.serverVersion >= "7.2.0" { + res2Rank, res2Score, err := client.ZRevRankWithScore(key, "one") + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(2), res2Rank.Value()) + assert.Equal(suite.T(), float64(1.5), res2Score.Value()) + res4Rank, res4Score, err := client.ZRevRankWithScore(key, "non-existing-member") + assert.Nil(suite.T(), err) + assert.True(suite.T(), res4Rank.IsNil()) + assert.True(suite.T(), res4Score.IsNil()) + } + + res3, err := client.ZRevRank(key, "non-existing-member") + assert.Nil(suite.T(), err) + assert.True(suite.T(), res3.IsNil()) + + // key exists, but it is not a set + setRes, err := client.Set(stringKey, "value") + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), "OK", setRes.Value()) + + _, err = client.ZRevRank(stringKey, "value") + assert.NotNil(suite.T(), err) + assert.IsType(suite.T(), &api.RequestError{}, err) + }) +} + +func (suite *GlideTestSuite) Test_XAdd_XLen_XTrim() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key1 := uuid.NewString() + key2 := uuid.NewString() + field1 := uuid.NewString() + field2 := uuid.NewString() + t := suite.T() + xAddResult, err := client.XAddWithOptions( + key1, + [][]string{{field1, "foo"}, {field2, "bar"}}, + options.NewXAddOptions().SetDontMakeNewStream(), + ) + assert.NoError(t, err) + assert.True(t, xAddResult.IsNil()) + + xAddResult, err = client.XAddWithOptions( + key1, + [][]string{{field1, "foo1"}, {field2, "bar1"}}, + options.NewXAddOptions().SetId("0-1"), + ) + assert.NoError(t, err) + assert.Equal(t, xAddResult.Value(), "0-1") + + xAddResult, err = client.XAdd( + key1, + [][]string{{field1, "foo2"}, {field2, "bar2"}}, + ) + assert.NoError(t, err) + assert.False(t, xAddResult.IsNil()) + + xLenResult, err := client.XLen(key1) + assert.NoError(t, err) + assert.Equal(t, xLenResult.Value(), int64(2)) + + // Trim the first entry. + xAddResult, err = client.XAddWithOptions( + key1, + [][]string{{field1, "foo3"}, {field2, "bar2"}}, + options.NewXAddOptions().SetTrimOptions( + options.NewXTrimOptionsWithMaxLen(2).SetExactTrimming(), + ), + ) + assert.NotNil(t, xAddResult.Value()) + assert.NoError(t, err) + id := xAddResult.Value() + xLenResult, err = client.XLen(key1) + assert.NoError(t, err) + assert.Equal(t, xLenResult.Value(), int64(2)) + + // Trim the second entry. + xAddResult, err = client.XAddWithOptions( + key1, + [][]string{{field1, "foo4"}, {field2, "bar4"}}, + options.NewXAddOptions().SetTrimOptions( + options.NewXTrimOptionsWithMinId(id).SetExactTrimming(), + ), + ) + assert.NoError(t, err) + assert.NotNil(t, xAddResult.Value()) + xLenResult, err = client.XLen(key1) + assert.NoError(t, err) + assert.Equal(t, xLenResult.Value(), int64(2)) + + // Test xtrim to remove 1 element + xTrimResult, err := client.XTrim( + key1, + options.NewXTrimOptionsWithMaxLen(1).SetExactTrimming(), + ) + assert.NoError(t, err) + assert.Equal(t, xTrimResult.Value(), int64(1)) + xLenResult, err = client.XLen(key1) + assert.NoError(t, err) + assert.Equal(t, xLenResult.Value(), int64(1)) + + // Key does not exist - returns 0 + xTrimResult, err = client.XTrim( + key2, + options.NewXTrimOptionsWithMaxLen(1).SetExactTrimming(), + ) + assert.NoError(t, err) + assert.Equal(t, xTrimResult.Value(), int64(0)) + xLenResult, err = client.XLen(key2) + assert.NoError(t, err) + assert.Equal(t, xLenResult.Value(), int64(0)) + + // Throw Exception: Key exists - but it is not a stream + setResult, err := client.Set(key2, "xtrimtest") + assert.NoError(t, err) + assert.Equal(t, setResult.Value(), "OK") + _, err = client.XTrim(key2, options.NewXTrimOptionsWithMinId("0-1")) + assert.NotNil(t, err) + assert.IsType(t, &api.RequestError{}, err) + _, err = client.XLen(key2) + assert.NotNil(t, err) + assert.IsType(t, &api.RequestError{}, err) + }) +} + func (suite *GlideTestSuite) TestZCount() { suite.runWithDefaultClients(func(client api.BaseClient) { key1 := uuid.NewString() diff --git a/java/client/src/main/java/glide/api/commands/ServerManagementClusterCommands.java b/java/client/src/main/java/glide/api/commands/ServerManagementClusterCommands.java index 92293de532..af6a1d3a24 100644 --- a/java/client/src/main/java/glide/api/commands/ServerManagementClusterCommands.java +++ b/java/client/src/main/java/glide/api/commands/ServerManagementClusterCommands.java @@ -170,6 +170,7 @@ public interface ServerManagementClusterCommands { /** * Get the values of configuration parameters.
+ * Starting from server version 7, command supports multiple parameters.
* The command will be sent to a random node. * * @see valkey.io for details. @@ -186,7 +187,8 @@ public interface ServerManagementClusterCommands { CompletableFuture> configGet(String[] parameters); /** - * Get the values of configuration parameters. + * Get the values of configuration parameters.
+ * Starting from server version 7, command supports multiple parameters. * * @see valkey.io for details. * @param parameters An array of configuration parameter names to retrieve values @@ -210,6 +212,7 @@ public interface ServerManagementClusterCommands { /** * Sets configuration parameters to the specified values.
+ * Starting from server version 7, command supports multiple parameters.
* The command will be sent to all nodes. * * @see valkey.io for details. @@ -226,7 +229,8 @@ public interface ServerManagementClusterCommands { CompletableFuture configSet(Map parameters); /** - * Sets configuration parameters to the specified values. + * Sets configuration parameters to the specified values.
+ * Starting from server version 7, command supports multiple parameters. * * @see valkey.io for details. * @param parameters A map consisting of configuration parameters and their diff --git a/java/client/src/main/java/glide/api/commands/ServerManagementCommands.java b/java/client/src/main/java/glide/api/commands/ServerManagementCommands.java index 3617ce3af0..9c7104d99b 100644 --- a/java/client/src/main/java/glide/api/commands/ServerManagementCommands.java +++ b/java/client/src/main/java/glide/api/commands/ServerManagementCommands.java @@ -89,7 +89,8 @@ public interface ServerManagementCommands { CompletableFuture configResetStat(); /** - * Get the values of configuration parameters. + * Get the values of configuration parameters.
+ * Starting from server version 7, command supports multiple parameters. * * @see valkey.io for details. * @param parameters An array of configuration parameter names to retrieve values @@ -105,7 +106,8 @@ public interface ServerManagementCommands { CompletableFuture> configGet(String[] parameters); /** - * Sets configuration parameters to the specified values. + * Sets configuration parameters to the specified values.
+ * Starting from server version 7, command supports multiple parameters. * * @see valkey.io for details. * @param parameters A map consisting of configuration parameters and their diff --git a/java/client/src/main/java/glide/api/models/BaseTransaction.java b/java/client/src/main/java/glide/api/models/BaseTransaction.java index bfdd81efc0..9ef52710e0 100644 --- a/java/client/src/main/java/glide/api/models/BaseTransaction.java +++ b/java/client/src/main/java/glide/api/models/BaseTransaction.java @@ -1648,7 +1648,8 @@ public T sunionstore(@NonNull ArgType destination, @NonNull ArgType[] } /** - * Reads the configuration parameters of the running server. + * Reads the configuration parameters of the running server.
+ * Starting from server version 7, command supports multiple parameters. * * @implNote {@link ArgType} is limited to {@link String} or {@link GlideString}, any other type * will throw {@link IllegalArgumentException}. @@ -1665,7 +1666,8 @@ public T configGet(@NonNull ArgType[] parameters) { } /** - * Sets configuration parameters to the specified values. + * Sets configuration parameters to the specified values.
+ * Starting from server version 7, command supports multiple parameters. * * @implNote {@link ArgType} is limited to {@link String} or {@link GlideString}, any other type * will throw {@link IllegalArgumentException}. diff --git a/java/integTest/src/test/java/glide/TransactionTestUtilities.java b/java/integTest/src/test/java/glide/TransactionTestUtilities.java index 46545f786a..c155ae908a 100644 --- a/java/integTest/src/test/java/glide/TransactionTestUtilities.java +++ b/java/integTest/src/test/java/glide/TransactionTestUtilities.java @@ -813,17 +813,40 @@ private static Object[] serverManagementCommands(BaseTransaction transaction) .flushdb(ASYNC) .dbsize(); - return new Object[] { - OK, // configSet(Map.of("timeout", "1000")) - Map.of("timeout", "1000"), // configGet(new String[] {"timeout"}) - OK, // configResetStat() - "Redis ver. " + SERVER_VERSION + '\n', // lolwut(1) - OK, // flushall() - OK, // flushall(ASYNC) - OK, // flushdb() - OK, // flushdb(ASYNC) - 0L, // dbsize() - }; + if (SERVER_VERSION.isGreaterThanOrEqualTo("7.0.0")) { + transaction + .configSet(Map.of("timeout", "2000", "rdb-save-incremental-fsync", "no")) + .configGet(new String[] {"timeout", "rdb-save-incremental-fsync"}); + } + + var expectedResults = + new Object[] { + OK, // configSet(Map.of("timeout", "1000")) + Map.of("timeout", "1000"), // configGet(new String[] {"timeout"}) + OK, // configResetStat() + "Redis ver. " + SERVER_VERSION + '\n', // lolwut(1) + OK, // flushall() + OK, // flushall(ASYNC) + OK, // flushdb() + OK, // flushdb(ASYNC) + 0L, // dbsize() + }; + + if (SERVER_VERSION.isGreaterThanOrEqualTo("7.0.0")) { + expectedResults = + concatenateArrays( + expectedResults, + new Object[] { + OK, // configSet(Map.of("timeout", "2000", "rdb-save-incremental-fsync", "no")) + Map.of( + "timeout", + "2000", + "rdb-save-incremental-fsync", + "no"), // configGet(new String[] {"timeout", "rdb-save-incremental-fsync"}) + }); + } + + return expectedResults; } private static Object[] connectionManagementCommands(BaseTransaction transaction) { diff --git a/node/src/BaseClient.ts b/node/src/BaseClient.ts index a9aed75560..ac3de2be06 100644 --- a/node/src/BaseClient.ts +++ b/node/src/BaseClient.ts @@ -4443,7 +4443,6 @@ export class BaseClient { * @param key - The key of the sorted set. * @param rangeQuery - The range query object representing the type of range query to perform. * - For range queries by index (rank), use {@link RangeByIndex}. - * - For range queries by lexicographical order, use {@link RangeByLex}. * - For range queries by score, use {@link RangeByScore}. * @param options - (Optional) Additional parameters: * - (Optional) `reverse`: if `true`, reverses the sorted set, with index `0` as the element with the highest score. @@ -4476,7 +4475,7 @@ export class BaseClient { */ public async zrangeWithScores( key: GlideString, - rangeQuery: RangeByScore | RangeByLex | RangeByIndex, + rangeQuery: RangeByScore | RangeByIndex, options?: { reverse?: boolean } & DecoderOption, ): Promise { return this.createWritePromise>( diff --git a/node/src/GlideClient.ts b/node/src/GlideClient.ts index fc9301bd75..9270e7f814 100644 --- a/node/src/GlideClient.ts +++ b/node/src/GlideClient.ts @@ -490,6 +490,7 @@ export class GlideClient extends BaseClient { /** * Reads the configuration parameters of the running server. + * Starting from server version 7, command supports multiple parameters. * * @see {@link https://valkey.io/commands/config-get/|valkey.io} for details. * @@ -517,6 +518,7 @@ export class GlideClient extends BaseClient { /** * Sets configuration parameters to the specified values. + * Starting from server version 7, command supports multiple parameters. * * @see {@link https://valkey.io/commands/config-set/|valkey.io} for details. * @param parameters - A map consisting of configuration parameters and their respective values to set. diff --git a/node/src/GlideClusterClient.ts b/node/src/GlideClusterClient.ts index c12264f078..d21914ec46 100644 --- a/node/src/GlideClusterClient.ts +++ b/node/src/GlideClusterClient.ts @@ -943,6 +943,7 @@ export class GlideClusterClient extends BaseClient { /** * Reads the configuration parameters of the running server. + * Starting from server version 7, command supports multiple parameters. * * The command will be routed to a random node, unless `route` is provided. * @@ -981,6 +982,7 @@ export class GlideClusterClient extends BaseClient { /** * Sets configuration parameters to the specified values. + * Starting from server version 7, command supports multiple parameters. * * The command will be routed to all nodes, unless `route` is provided. * diff --git a/node/src/Transaction.ts b/node/src/Transaction.ts index bdccbe151f..460c32ff82 100644 --- a/node/src/Transaction.ts +++ b/node/src/Transaction.ts @@ -744,6 +744,7 @@ export class BaseTransaction> { /** * Reads the configuration parameters of the running server. + * Starting from server version 7, command supports multiple parameters. * * @see {@link https://valkey.io/commands/config-get/|valkey.io} for details. * @@ -758,6 +759,7 @@ export class BaseTransaction> { /** * Sets configuration parameters to the specified values. + * Starting from server version 7, command supports multiple parameters. * * @see {@link https://valkey.io/commands/config-set/|valkey.io} for details. * @@ -1989,7 +1991,6 @@ export class BaseTransaction> { * @param key - The key of the sorted set. * @param rangeQuery - The range query object representing the type of range query to perform. * - For range queries by index (rank), use {@link RangeByIndex}. - * - For range queries by lexicographical order, use {@link RangeByLex}. * - For range queries by score, use {@link RangeByScore}. * @param reverse - If `true`, reverses the sorted set, with index `0` as the element with the highest score. * @@ -1999,7 +2000,7 @@ export class BaseTransaction> { */ public zrangeWithScores( key: GlideString, - rangeQuery: RangeByScore | RangeByLex | RangeByIndex, + rangeQuery: RangeByScore | RangeByIndex, reverse = false, ): T { return this.addAndReturn( diff --git a/node/tests/GlideClusterClient.test.ts b/node/tests/GlideClusterClient.test.ts index f2553131f1..2d5b86f52e 100644 --- a/node/tests/GlideClusterClient.test.ts +++ b/node/tests/GlideClusterClient.test.ts @@ -34,6 +34,7 @@ import { SlotKeyTypes, SortOrder, convertRecordToGlideRecord, + convertGlideRecordToRecord, } from ".."; import { ValkeyCluster } from "../../utils/TestUtils"; import { runBaseTests } from "./SharedTests"; @@ -323,6 +324,27 @@ describe("GlideClusterClient", () => { "OK", convertRecordToGlideRecord({ timeout: "1000" }), ]); + + if (!cluster.checkIfServerVersionLessThan("7.0.0")) { + const transaction = new ClusterTransaction() + .configSet({ + timeout: "2000", + "cluster-node-timeout": "16000", + }) + .configGet(["timeout", "cluster-node-timeout"]); + const result = await client.exec(transaction); + const convertedResult = [ + result[0], + convertGlideRecordToRecord(result[1]), + ]; + expect(convertedResult).toEqual([ + "OK", + { + timeout: "2000", + "cluster-node-timeout": "16000", + }, + ]); + } }, TIMEOUT, ); diff --git a/node/tests/SharedTests.ts b/node/tests/SharedTests.ts index 7ed788f990..5ba0ebbed4 100644 --- a/node/tests/SharedTests.ts +++ b/node/tests/SharedTests.ts @@ -1206,9 +1206,9 @@ export function runBaseTests(config: { ); it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( - `config get and config set with timeout parameter_%p`, + `config get and config set with multiple parameters_%p`, async (protocol) => { - await runTest(async (client: BaseClient) => { + await runTest(async (client: BaseClient, cluster) => { const prevTimeout = (await client.configGet([ "timeout", ])) as Record; @@ -1225,6 +1225,37 @@ export function runBaseTests(config: { timeout: prevTimeout["timeout"], }), ).toEqual("OK"); + + if (!cluster.checkIfServerVersionLessThan("7.0.0")) { + const prevTimeout = (await client.configGet([ + "timeout", + ])) as Record; + const prevClusterNodeTimeout = (await client.configGet([ + "cluster-node-timeout", + ])) as Record; + expect( + await client.configSet({ + timeout: "1000", + "cluster-node-timeout": "16000", + }), + ).toEqual("OK"); + const currParameterValues = (await client.configGet([ + "timeout", + "cluster-node-timeout", + ])) as Record; + expect(currParameterValues).toEqual({ + timeout: "1000", + "cluster-node-timeout": "16000", + }); + /// Revert to the previous configuration + expect( + await client.configSet({ + timeout: prevTimeout["timeout"], + "cluster-node-timeout": + prevClusterNodeTimeout["cluster-node-timeout"], + }), + ).toEqual("OK"); + } }, protocol); }, config.timeout, diff --git a/python/DEVELOPER.md b/python/DEVELOPER.md index 02b4ee3001..6e0164139d 100644 --- a/python/DEVELOPER.md +++ b/python/DEVELOPER.md @@ -235,6 +235,6 @@ Run from the main `/python` folder - [Python](https://marketplace.visualstudio.com/items?itemName=ms-python.python) - [isort](https://marketplace.visualstudio.com/items?itemName=ms-python.isort) -- [Black Formetter](https://marketplace.visualstudio.com/items?itemName=ms-python.black-formatter) +- [Black Formatter](https://marketplace.visualstudio.com/items?itemName=ms-python.black-formatter) - [Flake8](https://marketplace.visualstudio.com/items?itemName=ms-python.flake8) - [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer) diff --git a/python/python/glide/async_commands/cluster_commands.py b/python/python/glide/async_commands/cluster_commands.py index ab73e5ef0e..e1b9135221 100644 --- a/python/python/glide/async_commands/cluster_commands.py +++ b/python/python/glide/async_commands/cluster_commands.py @@ -204,6 +204,7 @@ async def config_get( ) -> TClusterResponse[Dict[bytes, bytes]]: """ Get the values of configuration parameters. + Starting from server version 7, command supports multiple parameters. See https://valkey.io/commands/config-get/ for details. Args: @@ -236,6 +237,7 @@ async def config_set( ) -> TOK: """ Set configuration parameters to the specified values. + Starting from server version 7, command supports multiple parameters. See https://valkey.io/commands/config-set/ for details. Args: diff --git a/python/python/glide/async_commands/core.py b/python/python/glide/async_commands/core.py index 85c361f7d3..94b5ec4093 100644 --- a/python/python/glide/async_commands/core.py +++ b/python/python/glide/async_commands/core.py @@ -5739,7 +5739,7 @@ async def bitop( Examples: >>> await client.set("key1", "A") # "A" has binary value 01000001 - >>> await client.set("key1", "B") # "B" has binary value 01000010 + >>> await client.set("key2", "B") # "B" has binary value 01000010 >>> await client.bitop(BitwiseOperation.AND, "destination", ["key1", "key2"]) 1 # The size of the resulting string stored in "destination" is 1 >>> await client.get("destination") diff --git a/python/python/glide/async_commands/standalone_commands.py b/python/python/glide/async_commands/standalone_commands.py index b02b29a77b..4595d894dc 100644 --- a/python/python/glide/async_commands/standalone_commands.py +++ b/python/python/glide/async_commands/standalone_commands.py @@ -153,6 +153,7 @@ async def ping(self, message: Optional[TEncodable] = None) -> bytes: async def config_get(self, parameters: List[TEncodable]) -> Dict[bytes, bytes]: """ Get the values of configuration parameters. + Starting from server version 7, command supports multiple parameters. See https://valkey.io/commands/config-get/ for details. Args: @@ -175,6 +176,7 @@ async def config_get(self, parameters: List[TEncodable]) -> Dict[bytes, bytes]: async def config_set(self, parameters_map: Mapping[TEncodable, TEncodable]) -> TOK: """ Set configuration parameters to the specified values. + Starting from server version 7, command supports multiple parameters. See https://valkey.io/commands/config-set/ for details. Args: diff --git a/python/python/glide/async_commands/transaction.py b/python/python/glide/async_commands/transaction.py index 9bc7879c65..9b84c6ac4e 100644 --- a/python/python/glide/async_commands/transaction.py +++ b/python/python/glide/async_commands/transaction.py @@ -313,6 +313,7 @@ def delete(self: TTransaction, keys: List[TEncodable]) -> TTransaction: def config_get(self: TTransaction, parameters: List[TEncodable]) -> TTransaction: """ Get the values of configuration parameters. + Starting from server version 7, command supports multiple parameters. See https://valkey.io/commands/config-get/ for details. Args: @@ -329,6 +330,7 @@ def config_set( ) -> TTransaction: """ Set configuration parameters to the specified values. + Starting from server version 7, command supports multiple parameters. See https://valkey.io/commands/config-set/ for details. Args: diff --git a/python/python/tests/test_async_client.py b/python/python/tests/test_async_client.py index 3db7e965db..bbd1060a40 100644 --- a/python/python/tests/test_async_client.py +++ b/python/python/tests/test_async_client.py @@ -855,6 +855,46 @@ async def test_config_get_set(self, glide_client: TGlideClient): == OK ) + if not await check_if_server_version_lt(glide_client, "7.0.0"): + previous_timeout = await glide_client.config_get(["timeout"]) + previous_cluster_node_timeout = await glide_client.config_get( + ["cluster-node-timeout"] + ) + assert ( + await glide_client.config_set( + {"timeout": "2000", "cluster-node-timeout": "16000"} + ) + == OK + ) + assert await glide_client.config_get( + ["timeout", "cluster-node-timeout"] + ) == { + b"timeout": b"2000", + b"cluster-node-timeout": b"16000", + } + # revert changes to previous timeout + previous_timeout_decoded = convert_bytes_to_string_object(previous_timeout) + previous_cluster_node_timeout_decoded = convert_bytes_to_string_object( + previous_cluster_node_timeout + ) + assert isinstance(previous_timeout_decoded, dict) + assert isinstance(previous_cluster_node_timeout_decoded, dict) + assert isinstance(previous_timeout_decoded["timeout"], str) + assert isinstance( + previous_cluster_node_timeout_decoded["cluster-node-timeout"], str + ) + assert ( + await glide_client.config_set( + { + "timeout": previous_timeout_decoded["timeout"], + "cluster-node-timeout": previous_cluster_node_timeout_decoded[ + "cluster-node-timeout" + ], + } + ) + == OK + ) + @pytest.mark.parametrize("cluster_mode", [True]) @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) async def test_config_get_with_wildcard_and_multi_node_route( diff --git a/python/python/tests/test_transaction.py b/python/python/tests/test_transaction.py index 7affca711b..fe0033c9b4 100644 --- a/python/python/tests/test_transaction.py +++ b/python/python/tests/test_transaction.py @@ -271,6 +271,11 @@ async def transaction_test( args.append(OK) transaction.config_get(["timeout"]) args.append({b"timeout": b"1000"}) + if not await check_if_server_version_lt(glide_client, "7.0.0"): + transaction.config_set({"timeout": "2000", "cluster-node-timeout": "16000"}) + args.append(OK) + transaction.config_get(["timeout", "cluster-node-timeout"]) + args.append({b"timeout": b"2000", b"cluster-node-timeout": b"16000"}) transaction.hset(key4, {key: value, key2: value2}) args.append(2)