Skip to content

Commit 18561f9

Browse files
oshadmirafiegkorland
authored
Add get_redis_version command (#184)
* WIP Add get_redis_command * Fix test for test_helper example * Fixes per code review by Gavrie * Fixes per code review by Gavrie (addition) * fix build and format * Update Makefile - use a single cargo build command Co-authored-by: Guy Korland <[email protected]> * Update build.sh - use a single cargo build command Co-authored-by: Guy Korland <[email protected]> Co-authored-by: Rafi Einstein <[email protected]> Co-authored-by: Guy Korland <[email protected]>
1 parent a61775e commit 18561f9

File tree

10 files changed

+170
-17
lines changed

10 files changed

+170
-17
lines changed

Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ name = "events"
5151
crate-type = ["cdylib"]
5252
required-features = ["experimental-api"]
5353

54+
[[example]]
55+
name = "test_helper"
56+
crate-type = ["cdylib"]
57+
required-features = ["test"]
58+
5459
[[example]]
5560
name = "info"
5661
crate-type = ["cdylib"]
@@ -61,6 +66,7 @@ bitflags = "1.2"
6166
libc = "0.2"
6267
enum-primitive-derive = "^0.1"
6368
num-traits = "^0.2"
69+
regex = "1"
6470
strum_macros = "0.23"
6571
#failure = "0.1"
6672
backtrace = "0.3"

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ RUST_SOEXT.freebsd=so
6464
RUST_SOEXT.macos=dylib
6565

6666
build:
67-
cargo build --features experimental-api --all --all-targets $(CARGO_FLAGS)
67+
cargo build --features experimental-api,test --all --all-targets $(CARGO_FLAGS)
6868
# cp $(TARGET_DIR)/librejson.$(RUST_SOEXT.$(OS)) $(TARGET)
6969

7070
clean:

build.sh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
#!/usr/bin/env sh
2-
cargo build --features experimental-api --all --all-targets
2+
cargo build --features experimental-api,test --all --all-targets
3+
34

examples/test_helper.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#[macro_use]
2+
extern crate redis_module;
3+
4+
use redis_module::{Context, RedisResult, RedisString};
5+
6+
fn test_helper_version(ctx: &Context, _args: Vec<RedisString>) -> RedisResult {
7+
let ver = ctx.get_redis_version()?;
8+
let response: Vec<i64> = vec![ver.major.into(), ver.minor.into(), ver.patch.into()];
9+
10+
Ok(response.into())
11+
}
12+
13+
#[cfg(feature = "test")]
14+
fn test_helper_version_rm_call(ctx: &Context, _args: Vec<RedisString>) -> RedisResult {
15+
let ver = ctx.get_redis_version_rm_call()?;
16+
let response: Vec<i64> = vec![ver.major.into(), ver.minor.into(), ver.patch.into()];
17+
18+
Ok(response.into())
19+
}
20+
21+
//////////////////////////////////////////////////////
22+
23+
#[cfg(not(feature = "test"))]
24+
redis_module! {
25+
name: "test_helper",
26+
version: 1,
27+
data_types: [],
28+
commands: [
29+
["test_helper.version", test_helper_version, "", 0, 0, 0],
30+
],
31+
}
32+
33+
#[cfg(feature = "test")]
34+
redis_module! {
35+
name: "test_helper",
36+
version: 1,
37+
data_types: [],
38+
commands: [
39+
["test_helper.version", test_helper_version, "", 0, 0, 0],
40+
["test_helper._version_rm_call", test_helper_version_rm_call, "", 0, 0, 0],
41+
],
42+
}

src/context/mod.rs

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ use std::os::raw::{c_char, c_int, c_long, c_longlong};
33
use std::ptr;
44

55
use crate::key::{RedisKey, RedisKeyWritable};
6-
use crate::raw::ModuleOptions;
7-
use crate::{add_info_field_long_long, add_info_field_str, raw, Status};
6+
use crate::raw::{ModuleOptions, Version};
7+
use crate::{add_info_field_long_long, add_info_field_str, raw, utils, Status};
88
use crate::{add_info_section, LogLevel};
99
use crate::{RedisError, RedisResult, RedisString, RedisValue};
1010

@@ -282,6 +282,45 @@ impl Context {
282282
raw::notify_keyspace_event(self.ctx, event_type, event, keyname)
283283
}
284284

285+
/// Returns the redis version either by calling RedisModule_GetServerVersion API,
286+
/// Or if it is not available, by calling "info server" API and parsing the reply
287+
pub fn get_redis_version(&self) -> Result<Version, RedisError> {
288+
self.get_redis_version_internal(false)
289+
}
290+
291+
/// Returns the redis version by calling "info server" API and parsing the reply
292+
#[cfg(feature = "test")]
293+
pub fn get_redis_version_rm_call(&self) -> Result<Version, RedisError> {
294+
self.get_redis_version_internal(true)
295+
}
296+
297+
#[allow(clippy::not_unsafe_ptr_arg_deref)]
298+
fn get_redis_version_internal(&self, force_use_rm_call: bool) -> Result<Version, RedisError> {
299+
match unsafe { raw::RedisModule_GetServerVersion } {
300+
Some(api) if !force_use_rm_call => {
301+
// Call existing API
302+
Ok(Version::from(unsafe { api() }))
303+
}
304+
_ => {
305+
// Call "info server"
306+
if let Ok(RedisValue::SimpleString(s)) = self.call("info", &["server"]) {
307+
if let Some(ver) = utils::get_regexp_captures(
308+
s.as_str(),
309+
r"(?m)\bredis_version:([0-9]+)\.([0-9]+)\.([0-9]+)\b",
310+
) {
311+
return Ok(Version {
312+
major: ver[0].parse::<c_int>().unwrap(),
313+
minor: ver[1].parse::<c_int>().unwrap(),
314+
patch: ver[2].parse::<c_int>().unwrap(),
315+
});
316+
}
317+
}
318+
Err(RedisError::Str(
319+
"Error getting redis_version from \"info server\" call",
320+
))
321+
}
322+
}
323+
}
285324
pub fn set_module_options(&self, options: ModuleOptions) {
286325
unsafe { raw::RedisModule_SetModuleOptions.unwrap()(self.ctx, options.bits()) };
287326
}

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ mod context;
2121
pub mod key;
2222
pub mod logging;
2323
mod macros;
24+
mod utils;
2425

2526
#[cfg(feature = "experimental-api")]
2627
pub use crate::context::blocked::BlockedClient;

src/raw.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -643,6 +643,24 @@ pub fn get_keyspace_events() -> NotifyEvent {
643643
}
644644
}
645645

646+
#[derive(Debug, PartialEq)]
647+
pub struct Version {
648+
pub major: i32,
649+
pub minor: i32,
650+
pub patch: i32,
651+
}
652+
653+
impl From<c_int> for Version {
654+
fn from(ver: c_int) -> Self {
655+
// Expected format: 0x00MMmmpp for Major, minor, patch
656+
Version {
657+
major: (ver & 0x00FF_0000) >> 16,
658+
minor: (ver & 0x0000_FF00) >> 8,
659+
patch: (ver & 0x0000_00FF),
660+
}
661+
}
662+
}
663+
646664
#[allow(clippy::not_unsafe_ptr_arg_deref)]
647665
pub fn is_io_error(rdb: *mut RedisModuleIO) -> bool {
648666
unsafe { RedisModule_IsIOError.unwrap()(rdb) != 0 }

src/utils.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
use regex::Regex;
2+
3+
/// Extracts regexp captures
4+
///
5+
/// Extract from `s` the captures defined in `reg_exp`
6+
pub fn get_regexp_captures<'a, 'b>(s: &'a str, reg_exp: &'b str) -> Option<Vec<&'a str>> {
7+
Regex::new(reg_exp).map_or_else(
8+
|_| None,
9+
|re| {
10+
let mut res: Vec<&str> = Vec::new();
11+
re.captures_iter(s).for_each(|captures| {
12+
for i in 1..captures.len() {
13+
res.push(captures.get(i).map_or_else(|| "", |m| m.as_str()));
14+
}
15+
});
16+
Some(res)
17+
},
18+
)
19+
}

tests/integration.rs

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
1-
mod utils;
2-
31
use anyhow::Context;
42
use anyhow::Result;
53
use redis::RedisError;
4+
65
use utils::{get_redis_connection, start_redis_server_with_module};
76

7+
mod utils;
8+
89
#[test]
910
fn test_hello() -> Result<()> {
10-
let _guards = vec![start_redis_server_with_module("hello", 6479)
11+
let port: u16 = 6479;
12+
let _guards = vec![start_redis_server_with_module("hello", port)
1113
.with_context(|| "failed to start redis server")?];
1214
let mut con =
13-
get_redis_connection(6479).with_context(|| "failed to connect to redis server")?;
15+
get_redis_connection(port).with_context(|| "failed to connect to redis server")?;
1416

1517
let res: Vec<i32> = redis::cmd("hello.mul")
1618
.arg(&[3, 4])
@@ -29,10 +31,11 @@ fn test_hello() -> Result<()> {
2931

3032
#[test]
3133
fn test_keys_pos() -> Result<()> {
32-
let _guards = vec![start_redis_server_with_module("keys_pos", 6480)
34+
let port: u16 = 6480;
35+
let _guards = vec![start_redis_server_with_module("keys_pos", port)
3336
.with_context(|| "failed to start redis server")?];
3437
let mut con =
35-
get_redis_connection(6480).with_context(|| "failed to connect to redis server")?;
38+
get_redis_connection(port).with_context(|| "failed to connect to redis server")?;
3639

3740
let res: Vec<String> = redis::cmd("keys_pos")
3841
.arg(&["a", "1", "b", "2"])
@@ -43,23 +46,46 @@ fn test_keys_pos() -> Result<()> {
4346
let res: Result<Vec<String>, RedisError> =
4447
redis::cmd("keys_pos").arg(&["a", "1", "b"]).query(&mut con);
4548
if res.is_ok() {
46-
return Err(anyhow::Error::msg("Shuold return an error"));
49+
return Err(anyhow::Error::msg("Should return an error"));
4750
}
4851

4952
Ok(())
5053
}
5154

55+
#[test]
56+
fn test_test_helper_version() -> Result<()> {
57+
let port: u16 = 6481;
58+
let _guards = vec![start_redis_server_with_module("test_helper", port)
59+
.with_context(|| "failed to start redis server")?];
60+
let mut con =
61+
get_redis_connection(port).with_context(|| "failed to connect to redis server")?;
62+
63+
let res: Vec<i64> = redis::cmd("test_helper.version")
64+
.query(&mut con)
65+
.with_context(|| "failed to run test_helper.version")?;
66+
assert!(res[0] > 0);
67+
68+
// Test also an internal implementation that might not always be reached
69+
let res2: Vec<i64> = redis::cmd("test_helper._version_rm_call")
70+
.query(&mut con)
71+
.with_context(|| "failed to run test_helper._version_rm_call")?;
72+
assert_eq!(res, res2);
73+
74+
Ok(())
75+
}
76+
5277
#[test]
5378
fn test_hello_info() -> Result<()> {
54-
let _guards = vec![start_redis_server_with_module("hello", 6481)
79+
let port: u16 = 6482;
80+
let _guards = vec![start_redis_server_with_module("hello", port)
5581
.with_context(|| "failed to start redis server")?];
5682
let mut con =
57-
get_redis_connection(6481).with_context(|| "failed to connect to redis server")?;
83+
get_redis_connection(port).with_context(|| "failed to connect to redis server")?;
5884

5985
let res: String = redis::cmd("INFO")
6086
.arg("HELLO")
6187
.query(&mut con)
62-
.with_context(|| "failed to run hello.mul")?;
88+
.with_context(|| "failed to run INFO HELLO")?;
6389
assert!(res.contains("hello_field:hello_value"));
6490

6591
Ok(())
@@ -68,10 +94,11 @@ fn test_hello_info() -> Result<()> {
6894
#[allow(unused_must_use)]
6995
#[test]
7096
fn test_hello_err() -> Result<()> {
71-
let _guards = vec![start_redis_server_with_module("hello", 6482)
97+
let port: u16 = 6483;
98+
let _guards = vec![start_redis_server_with_module("hello", port)
7299
.with_context(|| "failed to start redis server")?];
73100
let mut con =
74-
get_redis_connection(6482).with_context(|| "failed to connect to redis server")?;
101+
get_redis_connection(port).with_context(|| "failed to connect to redis server")?;
75102

76103
// Make sure embedded nulls do not cause a crash
77104
redis::cmd("hello.err")

tests/utils.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ pub fn start_redis_server_with_module(module_name: &str, port: u16) -> Result<Ch
6969
}
7070

7171
// Get connection to Redis
72-
pub fn get_redis_connection(port: u64) -> Result<Connection> {
72+
pub fn get_redis_connection(port: u16) -> Result<Connection> {
7373
let client = redis::Client::open(format!("redis://127.0.0.1:{}/", port))?;
7474
loop {
7575
let res = client.get_connection();

0 commit comments

Comments
 (0)