diff --git a/CHANGELOG.md b/CHANGELOG.md index ec415776a1..d3432f4ae2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ #### Changes - +* Go: Add Zcount command ([#2930](https://github.com/valkey-io/valkey-glide/pull/2930)) * Go: Add `HScan` command ([#2917](https://github.com/valkey-io/valkey-glide/pull/2917)) * 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)) diff --git a/go/api/base_client.go b/go/api/base_client.go index e43d664e01..d65228dbb6 100644 --- a/go/api/base_client.go +++ b/go/api/base_client.go @@ -1579,6 +1579,14 @@ func (client *baseClient) Persist(key string) (Result[bool], error) { return handleBooleanResponse(result) } +func (client *baseClient) ZCount(key string, rangeOptions *options.ZCountRange) (Result[int64], error) { + zCountRangeArgs, err := rangeOptions.ToArgs() + if err != nil { + 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 { diff --git a/go/api/options/zcount_options.go b/go/api/options/zcount_options.go new file mode 100644 index 0000000000..e225be6f23 --- /dev/null +++ b/go/api/options/zcount_options.go @@ -0,0 +1,94 @@ +// Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 + +package options + +import "github.com/valkey-io/valkey-glide/go/glide/utils" + +// The common interface for representing all the range type for Zcount command. +type ScoreRange interface { + ToArgs() ([]string, error) +} + +type ( + InfBoundary string +) + +const ( + // The highest bound in the sorted set + PositiveInfinity InfBoundary = "+inf" + // The lowest bound in the sorted set + NegativeInfinity InfBoundary = "-inf" +) + +// This struct represents the infinity boundary for a score range. +type InfScoreBound struct { + value InfBoundary +} + +// Create a new infinite score boundary +func NewInfScoreBoundBuilder(value InfBoundary) *InfScoreBound { + return &InfScoreBound{value: value} +} + +func (infScoreBound *InfScoreBound) ToArgs() ([]string, error) { + return []string{string(infScoreBound.value)}, nil +} + +// This struct represents score boundary for a bound. +type ScoreBoundary struct { + bound float64 + isInclusive bool +} + +// Create a new score boundary. +func NewScoreBoundaryBuilder() *ScoreBoundary { + return &ScoreBoundary{isInclusive: true} +} + +// Set the bound for a score boundary. +func (scoreBoundary *ScoreBoundary) SetBound(bound float64) *ScoreBoundary { + scoreBoundary.bound = bound + return scoreBoundary +} + +// Set if the bound for a score boundary is inclusive or not inclusive in the boundary. +func (scoreBoundary *ScoreBoundary) SetIsInclusive(isInclusive bool) *ScoreBoundary { + scoreBoundary.isInclusive = isInclusive + return scoreBoundary +} + +func (scoreBoundary *ScoreBoundary) ToArgs() ([]string, error) { + args := []string{} + if !scoreBoundary.isInclusive { + args = append(args, "("+utils.FloatToString(scoreBoundary.bound)) + } else { + args = append(args, utils.FloatToString(scoreBoundary.bound)) + } + return args, nil +} + +// This struct represents the min and max boundary for the Zcount command. +type ZCountRange struct { + min ScoreRange + max ScoreRange +} + +// Create a new Zcount range. +func NewZCountRangeBuilder(min ScoreRange, max ScoreRange) *ZCountRange { + return &ZCountRange{min, max} +} + +func (zCountRange *ZCountRange) ToArgs() ([]string, error) { + args := []string{} + minArgs, err := zCountRange.min.ToArgs() + if err != nil { + return nil, err + } + args = append(args, minArgs...) + maxArgs, err := zCountRange.max.ToArgs() + if err != nil { + return nil, err + } + args = append(args, maxArgs...) + return args, nil +} diff --git a/go/api/sorted_set_commands.go b/go/api/sorted_set_commands.go index e6b18c66b8..ee270c80d9 100644 --- a/go/api/sorted_set_commands.go +++ b/go/api/sorted_set_commands.go @@ -376,4 +376,34 @@ type SortedSetCommands interface { // // [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. + // + // Parameters: + // key - The key of the set. + // rangeOptions - Contains `min` and `max` score. `min` contains the minimum score to count from. + // `max` contains the maximum score to count up to. Can be positive/negative infinity, or + // specific score and inclusivity. + // + // Return value: + // Result[int64] - The number of members in the specified score range. + // + // Example: + // key1 := uuid.NewString() + // membersScores := map[string]float64{"one": 1.0, "two": 2.0, "three": 3.0 } + // zAddResult, err := client.ZAdd(key1, membersScores) + // zCountRange := options.NewZCountRangeBuilder( + // options.NewInfScoreBoundBuilder(options.NegativeInfinity), + // options.NewInfScoreBoundBuilder(options.PositiveInfinity), + // ) + // zCountResult, err := client.ZCount(key1, zCountRange) + // if err!= nil { + // // Print err + // } + // fmt.Println(zCountResult.Value()) // Output: 3 + // + // [valkey.io]: https://valkey.io/commands/zcount/ + ZCount(key string, rangeOptions *options.ZCountRange) (Result[int64], error) } diff --git a/go/integTest/shared_commands_test.go b/go/integTest/shared_commands_test.go index b21a81bd2f..2ce4503361 100644 --- a/go/integTest/shared_commands_test.go +++ b/go/integTest/shared_commands_test.go @@ -4889,3 +4889,82 @@ func (suite *GlideTestSuite) Test_XAdd_XLen_XTrim() { assert.IsType(t, &api.RequestError{}, err) }) } + +func (suite *GlideTestSuite) TestZCount() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key1 := uuid.NewString() + key2 := uuid.NewString() + membersScores := map[string]float64{ + "one": 1.0, + "two": 2.0, + "three": 3.0, + } + t := suite.T() + res1, err := client.ZAdd(key1, membersScores) + assert.Nil(t, err) + assert.Equal(t, int64(3), res1.Value()) + + // In range negative to positive infinity. + zCountRange := options.NewZCountRangeBuilder( + options.NewInfScoreBoundBuilder(options.NegativeInfinity), + options.NewInfScoreBoundBuilder(options.PositiveInfinity), + ) + zCountResult, err := client.ZCount(key1, zCountRange) + assert.Nil(t, err) + assert.Equal(t, int64(3), zCountResult.Value()) + zCountRange = options.NewZCountRangeBuilder( + options.NewScoreBoundaryBuilder().SetBound(math.Inf(-1)), + options.NewScoreBoundaryBuilder().SetBound(math.Inf(+1)), + ) + zCountResult, err = client.ZCount(key1, zCountRange) + assert.Nil(t, err) + assert.Equal(t, int64(3), zCountResult.Value()) + + // In range 1 (exclusive) to 3 (inclusive) + zCountRange = options.NewZCountRangeBuilder( + options.NewScoreBoundaryBuilder().SetBound(1).SetIsInclusive(false), + options.NewScoreBoundaryBuilder().SetBound(3).SetIsInclusive(true), + ) + zCountResult, err = client.ZCount(key1, zCountRange) + assert.Nil(t, err) + assert.Equal(t, int64(2), zCountResult.Value()) + + // In range negative infinity to 3 (inclusive) + zCountRange = options.NewZCountRangeBuilder( + options.NewInfScoreBoundBuilder(options.NegativeInfinity), + options.NewScoreBoundaryBuilder().SetBound(3).SetIsInclusive(true), + ) + zCountResult, err = client.ZCount(key1, zCountRange) + assert.Nil(t, err) + assert.Equal(t, int64(3), zCountResult.Value()) + + // Incorrect range start > end + zCountRange = options.NewZCountRangeBuilder( + options.NewInfScoreBoundBuilder(options.PositiveInfinity), + options.NewScoreBoundaryBuilder().SetBound(3).SetIsInclusive(true), + ) + zCountResult, err = client.ZCount(key1, zCountRange) + assert.Nil(t, err) + assert.Equal(t, int64(0), zCountResult.Value()) + + // Non-existing key + zCountRange = options.NewZCountRangeBuilder( + options.NewInfScoreBoundBuilder(options.NegativeInfinity), + options.NewInfScoreBoundBuilder(options.PositiveInfinity), + ) + zCountResult, err = client.ZCount("non_existing_key", zCountRange) + assert.Nil(t, err) + assert.Equal(t, int64(0), zCountResult.Value()) + + // Key exists, but it is not a set + setResult, _ := client.Set(key2, "value") + assert.Equal(t, setResult.Value(), "OK") + zCountRange = options.NewZCountRangeBuilder( + options.NewInfScoreBoundBuilder(options.NegativeInfinity), + options.NewInfScoreBoundBuilder(options.PositiveInfinity), + ) + _, err = client.ZCount(key2, zCountRange) + assert.NotNil(t, err) + assert.IsType(suite.T(), &api.RequestError{}, err) + }) +}