diff --git a/go/api/base_client.go b/go/api/base_client.go index 39584164ce..7ae29c9060 100644 --- a/go/api/base_client.go +++ b/go/api/base_client.go @@ -11,6 +11,7 @@ import "C" import ( "errors" + "slices" "strconv" "unsafe" @@ -24,6 +25,7 @@ type BaseClient interface { StringCommands HashCommands ListCommands + SetCommands ConnectionManagementCommands // Close terminates the client by closing all associated resources. Close() @@ -513,6 +515,116 @@ func (client *baseClient) RPush(key string, elements []string) (Result[int64], e return handleLongResponse(result) } +func (client *baseClient) SAdd(key string, members []string) (Result[int64], error) { + result, err := client.executeCommand(C.SAdd, append([]string{key}, members...)) + if err != nil { + return CreateNilInt64Result(), err + } + + return handleLongResponse(result) +} + +func (client *baseClient) SRem(key string, members []string) (Result[int64], error) { + result, err := client.executeCommand(C.SRem, append([]string{key}, members...)) + if err != nil { + return CreateNilInt64Result(), err + } + + return handleLongResponse(result) +} + +func (client *baseClient) SMembers(key string) (map[Result[string]]struct{}, error) { + result, err := client.executeCommand(C.SMembers, []string{key}) + if err != nil { + return nil, err + } + + return handleStringSetResponse(result) +} + +func (client *baseClient) SCard(key string) (Result[int64], error) { + result, err := client.executeCommand(C.SCard, []string{key}) + if err != nil { + return CreateNilInt64Result(), err + } + + return handleLongResponse(result) +} + +func (client *baseClient) SIsMember(key string, member string) (Result[bool], error) { + result, err := client.executeCommand(C.SIsMember, []string{key, member}) + if err != nil { + return CreateNilBoolResult(), err + } + + return handleBooleanResponse(result) +} + +func (client *baseClient) SDiff(keys []string) (map[Result[string]]struct{}, error) { + result, err := client.executeCommand(C.SDiff, keys) + if err != nil { + return nil, err + } + + return handleStringSetResponse(result) +} + +func (client *baseClient) SDiffStore(destination string, keys []string) (Result[int64], error) { + result, err := client.executeCommand(C.SDiffStore, append([]string{destination}, keys...)) + if err != nil { + return CreateNilInt64Result(), err + } + + return handleLongResponse(result) +} + +func (client *baseClient) SInter(keys []string) (map[Result[string]]struct{}, error) { + result, err := client.executeCommand(C.SInter, keys) + if err != nil { + return nil, err + } + + return handleStringSetResponse(result) +} + +func (client *baseClient) SInterCard(keys []string) (Result[int64], error) { + result, err := client.executeCommand(C.SInterCard, append([]string{strconv.Itoa(len(keys))}, keys...)) + if err != nil { + return CreateNilInt64Result(), err + } + + return handleLongResponse(result) +} + +func (client *baseClient) SInterCardLimit(keys []string, limit int64) (Result[int64], error) { + args := slices.Concat([]string{utils.IntToString(int64(len(keys)))}, keys, []string{"LIMIT", utils.IntToString(limit)}) + + result, err := client.executeCommand(C.SInterCard, args) + if err != nil { + return CreateNilInt64Result(), err + } + + return handleLongResponse(result) +} + +func (client *baseClient) SRandMember(key string) (Result[string], error) { + result, err := client.executeCommand(C.SRandMember, []string{key}) + if err != nil { + return CreateNilStringResult(), err + } + + return handleStringResponse(result) +} + +func (client *baseClient) SPop(key string) (Result[string], error) { + result, err := client.executeCommand(C.SPop, []string{key}) + if err != nil { + return CreateNilStringResult(), err + } + + return handleStringResponse(result) +} + func (client *baseClient) LRange(key string, start int64, end int64) ([]Result[string], error) { result, err := client.executeCommand(C.LRange, []string{key, utils.IntToString(start), utils.IntToString(end)}) if err != nil { diff --git a/go/api/response_handlers.go b/go/api/response_handlers.go index 554bfcec43..08714c8b22 100644 --- a/go/api/response_handlers.go +++ b/go/api/response_handlers.go @@ -203,3 +203,23 @@ func handleStringToStringMapResponse(response *C.struct_CommandResponse) (map[Re return m, nil } + +func handleStringSetResponse(response *C.struct_CommandResponse) (map[Result[string]]struct{}, error) { + defer C.free_command_response(response) + + typeErr := checkResponseType(response, C.Sets, false) + if typeErr != nil { + return nil, typeErr + } + + slice := make(map[Result[string]]struct{}, response.sets_value_len) + for _, v := range unsafe.Slice(response.sets_value, response.sets_value_len) { + res, err := convertCharArrayToString(&v, true) + if err != nil { + return nil, err + } + slice[res] = struct{}{} + } + + return slice, nil +} diff --git a/go/api/set_commands.go b/go/api/set_commands.go new file mode 100644 index 0000000000..0992f1708d --- /dev/null +++ b/go/api/set_commands.go @@ -0,0 +1,281 @@ +// Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 + +package api + +// SetCommands supports commands and transactions for the "Set Commands" group for standalone and cluster clients. +// +// See [valkey.io] for details. +// +// [valkey.io]: https://valkey.io/commands/?group=set +type SetCommands interface { + // SAdd adds specified members to the set stored at key. + // + // See [valkey.io] for details. + // + // Parameters: + // key - The key where members will be added to its set. + // members - A list of members to add to the set stored at key. + // + // Return value: + // The Result[int64] containing number of members that were added to the set, + // or [api.NilResult[int64]](api.CreateNilInt64Result()) when the key does not exist. + // + // For example: + // result, err := client.SAdd("my_set", []string{"member1", "member2"}) + // // result.Value(): 2 + // // result.IsNil(): false + // + // [valkey.io]: https://valkey.io/commands/sadd/ + SAdd(key string, members []string) (Result[int64], error) + + // SRem removes specified members from the set stored at key. + // + // See [valkey.io] for details. + // + // Parameters: + // key - The key from which members will be removed. + // members - A list of members to remove from the set stored at key. + // + // Return value: + // The Result[int64] containing number of members that were removed from the set, excluding non-existing members. + // Returns [api.NilResult[int64]](api.CreateNilInt64Result()) if key does not exist. + // + // For example: + // result, err := client.SRem("my_set", []string{"member1", "member2"}) + // // result.Value(): 2 + // // result.IsNil(): false + // + // [valkey.io]: https://valkey.io/commands/srem/ + SRem(key string, members []string) (Result[int64], error) + + // SMembers retrieves all the members of the set value stored at key. + // + // See [valkey.io] for details. + // + // Parameters: + // key - The key from which to retrieve the set members. + // + // Return value: + // A map[Result[string]]struct{} containing all members of the set. + // Returns an empty map if key does not exist. + // + // For example: + // // Assume set "my_set" contains: "member1", "member2" + // result, err := client.SMembers("my_set") + // // result equals: + // // map[Result[string]]struct{}{ + // // api.CreateStringResult("member1"): {}, + // // api.CreateStringResult("member2"): {} + // // } + // + // [valkey.io]: https://valkey.io/commands/smembers/ + SMembers(key string) (map[Result[string]]struct{}, error) + + // SCard retrieves the set cardinality (number of elements) of the set stored at key. + // + // See [valkey.io] for details. + // + // Parameters: + // key - The key from which to retrieve the number of set members. + // + // Return value: + // The Result[int64] containing the cardinality (number of elements) of the set, + // or 0 if the key does not exist. + // + // Example: + // result, err := client.SCard("my_set") + // // result.Value(): 3 + // // result.IsNil(): false + // + // [valkey.io]: https://valkey.io/commands/scard/ + SCard(key string) (Result[int64], error) + + // SIsMember returns if member is a member of the set stored at key. + // + // See [valkey.io] for details. + // + // Parameters: + // key - The key of the set. + // member - The member to check for existence in the set. + // + // Return value: + // A Result[bool] containing true if the member exists in the set, false otherwise. + // If key doesn't exist, it is treated as an empty set and the method returns false. + // + // Example: + // result1, err := client.SIsMember("mySet", "member1") + // // result1.Value(): true + // // Indicates that "member1" exists in the set "mySet". + // result2, err := client.SIsMember("mySet", "nonExistingMember") + // // result2.Value(): false + // // Indicates that "nonExistingMember" does not exist in the set "mySet". + // + // [valkey.io]: https://valkey.io/commands/sismember/ + SIsMember(key string, member string) (Result[bool], error) + + // SDiff computes the difference between the first set and all the successive sets in keys. + // + // Note: When in cluster mode, all keys must map to the same hash slot. + // + // See [valkey.io] for details. + // + // Parameters: + // keys - The keys of the sets to diff. + // + // Return value: + // A map[Result[string]]struct{} representing the difference between the sets. + // If a key does not exist, it is treated as an empty set. + // + // Example: + // result, err := client.SDiff([]string{"set1", "set2"}) + // // result might contain: + // // map[Result[string]]struct{}{ + // // api.CreateStringResult("element"): {}, + // // } + // // Indicates that "element" is present in "set1", but missing in "set2" + // + // [valkey.io]: https://valkey.io/commands/sdiff/ + SDiff(keys []string) (map[Result[string]]struct{}, error) + + // SDiffStore stores the difference between the first set and all the successive sets in keys + // into a new set at destination. + // + // Note: When in cluster mode, destination and all keys must map to the same hash slot. + // + // See [valkey.io] for details. + // + // Parameters: + // destination - The key of the destination set. + // keys - The keys of the sets to diff. + // + // Return value: + // A Result[int64] containing the number of elements in the resulting set. + // + // Example: + // result, err := client.SDiffStore("mySet", []string{"set1", "set2"}) + // // result.Value(): 5 + // // Indicates that the resulting set "mySet" contains 5 elements + // + // [valkey.io]: https://valkey.io/commands/sdiffstore/ + SDiffStore(destination string, keys []string) (Result[int64], error) + + // SInter gets the intersection of all the given sets. + // + // Note: When in cluster mode, all keys must map to the same hash slot. + // + // See [valkey.io] for details. + // + // Parameters: + // keys - The keys of the sets to intersect. + // + // Return value: + // A map[Result[string]]struct{} containing members which are present in all given sets. + // If one or more sets do not exist, an empty map will be returned. + // + // + // Example: + // result, err := client.SInter([]string{"set1", "set2"}) + // // result might contain: + // // map[Result[string]]struct{}{ + // // api.CreateStringResult("element"): {}, + // // } + // // Indicates that "element" is present in both "set1" and "set2" + // + // [valkey.io]: https://valkey.io/commands/sinter/ + SInter(keys []string) (map[Result[string]]struct{}, error) + + // SInterCard gets the cardinality of the intersection of all the given sets. + // + // Since: + // Valkey 7.0 and above. + // + // Note: When in cluster mode, all keys must map to the same hash slot. + // + // See [valkey.io] for details. + // + // Parameters: + // keys - The keys of the sets to intersect. + // + // Return value: + // A Result[int64] containing the cardinality of the intersection result. + // If one or more sets do not exist, 0 is returned. + // + // Example: + // result, err := client.SInterCard([]string{"set1", "set2"}) + // // result.Value(): 2 + // // Indicates that the intersection of "set1" and "set2" contains 2 elements + // result, err := client.SInterCard([]string{"set1", "nonExistingSet"}) + // // result.Value(): 0 + // + // [valkey.io]: https://valkey.io/commands/sintercard/ + SInterCard(keys []string) (Result[int64], error) + + // SInterCardLimit gets the cardinality of the intersection of all the given sets, up to the specified limit. + // + // Since: + // Valkey 7.0 and above. + // + // Note: When in cluster mode, all keys must map to the same hash slot. + // + // See [valkey.io] for details. + // + // Parameters: + // keys - The keys of the sets to intersect. + // limit - The limit for the intersection cardinality value. + // + // Return value: + // A Result[int64] containing the cardinality of the intersection result, or the limit if reached. + // If one or more sets do not exist, 0 is returned. + // If the intersection cardinality reaches 'limit' partway through the computation, returns 'limit' as the cardinality. + // + // Example: + // result, err := client.SInterCardLimit([]string{"set1", "set2"}, 3) + // // result.Value(): 2 + // // Indicates that the intersection of "set1" and "set2" contains 2 elements (or at least 3 if the actual + // // intersection is larger) + // + // [valkey.io]: https://valkey.io/commands/sintercard/ + SInterCardLimit(keys []string, limit int64) (Result[int64], error) + + // SRandMember returns a random element from the set value stored at key. + // + // See [valkey.io] for details. + // + // Parameters: + // key - The key from which to retrieve the set member. + // + // Return value: + // A Result[string] containing a random element from the set. + // Returns api.CreateNilStringResult() if key does not exist. + // + // Example: + // client.SAdd("test", []string{"one"}) + // response, err := client.SRandMember("test") + // // response.Value() == "one" + // // err == nil + // + // [valkey.io]: https://valkey.io/commands/srandmember/ + SRandMember(key string) (Result[string], error) + + // SPop removes and returns one random member from the set stored at key. + // + // See [valkey.io] for details. + // + // Parameters: + // key - The key of the set. + // + // Return value: + // A Result[string] containing the value of the popped member. + // Returns a NilResult if key does not exist. + // + // Example: + // value1, err := client.SPop("mySet") + // // value1.Value() might be "value1" + // // err == nil + // value2, err := client.SPop("nonExistingSet") + // // value2.IsNil() == true + // // err == nil + // + // [valkey.io]: https://valkey.io/commands/spop/ + SPop(key string) (Result[string], error) +} diff --git a/go/integTest/glide_test_suite_test.go b/go/integTest/glide_test_suite_test.go index e2f51e67f6..ddfe99787b 100644 --- a/go/integTest/glide_test_suite_test.go +++ b/go/integTest/glide_test_suite_test.go @@ -190,3 +190,9 @@ func (suite *GlideTestSuite) verifyOK(result api.Result[string], err error) { assert.Nil(suite.T(), err) assert.Equal(suite.T(), api.OK, result.Value()) } + +func (suite *GlideTestSuite) SkipIfServerVersionLowerThanBy(version string) { + if suite.serverVersion < version { + suite.T().Skipf("This feature is added in version %s", version) + } +} diff --git a/go/integTest/shared_commands_test.go b/go/integTest/shared_commands_test.go index a25c9cbe54..520ce9b215 100644 --- a/go/integTest/shared_commands_test.go +++ b/go/integTest/shared_commands_test.go @@ -1247,6 +1247,442 @@ func (suite *GlideTestSuite) TestRPush() { }) } +func (suite *GlideTestSuite) TestSAdd() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.NewString() + members := []string{"member1", "member2"} + + res, err := client.SAdd(key, members) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(2), res.Value()) + assert.False(suite.T(), res.IsNil()) + }) +} + +func (suite *GlideTestSuite) TestSAdd_WithExistingKey() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.NewString() + members := []string{"member1", "member2"} + + res1, err := client.SAdd(key, members) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(2), res1.Value()) + assert.False(suite.T(), res1.IsNil()) + + res2, err := client.SAdd(key, members) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(0), res2.Value()) + assert.False(suite.T(), res2.IsNil()) + }) +} + +func (suite *GlideTestSuite) TestSRem() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.NewString() + members := []string{"member1", "member2", "member3"} + + res1, err := client.SAdd(key, members) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(3), res1.Value()) + assert.False(suite.T(), res1.IsNil()) + + res2, err := client.SRem(key, []string{"member1", "member2"}) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(2), res2.Value()) + assert.False(suite.T(), res2.IsNil()) + }) +} + +func (suite *GlideTestSuite) TestSRem_WithExistingKey() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.NewString() + members := []string{"member1", "member2"} + + res1, err := client.SAdd(key, members) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(2), res1.Value()) + assert.False(suite.T(), res1.IsNil()) + + res2, err := client.SRem(key, []string{"member3", "member4"}) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(0), res2.Value()) + assert.False(suite.T(), res2.IsNil()) + }) +} + +func (suite *GlideTestSuite) TestSRem_WithNotExistingKey() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.NewString() + + res2, err := client.SRem(key, []string{"member1", "member2"}) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(0), res2.Value()) + assert.False(suite.T(), res2.IsNil()) + }) +} + +func (suite *GlideTestSuite) TestSRem_WithExistingKeyAndDifferentMembers() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.NewString() + members := []string{"member1", "member2", "member3"} + + res1, err := client.SAdd(key, members) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(3), res1.Value()) + assert.False(suite.T(), res1.IsNil()) + + res2, err := client.SRem(key, []string{"member1", "member3", "member4"}) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(2), res2.Value()) + assert.False(suite.T(), res2.IsNil()) + }) +} + +func (suite *GlideTestSuite) TestSMembers() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.NewString() + members := []string{"member1", "member2", "member3"} + + res1, err := client.SAdd(key, members) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(3), res1.Value()) + assert.False(suite.T(), res1.IsNil()) + + res2, err := client.SMembers(key) + assert.Nil(suite.T(), err) + assert.Len(suite.T(), res2, 3) + }) +} + +func (suite *GlideTestSuite) TestSMembers_WithNotExistingKey() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.NewString() + + res, err := client.SMembers(key) + assert.Nil(suite.T(), err) + assert.Empty(suite.T(), res) + }) +} + +func (suite *GlideTestSuite) TestSCard() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.NewString() + members := []string{"member1", "member2", "member3"} + + res1, err := client.SAdd(key, members) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(3), res1.Value()) + assert.False(suite.T(), res1.IsNil()) + + res2, err := client.SCard(key) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(3), res2.Value()) + assert.False(suite.T(), res2.IsNil()) + }) +} + +func (suite *GlideTestSuite) TestSCard_WithNotExistingKey() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.NewString() + + res, err := client.SCard(key) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(0), res.Value()) + assert.False(suite.T(), res.IsNil()) + }) +} + +func (suite *GlideTestSuite) TestSIsMember() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.NewString() + members := []string{"member1", "member2", "member3"} + + res1, err := client.SAdd(key, members) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(3), res1.Value()) + + res2, err := client.SIsMember(key, "member2") + assert.Nil(suite.T(), err) + assert.True(suite.T(), res2.Value()) + assert.False(suite.T(), res2.IsNil()) + }) +} + +func (suite *GlideTestSuite) TestSIsMember_WithNotExistingKey() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.NewString() + + res, err := client.SIsMember(key, "member2") + assert.Nil(suite.T(), err) + assert.False(suite.T(), res.Value()) + assert.False(suite.T(), res.IsNil()) + }) +} + +func (suite *GlideTestSuite) TestSIsMember_WithNotExistingMember() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.NewString() + members := []string{"member1", "member2", "member3"} + + res1, err := client.SAdd(key, members) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(3), res1.Value()) + assert.False(suite.T(), res1.IsNil()) + + res2, err := client.SIsMember(key, "nonExistingMember") + assert.Nil(suite.T(), err) + assert.False(suite.T(), res2.Value()) + assert.False(suite.T(), res2.IsNil()) + }) +} + +func (suite *GlideTestSuite) TestSDiff() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key1 := "{key}-1-" + uuid.NewString() + key2 := "{key}-2-" + uuid.NewString() + + res1, err := client.SAdd(key1, []string{"a", "b", "c", "d"}) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(4), res1.Value()) + assert.False(suite.T(), res1.IsNil()) + + res2, err := client.SAdd(key2, []string{"c", "d", "e"}) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(3), res2.Value()) + assert.False(suite.T(), res2.IsNil()) + + result, err := client.SDiff([]string{key1, key2}) + assert.Nil(suite.T(), err) + assert.Len(suite.T(), result, 2) + assert.Contains(suite.T(), result, api.CreateStringResult("a")) + assert.Contains(suite.T(), result, api.CreateStringResult("b")) + }) +} + +func (suite *GlideTestSuite) TestSDiff_WithNotExistingKey() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key1 := "{key}-1-" + uuid.NewString() + key2 := "{key}-2-" + uuid.NewString() + + result, err := client.SDiff([]string{key1, key2}) + assert.Nil(suite.T(), err) + assert.Empty(suite.T(), result) + }) +} + +func (suite *GlideTestSuite) TestSDiff_WithSingleKeyExist() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key1 := "{key}-1-" + uuid.NewString() + key2 := "{key}-2-" + uuid.NewString() + + res1, err := client.SAdd(key1, []string{"a", "b", "c"}) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(3), res1.Value()) + assert.False(suite.T(), res1.IsNil()) + + res2, err := client.SDiff([]string{key1, key2}) + assert.Nil(suite.T(), err) + assert.Len(suite.T(), res2, 3) + assert.Contains(suite.T(), res2, api.CreateStringResult("a")) + assert.Contains(suite.T(), res2, api.CreateStringResult("b")) + assert.Contains(suite.T(), res2, api.CreateStringResult("c")) + }) +} + +func (suite *GlideTestSuite) TestSDiffStore() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key1 := "{key}-1-" + uuid.NewString() + key2 := "{key}-2-" + uuid.NewString() + key3 := "{key}-3-" + uuid.NewString() + + res1, err := client.SAdd(key1, []string{"a", "b", "c"}) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(3), res1.Value()) + assert.False(suite.T(), res1.IsNil()) + + res2, err := client.SAdd(key2, []string{"c", "d", "e"}) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(3), res2.Value()) + assert.False(suite.T(), res2.IsNil()) + + res3, err := client.SDiffStore(key3, []string{key1, key2}) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(2), res3.Value()) + assert.False(suite.T(), res3.IsNil()) + + members, err := client.SMembers(key3) + assert.Nil(suite.T(), err) + assert.Len(suite.T(), members, 2) + assert.Contains(suite.T(), members, api.CreateStringResult("a")) + assert.Contains(suite.T(), members, api.CreateStringResult("b")) + }) +} + +func (suite *GlideTestSuite) TestSDiffStore_WithNotExistingKeys() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key1 := "{key}-1-" + uuid.NewString() + key2 := "{key}-2-" + uuid.NewString() + key3 := "{key}-3-" + uuid.NewString() + + res, err := client.SDiffStore(key3, []string{key1, key2}) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(0), res.Value()) + assert.False(suite.T(), res.IsNil()) + + members, err := client.SMembers(key3) + assert.Nil(suite.T(), err) + assert.Empty(suite.T(), members) + }) +} + +func (suite *GlideTestSuite) TestSinter() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key1 := "{key}-1-" + uuid.NewString() + key2 := "{key}-2-" + uuid.NewString() + + res1, err := client.SAdd(key1, []string{"a", "b", "c", "d"}) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(4), res1.Value()) + assert.False(suite.T(), res1.IsNil()) + + res2, err := client.SAdd(key2, []string{"c", "d", "e"}) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(3), res2.Value()) + assert.False(suite.T(), res2.IsNil()) + + members, err := client.SInter([]string{key1, key2}) + assert.Nil(suite.T(), err) + assert.Len(suite.T(), members, 2) + assert.Contains(suite.T(), members, api.CreateStringResult("c")) + assert.Contains(suite.T(), members, api.CreateStringResult("d")) + }) +} + +func (suite *GlideTestSuite) TestSinter_WithNotExistingKeys() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key1 := "{key}-1-" + uuid.NewString() + key2 := "{key}-2-" + uuid.NewString() + + members, err := client.SInter([]string{key1, key2}) + assert.Nil(suite.T(), err) + assert.Empty(suite.T(), members) + }) +} + +func (suite *GlideTestSuite) TestSInterCard() { + suite.SkipIfServerVersionLowerThanBy("7.0.0") + + suite.runWithDefaultClients(func(client api.BaseClient) { + key1 := "{key}-1-" + uuid.NewString() + key2 := "{key}-2-" + uuid.NewString() + + res1, err := client.SAdd(key1, []string{"one", "two", "three", "four"}) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(4), res1.Value()) + assert.False(suite.T(), res1.IsNil()) + + result1, err := client.SInterCard([]string{key1, key2}) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(0), result1.Value()) + assert.False(suite.T(), result1.IsNil()) + + res2, err := client.SAdd(key2, []string{"two", "three", "four", "five"}) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(4), res2.Value()) + assert.False(suite.T(), res2.IsNil()) + + result2, err := client.SInterCard([]string{key1, key2}) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(3), result2.Value()) + assert.False(suite.T(), result2.IsNil()) + }) +} + +func (suite *GlideTestSuite) TestSInterCardLimit() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key1 := "{key}-1-" + uuid.NewString() + key2 := "{key}-2-" + uuid.NewString() + + res1, err := client.SAdd(key1, []string{"one", "two", "three", "four"}) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(4), res1.Value()) + assert.False(suite.T(), res1.IsNil()) + + res2, err := client.SAdd(key2, []string{"two", "three", "four", "five"}) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(4), res2.Value()) + assert.False(suite.T(), res2.IsNil()) + + result1, err := client.SInterCardLimit([]string{key1, key2}, 2) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(2), result1.Value()) + assert.False(suite.T(), result1.IsNil()) + + result2, err := client.SInterCardLimit([]string{key1, key2}, 4) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(3), result2.Value()) + assert.False(suite.T(), result2.IsNil()) + }) +} + +func (suite *GlideTestSuite) TestSRandMember() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.NewString() + + res, err := client.SAdd(key, []string{"one"}) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(1), res.Value()) + assert.False(suite.T(), res.IsNil()) + + member, err := client.SRandMember(key) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), "one", member.Value()) + assert.False(suite.T(), member.IsNil()) + }) +} + +func (suite *GlideTestSuite) TestSPop() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.NewString() + members := []string{"value1", "value2", "value3"} + + res, err := client.SAdd(key, members) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(3), res.Value()) + assert.False(suite.T(), res.IsNil()) + + popMember, err := client.SPop(key) + assert.Nil(suite.T(), err) + assert.Contains(suite.T(), members, popMember.Value()) + assert.False(suite.T(), popMember.IsNil()) + + remainingMembers, err := client.SMembers(key) + assert.Nil(suite.T(), err) + assert.Len(suite.T(), remainingMembers, 2) + assert.NotContains(suite.T(), remainingMembers, popMember) + }) +} + +func (suite *GlideTestSuite) TestSPop_LastMember() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.NewString() + + res1, err := client.SAdd(key, []string{"lastValue"}) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(1), res1.Value()) + assert.False(suite.T(), res1.IsNil()) + + popMember, err := client.SPop(key) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), "lastValue", popMember.Value()) + assert.False(suite.T(), popMember.IsNil()) + + remainingMembers, err := client.SMembers(key) + assert.Nil(suite.T(), err) + assert.Empty(suite.T(), remainingMembers) + }) +} + func (suite *GlideTestSuite) TestLRange() { suite.runWithDefaultClients(func(client api.BaseClient) { list := []string{"value4", "value3", "value2", "value1"} diff --git a/go/src/lib.rs b/go/src/lib.rs index 57e6e3a44a..d9bdf14e68 100644 --- a/go/src/lib.rs +++ b/go/src/lib.rs @@ -57,6 +57,13 @@ pub struct CommandResponse { map_key: *mut CommandResponse, #[derivative(Default(value = "std::ptr::null_mut()"))] map_value: *mut CommandResponse, + + /// Below two values are related to each other. + /// `sets_value` represents the set of CommandResponse. + /// `sets_value_len` represents the length of the set. + #[derivative(Default(value = "std::ptr::null_mut()"))] + sets_value: *mut CommandResponse, + sets_value_len: c_long, } #[repr(C)] @@ -70,6 +77,7 @@ pub enum ResponseType { String = 4, Array = 5, Map = 6, + Sets = 7, } /// Success callback that is called when a command succeeds. @@ -244,6 +252,7 @@ pub extern "C" fn get_response_type_string(response_type: ResponseType) -> *mut ResponseType::String => "String", ResponseType::Array => "Array", ResponseType::Map => "Map", + ResponseType::Sets => "Sets", }; let c_str = CString::new(s).unwrap_or_default(); c_str.into_raw() @@ -298,6 +307,8 @@ fn free_command_response_elements(command_response: CommandResponse) { let array_value_len = command_response.array_value_len; let map_key = command_response.map_key; let map_value = command_response.map_value; + let sets_value = command_response.sets_value; + let sets_value_len = command_response.sets_value_len; if !string_value.is_null() { let len = string_value_len as usize; unsafe { Vec::from_raw_parts(string_value, len, len) }; @@ -315,6 +326,13 @@ fn free_command_response_elements(command_response: CommandResponse) { if !map_value.is_null() { unsafe { free_command_response(map_value) }; } + if !sets_value.is_null() { + let len = sets_value_len as usize; + let vec = unsafe { Vec::from_raw_parts(sets_value, len, len) }; + for element in vec.into_iter() { + free_command_response_elements(element); + } + } } /// Frees the error_message received on a command failure. @@ -453,6 +471,20 @@ fn valkey_value_to_command_response(value: Value) -> RedisResult { + let vec: Vec = array + .into_iter() + .map(|v| { + valkey_value_to_command_response(v) + .expect("Value couldn't be converted to CommandResponse") + }) + .collect(); + let (vec_ptr, len) = convert_vec_to_pointer(vec); + command_response.sets_value = vec_ptr; + command_response.sets_value_len = len; + command_response.response_type = ResponseType::Sets; + Ok(command_response) + } // TODO: Add support for other return types. _ => todo!(), };