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: Add BZPopMin command #2849

Merged
merged 10 commits into from
Jan 6, 2025
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#### Changes

jbrinkman marked this conversation as resolved.
Show resolved Hide resolved
* 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 {
jbrinkman marked this conversation as resolved.
Show resolved Hide resolved
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
Loading