diff --git a/go/api/base_client.go b/go/api/base_client.go index 16c2f97256..d29b3ea5e2 100644 --- a/go/api/base_client.go +++ b/go/api/base_client.go @@ -11,6 +11,7 @@ import "C" import ( "errors" + "strconv" "unsafe" "github.com/valkey-io/valkey-glide/go/glide/protobuf" @@ -158,6 +159,38 @@ func (client *baseClient) Get(key string) (string, error) { return handleStringOrNullResponse(result), nil } +func (client *baseClient) MSet(keyValueMap map[string]string) (string, error) { + flat := []string{} + for key, value := range keyValueMap { + flat = append(flat, key, value) + } + result, err := client.executeCommand(C.MSet, flat) + if err != nil { + return "", err + } + return handleStringResponse(result), nil +} + +func (client *baseClient) MSetNX(keyValueMap map[string]string) (bool, error) { + flat := []string{} + for key, value := range keyValueMap { + flat = append(flat, key, value) + } + result, err := client.executeCommand(C.MSetNX, flat) + if err != nil { + return false, err + } + return handleBooleanResponse(result), nil +} + +func (client *baseClient) MGet(keys []string) ([]string, error) { + result, err := client.executeCommand(C.MGet, keys) + if err != nil { + return nil, err + } + return handleStringArrayResponse(result), nil +} + func (client *baseClient) Incr(key string) (int64, error) { result, err := client.executeCommand(C.Incr, []string{key}) if err != nil { @@ -201,6 +234,46 @@ func (client *baseClient) DecrBy(key string, amount int64) (int64, error) { return handleLongResponse(result), nil } +func (client *baseClient) Strlen(key string) (int64, error) { + result, err := client.executeCommand(C.Strlen, []string{key}) + if err != nil { + return 0, err + } + return handleLongResponse(result), nil +} + +func (client *baseClient) SetRange(key string, offset int, value string) (int64, error) { + result, err := client.executeCommand(C.SetRange, []string{key, strconv.Itoa(offset), value}) + if err != nil { + return 0, err + } + return handleLongResponse(result), nil +} + +func (client *baseClient) GetRange(key string, start int, end int) (string, error) { + result, err := client.executeCommand(C.GetRange, []string{key, strconv.Itoa(start), strconv.Itoa(end)}) + if err != nil { + return "", err + } + return handleStringResponse(result), nil +} + +func (client *baseClient) Append(key string, value string) (int64, error) { + result, err := client.executeCommand(C.Append, []string{key, value}) + if err != nil { + return 0, err + } + return handleLongResponse(result), nil +} + +func (client *baseClient) LCS(key1 string, key2 string) (string, error) { + result, err := client.executeCommand(C.LCS, []string{key1, key2}) + if err != nil { + return "", err + } + return handleStringResponse(result), nil +} + func (client *baseClient) GetDel(key string) (string, error) { if key == "" { return "", errors.New("key is required") @@ -211,5 +284,5 @@ func (client *baseClient) GetDel(key string) (string, error) { return "", err } - return handleStringOrNullResponseWithFree(result), nil + return handleStringOrNullResponse(result), nil } diff --git a/go/api/commands.go b/go/api/commands.go index a00fad5002..652b896e42 100644 --- a/go/api/commands.go +++ b/go/api/commands.go @@ -21,6 +21,7 @@ type StringCommands interface { // // For example: // result, err := client.Set("key", "value") + // result : "OK" // // [valkey.io]: https://valkey.io/commands/set/ Set(key string, value string) (string, error) @@ -43,6 +44,7 @@ type StringCommands interface { // If SetOptions.returnOldValue is set, return the old value as a String. // // For example: + // key: initialValue // result, err := client.SetWithOptions("key", "value", &api.SetOptions{ // ConditionalSet: api.OnlyIfExists, // Expiry: &api.Expiry{ @@ -50,6 +52,7 @@ type StringCommands interface { // Count: uint64(5), // }, // }) + // result: "OK" // // [valkey.io]: https://valkey.io/commands/set/ SetWithOptions(key string, value string, options *SetOptions) (string, error) @@ -65,11 +68,81 @@ type StringCommands interface { // If key exists, returns the value of key as a String. Otherwise, return (""). // // For example: - // result, err := client.Get("key") + // 1. key: value + // result, err := client.Get("key") + // result: "value" + // 2. result, err := client.Get("nonExistentKey") + // result: "" // // [valkey.io]: https://valkey.io/commands/get/ Get(key string) (string, error) + // Sets multiple keys to multiple values in a single operation. + // + // Note: + // When in cluster mode, the command may route to multiple nodes when keys in keyValueMap map to different hash slots. + // + // See [valkey.io] for details. + // + // Parameters: + // keyValueMap - A key-value map consisting of keys and their respective values to set. + // + // Return value: + // "OK" on success. + // + // For example: + // result, err := client.MSet(map[string]string{"key1": "value1", "key2": "value2"}) + // result: "OK" + // + // [valkey.io]: https://valkey.io/commands/mset/ + MSet(keyValueMap map[string]string) (string, error) + + // Retrieves the values of multiple keys. + // + // Note: + // When in cluster mode, the command may route to multiple nodes when keys map to different hash slots. + // + // See [valkey.io] for details. + // + // Parameters: + // keys - A list of keys to retrieve values for. + // + // Return value: + // An array of values corresponding to the provided keys. + // If a key is not found, its corresponding value in the list will be an empty string(""). + // + // For example: + // key1: value1, key2: value2 + // result, err := client.MGet([]string{"key1", "key2", "key3"}) + // result : {"value1", "value2", ""} + // + // [valkey.io]: https://valkey.io/commands/mget/ + MGet(keys []string) ([]string, error) + + // Sets multiple keys to values if the key does not exist. The operation is atomic, and if one or more keys already exist, + // the entire operation fails. + // + // Note: + // When in cluster mode, all keys in keyValueMap must map to the same hash slot. + // + // See [valkey.io] for details. + // + // Parameters: + // keyValueMap - A key-value map consisting of keys and their respective values to set. + // + // Return value: + // true, if all keys were set. false, if no key was set. + // + // For example: + // 1. result, err := client.MSetNX(map[string]string{"key1": "value1", "key2": "value2"}) + // result: true + // 2. key3: initialValue + // result, err := client.MSetNX(map[string]string{"key3": "value3", "key4": "value4"}) + // result: false + // + // [valkey.io]: https://valkey.io/commands/msetnx/ + MSetNX(keyValueMap map[string]string) (bool, error) + // Increments the number stored at key by one. If key does not exist, it is set to 0 before performing the operation. // // See [valkey.io] for details. @@ -81,7 +154,9 @@ type StringCommands interface { // The value of key after the increment. // // For example: + // key: 1 // result, err := client.Incr("key"); + // result: 2 // // [valkey.io]: https://valkey.io/commands/incr/ Incr(key string) (int64, error) @@ -98,7 +173,9 @@ type StringCommands interface { // The value of key after the increment. // // For example: + // key: 1 // result, err := client.IncrBy("key", 2); + // result: 3 // // [valkey.io]: https://valkey.io/commands/incrby/ IncrBy(key string, amount int64) (int64, error) @@ -117,7 +194,9 @@ type StringCommands interface { // The value of key after the increment. // // For example: + // key: 1 // result, err := client.IncrBy("key", 0.5); + // result: 1.5 // // [valkey.io]: https://valkey.io/commands/incrbyfloat/ IncrByFloat(key string, amount float64) (float64, error) @@ -133,7 +212,9 @@ type StringCommands interface { // The value of key after the decrement. // // For example: + // key: 1 // result, err := client.Decr("key"); + // result: 0 // // [valkey.io]: https://valkey.io/commands/decr/ Decr(key string) (int64, error) @@ -150,11 +231,132 @@ type StringCommands interface { // The value of key after the decrement. // // For example: - // result, err := client.Decr("key"); + // key: 1 + // result, err := client.DecrBy("key", 2); + // result: -1 // // [valkey.io]: https://valkey.io/commands/decrby/ DecrBy(key string, amount int64) (int64, error) + // Returns the length of the string value stored at key. + // + // See [valkey.io] for details. + // + // Parameters: + // key - The key to check its length. + // + // Return value: + // The length of the string value stored at key. + // If key does not exist, it is treated as an empty string, and the command returns 0. + // + // For example: + // key: value + // result, err := client.Strlen("key"); + // result: 5 + // + // [valkey.io]: https://valkey.io/commands/strlen/ + Strlen(key string) (int64, error) + + // Overwrites part of the string stored at key, starting at the specified byte's offset, for the entire length of value. + // If the offset is larger than the current length of the string at key, the string is padded with zero bytes to make + // offset fit. + // Creates the key if it doesn't exist. + // + // See [valkey.io] for details. + // + // Parameters: + // key - The key of the string to update. + // offset - The position in the string where value should be written. + // value - The string written with offset. + // + // Return value: + // The length of the string stored at key after it was modified. + // + // For example: + // 1. result, err := client.SetRange("key", 6, "GLIDE"); + // result: 11 (New key created with length of 11 bytes) + // value, err := client.Get("key") + // value: "\x00\x00\x00\x00\x00\x00GLIDE" + // 2. "key": "愛" (value char takes 3 bytes) + // result, err = client.SetRange("key", 1, "a") + // "key": �a� // (becomes an invalid UTF-8 string) + // + // [valkey.io]: https://valkey.io/commands/setrange/ + SetRange(key string, offset int, value string) (int64, error) + + // Returns the substring of the string value stored at key, determined by the byte's offsets start and end (both are + // inclusive). + // Negative offsets can be used in order to provide an offset starting from the end of the string. So -1 means the last + // character, -2 the penultimate and so forth. + // + // See [valkey.io] for details. + // + // Parameters: + // key - The key of the string. + // start - The starting offset. + // end - The ending offset. + // + // Return value: + // A substring extracted from the value stored at key. + // An ("") empty string is returned if the offset is out of bounds. + // + // For example: + // 1. mykey: "This is a string" + // result, err := client.GetRange("mykey", 0, 3); + // result: "This" + // result, err := client.GetRange("mykey", -3, -1); + // result: "ing" (extracted last 3 characters of a string) + // 2. "key": "愛" (value char takes 3 bytes) + // result, err = client.GetRange("key", 0, 1) + // result: � // (returns an invalid UTF-8 string) + // + // [valkey.io]: https://valkey.io/commands/getrange/ + GetRange(key string, start int, end int) (string, error) + + // Appends a value to a key. If key does not exist it is created and set as an empty string, so APPEND will be similar to + // SET in this special case. + // + // See [valkey.io] for details. + // + // Parameters: + // key - The key of the string. + // value - The value to append. + // + // Return value: + // The length of the string after appending the value. + // + // For example: + // result, err := client.Append("key", "value"); + // result: 5 + // + // [valkey.io]: https://valkey.io/commands/append/ + Append(key string, value string) (int64, error) + + // Returns the longest common subsequence between strings stored at key1 and key2. + // + // Since: + // Valkey 7.0 and above. + // + // Note: + // When in cluster mode, key1 and key2 must map to the same hash slot. + // + // See [valkey.io] for details. + // + // Parameters: + // key1 - The key that stores the first string. + // key2 - The key that stores the second string. + // + // Return value: + // A String containing the longest common subsequence between the 2 strings. + // An empty String is returned if the keys do not exist or have no common subsequences. + // + // For example: + // testKey1: foo, testKey2: fao + // result, err := client.LCS("testKey1", "testKey2"); + // result: "fo" + // + // [valkey.io]: https://valkey.io/commands/lcs/ + LCS(key1 string, key2 string) (string, error) // GetDel gets the value associated with the given key and deletes the key. // // Parameters: diff --git a/go/api/response_handlers.go b/go/api/response_handlers.go index eae4a59dc7..f6ecfe2ac7 100644 --- a/go/api/response_handlers.go +++ b/go/api/response_handlers.go @@ -31,13 +31,15 @@ func handleStringOrNullResponse(response *C.struct_CommandResponse) string { return handleStringResponse(response) } -func handleStringOrNullResponseWithFree(response *C.struct_CommandResponse) string { - if response == nil { - return "" - } +func handleStringArrayResponse(response *C.struct_CommandResponse) []string { defer C.free_command_response(response) - - return convertCharArrayToString(response.string_value, response.string_value_len) + var len []C.long + len = append(len, unsafe.Slice(response.array_elements_len, response.array_value_len)...) + var slice []string + for k, v := range unsafe.Slice(response.array_value, response.array_value_len) { + slice = append(slice, convertCharArrayToString(v, len[k])) + } + return slice } func handleLongResponse(response *C.struct_CommandResponse) int64 { @@ -49,3 +51,8 @@ func handleDoubleResponse(response *C.struct_CommandResponse) float64 { defer C.free_command_response(response) return float64(response.float_value) } + +func handleBooleanResponse(response *C.struct_CommandResponse) bool { + defer C.free_command_response(response) + return bool(response.bool_value) +} diff --git a/go/integTest/shared_commands_test.go b/go/integTest/shared_commands_test.go index c74ac4e9b5..c7c6b3d69f 100644 --- a/go/integTest/shared_commands_test.go +++ b/go/integTest/shared_commands_test.go @@ -3,6 +3,7 @@ package integTest import ( + "math" "time" "github.com/google/uuid" @@ -172,6 +173,76 @@ func (suite *GlideTestSuite) TestSetWithOptions_ReturnOldValue_nonExistentKey() }) } +func (suite *GlideTestSuite) TestMSetAndMGet_existingAndNonExistingKeys() { + suite.runWithDefaultClients(func(client api.BaseClient) { + 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 := []string{value, value, ""} + result, err := client.MGet(keys) + + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), values, result) + }) +} + +func (suite *GlideTestSuite) TestMSetNXAndMGet_nonExistingKey_valuesSet() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key1 := "{key}" + uuid.New().String() + key2 := "{key}" + uuid.New().String() + key3 := "{key}" + uuid.New().String() + value := uuid.New().String() + keyValueMap := map[string]string{ + key1: value, + key2: value, + key3: value, + } + res, err := client.MSetNX(keyValueMap) + assert.Nil(suite.T(), err) + assert.True(suite.T(), res) + keys := []string{key1, key2, key3} + values := []string{value, value, value} + result, err := client.MGet(keys) + + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), values, result) + }) +} + +func (suite *GlideTestSuite) TestMSetNXAndMGet_existingKey_valuesNotUpdated() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key1 := "{key}" + uuid.New().String() + key2 := "{key}" + uuid.New().String() + key3 := "{key}" + 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, + key3: value, + } + res, err := client.MSetNX(keyValueMap) + assert.Nil(suite.T(), err) + assert.False(suite.T(), res) + keys := []string{key1, key2, key3} + values := []string{oldValue, "", ""} + result, err := client.MGet(keys) + + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), values, result) + }) +} + func (suite *GlideTestSuite) TestIncrCommands_existingKey() { suite.runWithDefaultClients(func(client api.BaseClient) { key := uuid.New().String() @@ -261,6 +332,159 @@ func (suite *GlideTestSuite) TestDecrCommands_nonExistingKey() { }) } +func (suite *GlideTestSuite) TestStrlen_existingKey() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.New().String() + value := uuid.New().String() + suite.verifyOK(client.Set(key, value)) + + res, err := client.Strlen(key) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(len(value)), res) + }) +} + +func (suite *GlideTestSuite) TestStrlen_nonExistingKey() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.New().String() + res, err := client.Strlen(key) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(0), res) + }) +} + +func (suite *GlideTestSuite) TestSetRange_existingAndNonExistingKeys() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.New().String() + res, err := client.SetRange(key, 0, "Dummy string") + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(12), res) + + res, err = client.SetRange(key, 6, "values") + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(12), res) + res1, err := client.Get(key) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), "Dummy values", res1) + + res, err = client.SetRange(key, 15, "test") + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(19), res) + res1, err = client.Get(key) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), "Dummy values\x00\x00\x00test", res1) + + res, err = client.SetRange(key, math.MaxInt32, "test") + assert.Equal(suite.T(), int64(0), res) + assert.NotNil(suite.T(), err) + assert.IsType(suite.T(), &api.RequestError{}, err) + }) +} + +func (suite *GlideTestSuite) TestSetRange_existingAndNonExistingKeys_binaryString() { + suite.runWithDefaultClients(func(client api.BaseClient) { + nonUtf8String := "Dummy \xFF string" + key := uuid.New().String() + res, err := client.SetRange(key, 0, nonUtf8String) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(14), res) + + res, err = client.SetRange(key, 6, "values ") + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(14), res) + res1, err := client.Get(key) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), "Dummy values g", res1) + + res, err = client.SetRange(key, 15, "test") + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(19), res) + res1, err = client.Get(key) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), "Dummy values g\x00test", res1) + }) +} + +func (suite *GlideTestSuite) TestGetRange_existingAndNonExistingKeys() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.New().String() + suite.verifyOK(client.Set(key, "Dummy string")) + + res, err := client.GetRange(key, 0, 4) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), "Dummy", res) + + res, err = client.GetRange(key, -6, -1) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), "string", res) + + res, err = client.GetRange(key, -1, -6) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), "", res) + + res, err = client.GetRange(key, 15, 16) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), "", res) + + nonExistingKey := uuid.New().String() + res, err = client.GetRange(nonExistingKey, 0, 5) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), "", res) + }) +} + +func (suite *GlideTestSuite) TestGetRange_binaryString() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.New().String() + nonUtf8String := "Dummy \xFF string" + suite.verifyOK(client.Set(key, nonUtf8String)) + + res, err := client.GetRange(key, 4, 6) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), "y \xFF", res) + }) +} + +func (suite *GlideTestSuite) TestAppend_existingAndNonExistingKeys() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.New().String() + value1 := uuid.New().String() + value2 := uuid.New().String() + + res, err := client.Append(key, value1) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(len(value1)), res) + res1, err := client.Get(key) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), value1, res1) + + res, err = client.Append(key, value2) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(len(value1)+len(value2)), res) + res1, err = client.Get(key) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), value1+value2, res1) + }) +} + +func (suite *GlideTestSuite) TestLCS_existingAndNonExistingKeys() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key1 := "{key}" + uuid.New().String() + key2 := "{key}" + uuid.New().String() + + res, err := client.LCS(key1, key2) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), "", res) + + suite.verifyOK(client.Set(key1, "Dummy string")) + suite.verifyOK(client.Set(key2, "Dummy value")) + + res, err = client.LCS(key1, key2) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), "Dummy ", res) + }) +} + func (suite *GlideTestSuite) TestGetDel_ExistingKey() { suite.runWithDefaultClients(func(client api.BaseClient) { key := uuid.New().String() diff --git a/go/src/lib.rs b/go/src/lib.rs index a32682c613..4a1c14c5a8 100644 --- a/go/src/lib.rs +++ b/go/src/lib.rs @@ -11,6 +11,7 @@ use glide_core::errors::RequestErrorType; use glide_core::request_type::RequestType; use glide_core::ConnectionRequest; use protobuf::Message; +use redis::FromRedisValue; use redis::{RedisResult, Value}; use std::slice::from_raw_parts; use std::{ @@ -26,7 +27,6 @@ use tokio::runtime::Runtime; /// It will have one of the value populated depending on the return type of the command. /// /// The struct is freed by the external caller by using `free_command_response` to avoid memory leaks. -/// TODO: Free the array pointer. #[repr(C)] #[derive(Derivative)] #[derivative(Debug, Default)] @@ -229,17 +229,35 @@ pub unsafe extern "C" fn free_connection_response( /// * `command_response_ptr` must be valid until `free_command_response` is called. /// * The contained `string_value` must be obtained from the `CommandResponse` returned in [`SuccessCallback`] from [`command`]. /// * The contained `string_value` must be valid until `free_command_response` is called and it must outlive the `CommandResponse` that contains it. +/// * The contained `array_elements_len` must be obtained from the `CommandResponse` returned in [`SuccessCallback`] from [`command`]. +/// * The contained `array_elements_len` must be valid until `free_command_response` is called and it must outlive the `CommandResponse` that contains it. +/// * The contained `array_value` must be obtained from the `CommandResponse` returned in [`SuccessCallback`] from [`command`]. +/// * The contained `array_value` must be valid until `free_command_response` is called and it must outlive the `CommandResponse` that contains it. #[no_mangle] pub unsafe extern "C" fn free_command_response(command_response_ptr: *mut CommandResponse) { assert!(!command_response_ptr.is_null()); let command_response = unsafe { Box::from_raw(command_response_ptr) }; let string_value = command_response.string_value; let string_value_len = command_response.string_value_len; + let array_value = command_response.array_value; + let array_value_len = command_response.array_value_len; + let array_elements_len = command_response.array_elements_len; drop(command_response); if !string_value.is_null() { let len = string_value_len as usize; unsafe { Vec::from_raw_parts(string_value, len, len) }; } + 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) }; + for i in 0..len { + if !vec[i].is_null() { + let e_len = elem_len[i] as usize; + unsafe { Vec::from_raw_parts(vec[i], e_len, e_len) }; + } + } + } } /// Frees the error_message received on a command failure. @@ -379,6 +397,37 @@ pub unsafe extern "C" fn command( command_response.float_value = num; Ok(Some(command_response)) } + Value::Boolean(boolean) => { + command_response.bool_value = boolean; + Ok(Some(command_response)) + } + Value::Array(array) => { + let len = array.len(); + let mut vec_elements_len: Vec = Vec::::with_capacity(len); + let vec = array + .iter() + .map(|value| { + if *value == Value::Nil { + vec_elements_len.push(0); + return std::ptr::null_mut(); + } + let res = >::from_redis_value(value) + .expect("Couldn't read bytes from RedisValue") + .into_iter() + .map(|b| b as c_char) + .collect::>(); + let (res_ptr, res_len) = convert_vec_to_pointer(res); + vec_elements_len.push(res_len); + res_ptr + }) + .collect::>(); + let (vec_ptr, len) = convert_vec_to_pointer(vec); + let (vec_elements_len_ptr, _) = convert_vec_to_pointer(vec_elements_len); + command_response.array_value = vec_ptr; + command_response.array_value_len = len; + command_response.array_elements_len = vec_elements_len_ptr; + Ok(Some(command_response)) + } // TODO: Add support for other return types. _ => todo!(), }; diff --git a/go/utils/transform_utils.go b/go/utils/transform_utils.go index f19917cc00..fa58badab7 100644 --- a/go/utils/transform_utils.go +++ b/go/utils/transform_utils.go @@ -1,3 +1,5 @@ +// Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 + package utils import (