From ac2cfada5514bff7959a535f72ecda3c826ed7ab Mon Sep 17 00:00:00 2001 From: Janhavi Gupta Date: Mon, 21 Oct 2024 03:36:27 +0000 Subject: [PATCH 1/3] GO:Handling interface response Signed-off-by: Janhavi Gupta --- go/api/glide_client.go | 8 +- go/api/response_handlers.go | 96 ++++++++++++++++++++++++ go/integTest/standalone_commands_test.go | 29 ++++++- 3 files changed, 125 insertions(+), 8 deletions(-) diff --git a/go/api/glide_client.go b/go/api/glide_client.go index 27f3dbac75..2f8e8e5235 100644 --- a/go/api/glide_client.go +++ b/go/api/glide_client.go @@ -33,18 +33,12 @@ func NewGlideClient(config *GlideClientConfiguration) (*GlideClient, error) { // For example, to return a list of all pub/sub clients: // // client.CustomCommand([]string{"CLIENT", "LIST","TYPE", "PUBSUB"}) -// -// TODO: Add support for complex return types. func (client *GlideClient) CustomCommand(args []string) (interface{}, error) { res, err := client.executeCommand(C.CustomCommand, args) if err != nil { return nil, err } - resString, err := handleStringOrNullResponse(res) - if err != nil { - return nil, err - } - return resString.Value(), err + return handleInterfaceResponse(res) } // Sets configuration parameters to the specified values. diff --git a/go/api/response_handlers.go b/go/api/response_handlers.go index 08714c8b22..d069d0b524 100644 --- a/go/api/response_handlers.go +++ b/go/api/response_handlers.go @@ -59,6 +59,102 @@ func convertCharArrayToString(response *C.struct_CommandResponse, isNilable bool return CreateStringResult(string(byteSlice)), nil } +func handleInterfaceResponse(response *C.struct_CommandResponse) (interface{}, error) { + defer C.free_command_response(response) + + return parseInterface(response) +} + +func parseInterface(response *C.struct_CommandResponse) (interface{}, error) { + if response == nil { + return nil, nil + } + + switch response.response_type { + case C.Null: + return nil, nil + case C.String: + return parseString(response) + case C.Int: + return int64(response.int_value), nil + case C.Float: + return float64(response.float_value), nil + case C.Bool: + return bool(response.bool_value), nil + case C.Array: + return parseArray(response) + case C.Map: + return parseMap(response) + case C.Sets: + return parseSet(response) + } + + return nil, &RequestError{"Unexpected return type from Valkey"} +} + +func parseString(response *C.struct_CommandResponse) (interface{}, error) { + if response.string_value == nil { + return nil, nil + } + byteSlice := C.GoBytes(unsafe.Pointer(response.string_value), C.int(int64(response.string_value_len))) + + // Create Go string from byte slice (preserving null characters) + return string(byteSlice), nil +} + +func parseArray(response *C.struct_CommandResponse) (interface{}, error) { + if response.array_value == nil { + return nil, nil + } + + var slice []interface{} + for _, v := range unsafe.Slice(response.array_value, response.array_value_len) { + res, err := parseInterface(&v) + if err != nil { + return nil, err + } + slice = append(slice, res) + } + return slice, nil +} + +func parseMap(response *C.struct_CommandResponse) (interface{}, error) { + if response.array_value == nil { + return nil, nil + } + + var value_map = make(map[interface{}]interface{}, response.array_value_len) + for _, v := range unsafe.Slice(response.array_value, response.array_value_len) { + res_key, err := parseInterface(v.map_key) + if err != nil { + return nil, err + } + res_val, err := parseInterface(v.map_value) + if err != nil { + return nil, err + } + value_map[res_key] = res_val + } + return value_map, nil +} + +func parseSet(response *C.struct_CommandResponse) (interface{}, error) { + if response.array_value == nil { + return nil, nil + } + + slice := make(map[interface{}]struct{}, response.sets_value_len) + for _, v := range unsafe.Slice(response.sets_value, response.sets_value_len) { + res, err := parseInterface(&v) + if err != nil { + return nil, err + } + slice[res] = struct{}{} + } + + return slice, nil +} + func handleStringResponse(response *C.struct_CommandResponse) (Result[string], error) { defer C.free_command_response(response) diff --git a/go/integTest/standalone_commands_test.go b/go/integTest/standalone_commands_test.go index 4186c6b184..36ef3ebce4 100644 --- a/go/integTest/standalone_commands_test.go +++ b/go/integTest/standalone_commands_test.go @@ -6,6 +6,7 @@ import ( "fmt" "strings" + "github.com/google/uuid" "github.com/valkey-io/valkey-glide/go/glide/api" "github.com/stretchr/testify/assert" @@ -26,7 +27,7 @@ func (suite *GlideTestSuite) TestCustomCommandPing() { result, err := client.CustomCommand([]string{"PING"}) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), "PONG", result) + assert.Equal(suite.T(), "PONG", result.(string)) } func (suite *GlideTestSuite) TestCustomCommandClientInfo() { @@ -44,6 +45,32 @@ func (suite *GlideTestSuite) TestCustomCommandClientInfo() { assert.True(suite.T(), strings.Contains(strResult, fmt.Sprintf("name=%s", clientName))) } +func (suite *GlideTestSuite) TestCustomCommandMGET() { + clientName := "TEST_CLIENT_NAME" + config := api.NewGlideClientConfiguration(). + WithAddress(&api.NodeAddress{Port: suite.standalonePorts[0]}). + WithClientName(clientName) + client := suite.client(config) + + key1 := uuid.New().String() + key2 := uuid.New().String() + key3 := uuid.New().String() + oldValue := uuid.New().String() + value := uuid.New().String() + suite.verifyOK(client.Set(key1, oldValue)) + keyValueMap := map[string]string{ + key1: value, + key2: value, + } + suite.verifyOK(client.MSet(keyValueMap)) + keys := []string{key1, key2, key3} + values := []interface{}{value, value, nil} + result, err := client.CustomCommand(append([]string{"MGET"}, keys...)) + + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), values, result.([]interface{})) +} + func (suite *GlideTestSuite) TestCustomCommand_invalidCommand() { client := suite.defaultClient() result, err := client.CustomCommand([]string{"pewpew"}) From 7d2fdbc006f1c5409338b2729ee853078a0eb3a0 Mon Sep 17 00:00:00 2001 From: Janhavi Gupta Date: Mon, 4 Nov 2024 06:59:29 +0000 Subject: [PATCH 2/3] Fixing lint Signed-off-by: Janhavi Gupta --- go/api/response_handlers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go/api/response_handlers.go b/go/api/response_handlers.go index e7d97fd894..23f9181c3b 100644 --- a/go/api/response_handlers.go +++ b/go/api/response_handlers.go @@ -123,7 +123,7 @@ func parseMap(response *C.struct_CommandResponse) (interface{}, error) { return nil, nil } - var value_map = make(map[interface{}]interface{}, response.array_value_len) + value_map := make(map[interface{}]interface{}, response.array_value_len) for _, v := range unsafe.Slice(response.array_value, response.array_value_len) { res_key, err := parseInterface(v.map_key) if err != nil { From cc59ece64f8d7754111f8b3ba989f54f3c541e7b Mon Sep 17 00:00:00 2001 From: Janhavi Gupta Date: Fri, 15 Nov 2024 04:13:55 +0000 Subject: [PATCH 3/3] Adding more tests for custom command Signed-off-by: Janhavi Gupta --- go/api/response_handlers.go | 2 +- go/integTest/standalone_commands_test.go | 78 +++++++++++++++++++++++- 2 files changed, 77 insertions(+), 3 deletions(-) diff --git a/go/api/response_handlers.go b/go/api/response_handlers.go index 23f9181c3b..63f0ac0007 100644 --- a/go/api/response_handlers.go +++ b/go/api/response_handlers.go @@ -139,7 +139,7 @@ func parseMap(response *C.struct_CommandResponse) (interface{}, error) { } func parseSet(response *C.struct_CommandResponse) (interface{}, error) { - if response.array_value == nil { + if response.sets_value == nil { return nil, nil } diff --git a/go/integTest/standalone_commands_test.go b/go/integTest/standalone_commands_test.go index 36ef3ebce4..80cbb63581 100644 --- a/go/integTest/standalone_commands_test.go +++ b/go/integTest/standalone_commands_test.go @@ -22,7 +22,7 @@ func (suite *GlideTestSuite) TestCustomCommandInfo() { assert.True(suite.T(), strings.Contains(strResult, "# Stats")) } -func (suite *GlideTestSuite) TestCustomCommandPing() { +func (suite *GlideTestSuite) TestCustomCommandPing_StringResponse() { client := suite.defaultClient() result, err := client.CustomCommand([]string{"PING"}) @@ -45,7 +45,51 @@ func (suite *GlideTestSuite) TestCustomCommandClientInfo() { assert.True(suite.T(), strings.Contains(strResult, fmt.Sprintf("name=%s", clientName))) } -func (suite *GlideTestSuite) TestCustomCommandMGET() { +func (suite *GlideTestSuite) TestCustomCommandGet_NullResponse() { + client := suite.defaultClient() + key := uuid.New().String() + result, err := client.CustomCommand([]string{"GET", key}) + + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), nil, result) +} + +func (suite *GlideTestSuite) TestCustomCommandDel_LongResponse() { + client := suite.defaultClient() + key := uuid.New().String() + suite.verifyOK(client.Set(key, "value")) + result, err := client.CustomCommand([]string{"DEL", key}) + + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(1), result.(int64)) +} + +func (suite *GlideTestSuite) TestCustomCommandHExists_BoolResponse() { + client := suite.defaultClient() + fields := map[string]string{"field1": "value1"} + key := uuid.New().String() + + res1, err := client.HSet(key, fields) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(1), res1.Value()) + + result, err := client.CustomCommand([]string{"HEXISTS", key, "field1"}) + + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), true, result.(bool)) +} + +func (suite *GlideTestSuite) TestCustomCommandIncrByFloat_FloatResponse() { + client := suite.defaultClient() + key := uuid.New().String() + + result, err := client.CustomCommand([]string{"INCRBYFLOAT", key, fmt.Sprintf("%f", 0.1)}) + + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), float64(0.1), result.(float64)) +} + +func (suite *GlideTestSuite) TestCustomCommandMGet_ArrayResponse() { clientName := "TEST_CLIENT_NAME" config := api.NewGlideClientConfiguration(). WithAddress(&api.NodeAddress{Port: suite.standalonePorts[0]}). @@ -71,6 +115,36 @@ func (suite *GlideTestSuite) TestCustomCommandMGET() { assert.Equal(suite.T(), values, result.([]interface{})) } +func (suite *GlideTestSuite) TestCustomCommandConfigGet_MapResponse() { + client := suite.defaultClient() + + if suite.serverVersion < "7.0.0" { + suite.T().Skip("This feature is added in version 7") + } + configMap := map[string]string{"timeout": "1000", "maxmemory": "1GB"} + result, err := client.ConfigSet(configMap) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), "OK", result.Value()) + + result2, err := client.CustomCommand([]string{"CONFIG", "GET", "timeout", "maxmemory"}) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), map[interface{}]interface{}{"timeout": "1000", "maxmemory": "1073741824"}, result2) +} + +func (suite *GlideTestSuite) TestCustomCommandConfigSMembers_SetResponse() { + client := suite.defaultClient() + 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()) + + result2, err := client.CustomCommand([]string{"SMEMBERS", key}) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), map[interface{}]struct{}{"member1": {}, "member2": {}, "member3": {}}, result2) +} + func (suite *GlideTestSuite) TestCustomCommand_invalidCommand() { client := suite.defaultClient() result, err := client.CustomCommand([]string{"pewpew"})