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: Implementing Hash commands #2226

Merged
merged 2 commits into from
Sep 23, 2024

Conversation

umit
Copy link
Collaborator

@umit umit commented Sep 4, 2024

Implement Valkey Hash Commands

This PR implements the following Valkey hash commands:

  • HGet: Get value of a hash field
  • HGetAll: Get all fields and values in a hash
  • HMGet: Get values of multiple hash fields
  • HSet: Set value of a hash field
  • HSetNX: Set value of a hash field if not exists
  • HDel: Delete hash field(s)
  • HLen: Get number of fields in a hash
  • HVals: Get all values in a hash
  • HExists: Check if a hash field exists
  • HKeys: Get all field names in a hash
  • HStrLen: Get string length of a hash field's value

@umit umit requested a review from a team as a code owner September 4, 2024 19:28
@umit umit force-pushed the go/hash-commands-implementation branch 3 times, most recently from 5da6299 to 184a6a4 Compare September 4, 2024 20:20
@avifenesh avifenesh added the go golang wrapper label Sep 5, 2024
Copy link
Contributor

@eifrah-aws eifrah-aws left a comment

Choose a reason for hiding this comment

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

Thanks for providing this PR!, in general it looks good

My major concerns are:

  • Some of the methods implemented are "swallowing" errors (for example: HStrLen does not return NULL by Redis, and yet we assume that null is valid and we convert it into 0 while we should return an error to the caller
  • The Rust code assumes valid strings are returned from redis/valkey, while this is not the case. Do not use String. Assume that a "string" is &[u8] or Vec<u8>

go/api/response_handlers.go Outdated Show resolved Hide resolved
go/api/base_client.go Outdated Show resolved Hide resolved
go/api/base_client.go Outdated Show resolved Hide resolved
go/api/base_client.go Outdated Show resolved Hide resolved
go/api/response_handlers.go Outdated Show resolved Hide resolved
go/api/response_handlers.go Outdated Show resolved Hide resolved
go/api/base_client.go Outdated Show resolved Hide resolved
go/src/lib.rs Outdated Show resolved Hide resolved
go/src/lib.rs Outdated Show resolved Hide resolved
@umit umit force-pushed the go/hash-commands-implementation branch 2 times, most recently from 9ed4260 to 3f9ec9d Compare September 5, 2024 16:23
go/api/commands.go Outdated Show resolved Hide resolved
go/api/commands.go Outdated Show resolved Hide resolved
go/api/commands.go Show resolved Hide resolved
// See [valkey.io] for details.
//
// For example:
//
Copy link
Collaborator

Choose a reason for hiding this comment

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

To have docs aligned and common style

Suggested change
//

Copy link
Collaborator

Choose a reason for hiding this comment

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

+1

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@janhavigupta007 I've removed.

go/api/commands.go Outdated Show resolved Hide resolved
go/api/commands.go Outdated Show resolved Hide resolved
go/integTest/shared_commands_test.go Outdated Show resolved Hide resolved
go/src/lib.rs Outdated Show resolved Hide resolved
go/utils/transform_utils_test.go Show resolved Hide resolved
go/src/lib.rs Outdated
Comment on lines 454 to 458
let (vec_ptr, len) = convert_vec_to_pointer(result);
let (vec_elements_len_ptr, _) = convert_vec_to_pointer(vec_elements_len);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please confirm that we're not leaking memory there, especially on an error

Copy link
Collaborator

Choose a reason for hiding this comment

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

+1. Also, I'm curious to know why convert_vec_to_pointer uses mem::forget.

@umit umit force-pushed the go/hash-commands-implementation branch from 70723d9 to 420f031 Compare September 5, 2024 18:38
go/api/commands.go Outdated Show resolved Hide resolved
@umit umit force-pushed the go/hash-commands-implementation branch 2 times, most recently from f476f8e to 14200f1 Compare September 5, 2024 20:04
go/src/lib.rs Outdated
if !array_value.is_null() {
let len = array_value_len as usize;
let vec = unsafe { Vec::from_raw_parts(array_value, len, len) };
let elem_len = unsafe { Vec::from_raw_parts(array_elements_len, len, len) };
Copy link
Collaborator

Choose a reason for hiding this comment

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

array_elements_len is a pointer, so it could be null here.

go/src/lib.rs Outdated Show resolved Hide resolved
go/api/response_handlers.go Outdated Show resolved Hide resolved
go/api/response_handlers.go Outdated Show resolved Hide resolved
numElements := int(response.array_value_len)
if len(lengths) != numElements {
// Handle inconsistent array lengths (just in case)
return nil
Copy link
Contributor

Choose a reason for hiding this comment

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

Return an error, don't hide it with nil

go/api/response_handlers.go Outdated Show resolved Hide resolved
go/api/base_client.go Outdated Show resolved Hide resolved
@@ -31,6 +32,12 @@ func handleStringOrNullResponse(response *C.struct_CommandResponse) string {
return handleStringResponse(response)
}

// handleBooleanResponse converts a C struct_CommandResponse's bool_value to a Go bool.
func handleBooleanResponse(response *C.struct_CommandResponse) bool {

Choose a reason for hiding this comment

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

Can we have the methods consistent please?

E.g.:

Either

  • getXXXResponse returns XXX OR
  • getXXXResponse returns XXX, err

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@ravisi21 It's done.

go/src/lib.rs Outdated
let result = map
.into_iter()
.flat_map(|(key, value)| {
let key = <Vec<u8>>::from_redis_value(&key)

Choose a reason for hiding this comment

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

This seems like reducing a redis::Value to always a string.
That may serve a specific purpose now of Map<String, String>, but I dont think that would be a right implementation.

We should try to convert the redis::Value to CommandResponse so that the generic nature is maintained.

We are working towards that as you can check in the PR: #2289

Copy link
Contributor

@eifrah-aws eifrah-aws left a comment

Choose a reason for hiding this comment

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

Hey, I did not forget about this PR. I am waiting for this PR: #2289 to be merged as it fixes basic data exchanging between Rust <-> Go

Copy link
Collaborator

@Yury-Fridlyand Yury-Fridlyand left a comment

Choose a reason for hiding this comment

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

LGTM if all other comments are addressed.

Copy link
Collaborator

@janhavigupta007 janhavigupta007 left a comment

Choose a reason for hiding this comment

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

Thank you so much for adding hash commands implementation. I am waiting on reviewing this PR to get these PRs #2303 and #2289 merged first. Please let me know if there are any questions/concerns.

Copy link
Collaborator

@janhavigupta007 janhavigupta007 left a comment

Choose a reason for hiding this comment

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

The pending PRs #2289 amd #2303 have been merged. Please rebase your changes so that we can have a review for this PR.

@umit umit force-pushed the go/hash-commands-implementation branch from ae02a9a to 632d990 Compare September 19, 2024 17:51
// See [valkey.io] for details.
//
// For example:
//
Copy link
Collaborator

Choose a reason for hiding this comment

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

+1

//
// For example:
// payload, err := client.HGet("my_hash", "field1")
// // payload equals "value"
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we also show what my_hash contains in order to be clear on how "value" is being returned.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@janhavigupta007 I've added my_hash values in the comment.

Comment on lines 431 to 432
// 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.
Copy link
Collaborator

Choose a reason for hiding this comment

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

If the value is not present, an empty string would be returned as string can't be null in GO.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@janhavigupta007 I've updated the message according to your comment.

// For example:
// num1, err := client.HLen("myHash")
// // num1 equals 3
//
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
//

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@janhavigupta007 It's done.

// For example:
// exists, err := client.HExists("my_hash", "field1")
// // exists equals true
//
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
//

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@janhavigupta007 It's done.

}

func (client *baseClient) MSetNX(keyValueMap map[string]string) (bool, error) {
result, err := client.executeCommand(C.MSetNX, utils.MapToString(keyValueMap))
if err != nil {
return false, err
}
return handleBooleanResponse(result), nil

Copy link
Collaborator

Choose a reason for hiding this comment

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

In some functions, extra spaces are added and somewhere it's not. Let's keep it consistent.

Suggested change

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@janhavigupta007 I've updated with extra spaces. The separate return statement on a new line is generally considered better practice in Go.

Copy link
Contributor

Choose a reason for hiding this comment

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

Does this something that can be handled by gofmt?

Comment on lines 88 to 96

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
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Any reason we are modifying this? Also, please retain the ordering of the functions so that it can only reflect the changes made and it will be easier for review.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@janhavigupta007 The function was updated during the rebase operation. I revert it and added null response check.

Comment on lines 313 to 318
res, err := handleStringToStringMapResponse(result)
if err != nil {
return nil, err
}

return res, nil
Copy link
Collaborator

Choose a reason for hiding this comment

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

We can directly return handleStringToStringMapResponse(result) here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@janhavigupta007 It's done.

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
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please return nil, err from here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@janhavigupta007 It's fixed.

@eifrah-aws eifrah-aws self-requested a review September 23, 2024 06:48
@@ -146,63 +147,71 @@ func (client *baseClient) Set(key string, value string) (string, error) {
if err != nil {
return "", err
}
return handleStringResponse(result), nil

return handleStringResponse(result)
Copy link
Contributor

Choose a reason for hiding this comment

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

Thank you for fixing this 👍

}

func (client *baseClient) MSetNX(keyValueMap map[string]string) (bool, error) {
result, err := client.executeCommand(C.MSetNX, utils.MapToString(keyValueMap))
if err != nil {
return false, err
}
return handleBooleanResponse(result), nil

Copy link
Contributor

Choose a reason for hiding this comment

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

Does this something that can be handled by gofmt?

@eifrah-aws eifrah-aws merged commit 634f1c8 into valkey-io:main Sep 23, 2024
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
go golang wrapper
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants