-
Notifications
You must be signed in to change notification settings - Fork 53
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
Java: Add FUNCTION LIST
command.
#1452
Merged
Yury-Fridlyand
merged 15 commits into
valkey-io:main
from
Bit-Quill:java/integ_yuryf_flist
Jun 5, 2024
Merged
Changes from 4 commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
2f672a6
Add `FUNCTION LOAD` command.
Yury-Fridlyand ce72b3b
Fix IT on redis 6.
Yury-Fridlyand a0ff9be
Add `FUNCTION LIST` command. (#277)
Yury-Fridlyand 93c4a18
Merge remote-tracking branch 'upstream/main' into java/integ_yuryf_flist
Yury-Fridlyand f58cf63
Apply suggestions from code review
Yury-Fridlyand 3bd823e
Address PR comments.
Yury-Fridlyand b0f78cb
Address PR comments.
Yury-Fridlyand 90e1c2a
Merge remote-tracking branch 'upstream/main' into java/integ_yuryf_flist
Yury-Fridlyand 9af9d4f
Fix rust code docs.
Yury-Fridlyand d248ff1
Merge function signatures by adding `withCode` argument.
Yury-Fridlyand 3cc2446
Rework response conversion recursion.
Yury-Fridlyand fe7d2b7
Address PR comments.
Yury-Fridlyand a855672
Update rust docs.
Yury-Fridlyand f9e0450
Merge remote-tracking branch 'upstream/main' into java/integ_yuryf_flist
Yury-Fridlyand 1da0ca0
Fix merge error.
Yury-Fridlyand File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,7 +5,7 @@ use redis::{ | |
cluster_routing::Routable, from_owned_redis_value, Cmd, ErrorKind, RedisResult, Value, | ||
}; | ||
|
||
#[derive(Clone, Copy)] | ||
#[derive(Clone, Copy, Debug)] | ||
pub(crate) enum ExpectedReturnType { | ||
Map, | ||
MapOfStringToDouble, | ||
|
@@ -20,6 +20,9 @@ pub(crate) enum ExpectedReturnType { | |
ArrayOfDoubleOrNull, | ||
Lolwut, | ||
ArrayOfArraysOfDoubleOrNull, | ||
ArrayOfMapsRecursive, | ||
ArrayOfMaps, | ||
StringOrSet, | ||
ArrayOfPairs, | ||
ArrayOfMemberScorePairs, | ||
ZMPopReturnType, | ||
|
@@ -346,6 +349,66 @@ pub(crate) fn convert_to_expected_type( | |
) | ||
.into()), | ||
}, | ||
// `FUNCTION LIST` returns an array of maps with nested list of maps. | ||
// In RESP2 these maps are represented by arrays - we're going to convert them. | ||
/* RESP2 response | ||
1) 1) "library_name" | ||
2) "mylib" | ||
3) "engine" | ||
4) "LUA" | ||
5) "functions" | ||
6) 1) 1) "name" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is this a set? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
2) "myfunc2" | ||
3) "description" | ||
4) (nil) | ||
5) "flags" | ||
6) (empty array) | ||
2) 1) "name" | ||
... | ||
2) 1) "library_name" | ||
... | ||
|
||
RESP3 response | ||
1) 1# "library_name" => "mylib" | ||
2# "engine" => "LUA" | ||
3# "functions" => | ||
1) 1# "name" => "myfunc2" | ||
2# "description" => (nil) | ||
3# "flags" => (empty set) | ||
2) 1# "name" => "myfunc" | ||
... | ||
2) 1# "library_name" => "mylib1" | ||
... | ||
*/ | ||
ExpectedReturnType::ArrayOfMapsRecursive => match value { | ||
// empty array, or it is already contains a map (RESP3 response) - no conversion needed | ||
Value::Array(ref array) if array.is_empty() || matches!(array[0], Value::Map(_)) => { | ||
Yury-Fridlyand marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Ok(value) | ||
} | ||
Value::Array(array) => { | ||
convert_array_of_flat_maps(array, Some(ExpectedReturnType::ArrayOfMaps)) | ||
} | ||
_ => Err((ErrorKind::TypeError, "Response couldn't be converted").into()), | ||
Yury-Fridlyand marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}, | ||
// Not used for a command, but used as a helper for `ArrayOfMapsRecursive` to process the inner map. | ||
ExpectedReturnType::ArrayOfMaps => match value { | ||
Value::Array(array) => { | ||
convert_array_of_flat_maps(array, Some(ExpectedReturnType::StringOrSet)) | ||
} | ||
// Due to recursion, it would be called to convert every map value, including simple strings, do nothing with them | ||
Yury-Fridlyand marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Value::BulkString(_) | Value::SimpleString(_) | Value::VerbatimString { .. } => { | ||
Ok(value) | ||
} | ||
_ => Err((ErrorKind::TypeError, "Response couldn't be converted").into()), | ||
}, | ||
// Not used for a command, but used as a helper for `ArrayOfMaps` to process the inner map. | ||
// It may contain a string (name, description) or set (flags), or nil (description). | ||
// The set is stored as array. See comment for `ArrayOfMapsRecursive`. | ||
ExpectedReturnType::StringOrSet => match value { | ||
Value::Nil => Ok(value), | ||
Value::Array(_) => convert_to_expected_type(value, Some(ExpectedReturnType::Set)), | ||
_ => convert_to_expected_type(value, Some(ExpectedReturnType::BulkString)), | ||
Yury-Fridlyand marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}, | ||
} | ||
} | ||
|
||
|
@@ -378,6 +441,48 @@ fn convert_array_elements( | |
Ok(Value::Array(converted_array)) | ||
} | ||
|
||
/// Converts an array of flatten maps into an array of maps. | ||
Yury-Fridlyand marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// Input: | ||
/// ```text | ||
/// 1) 1) "map 1 key 1" | ||
/// 2) "map 1 value 1" | ||
/// 3) "map 1 key 2" | ||
/// 4) "map 1 value 2" | ||
/// ... | ||
/// 1) 1) "map 2 key 1" | ||
Yury-Fridlyand marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// 2) "map 2 value 1" | ||
/// ... | ||
/// ``` | ||
/// Output: | ||
/// ```text | ||
/// 1) 1# "map 1 key 1" => "map 1 value 1" | ||
/// 2# "map 1 key 2" => "map 1 value 2" | ||
/// ... | ||
/// 2) 1# "map 2 key 1" => "map 2 value 1" | ||
/// ... | ||
/// ``` | ||
/// | ||
/// `array` is an input. | ||
/// `value_expected_return_type` is the type of the values stored in the maps to convert to. | ||
Yury-Fridlyand marked this conversation as resolved.
Show resolved
Hide resolved
|
||
fn convert_array_of_flat_maps( | ||
array: Vec<Value>, | ||
value_expected_return_type: Option<ExpectedReturnType>, | ||
) -> RedisResult<Value> { | ||
let mut result: Vec<Value> = Vec::with_capacity(array.len()); | ||
for entry in array { | ||
let Value::Array(entry_as_array) = entry else { | ||
return Err((ErrorKind::TypeError, "Incorrect value type received").into()); | ||
}; | ||
let map = convert_array_to_map( | ||
entry_as_array, | ||
Some(ExpectedReturnType::BulkString), | ||
value_expected_return_type, | ||
)?; | ||
result.push(map); | ||
} | ||
Ok(Value::Array(result)) | ||
} | ||
|
||
fn convert_array_to_map( | ||
array: Vec<Value>, | ||
key_expected_return_type: Option<ExpectedReturnType>, | ||
|
@@ -532,6 +637,7 @@ pub(crate) fn expected_type_for_cmd(cmd: &Cmd) -> Option<ExpectedReturnType> { | |
} | ||
} | ||
b"LOLWUT" => Some(ExpectedReturnType::Lolwut), | ||
b"FUNCTION LIST" => Some(ExpectedReturnType::ArrayOfMapsRecursive), | ||
_ => None, | ||
} | ||
} | ||
|
@@ -540,6 +646,140 @@ pub(crate) fn expected_type_for_cmd(cmd: &Cmd) -> Option<ExpectedReturnType> { | |
mod tests { | ||
use super::*; | ||
|
||
#[test] | ||
fn convert_function_list() { | ||
assert!(matches!( | ||
expected_type_for_cmd(redis::cmd("FUNCTION").arg("LIST")), | ||
Some(ExpectedReturnType::ArrayOfMapsRecursive) | ||
)); | ||
|
||
let resp2_response = Value::Array(vec![ | ||
Value::Array(vec![ | ||
Value::BulkString("library_name".to_string().into_bytes()), | ||
Value::BulkString("mylib1".to_string().into_bytes()), | ||
Value::BulkString("engine".to_string().into_bytes()), | ||
Value::BulkString("LUA".to_string().into_bytes()), | ||
Value::BulkString("functions".to_string().into_bytes()), | ||
Value::Array(vec![ | ||
Value::Array(vec![ | ||
Value::BulkString("name".to_string().into_bytes()), | ||
Value::BulkString("myfunc1".to_string().into_bytes()), | ||
Value::BulkString("description".to_string().into_bytes()), | ||
Value::Nil, | ||
Value::BulkString("flags".to_string().into_bytes()), | ||
Value::Array(vec![ | ||
Value::BulkString("read".to_string().into_bytes()), | ||
Value::BulkString("write".to_string().into_bytes()), | ||
]), | ||
]), | ||
Value::Array(vec![ | ||
Value::BulkString("name".to_string().into_bytes()), | ||
Value::BulkString("myfunc2".to_string().into_bytes()), | ||
Value::BulkString("description".to_string().into_bytes()), | ||
Value::BulkString("blahblah".to_string().into_bytes()), | ||
Value::BulkString("flags".to_string().into_bytes()), | ||
Value::Array(vec![]), | ||
]), | ||
]), | ||
]), | ||
Value::Array(vec![ | ||
Value::BulkString("library_name".to_string().into_bytes()), | ||
Value::BulkString("mylib2".to_string().into_bytes()), | ||
Value::BulkString("engine".to_string().into_bytes()), | ||
Value::BulkString("LUA".to_string().into_bytes()), | ||
Value::BulkString("functions".to_string().into_bytes()), | ||
Value::Array(vec![]), | ||
Value::BulkString("library_code".to_string().into_bytes()), | ||
Value::BulkString("<code>".to_string().into_bytes()), | ||
]), | ||
]); | ||
|
||
let resp3_response = Value::Array(vec![ | ||
Value::Map(vec![ | ||
( | ||
Value::BulkString("library_name".to_string().into_bytes()), | ||
Value::BulkString("mylib1".to_string().into_bytes()), | ||
), | ||
( | ||
Value::BulkString("engine".to_string().into_bytes()), | ||
Value::BulkString("LUA".to_string().into_bytes()), | ||
), | ||
( | ||
Value::BulkString("functions".to_string().into_bytes()), | ||
Value::Array(vec![ | ||
Value::Map(vec![ | ||
( | ||
Value::BulkString("name".to_string().into_bytes()), | ||
Value::BulkString("myfunc1".to_string().into_bytes()), | ||
), | ||
( | ||
Value::BulkString("description".to_string().into_bytes()), | ||
Value::Nil, | ||
), | ||
( | ||
Value::BulkString("flags".to_string().into_bytes()), | ||
Value::Set(vec![ | ||
Value::BulkString("read".to_string().into_bytes()), | ||
Value::BulkString("write".to_string().into_bytes()), | ||
]), | ||
), | ||
]), | ||
Value::Map(vec![ | ||
( | ||
Value::BulkString("name".to_string().into_bytes()), | ||
Value::BulkString("myfunc2".to_string().into_bytes()), | ||
), | ||
( | ||
Value::BulkString("description".to_string().into_bytes()), | ||
Value::BulkString("blahblah".to_string().into_bytes()), | ||
), | ||
( | ||
Value::BulkString("flags".to_string().into_bytes()), | ||
Value::Set(vec![]), | ||
), | ||
]), | ||
]), | ||
), | ||
]), | ||
Value::Map(vec![ | ||
( | ||
Value::BulkString("library_name".to_string().into_bytes()), | ||
Value::BulkString("mylib2".to_string().into_bytes()), | ||
), | ||
( | ||
Value::BulkString("engine".to_string().into_bytes()), | ||
Value::BulkString("LUA".to_string().into_bytes()), | ||
), | ||
( | ||
Value::BulkString("functions".to_string().into_bytes()), | ||
Value::Array(vec![]), | ||
), | ||
( | ||
Value::BulkString("library_code".to_string().into_bytes()), | ||
Value::BulkString("<code>".to_string().into_bytes()), | ||
), | ||
]), | ||
]); | ||
|
||
assert_eq!( | ||
convert_to_expected_type( | ||
resp2_response.clone(), | ||
Some(ExpectedReturnType::ArrayOfMapsRecursive) | ||
) | ||
.unwrap(), | ||
resp3_response.clone() | ||
); | ||
|
||
assert_eq!( | ||
convert_to_expected_type( | ||
resp3_response.clone(), | ||
Some(ExpectedReturnType::ArrayOfMapsRecursive) | ||
) | ||
.unwrap(), | ||
resp3_response.clone() | ||
); | ||
} | ||
|
||
#[test] | ||
fn convert_lolwut() { | ||
assert!(matches!( | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is required for debug logging. I decided to keep it.