Skip to content

Commit

Permalink
GO: Add BZPopMin command (#2849)
Browse files Browse the repository at this point in the history
* GO: Add BZPopMin command

Signed-off-by: jbrinkman <[email protected]>

---------

Signed-off-by: jbrinkman <[email protected]>
Signed-off-by: Joseph Brinkman <[email protected]>
Co-authored-by: Yury-Fridlyand <[email protected]>
Co-authored-by: prateek-kumar-improving <[email protected]>
  • Loading branch information
3 people authored Jan 6, 2025
1 parent ffc679a commit 2bd6893
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 0 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#### Changes

* Java, Node, Python: Add transaction commands for JSON module ([#2862](https://github.com/valkey-io/valkey-glide/pull/2862))
* Go: Add HINCRBY command ([#2847](https://github.com/valkey-io/valkey-glide/pull/2847))
* Go: Add HINCRBYFLOAT command ([#2846](https://github.com/valkey-io/valkey-glide/pull/2846))
Expand All @@ -13,6 +14,7 @@
* 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))
* Go: Add `BZPopMin` ([#2849](https://github.com/valkey-io/valkey-glide/pull/2849))

#### Breaking Changes

Expand Down
9 changes: 9 additions & 0 deletions go/api/base_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -1441,3 +1441,12 @@ func (client *baseClient) ZCard(key string) (Result[int64], error) {

return handleLongResponse(result)
}

func (client *baseClient) BZPopMin(keys []string, timeoutSecs float64) (Result[KeyWithMemberAndScore], error) {
result, err := client.executeCommand(C.BZPopMin, append(keys, utils.FloatToString(timeoutSecs)))
if err != nil {
return CreateNilKeyWithMemberAndScoreResult(), err
}

return handleKeyWithMemberAndScoreResponse(result)
}
24 changes: 24 additions & 0 deletions go/api/response_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,30 @@ func handleStringSetResponse(response *C.struct_CommandResponse) (map[Result[str
return slice, nil
}

func handleKeyWithMemberAndScoreResponse(response *C.struct_CommandResponse) (Result[KeyWithMemberAndScore], error) {
defer C.free_command_response(response)

if response == nil || response.response_type == uint32(C.Null) {
return CreateNilKeyWithMemberAndScoreResult(), nil
}

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

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

arr := slice.([]interface{})
key := arr[0].(string)
member := arr[1].(string)
score := arr[2].(float64)
return CreateKeyWithMemberAndScoreResult(KeyWithMemberAndScore{key, member, score}), nil
}

func handleScanResponse(
response *C.struct_CommandResponse,
) (Result[string], []Result[string], error) {
Expand Down
16 changes: 16 additions & 0 deletions go/api/response_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ type Result[T any] struct {
isNil bool
}

// KeyWithMemberAndScore is used by BZPOPMIN/BZPOPMAX, which return an object consisting of the key of the sorted set that was
// popped, the popped member, and its score.
type KeyWithMemberAndScore struct {
Key string
Member string
Score float64
}

func (result Result[T]) IsNil() bool {
return result.isNil
}
Expand Down Expand Up @@ -47,6 +55,14 @@ func CreateNilBoolResult() Result[bool] {
return Result[bool]{val: false, isNil: true}
}

func CreateKeyWithMemberAndScoreResult(kmsVal KeyWithMemberAndScore) Result[KeyWithMemberAndScore] {
return Result[KeyWithMemberAndScore]{val: kmsVal, isNil: false}
}

func CreateNilKeyWithMemberAndScoreResult() Result[KeyWithMemberAndScore] {
return Result[KeyWithMemberAndScore]{val: KeyWithMemberAndScore{"", "", 0.0}, isNil: true}
}

// Enum to distinguish value types stored in `ClusterValue`
type ValueType int

Expand Down
30 changes: 30 additions & 0 deletions go/api/sorted_set_commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -232,4 +232,34 @@ type SortedSetCommands interface {
//
// [valkey.io]: https://valkey.io/commands/zcard/
ZCard(key string) (Result[int64], error)

// Blocks the connection until it removes and returns a member with the lowest score from the
// first non-empty sorted set, with the given `keys` being checked in the order they
// are provided.
// `BZPOPMIN` is the blocking variant of `ZPOPMIN`.
//
// Note:
// - When in cluster mode, all `keys` must map to the same hash slot.
// - `BZPOPMIN` is a client blocking command, see [Blocking Commands] for more details and best practices.
//
// See [valkey.io] for more details.
//
// Parameters:
// keys - The keys of the sorted sets.
// timeout - The number of seconds to wait for a blocking operation to complete. A value of
// `0` will block indefinitely.
//
// Return value:
// 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
// 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))
// fmt.Println(res.Value()) // Output: {key: key1 member:a, score:1}
//
// [valkey.io]: https://valkey.io/commands/bzpopmin/
// [blocking commands]: https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#blocking-commands
BZPopMin(keys []string, timeoutSecs float64) (Result[KeyWithMemberAndScore], error)
}
44 changes: 44 additions & 0 deletions go/integTest/shared_commands_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4138,6 +4138,50 @@ func (suite *GlideTestSuite) TestZincrBy() {
})
}

func (suite *GlideTestSuite) TestBZPopMin() {
suite.runWithDefaultClients(func(client api.BaseClient) {
key1 := "{zset}-1-" + uuid.NewString()
key2 := "{zset}-2-" + uuid.NewString()
key3 := "{zset}-2-" + uuid.NewString()

// Add elements to key1
zaddResult1, err := client.ZAdd(key1, map[string]float64{"a": 1.0, "b": 1.5})
assert.Nil(suite.T(), err)
assert.Equal(suite.T(), int64(2), zaddResult1.Value())

// Add elements to key2
zaddResult2, err := client.ZAdd(key2, map[string]float64{"c": 2.0})
assert.Nil(suite.T(), err)
assert.Equal(suite.T(), int64(1), zaddResult2.Value())

// Pop minimum element from key1 and key2
bzpopminResult1, err := client.BZPopMin([]string{key1, key2}, float64(.5))
assert.Nil(suite.T(), err)
assert.Equal(suite.T(), api.KeyWithMemberAndScore{Key: key1, Member: "a", Score: 1.0}, bzpopminResult1.Value())

// Attempt to pop from non-existent key3
bzpopminResult2, err := client.BZPopMin([]string{key3}, float64(1))
assert.Nil(suite.T(), err)
assert.True(suite.T(), bzpopminResult2.IsNil())

// Pop minimum element from key2
bzpopminResult3, err := client.BZPopMin([]string{key3, key2}, float64(.5))
assert.Nil(suite.T(), err)
assert.Equal(suite.T(), api.KeyWithMemberAndScore{Key: key2, Member: "c", Score: 2.0}, bzpopminResult3.Value())

// Set key3 to a non-sorted set value
setResult, err := client.Set(key3, "value")
assert.Nil(suite.T(), err)
assert.Equal(suite.T(), "OK", setResult.Value())

// Attempt to pop from key3 which is not a sorted set
_, err = client.BZPopMin([]string{key3}, float64(.5))
if assert.Error(suite.T(), err) {
assert.IsType(suite.T(), &api.RequestError{}, err)
}
})
}

func (suite *GlideTestSuite) TestZPopMin() {
suite.runWithDefaultClients(func(client api.BaseClient) {
key1 := uuid.New().String()
Expand Down

0 comments on commit 2bd6893

Please sign in to comment.