Skip to content

Commit

Permalink
Go: Implement Pfadd, Pfcount and Select commands (#2822)
Browse files Browse the repository at this point in the history
* pfadd, pfcount and select commands

Signed-off-by: Niharika Bhavaraju <[email protected]>
  • Loading branch information
niharikabhavaraju authored Dec 31, 2024
1 parent 8e98a0c commit abec885
Show file tree
Hide file tree
Showing 5 changed files with 240 additions and 0 deletions.
19 changes: 19 additions & 0 deletions go/api/base_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ type BaseClient interface {
StreamCommands
SortedSetCommands
ConnectionManagementCommands
HyperLogLogCommands
GenericBaseCommands
// Close terminates the client by closing all associated resources.
Close()
Expand Down Expand Up @@ -1213,6 +1214,24 @@ func (client *baseClient) PTTL(key string) (Result[int64], error) {
return handleLongResponse(result)
}

func (client *baseClient) PfAdd(key string, elements []string) (Result[int64], error) {
result, err := client.executeCommand(C.PfAdd, append([]string{key}, elements...))
if err != nil {
return CreateNilInt64Result(), err
}

return handleLongResponse(result)
}

func (client *baseClient) PfCount(keys []string) (Result[int64], error) {
result, err := client.executeCommand(C.PfCount, keys)
if err != nil {
return CreateNilInt64Result(), err
}

return handleLongResponse(result)
}

func (client *baseClient) Unlink(keys []string) (Result[int64], error) {
result, err := client.executeCommand(C.Unlink, keys)
if err != nil {
Expand Down
26 changes: 26 additions & 0 deletions go/api/glide_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,29 @@ func (client *GlideClient) ConfigGet(args []string) (map[Result[string]]Result[s
}
return handleStringToStringMapResponse(res)
}

// Select changes the currently selected database.
//
// Parameters:
//
// index - The index of the database to select.
//
// Return value:
//
// A simple OK response.
//
// Example:
//
// result, err := client.Select(2)
// result.Value() : "OK"
// result.IsNil() : false
//
// [valkey.io]: https://valkey.io/commands/select/
func (client *GlideClient) Select(index int64) (Result[string], error) {
result, err := client.executeCommand(C.Select, []string{utils.IntToString(index)})
if err != nil {
return CreateNilStringResult(), err
}

return handleStringResponse(result)
}
57 changes: 57 additions & 0 deletions go/api/hyperloglog_commands.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0

package api

// Supports commands and transactions for the "HyperLogLog" group of commands for standalone and cluster clients.
//
// See [valkey.io] for details.
//
// [valkey.io]: https://valkey.io/commands/#hyperloglog
type HyperLogLogCommands interface {
// PfAdd adds all elements to the HyperLogLog data structure stored at the specified key.
// Creates a new structure if the key does not exist.
// When no elements are provided, and key exists and is a HyperLogLog, then no operation is performed.
// If key does not exist, then the HyperLogLog structure is created.
//
// Parameters:
// key - The key of the HyperLogLog data structure to add elements into.
// elements - An array of members to add to the HyperLogLog stored at key.
//
// Return value:
// If the HyperLogLog is newly created, or if the HyperLogLog approximated cardinality is
// altered, then returns 1. Otherwise, returns 0.
//
// Example:
// result, err := client.PfAdd("key",[]string{"value1", "value2", "value3"})
// result.Value(): 1
// result.IsNil(): false
//
// [valkey.io]: https://valkey.io/commands/pfadd/
PfAdd(key string, elements []string) (Result[int64], error)

// Estimates the cardinality of the data stored in a HyperLogLog structure for a single key or
// calculates the combined cardinality of multiple keys by merging their HyperLogLogs temporarily.
//
// Note:
// In cluster mode, if keys in `keyValueMap` map to different hash slots, the command
// will be split across these slots and executed separately for each. This means the command
// is atomic only at the slot level. If one or more slot-specific requests fail, the entire
// call will return the first encountered error, even though some requests may have succeeded
// while others did not. If this behavior impacts your application logic, consider splitting
// the request into sub-requests per slot to ensure atomicity.
//
// Parameters:
// key - The keys of the HyperLogLog data structures to be analyzed.
//
// Return value:
// The approximated cardinality of given HyperLogLog data structures.
// The cardinality of a key that does not exist is 0.
//
// Example:
// result, err := client.PfCount([]string{"key1","key2"})
// result.Value(): 5
// result.IsNil(): false
//
// [valkey.io]: https://valkey.io/commands/pfcount/
PfCount(keys []string) (Result[int64], error)
}
81 changes: 81 additions & 0 deletions go/integTest/shared_commands_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3623,6 +3623,87 @@ func (suite *GlideTestSuite) TestPTTL_WithExpiredKey() {
})
}

func (suite *GlideTestSuite) TestPfAdd_SuccessfulAddition() {
suite.runWithDefaultClients(func(client api.BaseClient) {
key := uuid.New().String()
res, err := client.PfAdd(key, []string{"a", "b", "c", "d", "e"})
assert.Nil(suite.T(), err)
assert.Equal(suite.T(), int64(1), res.Value())
})
}

func (suite *GlideTestSuite) TestPfAdd_DuplicateElements() {
suite.runWithDefaultClients(func(client api.BaseClient) {
key := uuid.New().String()

// case : Add elements and add same elements again
res, err := client.PfAdd(key, []string{"a", "b", "c", "d", "e"})
assert.Nil(suite.T(), err)
assert.Equal(suite.T(), int64(1), res.Value())

res2, err := client.PfAdd(key, []string{"a", "b", "c", "d", "e"})
assert.Nil(suite.T(), err)
assert.Equal(suite.T(), int64(0), res2.Value())

// case : (mixed elements) add new elements with 1 duplicate elements
res1, err := client.PfAdd(key, []string{"f", "g", "h"})
assert.Nil(suite.T(), err)
assert.Equal(suite.T(), int64(1), res1.Value())

res2, err = client.PfAdd(key, []string{"i", "j", "g"})
assert.Nil(suite.T(), err)
assert.Equal(suite.T(), int64(1), res2.Value())

// case : add empty array(no elements to the HyperLogLog)
res, err = client.PfAdd(key, []string{})
assert.Nil(suite.T(), err)
assert.Equal(suite.T(), int64(0), res.Value())
})
}

func (suite *GlideTestSuite) TestPfCount_SingleKey() {
suite.runWithDefaultClients(func(client api.BaseClient) {
key := uuid.New().String()
res, err := client.PfAdd(key, []string{"i", "j", "g"})
assert.Nil(suite.T(), err)
assert.Equal(suite.T(), int64(1), res.Value())

resCount, err := client.PfCount([]string{key})
assert.Nil(suite.T(), err)
assert.Equal(suite.T(), int64(3), resCount.Value())
})
}

func (suite *GlideTestSuite) TestPfCount_MultipleKeys() {
suite.runWithDefaultClients(func(client api.BaseClient) {
key1 := uuid.New().String() + "{group}"
key2 := uuid.New().String() + "{group}"

res, err := client.PfAdd(key1, []string{"a", "b", "c"})
assert.Nil(suite.T(), err)
assert.Equal(suite.T(), int64(1), res.Value())

res, err = client.PfAdd(key2, []string{"c", "d", "e"})
assert.Nil(suite.T(), err)
assert.Equal(suite.T(), int64(1), res.Value())

resCount, err := client.PfCount([]string{key1, key2})
assert.Nil(suite.T(), err)
assert.Equal(suite.T(), int64(5), resCount.Value())
})
}

func (suite *GlideTestSuite) TestPfCount_NoExistingKeys() {
suite.runWithDefaultClients(func(client api.BaseClient) {
key1 := uuid.New().String() + "{group}"
key2 := uuid.New().String() + "{group}"

resCount, err := client.PfCount([]string{key1, key2})
assert.Nil(suite.T(), err)
assert.Equal(suite.T(), int64(0), resCount.Value())
})
}

func (suite *GlideTestSuite) TestBLMove() {
if suite.serverVersion < "6.2.0" {
suite.T().Skip("This feature is added in version 6.2.0")
Expand Down
57 changes: 57 additions & 0 deletions go/integTest/standalone_commands_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,3 +225,60 @@ func (suite *GlideTestSuite) TestConfigSetAndGet_invalidArgs() {
assert.Equal(suite.T(), map[api.Result[string]]api.Result[string]{}, result2)
assert.Nil(suite.T(), err)
}

func (suite *GlideTestSuite) TestSelect_WithValidIndex() {
client := suite.defaultClient()
index := int64(1)
result, err := client.Select(index)

assert.Nil(suite.T(), err)
assert.Equal(suite.T(), "OK", result.Value())

key := uuid.New().String()
value := uuid.New().String()
suite.verifyOK(client.Set(key, value))

res, err := client.Get(key)
assert.Nil(suite.T(), err)
assert.Equal(suite.T(), value, res.Value())
}

func (suite *GlideTestSuite) TestSelect_InvalidIndex_OutOfBounds() {
client := suite.defaultClient()

result, err := client.Select(-1)
assert.NotNil(suite.T(), err)
assert.Equal(suite.T(), "", result.Value())

result, err = client.Select(1000)
assert.NotNil(suite.T(), err)
assert.Equal(suite.T(), "", result.Value())
}

func (suite *GlideTestSuite) TestSelect_SwitchBetweenDatabases() {
client := suite.defaultClient()

key1 := uuid.New().String()
value1 := uuid.New().String()
suite.verifyOK(client.Select(0))
suite.verifyOK(client.Set(key1, value1))

key2 := uuid.New().String()
value2 := uuid.New().String()
suite.verifyOK(client.Select(1))
suite.verifyOK(client.Set(key2, value2))

result, err := client.Get(key1)
assert.Nil(suite.T(), err)
assert.Equal(suite.T(), "", result.Value())

suite.verifyOK(client.Select(0))
result, err = client.Get(key2)
assert.Nil(suite.T(), err)
assert.Equal(suite.T(), "", result.Value())

suite.verifyOK(client.Select(1))
result, err = client.Get(key2)
assert.Nil(suite.T(), err)
assert.Equal(suite.T(), value2, result.Value())
}

0 comments on commit abec885

Please sign in to comment.