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