From 632d99000fe31eda7de574bdc1796e079d621e63 Mon Sep 17 00:00:00 2001 From: umit Date: Wed, 4 Sep 2024 22:03:31 +0300 Subject: [PATCH 1/2] add HashCommands interface and tests for hash-related commands Signed-off-by: umit --- go/api/base_client.go | 148 ++++++++-- go/api/commands.go | 227 +++++++++++++++ go/api/glide_client.go | 7 +- go/api/response_handlers.go | 70 +++-- go/integTest/shared_commands_test.go | 406 +++++++++++++++++++++++++++ go/utils/transform_utils.go | 19 ++ go/utils/transform_utils_test.go | 61 ++++ 7 files changed, 899 insertions(+), 39 deletions(-) create mode 100644 go/utils/transform_utils_test.go diff --git a/go/api/base_client.go b/go/api/base_client.go index fd22e348a5..844ec92f36 100644 --- a/go/api/base_client.go +++ b/go/api/base_client.go @@ -22,6 +22,7 @@ import ( // BaseClient defines an interface for methods common to both [GlideClient] and [GlideClusterClient]. type BaseClient interface { StringCommands + HashCommands // Close terminates the client by closing all associated resources. Close() @@ -146,7 +147,8 @@ func (client *baseClient) Set(key string, value string) (string, error) { if err != nil { return "", err } - return handleStringResponse(result), nil + + return handleStringResponse(result) } func (client *baseClient) SetWithOptions(key string, value string, options *SetOptions) (string, error) { @@ -154,7 +156,8 @@ func (client *baseClient) SetWithOptions(key string, value string, options *SetO if err != nil { return "", err } - return handleStringOrNullResponse(result), nil + + return handleStringOrNullResponse(result) } func (client *baseClient) Get(key string) (string, error) { @@ -162,7 +165,8 @@ func (client *baseClient) Get(key string) (string, error) { if err != nil { return "", err } - return handleStringOrNullResponse(result), nil + + return handleStringOrNullResponse(result) } func (client *baseClient) MSet(keyValueMap map[string]string) (string, error) { @@ -170,7 +174,7 @@ func (client *baseClient) MSet(keyValueMap map[string]string) (string, error) { if err != nil { return "", err } - return handleStringResponse(result), nil + return handleStringResponse(result) } func (client *baseClient) MSetNX(keyValueMap map[string]string) (bool, error) { @@ -178,7 +182,8 @@ func (client *baseClient) MSetNX(keyValueMap map[string]string) (bool, error) { if err != nil { return false, err } - return handleBooleanResponse(result), nil + + return handleBooleanResponse(result) } func (client *baseClient) MGet(keys []string) ([]string, error) { @@ -186,7 +191,8 @@ func (client *baseClient) MGet(keys []string) ([]string, error) { if err != nil { return nil, err } - return handleStringArrayResponse(result), nil + + return handleStringArrayResponse(result) } func (client *baseClient) Incr(key string) (int64, error) { @@ -194,7 +200,7 @@ func (client *baseClient) Incr(key string) (int64, error) { if err != nil { return 0, err } - return handleLongResponse(result), nil + return handleLongResponse(result) } func (client *baseClient) IncrBy(key string, amount int64) (int64, error) { @@ -202,7 +208,7 @@ func (client *baseClient) IncrBy(key string, amount int64) (int64, error) { if err != nil { return 0, err } - return handleLongResponse(result), nil + return handleLongResponse(result) } func (client *baseClient) IncrByFloat(key string, amount float64) (float64, error) { @@ -213,7 +219,8 @@ func (client *baseClient) IncrByFloat(key string, amount float64) (float64, erro if err != nil { return 0, err } - return handleDoubleResponse(result), nil + + return handleDoubleResponse(result) } func (client *baseClient) Decr(key string) (int64, error) { @@ -221,7 +228,7 @@ func (client *baseClient) Decr(key string) (int64, error) { if err != nil { return 0, err } - return handleLongResponse(result), nil + return handleLongResponse(result) } func (client *baseClient) DecrBy(key string, amount int64) (int64, error) { @@ -229,7 +236,7 @@ func (client *baseClient) DecrBy(key string, amount int64) (int64, error) { if err != nil { return 0, err } - return handleLongResponse(result), nil + return handleLongResponse(result) } func (client *baseClient) Strlen(key string) (int64, error) { @@ -237,7 +244,7 @@ func (client *baseClient) Strlen(key string) (int64, error) { if err != nil { return 0, err } - return handleLongResponse(result), nil + return handleLongResponse(result) } func (client *baseClient) SetRange(key string, offset int, value string) (int64, error) { @@ -245,7 +252,7 @@ func (client *baseClient) SetRange(key string, offset int, value string) (int64, if err != nil { return 0, err } - return handleLongResponse(result), nil + return handleLongResponse(result) } func (client *baseClient) GetRange(key string, start int, end int) (string, error) { @@ -253,7 +260,8 @@ func (client *baseClient) GetRange(key string, start int, end int) (string, erro if err != nil { return "", err } - return handleStringResponse(result), nil + + return handleStringResponse(result) } func (client *baseClient) Append(key string, value string) (int64, error) { @@ -261,7 +269,8 @@ func (client *baseClient) Append(key string, value string) (int64, error) { if err != nil { return 0, err } - return handleLongResponse(result), nil + + return handleLongResponse(result) } func (client *baseClient) LCS(key1 string, key2 string) (string, error) { @@ -269,7 +278,8 @@ func (client *baseClient) LCS(key1 string, key2 string) (string, error) { if err != nil { return "", err } - return handleStringResponse(result), nil + + return handleStringResponse(result) } func (client *baseClient) GetDel(key string) (string, error) { @@ -282,5 +292,109 @@ func (client *baseClient) GetDel(key string) (string, error) { return "", err } - return handleStringOrNullResponse(result), nil + return handleStringOrNullResponse(result) +} + +func (client *baseClient) HGet(key string, field string) (string, error) { + result, err := client.executeCommand(C.HGet, []string{key, field}) + if err != nil { + return "", err + } + + return handleStringOrNullResponse(result) +} + +func (client *baseClient) HGetAll(key string) (map[string]string, error) { + result, err := client.executeCommand(C.HGetAll, []string{key}) + if err != nil { + return nil, err + } + + res, err := handleStringToStringMapResponse(result) + if err != nil { + return nil, err + } + + return res, nil +} + +func (client *baseClient) HMGet(key string, fields []string) ([]string, error) { + result, err := client.executeCommand(C.HMGet, append([]string{key}, fields...)) + if err != nil { + return []string{}, err + } + + return handleStringArrayResponse(result) +} + +func (client *baseClient) HSet(key string, values map[string]string) (int64, error) { + result, err := client.executeCommand(C.HSet, utils.ConvertMapToKeyValueStringArray(key, values)) + if err != nil { + return 0, err + } + + return handleLongResponse(result) +} + +func (client *baseClient) HSetNX(key string, field string, value string) (bool, error) { + result, err := client.executeCommand(C.HSetNX, []string{key, field, value}) + if err != nil { + return false, err + } + + return handleBooleanResponse(result) +} + +func (client *baseClient) HDel(key string, fields []string) (int64, error) { + result, err := client.executeCommand(C.HDel, append([]string{key}, fields...)) + if err != nil { + return 0, err + } + + return handleLongResponse(result) +} + +func (client *baseClient) HLen(key string) (int64, error) { + result, err := client.executeCommand(C.HLen, []string{key}) + if err != nil { + return 0, err + } + + return handleLongResponse(result) +} + +func (client *baseClient) HVals(key string) ([]string, error) { + result, err := client.executeCommand(C.HVals, []string{key}) + if err != nil { + return nil, err + } + + return handleStringArrayResponse(result) +} + +func (client *baseClient) HExists(key string, field string) (bool, error) { + result, err := client.executeCommand(C.HExists, []string{key, field}) + if err != nil { + return false, err + } + + return handleBooleanResponse(result) +} + +func (client *baseClient) HKeys(key string) ([]string, error) { + result, err := client.executeCommand(C.HKeys, []string{key}) + if err != nil { + return nil, err + } + + return handleStringArrayResponse(result) +} + +func (client *baseClient) HStrLen(key string, field string) (int64, error) { + result, err := client.executeCommand(C.HStrlen, []string{key, field}) + if err != nil { + return 0, err + } + + return handleLongResponse(result) } diff --git a/go/api/commands.go b/go/api/commands.go index 652b896e42..f873b5b96b 100644 --- a/go/api/commands.go +++ b/go/api/commands.go @@ -372,3 +372,230 @@ type StringCommands interface { //[valkey.io]: https://valkey.io/commands/getdel/ GetDel(key string) (string, error) } + +// HashCommands supports commands and transactions for the "Hash Commands" group for standalone and cluster +// clients. +// +// See [valkey.io] for details. +// +// [valkey.io]: https://valkey.io/commands/?group=hash +type HashCommands interface { + // HGet returns the value associated with field in the hash stored at key. + // + // See [valkey.io] for details. + // + // Parameters: + // key - The key of the hash. + // field - The field in the hash stored at key to retrieve from the database. + // + // Return value: + // The value associated with field, or an empty string when field is not present in the hash or key does not exist. + // + // For example: + // payload, err := client.HGet("my_hash", "field1") + // // payload equals "value" + // + // payload, err = client.HGet("my_hash", "nonexistent_field") + // // payload equals "" + // + // [valkey.io]: https://valkey.io/commands/hget/ + HGet(key string, field string) (string, error) + + // HGetAll returns all fields and values of the hash stored at key. + // + // See [valkey.io] for details. + // + // Parameters: + // key - The key of the hash. + // + // Return value: + // A map of all fields and their values in the hash, or an empty map when key does not exist. + // + // For example: + // fieldValueMap, err := client.HGetAll("my_hash") + // // fieldValueMap equals map[string]string{"field1": "value1", "field2": "value2"} + // + // [valkey.io]: https://valkey.io/commands/hgetall/ + HGetAll(key string) (map[string]string, error) + + // HMGet returns the values associated with the specified fields in the hash stored at key. + // + // See [valkey.io] for details. + // + // Parameters: + // key - The key of the hash. + // fields - The fields in the hash stored at key to retrieve from the database. + // + // Return value: + // An array of values associated with the given fields, in the same order as they are requested. + // For every field that does not exist in the hash, a null value is returned. + // If key does not exist, it is treated as an empty hash, and it returns an array of null values. + // + // For example: + // values, err := client.HMGet("my_hash", []string{"field1", "field2"}) + // // values equals []string{"value1", "value2"} + // + // [valkey.io]: https://valkey.io/commands/hmget/ + HMGet(key string, fields []string) ([]string, error) + + // HSet sets the specified fields to their respective values in the hash stored at key. + // This command overwrites the values of specified fields that exist in the hash. + // If key doesn't exist, a new key holding a hash is created. + // + // See [valkey.io] for details. + // + // Parameters: + // key - The key of the hash. + // values - A map of field-value pairs to set in the hash. + // + // Return value: + // The number of fields that were added or updated. + // + // For example: + // num, err := client.HSet("my_hash", map[string]string{"field": "value", "field2": "value2"}) + // // num equals 2 + // + // [valkey.io]: https://valkey.io/commands/hset/ + HSet(key string, values map[string]string) (int64, error) + + // HSetNX sets field in the hash stored at key to value, only if field does not yet exist. + // If key does not exist, a new key holding a hash is created. + // If field already exists, this operation has no effect. + // + // See [valkey.io] for details. + // + // Parameters: + // key - The key of the hash. + // field - The field to set. + // value - The value to set. + // + // Return value: + // true if field is a new field in the hash and value was set. + // false if field already exists in the hash and no operation was performed. + // + // For example: + // payload1, err := client.HSetNX("myHash", "field", "value") + // // payload1 equals true + // + // payload2, err := client.HSetNX("myHash", "field", "newValue") + // // payload2 equals false + // + // [valkey.io]: https://valkey.io/commands/hsetnx/ + HSetNX(key string, field string, value string) (bool, error) + + // HDel removes the specified fields from the hash stored at key. + // Specified fields that do not exist within this hash are ignored. + // If key does not exist, it is treated as an empty hash and this command returns 0. + // + // See [valkey.io] for details. + // + // Parameters: + // key - The key of the hash. + // fields - The fields to remove from the hash stored at key. + // + // Return value: + // The number of fields that were removed from the hash, not including specified but non-existing fields. + // + // For example: + // num, err := client.HDel("my_hash", []string{"field_1", "field_2"}) + // // num equals 2 + // + // [valkey.io]: https://valkey.io/commands/hdel/ + HDel(key string, fields []string) (int64, error) + + // HLen returns the number of fields contained in the hash stored at key. + // + // See [valkey.io] for details. + // + // Parameters: + // key - The key of the hash. + // + // Return value: + // The number of fields in the hash, or 0 when key does not exist. + // If key holds a value that is not a hash, an error is returned. + // + // For example: + // num1, err := client.HLen("myHash") + // // num1 equals 3 + // + // num2, err := client.HLen("nonExistingKey") + // // num2 equals 0 + // + // [valkey.io]: https://valkey.io/commands/hlen/ + HLen(key string) (int64, error) + + // HVals returns all values in the hash stored at key. + // + // See [valkey.io] for details. + // + // Parameters: + // key - The key of the hash. + // + // Return value: + // A slice of strings containing all the values in the hash, or an empty slice when key does not exist. + // + // For example: + // values, err := client.HVals("myHash") + // // values equals []string{"value1", "value2", "value3"} + // + // [valkey.io]: https://valkey.io/commands/hvals/ + HVals(key string) ([]string, error) + + // HExists returns if field is an existing field in the hash stored at key. + // + // See [valkey.io] for details. + // + // Parameters: + // key - The key of the hash. + // field - The field to check in the hash stored at key. + // + // Return value: + // true if the hash contains the specified field. + // false if the hash does not contain the field, or if the key does not exist. + // + // For example: + // exists, err := client.HExists("my_hash", "field1") + // // exists equals true + // + // exists, err = client.HExists("my_hash", "non_existent_field") + // // exists equals false + // + // [valkey.io]: https://valkey.io/commands/hexists/ + HExists(key string, field string) (bool, error) + + // HKeys returns all field names in the hash stored at key. + // + // See [valkey.io] for details. + // + // Parameters: + // key - The key of the hash. + // + // Return value: + // A slice of strings containing all the field names in the hash, or an empty slice when key does not exist. + // + // For example: + // names, err := client.HKeys("my_hash") + // // names equals []string{"field_1", "field_2"} + // + // [valkey.io]: https://valkey.io/commands/hkeys/ + HKeys(key string) ([]string, error) + + // HStrLen returns the string length of the value associated with field in the hash stored at key. + // If the key or the field do not exist, 0 is returned. + // + // See [valkey.io] for details. + // + // Parameters: + // key - The key of the hash. + // field - The field to get the string length of its value. + // + // Return value: + // The length of the string value associated with field, or 0 when field or key do not exist. + // + // For example: + // strlen, err := client.HStrLen("my_hash", "my_field") + // // strlen equals 10 + // + // [valkey.io]: https://valkey.io/commands/hstrlen/ + HStrLen(key string, field string) (int64, error) +} diff --git a/go/api/glide_client.go b/go/api/glide_client.go index 2c4618ab58..a62573284b 100644 --- a/go/api/glide_client.go +++ b/go/api/glide_client.go @@ -40,7 +40,8 @@ func (client *GlideClient) CustomCommand(args []string) (interface{}, error) { if err != nil { return nil, err } - return handleStringOrNullResponse(res), nil + + return handleStringOrNullResponse(res) } // Sets configuration parameters to the specified values. @@ -66,7 +67,7 @@ func (client *GlideClient) ConfigSet(parameters map[string]string) (string, erro if err != nil { return "", err } - return handleStringResponse(result), nil + return handleStringResponse(result) } // Gets the values of configuration parameters. @@ -93,5 +94,5 @@ func (client *GlideClient) ConfigGet(args []string) (map[string]string, error) { if err != nil { return nil, err } - return handleStringToStringMapResponse(res), nil + return handleStringToStringMapResponse(res) } diff --git a/go/api/response_handlers.go b/go/api/response_handlers.go index ac79df9a32..5672638da5 100644 --- a/go/api/response_handlers.go +++ b/go/api/response_handlers.go @@ -7,6 +7,7 @@ package api import "C" import ( + "errors" "unsafe" ) @@ -19,49 +20,80 @@ func convertCharArrayToString(arr *C.char, length C.long) string { return string(byteSlice) } -func handleStringResponse(response *C.struct_CommandResponse) string { +func handleStringResponse(response *C.struct_CommandResponse) (string, error) { + if response == nil { + return "", errors.New("handleStringArrayResponse: command response is nil") + } + defer C.free_command_response(response) - return convertCharArrayToString(response.string_value, response.string_value_len) + return convertCharArrayToString(response.string_value, response.string_value_len), nil } -func handleStringOrNullResponse(response *C.struct_CommandResponse) string { +func handleStringOrNullResponse(response *C.struct_CommandResponse) (string, error) { if response == nil { - return "" + return "", nil } + return handleStringResponse(response) } -func handleStringArrayResponse(response *C.struct_CommandResponse) []string { +// handleBooleanResponse converts a C struct_CommandResponse's bool_value to a Go bool. +func handleBooleanResponse(response *C.struct_CommandResponse) (bool, error) { + if response == nil { + return false, errors.New("handleBooleanResponse: command response is nil") + } + + defer C.free_command_response(response) + return bool(response.bool_value), nil +} + +func handleStringArrayResponse(response *C.struct_CommandResponse) ([]string, error) { + if response == nil { + return nil, errors.New("handleStringArrayResponse: command response is nil") + } + defer C.free_command_response(response) var slice []string for _, v := range unsafe.Slice(response.array_value, response.array_value_len) { slice = append(slice, convertCharArrayToString(v.string_value, v.string_value_len)) } - return slice -} -func handleLongResponse(response *C.struct_CommandResponse) int64 { - defer C.free_command_response(response) - return int64(response.int_value) + return slice, nil } -func handleDoubleResponse(response *C.struct_CommandResponse) float64 { +func handleLongResponse(response *C.struct_CommandResponse) (int64, error) { + if response == nil { + return 0, errors.New("handleLongResponse: command response is nil") + } + defer C.free_command_response(response) - return float64(response.float_value) + return int64(response.int_value), nil } -func handleBooleanResponse(response *C.struct_CommandResponse) bool { +func handleDoubleResponse(response *C.struct_CommandResponse) (float64, error) { + if response == nil { + return 0, errors.New("handleStringArrayResponse: command response is nil") + } + defer C.free_command_response(response) - return bool(response.bool_value) + return float64(response.float_value), nil } -func handleStringToStringMapResponse(response *C.struct_CommandResponse) map[string]string { +func handleStringToStringMapResponse(response *C.struct_CommandResponse) (map[string]string, error) { + if response == nil { + return nil, errors.New("handleStringMapResponse: command response is nil") + } + defer C.free_command_response(response) - m := make(map[string]string, response.array_value_len) - for _, v := range unsafe.Slice(response.array_value, response.array_value_len) { + + result := make(map[string]string, response.array_value_len) + values := unsafe.Slice(response.array_value, response.array_value_len) + + for _, v := range values { key := convertCharArrayToString(v.map_key.string_value, v.map_key.string_value_len) value := convertCharArrayToString(v.map_value.string_value, v.map_value.string_value_len) - m[key] = value + result[key] = value } - return m + + return result, nil } diff --git a/go/integTest/shared_commands_test.go b/go/integTest/shared_commands_test.go index c7c6b3d69f..410e3a86d4 100644 --- a/go/integTest/shared_commands_test.go +++ b/go/integTest/shared_commands_test.go @@ -521,3 +521,409 @@ func (suite *GlideTestSuite) TestGetDel_EmptyKey() { assert.Equal(suite.T(), "key is required", err.Error()) }) } + +func (suite *GlideTestSuite) TestHSet_WithExistingKey() { + suite.runWithDefaultClients(func(client api.BaseClient) { + fields := map[string]string{"field1": "value1", "field2": "value2"} + key := uuid.New().String() + + res1, err := client.HSet(key, fields) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(2), res1) + + res2, err := client.HSet(key, fields) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(0), res2) + }) +} + +func (suite *GlideTestSuite) TestHSet_byteString() { + suite.runWithDefaultClients(func(client api.BaseClient) { + fields := map[string]string{ + string([]byte{0xFF, 0x00, 0xAA}): string([]byte{0xDE, 0xAD, 0xBE, 0xEF}), + string([]byte{0x01, 0x02, 0x03, 0xFE}): string([]byte{0xCA, 0xFE, 0xBA, 0xBE}), + } + key := string([]byte{0x01, 0x02, 0x03, 0xFE}) + + res1, err := client.HSet(key, fields) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(2), res1) + + res2, err := client.HGetAll(key) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), fields, res2) + }) +} + +func (suite *GlideTestSuite) TestHSet_WithAddNewField() { + suite.runWithDefaultClients(func(client api.BaseClient) { + fields := map[string]string{"field1": "value1", "field2": "value2"} + key := uuid.New().String() + + res1, err := client.HSet(key, fields) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(2), res1) + + res2, err := client.HSet(key, fields) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(0), res2) + + fields["field3"] = "value3" + res3, err := client.HSet(key, fields) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(1), res3) + }) +} + +func (suite *GlideTestSuite) TestHGet_WithExistingKey() { + suite.runWithDefaultClients(func(client api.BaseClient) { + fields := map[string]string{"field1": "value1", "field2": "value2"} + key := uuid.NewString() + + res1, err := client.HSet(key, fields) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(2), res1) + + res2, err := client.HGet(key, "field1") + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), "value1", res2) + }) +} + +func (suite *GlideTestSuite) TestHGet_WithNotExistingKey() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.NewString() + + res1, err := client.HGet(key, "field1") + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), "", res1) + }) +} + +func (suite *GlideTestSuite) TestHGet_WithNotExistingField() { + suite.runWithDefaultClients(func(client api.BaseClient) { + fields := map[string]string{"field1": "value1", "field2": "value2"} + key := uuid.NewString() + + res1, err := client.HSet(key, fields) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(2), res1) + + res2, err := client.HGet(key, "foo") + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), "", res2) + }) +} + +func (suite *GlideTestSuite) TestHGetAll_WithExistingKey() { + suite.runWithDefaultClients(func(client api.BaseClient) { + fields := map[string]string{"field1": "value1", "field2": "value2"} + key := uuid.NewString() + + res1, err := client.HSet(key, fields) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(2), res1) + + res2, err := client.HGetAll(key) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), fields, res2) + }) +} + +func (suite *GlideTestSuite) TestHGetAll_WithNotExistingKey() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.NewString() + + res, err := client.HGetAll(key) + assert.Nil(suite.T(), err) + assert.Empty(suite.T(), res) + }) +} + +func (suite *GlideTestSuite) TestHMGet() { + suite.runWithDefaultClients(func(client api.BaseClient) { + fields := map[string]string{"field1": "value1", "field2": "value2"} + key := uuid.NewString() + + res1, err := client.HSet(key, fields) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(2), res1) + + res2, err := client.HMGet(key, []string{"field1", "field2", "field3"}) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), []string{"value1", "value2", ""}, res2) + }) +} + +func (suite *GlideTestSuite) TestHMGet_WithNotExistingKey() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.NewString() + + res, err := client.HMGet(key, []string{"field1", "field2", "field3"}) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), []string{"", "", ""}, res) + }) +} + +func (suite *GlideTestSuite) TestHMGet_WithNotExistingField() { + suite.runWithDefaultClients(func(client api.BaseClient) { + fields := map[string]string{"field1": "value1", "field2": "value2"} + key := uuid.NewString() + + res1, err := client.HSet(key, fields) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(2), res1) + + res2, err := client.HMGet(key, []string{"field3", "field4"}) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), []string{"", ""}, res2) + }) +} + +func (suite *GlideTestSuite) TestHSetNX_WithExistingKey() { + suite.runWithDefaultClients(func(client api.BaseClient) { + fields := map[string]string{"field1": "value1", "field2": "value2"} + key := uuid.NewString() + + res1, err := client.HSet(key, fields) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(2), res1) + + res2, err := client.HSetNX(key, "field1", "value1") + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), false, res2) + }) +} + +func (suite *GlideTestSuite) TestHSetNX_WithNotExistingKey() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.NewString() + + res1, err := client.HSetNX(key, "field1", "value1") + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), true, res1) + + res2, err := client.HGetAll(key) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), map[string]string{"field1": "value1"}, res2) + }) +} + +func (suite *GlideTestSuite) TestHSetNX_WithExistingField() { + suite.runWithDefaultClients(func(client api.BaseClient) { + fields := map[string]string{"field1": "value1", "field2": "value2"} + key := uuid.NewString() + + res1, err := client.HSet(key, fields) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(2), res1) + + res2, err := client.HSetNX(key, "field1", "value1") + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), false, res2) + }) +} + +func (suite *GlideTestSuite) TestHDel() { + suite.runWithDefaultClients(func(client api.BaseClient) { + fields := map[string]string{"field1": "value1", "field2": "value2"} + key := uuid.NewString() + + res1, err := client.HSet(key, fields) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(2), res1) + + res2, err := client.HDel(key, []string{"field1", "field2"}) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(2), res2) + + res3, err := client.HGetAll(key) + assert.Nil(suite.T(), err) + assert.Empty(suite.T(), res3) + + res4, err := client.HDel(key, []string{"field1", "field2"}) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(0), res4) + }) +} + +func (suite *GlideTestSuite) TestHDel_WithNotExistingKey() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.NewString() + res, err := client.HDel(key, []string{"field1", "field2"}) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(0), res) + }) +} + +func (suite *GlideTestSuite) TestHDel_WithNotExistingField() { + suite.runWithDefaultClients(func(client api.BaseClient) { + fields := map[string]string{"field1": "value1", "field2": "value2"} + key := uuid.NewString() + + res1, err := client.HSet(key, fields) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(2), res1) + + res2, err := client.HDel(key, []string{"field3", "field4"}) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(0), res2) + }) +} + +func (suite *GlideTestSuite) TestHLen() { + suite.runWithDefaultClients(func(client api.BaseClient) { + fields := map[string]string{"field1": "value1", "field2": "value2"} + key := uuid.NewString() + + res1, err := client.HSet(key, fields) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(2), res1) + + res2, err := client.HLen(key) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(2), res2) + }) +} + +func (suite *GlideTestSuite) TestHLen_WithNotExistingKey() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.NewString() + res, err := client.HLen(key) + + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(0), res) + }) +} + +func (suite *GlideTestSuite) TestHVals_WithExistingKey() { + suite.runWithDefaultClients(func(client api.BaseClient) { + fields := map[string]string{"field1": "value1", "field2": "value2"} + key := uuid.NewString() + + res1, err := client.HSet(key, fields) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(2), res1) + + res2, err := client.HVals(key) + assert.Nil(suite.T(), err) + assert.Contains(suite.T(), res2, "value1") + assert.Contains(suite.T(), res2, "value2") + }) +} + +func (suite *GlideTestSuite) TestHVals_WithNotExistingKey() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.NewString() + + res, err := client.HVals(key) + assert.Nil(suite.T(), err) + assert.Nil(suite.T(), res) + }) +} + +func (suite *GlideTestSuite) TestHExists_WithExistingKey() { + suite.runWithDefaultClients(func(client api.BaseClient) { + fields := map[string]string{"field1": "value1", "field2": "value2"} + key := uuid.NewString() + + res1, err := client.HSet(key, fields) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(2), res1) + + res2, err := client.HExists(key, "field1") + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), true, res2) + }) +} + +func (suite *GlideTestSuite) TestHExists_WithNotExistingKey() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.NewString() + + res, err := client.HExists(key, "field1") + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), false, res) + }) +} + +func (suite *GlideTestSuite) TestHExists_WithNotExistingField() { + suite.runWithDefaultClients(func(client api.BaseClient) { + fields := map[string]string{"field1": "value1", "field2": "value2"} + key := uuid.NewString() + + res1, err := client.HSet(key, fields) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(2), res1) + + res2, err := client.HExists(key, "field3") + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), false, res2) + }) +} + +func (suite *GlideTestSuite) TestHKeys_WithExistingKey() { + suite.runWithDefaultClients(func(client api.BaseClient) { + fields := map[string]string{"field1": "value1", "field2": "value2"} + key := uuid.NewString() + + res1, err := client.HSet(key, fields) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(2), res1) + + res2, err := client.HKeys(key) + assert.Nil(suite.T(), err) + assert.Contains(suite.T(), res2, "field1") + assert.Contains(suite.T(), res2, "field2") + }) +} + +func (suite *GlideTestSuite) TestHKeys_WithNotExistingKey() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.NewString() + + res, err := client.HKeys(key) + assert.Nil(suite.T(), err) + assert.Nil(suite.T(), res) + }) +} + +func (suite *GlideTestSuite) TestHStrLen_WithExistingKey() { + suite.runWithDefaultClients(func(client api.BaseClient) { + fields := map[string]string{"field1": "value1", "field2": "value2"} + key := uuid.NewString() + + res1, err := client.HSet(key, fields) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(2), res1) + + res2, err := client.HStrLen(key, "field1") + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(6), res2) + }) +} + +func (suite *GlideTestSuite) TestHStrLen_WithNotExistingKey() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.NewString() + + res, err := client.HStrLen(key, "field1") + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(0), res) + }) +} + +func (suite *GlideTestSuite) TestHStrLen_WithNotExistingField() { + suite.runWithDefaultClients(func(client api.BaseClient) { + fields := map[string]string{"field1": "value1", "field2": "value2"} + key := uuid.NewString() + + res1, err := client.HSet(key, fields) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(2), res1) + + res2, err := client.HStrLen(key, "field3") + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(0), res2) + }) +} diff --git a/go/utils/transform_utils.go b/go/utils/transform_utils.go index 11470356dc..4427a091ee 100644 --- a/go/utils/transform_utils.go +++ b/go/utils/transform_utils.go @@ -22,6 +22,25 @@ func FloatToString(value float64) string { return strconv.FormatFloat(value, 'g', -1 /*precision*/, 64 /*bit*/) } +// ConvertMapToKeyValueStringArray converts a map of string keys and values to a slice of the initial key followed by the +// key-value pairs. +func ConvertMapToKeyValueStringArray(key string, args map[string]string) []string { + // Preallocate the slice with space for the initial key and twice the number of map entries (each entry has a key and a + // value). + values := make([]string, 1, 1+2*len(args)) + + // Set the first element of the slice to the provided key. + values[0] = key + + // Loop over each key-value pair in the map and append them to the slice. + for k, v := range args { + // Append the key and value directly to the slice. + values = append(values, k, v) + } + + return values +} + // Flattens the Map: { (key1, value1), (key2, value2), ..} to a slice { key1, value1, key2, value2, ..} func MapToString(parameter map[string]string) []string { flat := make([]string, 0, len(parameter)*2) diff --git a/go/utils/transform_utils_test.go b/go/utils/transform_utils_test.go new file mode 100644 index 0000000000..635fef7d56 --- /dev/null +++ b/go/utils/transform_utils_test.go @@ -0,0 +1,61 @@ +// Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 + +package utils + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestConvertMapToKeyValueStringArray(t *testing.T) { + // Define test cases + testCases := []struct { + name string + key string + args map[string]string + expected []string + }{ + { + name: "Test with single key-value pair", + key: "singleKey", + args: map[string]string{"key1": "value1"}, + expected: []string{"singleKey", "key1", "value1"}, + }, + { + name: "Test with multiple key-value pairs", + key: "multiKeys", + args: map[string]string{"key1": "value1", "key2": "value2", "key3": "value3"}, + expected: []string{"multiKeys", "key1", "value1", "key2", "value2", "key3", "value3"}, + }, + { + name: "Test with empty map", + key: "emptyKey", + args: map[string]string{}, + expected: []string{"emptyKey"}, + }, + } + + // Iterate through test cases. + for _, testCase := range testCases { + // Run each test case as a subtest. + t.Run(testCase.name, func(t *testing.T) { + // Call the function being tested. + actual := ConvertMapToKeyValueStringArray(testCase.key, testCase.args) + + // Check if the lengths of actual and expected slices match. + if len(actual) != len(testCase.expected) { + t.Errorf("Length mismatch. Expected %d, got %d", len(testCase.expected), len(actual)) + } + + // Check if the key is present in the actual result. + assert.Contains(t, actual, testCase.key, "The key should be present in the result") + + // Check if all key-value pairs from the input map are present in the actual result. + for k, v := range testCase.args { + assert.Contains(t, actual, k, "The key from the input map should be present in the result") + assert.Contains(t, actual, v, "The value from the input map should be present in the result") + } + }) + } +} From a5c3570120e62055aa881dffc85004a221da3e07 Mon Sep 17 00:00:00 2001 From: umit Date: Fri, 20 Sep 2024 18:15:05 +0300 Subject: [PATCH 2/2] update method comments and style Signed-off-by: umit --- go/api/base_client.go | 16 +++++++++------- go/api/commands.go | 8 +++----- go/api/response_handlers.go | 11 ++++------- 3 files changed, 16 insertions(+), 19 deletions(-) diff --git a/go/api/base_client.go b/go/api/base_client.go index 844ec92f36..0b7d9ef8e4 100644 --- a/go/api/base_client.go +++ b/go/api/base_client.go @@ -174,6 +174,7 @@ func (client *baseClient) MSet(keyValueMap map[string]string) (string, error) { if err != nil { return "", err } + return handleStringResponse(result) } @@ -200,6 +201,7 @@ func (client *baseClient) Incr(key string) (int64, error) { if err != nil { return 0, err } + return handleLongResponse(result) } @@ -208,6 +210,7 @@ func (client *baseClient) IncrBy(key string, amount int64) (int64, error) { if err != nil { return 0, err } + return handleLongResponse(result) } @@ -228,6 +231,7 @@ func (client *baseClient) Decr(key string) (int64, error) { if err != nil { return 0, err } + return handleLongResponse(result) } @@ -236,6 +240,7 @@ func (client *baseClient) DecrBy(key string, amount int64) (int64, error) { if err != nil { return 0, err } + return handleLongResponse(result) } @@ -244,6 +249,7 @@ func (client *baseClient) Strlen(key string) (int64, error) { if err != nil { return 0, err } + return handleLongResponse(result) } @@ -252,6 +258,7 @@ func (client *baseClient) SetRange(key string, offset int, value string) (int64, if err != nil { return 0, err } + return handleLongResponse(result) } @@ -310,18 +317,13 @@ func (client *baseClient) HGetAll(key string) (map[string]string, error) { return nil, err } - res, err := handleStringToStringMapResponse(result) - if err != nil { - return nil, err - } - - return res, nil + return handleStringToStringMapResponse(result) } func (client *baseClient) HMGet(key string, fields []string) ([]string, error) { result, err := client.executeCommand(C.HMGet, append([]string{key}, fields...)) if err != nil { - return []string{}, err + return nil, err } return handleStringArrayResponse(result) diff --git a/go/api/commands.go b/go/api/commands.go index f873b5b96b..56fbde418a 100644 --- a/go/api/commands.go +++ b/go/api/commands.go @@ -392,9 +392,10 @@ type HashCommands interface { // The value associated with field, or an empty string when field is not present in the hash or key does not exist. // // For example: + // Assume we have the following hash: + // my_hash := map[string]string{"field1": "value", "field2": "another_value"} // payload, err := client.HGet("my_hash", "field1") // // payload equals "value" - // // payload, err = client.HGet("my_hash", "nonexistent_field") // // payload equals "" // @@ -429,7 +430,7 @@ type HashCommands interface { // Return value: // An array of values associated with the given fields, in the same order as they are requested. // For every field that does not exist in the hash, a null value is returned. - // If key does not exist, it is treated as an empty hash, and it returns an array of null values. + // If key does not exist, returns an empty string array. // // For example: // values, err := client.HMGet("my_hash", []string{"field1", "field2"}) @@ -476,7 +477,6 @@ type HashCommands interface { // For example: // payload1, err := client.HSetNX("myHash", "field", "value") // // payload1 equals true - // // payload2, err := client.HSetNX("myHash", "field", "newValue") // // payload2 equals false // @@ -517,7 +517,6 @@ type HashCommands interface { // For example: // num1, err := client.HLen("myHash") // // num1 equals 3 - // // num2, err := client.HLen("nonExistingKey") // // num2 equals 0 // @@ -556,7 +555,6 @@ type HashCommands interface { // For example: // exists, err := client.HExists("my_hash", "field1") // // exists equals true - // // exists, err = client.HExists("my_hash", "non_existent_field") // // exists equals false // diff --git a/go/api/response_handlers.go b/go/api/response_handlers.go index 5672638da5..87e5fb5424 100644 --- a/go/api/response_handlers.go +++ b/go/api/response_handlers.go @@ -85,15 +85,12 @@ func handleStringToStringMapResponse(response *C.struct_CommandResponse) (map[st } defer C.free_command_response(response) - - result := make(map[string]string, response.array_value_len) - values := unsafe.Slice(response.array_value, response.array_value_len) - - for _, v := range values { + m := make(map[string]string, response.array_value_len) + for _, v := range unsafe.Slice(response.array_value, response.array_value_len) { key := convertCharArrayToString(v.map_key.string_value, v.map_key.string_value_len) value := convertCharArrayToString(v.map_value.string_value, v.map_value.string_value_len) - result[key] = value + m[key] = value } - return result, nil + return m, nil }