diff --git a/Cargo.toml b/Cargo.toml index 051ab57..ac977df 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "redis-graph" -version = "0.3.0" +version = "0.4.0" authors = ["tompro "] keywords = ["redis", "database", "graph"] description = "API for Redis graph database types." @@ -13,7 +13,7 @@ edition = "2018" exclude = ["docker"] [dependencies] -redis = { version = "0.19.0", optional = true } +redis = { version = "^0.20.0", optional = true } [features] default = ['redis'] diff --git a/Makefile.toml b/Makefile.toml new file mode 100644 index 0000000..6c8150a --- /dev/null +++ b/Makefile.toml @@ -0,0 +1,41 @@ +[tasks.publish] +description = "Publish to crates.io" +dependencies = ["gh_checks"] +command = "cargo" +args = ["publish", "--all-features"] + +[tasks.gh_checks] +dependencies = [ + "cargo_check", + "test", + "check_fmt", + "clippy" +] + +[tasks.cargo_check] +description = "Runs cargo check" +command = "cargo" +args = ["check"] + +[tasks.check_fmt] +description = "Runs fmt in check mode" +install_crate = "rustfmt" +command = "cargo" +args = ["fmt", "--all", "--", "--check"] + +[tasks.test] +description = "Runs tests with all features" +command = "cargo" +args = ["test", "--all-features"] + +[tasks.doc] +description = "Generates docs with all features" +command = "cargo" +args = ["doc", "--all-features"] + +[tasks.clippy] +description = "Runs clippy" +install_crate = "clippy" +command = "cargo" +args = ["clippy", "--", "-D warnings"] + diff --git a/README.md b/README.md index c8f3cc1..8c22305 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,25 @@ # redis_graph -[![crates.io](https://img.shields.io/badge/crates.io-v0.3.0-orange)](https://crates.io/crates/redis_graph) +[![crates.io](https://img.shields.io/badge/crates.io-v0.4.0-orange)](https://crates.io/crates/redis_graph) ![Continuous integration](https://github.com/tompro/redis_graph/workflows/Continuous%20integration/badge.svg) redis-graph provides a small trait with an extension function for the [redis](https://docs.rs/redis/) crate to allow working with redis graph data types that can be installed as a [redis module](https://oss.redislabs.com/redisgraph). -Redis graph operation are only using two top level Redis commands -(one for read/write operations and one for read-only operations), so -this crate only adds two functions to the redis commands. +Redis graph operations are mostly using two top level Redis commands +(one for read/write operations and one for read-only operations). In addition +to those there are some more maintenance oriented commands for perfomance, +configuration and clean-up which starting from v0.4.0 are also supported. The Graph commands are available in synchronous and asynchronous versions. The crate is called `redis-graph` and you can depend on it via cargo. You will -also need redis in your dependencies. +also need redis in your dependencies. This version was tested against redis 0.20.0 +but should run with versions higher than that. ```ini [dependencies] -redis = "0.19.0" -redis-graph = "*" +redis = "0.20.0" +redis-graph = "0.4.0" ``` Or via git: @@ -32,8 +34,8 @@ With async feature inherited from the [redis](https://docs.rs/redis) crate (eith ```ini [dependencies] -redis = "0.19.0" -redis-graph = { version = "0.3.0", features = ['tokio-comp'] } +redis = "0.20.0" +redis-graph = { version = "0.4.0", features = ['tokio-comp'] } ``` ## Synchronous usage diff --git a/src/async_commands.rs b/src/async_commands.rs index dd447d9..f9be1b8 100644 --- a/src/async_commands.rs +++ b/src/async_commands.rs @@ -1,6 +1,6 @@ use crate::types::*; use redis::aio::ConnectionLike; -use redis::{cmd, RedisFuture, ToRedisArgs}; +use redis::{cmd, FromRedisValue, RedisFuture, ToRedisArgs}; /// Provides a high level asynchronous API to work with Redis graph data types. /// The graph command becomes directly available on ConnectionLike types from @@ -39,6 +39,7 @@ pub trait AsyncGraphCommands: ConnectionLike + Send + Sized { .await }) } + fn graph_ro_query<'a, K: ToRedisArgs + Send + Sync + 'a, Q: ToRedisArgs + Send + Sync + 'a>( &'a mut self, key: K, @@ -52,6 +53,102 @@ pub trait AsyncGraphCommands: ConnectionLike + Send + Sized { .await }) } + + fn graph_profile< + 'a, + K: ToRedisArgs + Send + Sync + 'a, + Q: ToRedisArgs + Send + Sync + 'a, + RV: FromRedisValue, + >( + &'a mut self, + key: K, + query: Q, + ) -> RedisFuture { + Box::pin(async move { + cmd("GRAPH.PROFILE") + .arg(key) + .arg(query) + .query_async(self) + .await + }) + } + + fn graph_delete<'a, K: ToRedisArgs + Send + Sync + 'a>( + &'a mut self, + key: K, + ) -> RedisFuture { + Box::pin(async move { cmd("GRAPH.DELETE").arg(key).query_async(self).await }) + } + + fn graph_explain< + 'a, + K: ToRedisArgs + Send + Sync + 'a, + Q: ToRedisArgs + Send + Sync + 'a, + RV: FromRedisValue, + >( + &'a mut self, + key: K, + query: Q, + ) -> RedisFuture { + Box::pin(async move { + cmd("GRAPH.EXPLAIN") + .arg(key) + .arg(query) + .query_async(self) + .await + }) + } + + fn graph_slowlog<'a, K: ToRedisArgs + Send + Sync + 'a>( + &'a mut self, + key: K, + ) -> RedisFuture> { + Box::pin(async move { cmd("GRAPH.SLOWLOG").arg(key).query_async(self).await }) + } + + fn graph_config_set< + 'a, + K: ToRedisArgs + Send + Sync + 'a, + V: ToRedisArgs + Send + Sync + 'a, + >( + &'a mut self, + name: K, + value: V, + ) -> RedisFuture { + Box::pin(async move { + cmd("GRAPH.CONFIG") + .arg("SET") + .arg(name) + .arg(value) + .query_async(self) + .await + }) + } + + fn graph_config_get<'a, K: ToRedisArgs + Send + Sync + 'a, RV: FromRedisValue>( + &'a mut self, + name: K, + ) -> RedisFuture { + Box::pin(async move { + value_from_pair( + &cmd("GRAPH.CONFIG") + .arg("GET") + .arg(name) + .query_async(self) + .await?, + ) + }) + } + + fn graph_config_get_all<'a>(&'a mut self) -> RedisFuture { + Box::pin(async move { + cmd("GRAPH.CONFIG") + .arg("GET") + .arg("*") + .query_async(self) + .await + }) + } } impl AsyncGraphCommands for T where T: Send + ConnectionLike {} diff --git a/src/commands.rs b/src/commands.rs index 8c0ffd7..fdd58bd 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -1,5 +1,5 @@ use crate::types::*; -use redis::{cmd, ConnectionLike, RedisResult, ToRedisArgs}; +use redis::{cmd, ConnectionLike, FromRedisValue, RedisResult, ToRedisArgs}; /// Provides a high level synchronous API to work with Redis graph data types. /// The graph command becomes directly available on ConnectionLike types from @@ -32,6 +32,7 @@ pub trait GraphCommands: ConnectionLike + Sized { ) -> RedisResult { cmd("GRAPH.QUERY").arg(key).arg(query).query(self) } + fn graph_ro_query( &mut self, key: K, @@ -39,6 +40,50 @@ pub trait GraphCommands: ConnectionLike + Sized { ) -> RedisResult { cmd("GRAPH.RO_QUERY").arg(key).arg(query).query(self) } + + fn graph_profile( + &mut self, + key: K, + query: Q, + ) -> RedisResult { + cmd("GRAPH.PROFILE").arg(key).arg(query).query(self) + } + + fn graph_delete(&mut self, key: K) -> RedisResult { + cmd("GRAPH.DELETE").arg(key).query(self) + } + + fn graph_explain( + &mut self, + key: K, + query: Q, + ) -> RedisResult { + cmd("GRAPH.EXPLAIN").arg(key).arg(query).query(self) + } + + fn graph_slowlog(&mut self, key: K) -> RedisResult> { + cmd("GRAPH.SLOWLOG").arg(key).query(self) + } + + fn graph_config_set( + &mut self, + name: K, + value: V, + ) -> RedisResult { + cmd("GRAPH.CONFIG") + .arg("SET") + .arg(name) + .arg(value) + .query(self) + } + + fn graph_config_get(&mut self, name: K) -> RedisResult { + value_from_pair(&cmd("GRAPH.CONFIG").arg("GET").arg(name).query(self)?) + } + + fn graph_config_get_all(&mut self) -> RedisResult { + cmd("GRAPH.CONFIG").arg("GET").arg("*").query(self) + } } impl GraphCommands for T where T: ConnectionLike {} diff --git a/src/lib.rs b/src/lib.rs index 093af7a..5b30035 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,18 +1,20 @@ -//! redis_graph provides a small trait with two extension functions for the +//! redis_graph provides a trait with a small number of extension functions for the //! [redis](https://docs.rs/redis/) crate to allow working with redis graph //! data types that can be installed as a [redis module](https://oss.redislabs.com/redisgraph). -//! Redis graph operation are only using two top level Redis commands -//! (one for read/write operations and one for read-only operations), so -//! this crate only adds two functions to the redis commands. -//! The Graph commands are available in synchronous and asynchronous versions. +//!Redis graph operations are mostly using two top level Redis commands +//!(one for read/write operations and one for read-only operations). In addition +//!to those there are some more maintenance oriented commands for perfomance, configuration and +//!clean-up which starting from v0.4.0 are also supported. +//!The Graph commands are available in synchronous and asynchronous versions. //! -//! The crate is called `redis-graph` and you can depend on it via cargo. You will -//! also need redis in your dependencies. +//!The crate is called `redis-graph` and you can depend on it via cargo. You will +//!also need redis in your dependencies. This version was tested against redis 0.20.0 +//!but should run with versions higher than that. //! //! ```ini //! [dependencies] -//! redis = "0.19.0" -//! redis-graph = "*" +//! redis = "0.20.0" +//! redis-graph = "0.4.0" //! ``` //! //! Or via git: @@ -28,8 +30,8 @@ //! //! ```ini //! [dependencies] -//! redis = "0.19.0" -//! redis-graph = { version = "0.3.0", features = ['tokio-comp'] } +//! redis = "0.20.0" +//! redis-graph = { version = "0.4.0", features = ['tokio-comp'] } //! ``` //! //! # Synchronous usage @@ -143,6 +145,96 @@ //! let rider_name:Option = rider.unwrap().get_property_option("name"); //! //! # Ok(()) } +//! ``` +//! +//! ## GRAPH.PROFILE +//! Executes a query and produces an execution plan augmented with metrics +//! for each operation's execution. Returns strings in a list format. +//! +//! ```rust,no_run +//! # fn run() -> redis::RedisResult<()> { +//! # use redis::Commands; +//! # use redis_graph::*; +//! # let client = redis::Client::open("redis://127.0.0.1/")?; +//! # let mut con = client.get_connection()?; +//! let profile:Vec = con.graph_profile( +//! "my_graph", +//! "MATCH (rider:Rider)-[:rides]->(:Team {name:'Yamaha'}) RETURN rider" +//! )?; +//! +//! # Ok(()) } +//! ``` +//! +//! ## GRAPH.DELETE +//! Completely removes the graph and all of its entities. +//! ```rust,no_run +//! # fn run() -> redis::RedisResult<()> { +//! # use redis::Commands; +//! # use redis_graph::*; +//! # let client = redis::Client::open("redis://127.0.0.1/")?; +//! # let mut con = client.get_connection()?; +//! let res:String = con.graph_delete("my_graph")?; +//! +//! # Ok(()) } +//! ``` +//! +//! ## GRAPH.EXPLAIN +//! Constructs a query execution plan but does not run it. Inspect this +//! execution plan to better understand how your query will get executed. +//! +//! ```rust,no_run +//! # fn run() -> redis::RedisResult<()> { +//! # use redis::Commands; +//! # use redis_graph::*; +//! # let client = redis::Client::open("redis://127.0.0.1/")?; +//! # let mut con = client.get_connection()?; +//! let explanation:Vec = con.graph_explain( +//! "my_graph", +//! "MATCH (rider:Rider)-[:rides]->(:Team {name:'Yamaha'}) RETURN rider" +//! )?; +//! +//! # Ok(()) } +//! ``` +//! +//! ## GRAPH.SLOWLOG +//! Returns a list containing up to 10 of the slowest queries issued against the +//! given graph ID. Results will be read into a list of SlowLogEntry. +//! +//! ```rust,no_run +//! # fn run() -> redis::RedisResult<()> { +//! # use redis::Commands; +//! # use redis_graph::*; +//! # let client = redis::Client::open("redis://127.0.0.1/")?; +//! # let mut con = client.get_connection()?; +//! let log:Vec = con.graph_slowlog("my_graph")?; +//! +//! # Ok(()) } +//! ``` +//! +//! ## GRAPH.CONFIG +//! Allows configuring some global behaviour of redis graph for the redis server. +//! +//! ```rust,no_run +//! # fn run() -> redis::RedisResult<()> { +//! # use redis::Commands; +//! # use redis_graph::*; +//! # let client = redis::Client::open("redis://127.0.0.1/")?; +//! # let mut con = client.get_connection()?; +//! /// Set a config value. Be aware that setting non config fields here will +//! /// result in a redis error. +//! let success:bool = con.graph_config_set("RESULTSET_SIZE", 500)?; +//! +//! /// Get a single config value. +//! let resultSetSize:i32 = con.graph_config_get("RESULTSET_SIZE")?; +//! assert_eq!(resultSetSize, 500); +//! +//! /// Get all config values +//! let config: GraphConfig = con.graph_config_get_all()?; +//! let resSize:Option = config.get_value("RESULTSET_SIZE")?; +//! assert_eq!(resSize.unwrap(), 500); +//! +//! # Ok(()) } +//! ``` #[cfg(any(feature = "tokio-comp", feature = "async-std-comp"))] pub use crate::async_commands::AsyncGraphCommands; pub use crate::commands::GraphCommands; diff --git a/src/types.rs b/src/types.rs index f66dcad..c06ce90 100644 --- a/src/types.rs +++ b/src/types.rs @@ -70,6 +70,38 @@ pub struct RelationValue { pub properties: HashMap, } +/// Represents an entry returned from the GRAPH.SLOWLOG command. +#[derive(Default, Clone, Debug)] +pub struct SlowLogEntry { + /// A unix timestamp at which the log entry was processed. + pub timestamp: u64, + /// The issued command. + pub command: String, + /// The issued query. + pub query: String, + /// The amount of time needed for its execution, in milliseconds. + pub time: f64, +} + +/// Simple wrapper around a graph config map that allows derserializing config +/// values into rust types. +#[derive(Default, Clone, Debug)] +pub struct GraphConfig { + pub values: HashMap, +} + +impl GraphConfig { + /// Extracts a config Redis value at key into an Option of the desired type. Will + /// return None in case the key did not exists. Will return an error in case the + /// value at key failed to be parsed into T. + pub fn get_value(&self, key: &str) -> RedisResult> { + match self.values.get(key) { + Some(value) => from_redis_value(value), + _ => Ok(None), + } + } +} + impl GraphResultSet { fn from_metadata(metadata: Vec) -> Self { GraphResultSet { @@ -285,8 +317,30 @@ impl FromRedisValue for RelationValue { } } +impl FromRedisValue for SlowLogEntry { + fn from_redis_value(v: &Value) -> RedisResult { + match v { + Value::Bulk(ref values) if values.len() == 4 => Ok(SlowLogEntry { + timestamp: from_redis_value(values.get(0).unwrap())?, + command: from_redis_value(values.get(1).unwrap())?, + query: from_redis_value(values.get(2).unwrap())?, + time: from_redis_value(values.get(3).unwrap())?, + }), + _ => Err(create_error("invalid_slow_log_entry")), + } + } +} + +impl FromRedisValue for GraphConfig { + fn from_redis_value(v: &Value) -> RedisResult { + Ok(GraphConfig { + values: to_property_map(v)?, + }) + } +} + // Wraps a string error msg into a RedisError -fn create_error(msg: &str) -> RedisError { +pub fn create_error(msg: &str) -> RedisError { RedisError::from(std::io::Error::new( std::io::ErrorKind::Other, msg.to_string(), @@ -294,7 +348,7 @@ fn create_error(msg: &str) -> RedisError { } // Extracts a list of name value pairs from a graph result -fn to_property_map(v: &Value) -> RedisResult> { +pub fn to_property_map(v: &Value) -> RedisResult> { let t: Vec> = from_redis_value(v)?; let mut values: HashMap = HashMap::default(); for pair in t { @@ -304,3 +358,8 @@ fn to_property_map(v: &Value) -> RedisResult> { } Ok(values) } + +pub fn value_from_pair(v: &Value) -> RedisResult { + let r: (String, T) = from_redis_value(v)?; + Ok(r.1) +} diff --git a/tests/issue_async_commands.rs b/tests/issue_async_commands.rs index 9ec2b7a..df3590b 100644 --- a/tests/issue_async_commands.rs +++ b/tests/issue_async_commands.rs @@ -1,16 +1,22 @@ extern crate redis; extern crate redis_graph; +#[cfg(any(feature = "tokio-comp", feature = "async-std-comp"))] use redis::aio::Connection; -use redis::AsyncCommands; +#[cfg(any(feature = "tokio-comp", feature = "async-std-comp"))] +use redis::{AsyncCommands, RedisResult}; +#[cfg(any(feature = "tokio-comp", feature = "async-std-comp"))] use redis_graph::*; +#[cfg(any(feature = "tokio-comp", feature = "async-std-comp"))] use std::env; +#[cfg(any(feature = "tokio-comp", feature = "async-std-comp"))] async fn get_con() -> Connection { let client = redis::Client::open(get_redis_url()).unwrap(); client.get_async_connection().await.unwrap() } +#[cfg(any(feature = "tokio-comp", feature = "async-std-comp"))] async fn ensure_simple_data(name: &str) { let mut con = get_con().await; let _: () = con.del(name).await.unwrap(); @@ -20,6 +26,7 @@ async fn ensure_simple_data(name: &str) { ).await; } +#[cfg(any(feature = "tokio-comp", feature = "async-std-comp"))] async fn ensure_test_data(name: &str) { let mut con = get_con().await; let _: () = con.del(name).await.unwrap(); @@ -30,6 +37,7 @@ async fn ensure_test_data(name: &str) { ).await; } +#[cfg(any(feature = "tokio-comp", feature = "async-std-comp"))] pub async fn issue_graph_create_command(name: &str) -> GraphResultSet { let mut con = get_con().await; let _: () = con.del(name).await.unwrap(); @@ -39,6 +47,7 @@ pub async fn issue_graph_create_command(name: &str) -> GraphResultSet { ).await.unwrap() } +#[cfg(any(feature = "tokio-comp", feature = "async-std-comp"))] pub async fn issue_match_query_command(name: &str) -> GraphResultSet { ensure_simple_data(name).await; get_con() @@ -48,6 +57,7 @@ pub async fn issue_match_query_command(name: &str) -> GraphResultSet { .unwrap() } +#[cfg(any(feature = "tokio-comp", feature = "async-std-comp"))] pub async fn issue_match_ro_query_command(name: &str) -> GraphResultSet { ensure_simple_data(name).await; get_con() @@ -57,6 +67,7 @@ pub async fn issue_match_ro_query_command(name: &str) -> GraphResultSet { .unwrap() } +#[cfg(any(feature = "tokio-comp", feature = "async-std-comp"))] pub async fn issue_match_scalar_result(name: &str) -> GraphResultSet { ensure_test_data(name).await; get_con() @@ -69,6 +80,7 @@ pub async fn issue_match_scalar_result(name: &str) -> GraphResultSet { .unwrap() } +#[cfg(any(feature = "tokio-comp", feature = "async-std-comp"))] pub async fn issue_query_all_nodes(name: &str) -> GraphResultSet { ensure_test_data(name).await; get_con() @@ -78,6 +90,7 @@ pub async fn issue_query_all_nodes(name: &str) -> GraphResultSet { .unwrap() } +#[cfg(any(feature = "tokio-comp", feature = "async-std-comp"))] pub async fn issue_query_option(name: &str) -> GraphResultSet { ensure_test_data(name).await; get_con() @@ -87,6 +100,71 @@ pub async fn issue_query_option(name: &str) -> GraphResultSet { .unwrap() } +#[cfg(any(feature = "tokio-comp", feature = "async-std-comp"))] +pub async fn issue_graph_profile_query(name: &str) -> Vec { + ensure_test_data(name).await; + get_con() + .await + .graph_profile(name, "MATCH (r:Rider) RETURN r") + .await + .unwrap() +} + +#[cfg(any(feature = "tokio-comp", feature = "async-std-comp"))] +pub async fn issue_graph_slowlog_query(name: &str) -> Vec { + ensure_test_data(name).await; + get_con().await.graph_slowlog(name).await.unwrap() +} + +#[cfg(any(feature = "tokio-comp", feature = "async-std-comp"))] +pub async fn issue_graph_config_set() -> RedisResult { + get_con() + .await + .graph_config_set("RESULTSET_SIZE", 500) + .await +} + +#[cfg(any(feature = "tokio-comp", feature = "async-std-comp"))] +pub async fn issue_graph_config_set_invalid() -> RedisResult { + get_con().await.graph_config_set("SOME", 500).await +} + +#[cfg(any(feature = "tokio-comp", feature = "async-std-comp"))] +pub async fn issue_graph_config_get() -> RedisResult { + let _: bool = get_con() + .await + .graph_config_set("RESULTSET_SIZE", 500) + .await + .unwrap(); + get_con().await.graph_config_get("RESULTSET_SIZE").await +} + +#[cfg(any(feature = "tokio-comp", feature = "async-std-comp"))] +pub async fn issue_graph_config_get_all() -> GraphConfig { + let _: bool = get_con() + .await + .graph_config_set("RESULTSET_SIZE", 500) + .await + .unwrap(); + get_con().await.graph_config_get_all().await.unwrap() +} + +#[cfg(any(feature = "tokio-comp", feature = "async-std-comp"))] +pub async fn issue_graph_delete(name: &str) -> RedisResult { + ensure_test_data(name).await; + get_con().await.graph_delete(name).await +} + +#[cfg(any(feature = "tokio-comp", feature = "async-std-comp"))] +pub async fn issue_graph_explain(name: &str) -> RedisResult> { + ensure_test_data(name).await; + get_con() + .await + .graph_explain(name, "MATCH (r:Rider) RETURN r") + .await +} + +#[cfg(any(feature = "tokio-comp", feature = "async-std-comp"))] fn get_redis_url() -> String { let redis_host_key = "REDIS_HOST"; let redis_host_port = "REDIS_PORT"; diff --git a/tests/test_async_std_commands.rs b/tests/test_async_std_commands.rs index 746b7ab..5ef45cb 100644 --- a/tests/test_async_std_commands.rs +++ b/tests/test_async_std_commands.rs @@ -53,3 +53,51 @@ fn test_unserialize_option() { let res = task::block_on(issue_query_option("test_unserialize_option_std")); check_unserialize_option(res); } + +#[test] +fn test_graph_profile() { + let res = task::block_on(issue_graph_profile_query("test_graph_profile_std")); + check_graph_profile(res); +} + +#[test] +fn test_graph_slowlog() { + let res = task::block_on(issue_graph_slowlog_query("test_graph_slowlog_std")); + check_graph_slowlog(res); +} + +#[test] +fn test_graph_config_set_invalid() { + let err_res = task::block_on(issue_graph_config_set_invalid()); + check_graph_config_set_invalid(err_res); +} + +#[test] +fn test_graph_config_set() { + let res = task::block_on(issue_graph_config_set()); + check_graph_config_set_valid(res); +} + +#[test] +fn test_graph_config_get() { + let res = task::block_on(issue_graph_config_get()); + check_graph_config_get(res); +} + +#[test] +fn test_graph_config_get_all() { + let res = task::block_on(issue_graph_config_get_all()); + check_graph_config_get_all(res); +} + +#[test] +fn test_graph_delete() { + let res = task::block_on(issue_graph_delete("test_graph_delete_std")); + check_graph_delete_success(res); +} + +#[test] +fn test_graph_explain() { + let res = task::block_on(issue_graph_explain("test_graph_explain_std")); + check_graph_explain_result(res); +} diff --git a/tests/test_async_tokio_commands.rs b/tests/test_async_tokio_commands.rs index 52951d1..c476eb6 100644 --- a/tests/test_async_tokio_commands.rs +++ b/tests/test_async_tokio_commands.rs @@ -22,39 +22,88 @@ fn create_runtime() -> Runtime { #[test] fn test_issue_graph_create_command() { let r = create_runtime().block_on(issue_graph_create_command( - "test_issue_graph_create_command_std", + "test_issue_graph_create_command_tokio", )); check_graph_create_command(r); } #[test] fn test_match_query_result() { - let r = create_runtime().block_on(issue_match_query_command("test_match_query_result_std")); + let r = create_runtime().block_on(issue_match_query_command("test_match_query_result_tokio")); check_match_query_result(r); } #[test] fn test_match_ro_query_result() { let r = create_runtime().block_on(issue_match_ro_query_command( - "test_match_ro_query_result_std", + "test_match_ro_query_result_tokio", )); check_match_query_result(r); } #[test] fn test_match_scalar_result() { - let res = create_runtime().block_on(issue_match_scalar_result("test_match_scalar_result_std")); + let res = + create_runtime().block_on(issue_match_scalar_result("test_match_scalar_result_tokio")); check_match_scalar_result(res); } #[test] fn test_query_all_nodes() { - let res = create_runtime().block_on(issue_query_all_nodes("test_query_all_nodes_std")); + let res = create_runtime().block_on(issue_query_all_nodes("test_query_all_nodes_tokio")); check_query_all_nodes(res); } #[test] fn test_unserialize_option() { - let res = create_runtime().block_on(issue_query_option("test_unserialize_option_std")); + let res = create_runtime().block_on(issue_query_option("test_unserialize_option_tokio")); check_unserialize_option(res); } + +#[test] +fn test_graph_profile() { + let res = create_runtime().block_on(issue_graph_profile_query("test_graph_profile_tokio")); + check_graph_profile(res); +} + +#[test] +fn test_graph_slowlog() { + let res = create_runtime().block_on(issue_graph_slowlog_query("test_graph_slowlog_tokio")); + check_graph_slowlog(res); +} + +#[test] +fn test_graph_config_set_invalid() { + let err_res = create_runtime().block_on(issue_graph_config_set_invalid()); + check_graph_config_set_invalid(err_res); +} + +#[test] +fn test_graph_config_set() { + let res = create_runtime().block_on(issue_graph_config_set()); + check_graph_config_set_valid(res); +} + +#[test] +fn test_graph_config_get() { + let res = create_runtime().block_on(issue_graph_config_get()); + check_graph_config_get(res); +} + +#[test] +fn test_graph_config_get_all() { + let res = create_runtime().block_on(issue_graph_config_get_all()); + check_graph_config_get_all(res); +} + +#[test] +fn test_graph_delete() { + let res = create_runtime().block_on(issue_graph_delete("test_graph_delete_tokio")); + check_graph_delete_success(res); +} + +#[test] +fn test_graph_explain() { + let res = create_runtime().block_on(issue_graph_explain("test_graph_explain_tokio")); + check_graph_explain_result(res); +} diff --git a/tests/test_commands.rs b/tests/test_commands.rs index ddb0fee..4598007 100644 --- a/tests/test_commands.rs +++ b/tests/test_commands.rs @@ -105,6 +105,56 @@ fn test_unserialize_option() { check_unserialize_option(res); } +#[test] +fn test_graph_profile() { + ensure_test_data("test_graph_profile"); + let res: Vec = get_con() + .graph_profile("test_graph_profile", "MATCH (r:Rider) RETURN r") + .unwrap(); + check_graph_profile(res); +} + +#[test] +fn test_graph_slowlog() { + ensure_test_data("test_graph_slowlog"); + let res = get_con().graph_slowlog("test_graph_slowlog").unwrap(); + check_graph_slowlog(res); +} + +#[test] +fn test_graph_config_set() { + let err_res = get_con().graph_config_set("SOME", 1000); + check_graph_config_set_invalid(err_res); + let res = get_con().graph_config_set("RESULTSET_SIZE", 500); + check_graph_config_set_valid(res); +} + +#[test] +fn test_graph_config_get() { + let _ = get_con().graph_config_set("RESULTSET_SIZE", 500).unwrap(); + check_graph_config_get(get_con().graph_config_get("RESULTSET_SIZE")); +} + +#[test] +fn test_graph_config_get_all() { + let _ = get_con().graph_config_set("RESULTSET_SIZE", 500).unwrap(); + check_graph_config_get_all(get_con().graph_config_get_all().unwrap()); +} + +#[test] +fn test_graph_delete() { + ensure_test_data("test_graph_delete"); + check_graph_delete_success(get_con().graph_delete("test_graph_delete")); +} + +#[test] +fn test_graph_explain() { + ensure_test_data("test_graph_explain"); + check_graph_explain_result( + get_con().graph_explain("test_graph_explain", "MATCH (r:Rider) RETURN r"), + ); +} + fn get_redis_url() -> String { let redis_host_key = "REDIS_HOST"; let redis_host_port = "REDIS_PORT"; diff --git a/tests/test_graph_assertions.rs b/tests/test_graph_assertions.rs index f39b741..4aa68a7 100644 --- a/tests/test_graph_assertions.rs +++ b/tests/test_graph_assertions.rs @@ -1,4 +1,4 @@ -use redis::from_redis_value; +use redis::{from_redis_value, RedisResult}; use redis_graph::*; pub fn check_graph_create_command(r: GraphResultSet) { @@ -73,3 +73,39 @@ pub fn check_unserialize_option(res: GraphResultSet) { assert_eq!(born.iter().filter(|v| v.is_none()).count(), 1); assert_eq!(born.iter().filter(|v| v.is_some()).count(), 2); } + +pub fn check_graph_profile(res: Vec) { + assert!(res.len() > 0); +} + +pub fn check_graph_slowlog(res: Vec) { + let first = res.first().unwrap(); + assert_eq!(first.command, "GRAPH.QUERY"); + assert!(res.len() > 0); +} + +pub fn check_graph_config_set_valid(r: RedisResult) { + assert!(r.unwrap()); +} + +pub fn check_graph_config_set_invalid(r: RedisResult) { + assert_eq!(true, r.is_err()); +} + +pub fn check_graph_config_get(r: RedisResult) { + assert_eq!(r.unwrap(), 500); +} + +pub fn check_graph_config_get_all(r: GraphConfig) { + assert!(!r.values.is_empty()); + let v: i32 = r.get_value("RESULTSET_SIZE").unwrap().unwrap(); + assert_eq!(v, 500); +} + +pub fn check_graph_delete_success(r: RedisResult) { + assert!(r.unwrap().contains("Graph removed")) +} + +pub fn check_graph_explain_result(r: RedisResult>) { + assert!(r.unwrap().len() > 0); +}