Skip to content

Commit

Permalink
Go: ZRANDMEMBER.
Browse files Browse the repository at this point in the history
Signed-off-by: Yury-Fridlyand <[email protected]>
  • Loading branch information
Yury-Fridlyand committed Jan 21, 2025
1 parent 502a8d7 commit 34f0369
Show file tree
Hide file tree
Showing 6 changed files with 183 additions and 5 deletions.
82 changes: 82 additions & 0 deletions go/api/base_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -2731,6 +2731,88 @@ func (client *baseClient) ZRemRangeByScore(key string, rangeQuery options.RangeB
return handleIntResponse(result)
}

// Returns a random member from the sorted set stored at `key`.
//
// See [valkey.io] for details.
//
// Parameters:
//
// key - The key of the sorted set.
//
// Return value:
//
// A string representing a random member from the sorted set.
// If the sorted set does not exist or is empty, the response will be `nil`.
//
// Example:
//
// member, err := client.ZRandMember("key1")
//
// [valkey.io]: https://valkey.io/commands/zrandmember/
func (client *baseClient) ZRandMember(key string) (Result[string], error) {
result, err := client.executeCommand(C.ZRandMember, []string{key})
if err != nil {
return CreateNilStringResult(), err
}
return handleStringOrNilResponse(result)
}

// Returns a random member from the sorted set stored at `key`.
//
// See [valkey.io] for details.
//
// Parameters:
//
// key - The key of the sorted set.
// count - The number of field names to return.
// If `count` is positive, returns unique elements. If negative, allows for duplicates.
//
// Return value:
//
// An array of members from the sorted set.
// If the sorted set does not exist or is empty, the response will be an empty array.
//
// Example:
//
// members, err := client.ZRandMemberWithCount("key1", -5)
//
// [valkey.io]: https://valkey.io/commands/zrandmember/
func (client *baseClient) ZRandMemberWithCount(key string, count int64) ([]string, error) {
result, err := client.executeCommand(C.ZRandMember, []string{key, utils.IntToString(count)})
if err != nil {
return nil, err
}
return handleStringArrayResponse(result)
}

// Returns a random member from the sorted set stored at `key`.
//
// See [valkey.io] for details.
//
// Parameters:
//
// key - The key of the sorted set.
// count - The number of field names to return.
// If `count` is positive, returns unique elements. If negative, allows for duplicates.
//
// Return value:
//
// An array of `MemberAndScore` objects, which store member names and their respective scores.
// If the sorted set does not exist or is empty, the response will be an empty array.
//
// Example:
//
// membersAndScores, err := client.ZRandMemberWithCountWithScores("key1", 5)
//
// [valkey.io]: https://valkey.io/commands/zrandmember/
func (client *baseClient) ZRandMemberWithCountWithScores(key string, count int64) ([]MemberAndScore, error) {
result, err := client.executeCommand(C.ZRandMember, []string{key, utils.IntToString(count), options.WithScores})
if err != nil {
return nil, err
}
return handleMemberAndScoreArrayResponse(result)
}

// Returns the logarithmic access frequency counter of a Valkey object stored at key.
//
// Parameters:
Expand Down
11 changes: 6 additions & 5 deletions go/api/options/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
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.
WithScore string = "WITHSCORE" // Valkey API keyword for the with score option for zrank and zrevrank commands.
NoScores string = "NOSCORES" // Valkey API keyword for the no scores option for zscan 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.
WithScores string = "WITHSCORES" // Valkey API keyword for ZRandMember command to return scores along with members.
NoScores string = "NOSCORES" // Valkey API keyword for the no scores option for zscan command.
)
21 changes: 21 additions & 0 deletions go/api/response_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,27 @@ func handleKeyWithMemberAndScoreResponse(response *C.struct_CommandResponse) (Re
return CreateKeyWithMemberAndScoreResult(KeyWithMemberAndScore{key, member, score}), nil
}

func handleMemberAndScoreArrayResponse(response *C.struct_CommandResponse) ([]MemberAndScore, error) {
defer C.free_command_response(response)

typeErr := checkResponseType(response, C.Array, false)
if typeErr != nil {
return nil, typeErr
}

slice, err := parseArray(response)
if err != nil {
return nil, err
}

var result []MemberAndScore
for _, arr := range slice.([]interface{}) {
pair := arr.([]interface{})
result = append(result, MemberAndScore{pair[0].(string), pair[1].(float64)})
}
return result, nil
}

func handleScanResponse(response *C.struct_CommandResponse) (string, []string, error) {
defer C.free_command_response(response)

Expand Down
6 changes: 6 additions & 0 deletions go/api/response_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ type KeyWithMemberAndScore struct {
Score float64
}

// MemberAndScore is used by ZRANDMEMBER, which return an object consisting of the sorted set member, and its score.
type MemberAndScore struct {
Member string
Score float64
}

// Response type of [XAutoClaim] command.
type XAutoClaimResponse struct {
NextEntry string
Expand Down
6 changes: 6 additions & 0 deletions go/api/sorted_set_commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -391,4 +391,10 @@ type SortedSetCommands interface {
ZRemRangeByRank(key string, start int64, stop int64) (int64, error)

ZRemRangeByScore(key string, rangeQuery options.RangeByScore) (int64, error)

ZRandMember(key string) (Result[string], error)

ZRandMemberWithCount(key string, count int64) ([]string, error)

ZRandMemberWithCountWithScores(key string, count int64) ([]MemberAndScore, error)
}
62 changes: 62 additions & 0 deletions go/integTest/shared_commands_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6221,6 +6221,68 @@ func (suite *GlideTestSuite) TestZRemRangeByScore() {
})
}

func (suite *GlideTestSuite) TestZRandMember() {
suite.runWithDefaultClients(func(client api.BaseClient) {
t := suite.T()
key1 := uuid.NewString()
key2 := uuid.NewString()
members := []string{"one", "two"}

zadd, err := client.ZAdd(key1, map[string]float64{"one": 1.0, "two": 2.0})
assert.NoError(t, err)
assert.Equal(t, int64(2), zadd)

randomMember, err := client.ZRandMember(key1)
assert.NoError(t, err)
assert.Contains(t, members, randomMember.Value())

// unique values are expected as count is positive
randomMembers, err := client.ZRandMemberWithCount(key1, 4)
assert.NoError(t, err)
assert.ElementsMatch(t, members, randomMembers)

membersAndScores, err := client.ZRandMemberWithCountWithScores(key1, 4)
expectedMembersAndScores := []api.MemberAndScore{{Member: "one", Score: 1}, {Member: "two", Score: 2}}
assert.NoError(t, err)
assert.ElementsMatch(t, expectedMembersAndScores, membersAndScores)

// Duplicate values are expected as count is negative
randomMembers, err = client.ZRandMemberWithCount(key1, -4)
assert.NoError(t, err)
assert.Len(t, randomMembers, 4)
for _, member := range randomMembers {
assert.Contains(t, members, member)
}

membersAndScores, err = client.ZRandMemberWithCountWithScores(key1, -4)
assert.NoError(t, err)
assert.Len(t, membersAndScores, 4)
for _, memberAndScore := range membersAndScores {
assert.Contains(t, expectedMembersAndScores, memberAndScore)
}

// non existing key should return null or empty array
randomMember, err = client.ZRandMember(key2)
assert.NoError(t, err)
assert.True(t, randomMember.IsNil())
randomMembers, err = client.ZRandMemberWithCount(key2, -4)
assert.NoError(t, err)
assert.Len(t, randomMembers, 0)
membersAndScores, err = client.ZRandMemberWithCountWithScores(key2, -4)
assert.NoError(t, err)
assert.Len(t, membersAndScores, 0)

// Key exists, but is not a set
suite.verifyOK(client.Set(key2, "ZRandMember"))
_, err = client.ZRandMember(key2)
assert.IsType(suite.T(), &api.RequestError{}, err)
_, err = client.ZRandMemberWithCount(key2, 2)
assert.IsType(suite.T(), &api.RequestError{}, err)
_, err = client.ZRandMemberWithCountWithScores(key2, 2)
assert.IsType(suite.T(), &api.RequestError{}, err)
})
}

func (suite *GlideTestSuite) TestObjectIdleTime() {
suite.runWithDefaultClients(func(client api.BaseClient) {
defaultClient := suite.defaultClient()
Expand Down

0 comments on commit 34f0369

Please sign in to comment.