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: Implement Pfadd, Pfcount and Select commands #2822

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions go/api/base_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type BaseClient interface {
ListCommands
SetCommands
ConnectionManagementCommands
HyperLogLogCommands
GenericBaseCommands
// Close terminates the client by closing all associated resources.
Close()
Expand Down Expand Up @@ -1163,6 +1164,33 @@ 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) Select(index int64) (Result[string], error) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This must be implemented in standalone client only. Cluster has no databases to switch into, so this command is no-op there.

result, err := client.executeCommand(C.Select, []string{utils.IntToString(index)})
if err != nil {
return CreateNilStringResult(), err
}

return handleStringResponse(result)
}

func (client *baseClient) Unlink(keys []string) (Result[int64], error) {
result, err := client.executeCommand(C.Unlink, keys)
if err != nil {
Expand Down
16 changes: 16 additions & 0 deletions go/api/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -738,4 +738,20 @@ type ConnectionManagementCommands interface {
//
// [valkey.io]: https://valkey.io/commands/ping/
PingWithMessage(message string) (string, error)

// 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/
Select(index int64) (Result[string], error)
}
56 changes: 56 additions & 0 deletions go/api/hyperloglog_commands.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0

package api

// HyperLogLogCommands defines an interface for the "HyperLogLog Commands" group of commands for standalone and cluster
// clients.
//
// [valkey.io]: https://valkey.io/commands/?group=string#hyperloglog
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please fix the link and align the doc with #2844

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 @@ -3556,6 +3556,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())
}
Loading