Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Go: ZRANGE #2925

Merged
merged 5 commits into from
Jan 11, 2025
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions go/api/base_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -1476,6 +1476,36 @@ func (client *baseClient) BZPopMin(keys []string, timeoutSecs float64) (Result[K
return handleKeyWithMemberAndScoreResponse(result)
}

// TODO rework once we get other handlers - return `[]string`
func (client *baseClient) ZRange(key string, rangeQuery options.ZRangeQuery) ([]Result[string], error) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In most places, we have passed options as a pointer. Is there any difference?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rangeQuery is a mandatory argument for that command

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)
}

// TODO rework once we get other handlers - return `map[string]float64`
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 {
Expand Down
200 changes: 200 additions & 0 deletions go/api/options/zrange_options.go
Original file line number Diff line number Diff line change
@@ -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() {}
62 changes: 61 additions & 1 deletion go/api/sorted_set_commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -263,6 +263,66 @@ 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 specified range of elements in the sorted set stored at `key`.
Yury-Fridlyand marked this conversation as resolved.
Show resolved Hide resolved
// `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/
ZRange(key string, rangeQuery options.ZRangeQuery) ([]Result[string], error)

// 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/
ZRangeWithScores(key string, rangeQuery options.ZRangeQueryWithScores) (map[Result[string]]Result[float64], error)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can the map key be nil? If not then this should be `map[string]Result[float64].

Copy link
Collaborator Author

@Yury-Fridlyand Yury-Fridlyand Jan 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Never. This will be updated in another PR for all commands.

// 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].
Expand Down
2 changes: 1 addition & 1 deletion go/integTest/glide_test_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}

Expand Down
Loading
Loading