From cd6923b75653ecb1c717601d67ccc7c28e8f1597 Mon Sep 17 00:00:00 2001 From: Emily Matheys Date: Wed, 12 Jun 2024 21:46:39 +0300 Subject: [PATCH 01/11] implement QueryResult properly --- Cargo.lock | 196 +++++++++++++++++- Cargo.toml | 14 +- src/client/blocking.rs | 74 ++++--- src/client/builder.rs | 4 +- src/client/mod.rs | 6 +- src/connection/blocking.rs | 65 +++--- src/connection_info/mod.rs | 10 +- src/error/mod.rs | 18 +- src/graph/blocking.rs | 30 +-- src/graph/query_builder.rs | 100 ++++++--- src/graph_schema/mod.rs | 157 ++++++++------ src/lib.rs | 4 - src/parser/utils.rs | 102 +++++----- src/redis_ext.rs | 46 ++++- src/response/constraint.rs | 40 ++-- src/response/execution_plan.rs | 35 ++-- src/response/index.rs | 108 +++++----- src/response/lazy_result_set.rs | 12 +- src/response/mod.rs | 34 +--- src/response/slowlog_entry.rs | 33 ++- src/value/graph_entities.rs | 72 ++++--- src/value/map.rs | 161 --------------- src/value/mod.rs | 11 +- src/value/path.rs | 27 ++- src/value/point.rs | 56 +++-- src/value/utils.rs | 348 +++++++++++++++++++++++--------- 26 files changed, 1058 insertions(+), 705 deletions(-) delete mode 100644 src/value/map.rs diff --git a/Cargo.lock b/Cargo.lock index d6f927d..f265f67 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -130,6 +130,8 @@ dependencies = [ "strum", "thiserror", "tracing", + "tracing-subscriber", + "tracing-tracy", ] [[package]] @@ -168,6 +170,20 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "generator" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186014d53bc231d0090ef8d6f03e0920c54d85a5ed22f4f2f74315ec56cf83fb" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows", +] + [[package]] name = "getrandom" version = "0.2.15" @@ -257,6 +273,28 @@ version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + [[package]] name = "memchr" version = "2.7.2" @@ -290,6 +328,16 @@ dependencies = [ "tempfile", ] +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "object" version = "0.32.2" @@ -349,6 +397,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "parking_lot" version = "0.12.3" @@ -493,8 +547,17 @@ checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" dependencies = [ "aho-corasick", "memchr", - "regex-automata", - "regex-syntax", + "regex-automata 0.4.6", + "regex-syntax 0.8.3", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", ] [[package]] @@ -505,9 +568,15 @@ checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.8.3", ] +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + [[package]] name = "regex-syntax" version = "0.8.3" @@ -623,6 +692,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.2.0" @@ -652,6 +727,15 @@ dependencies = [ "libc", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "smallvec" version = "1.13.2" @@ -745,6 +829,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -789,6 +883,67 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "tracing-tracy" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6024d04f84a69fd0d1dc1eee3a2b070bd246530a0582f9982ae487cb6c703614" +dependencies = [ + "tracing-core", + "tracing-subscriber", + "tracy-client", +] + +[[package]] +name = "tracy-client" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59fb931a64ff88984f86d3e9bcd1ae8843aa7fe44dd0f8097527bc172351741d" +dependencies = [ + "loom", + "once_cell", + "tracy-client-sys", +] + +[[package]] +name = "tracy-client-sys" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d104d610dfa9dd154535102cc9c6164ae1fa37842bc2d9e83f9ac82b0ae0882" +dependencies = [ + "cc", ] [[package]] @@ -829,6 +984,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + [[package]] name = "vcpkg" version = "0.2.15" @@ -863,6 +1024,35 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.54.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49" +dependencies = [ + "windows-core", + "windows-targets", +] + +[[package]] +name = "windows-core" +version = "0.54.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65" +dependencies = [ + "windows-result", + "windows-targets", +] + +[[package]] +name = "windows-result" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.52.0" diff --git a/Cargo.toml b/Cargo.toml index 77bf36d..f9850d8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,14 +9,20 @@ license = "SSPL-1.0" [dependencies] log = { version = "0.4.21", default-features = false, features = ["std"] } parking_lot = { version = "0.12.3", default-features = false, features = ["deadlock_detection"] } -redis = { version = "0.25.4", default-features = false, optional = true, features = ["sentinel"] } +redis = { version = "0.25.4", default-features = false, features = ["sentinel"] } regex = { version = "1.10.5", default-features = false, features = ["std", "perf", "unicode-bool", "unicode-perl"] } strum = { version = "0.26.2", default-features = false, features = ["std", "derive"] } thiserror = "1.0.61" -tracing = { version = "0.1.40", default-features = false, features = ["std", "attributes"] } +tracing = { version = "0.1.40", default-features = false, features = ["std", "attributes"], optional = true } + +[dev-dependencies] +tracing-tracy = { version = "0.11.0" } +tracing-subscriber = { version = "0.3.18", default-features = false, features = ["std"] } [features] -default = ["redis"] +default = [] + native-tls = ["redis/tls-native-tls"] rustls = ["redis/tls-rustls"] -redis = ["dep:redis"] + +tracing = ["dep:tracing"] diff --git a/src/client/blocking.rs b/src/client/blocking.rs index 614eb3f..1017829 100644 --- a/src/client/blocking.rs +++ b/src/client/blocking.rs @@ -3,11 +3,12 @@ * Licensed under the Server Side Public License v1 (SSPLv1). */ +use crate::redis_ext::redis_value_as_string; use crate::{ client::FalkorClientProvider, connection::blocking::{BorrowedSyncConnection, FalkorSyncConnection}, parser::utils::string_vec_from_val, - ConfigValue, FalkorConnectionInfo, FalkorDBError, FalkorResult, FalkorValue, SyncGraph, + ConfigValue, FalkorConnectionInfo, FalkorDBError, FalkorResult, SyncGraph, }; use parking_lot::{Mutex, RwLock}; use std::{ @@ -24,6 +25,10 @@ pub(crate) struct FalkorSyncClientInner { } impl FalkorSyncClientInner { + #[cfg_attr( + feature = "tracing", + tracing::instrument(name = "Borrow Connection From Connection Pool", skip_all) + )] pub(crate) fn borrow_connection( &self, pool_owner: Arc, @@ -38,12 +43,15 @@ impl FalkorSyncClientInner { )) } + #[cfg_attr( + feature = "tracing", + tracing::instrument(name = "Get New Sync Connection From Client", skip_all) + )] pub(crate) fn get_connection(&self) -> FalkorResult { self._inner.lock().get_connection() } } -#[cfg(feature = "redis")] fn is_sentinel(conn: &mut FalkorSyncConnection) -> FalkorResult { let info_map = conn.get_redis_info(Some("server"))?; Ok(info_map @@ -52,7 +60,6 @@ fn is_sentinel(conn: &mut FalkorSyncConnection) -> FalkorResult { .unwrap_or_default()) } -#[cfg(feature = "redis")] pub(crate) fn get_sentinel_client( client: &mut FalkorClientProvider, connection_info: &redis::ConnectionInfo, @@ -66,21 +73,21 @@ pub(crate) fn get_sentinel_client( // Perhaps in the future we can use it if we only support the master instance to be called 'master'? let sentinel_masters = conn .execute_command(None, "SENTINEL", Some("MASTERS"), None)? - .into_vec()?; - - if sentinel_masters.len() != 1 { - return Err(FalkorDBError::SentinelMastersCount); - } + .into_sequence() + .ok() + .and_then(|vec| (vec.len() == 1).then_some(vec)) + .ok_or(FalkorDBError::SentinelMastersCount)?; let sentinel_master: HashMap<_, _> = sentinel_masters .into_iter() .next() + .and_then(|master| master.into_sequence().ok()) .ok_or(FalkorDBError::SentinelMastersCount)? - .into_vec()? .chunks_exact(2) - .flat_map(|chunk| TryInto::<[FalkorValue; 2]>::try_into(chunk.to_vec())) + .flat_map(|chunk| TryInto::<&[redis::Value; 2]>::try_into(chunk)) // TODO: check if this can be done with no copying .flat_map(|[key, val]| { - Result::<_, FalkorDBError>::Ok((key.into_string()?, val.into_string()?)) + redis_value_as_string(key.to_owned()) + .and_then(|key| redis_value_as_string(val.to_owned()).map(|val| (key, val))) }) .collect(); @@ -184,34 +191,42 @@ impl FalkorSyncClient { &self, config_key: &str, ) -> FalkorResult> { - let mut conn = self.borrow_connection()?; - let config = conn - .execute_command(None, "GRAPH.CONFIG", Some("GET"), Some(&[config_key]))? - .into_vec()?; + let config = self + .borrow_connection() + .and_then(|mut conn| { + conn.execute_command(None, "GRAPH.CONFIG", Some("GET"), Some(&[config_key])) + }) + .and_then(|res| res.into_sequence().map_err(|_| FalkorDBError::ParsingArray))?; if config.len() == 2 { - let [key, val]: [FalkorValue; 2] = config.try_into().map_err(|_| { + let [key, val]: [redis::Value; 2] = config.try_into().map_err(|_| { FalkorDBError::ParsingArrayToStructElementCount( - "Expected exactly 2 elements for configuration option".to_string(), + "Expected exactly 2 elements for configuration option", ) })?; - return Ok(HashMap::from([( - key.into_string()?, - ConfigValue::try_from(val)?, - )])); + return redis_value_as_string(key) + .and_then(|key| ConfigValue::try_from(val).map(|val| HashMap::from([(key, val)]))); } Ok(config .into_iter() .flat_map(|config| { - let [key, val]: [FalkorValue; 2] = config.into_vec()?.try_into().map_err(|_| { - FalkorDBError::ParsingArrayToStructElementCount( - "Expected exactly 2 elements for configuration option".to_string(), - ) - })?; - - Result::<_, FalkorDBError>::Ok((key.into_string()?, ConfigValue::try_from(val)?)) + config + .into_sequence() + .map_err(|_| FalkorDBError::ParsingArray) + .and_then(|as_vec| { + let [key, val]: [redis::Value; 2] = as_vec.try_into().map_err(|_| { + FalkorDBError::ParsingArrayToStructElementCount( + "Expected exactly 2 elements for configuration option", + ) + })?; + + Result::<_, FalkorDBError>::Ok(( + redis_value_as_string(key)?, + ConfigValue::try_from(val)?, + )) + }) }) .collect::>()) } @@ -226,7 +241,7 @@ impl FalkorSyncClient { &self, config_key: &str, value: C, - ) -> FalkorResult { + ) -> FalkorResult { self.borrow_connection()?.execute_command( None, "GRAPH.CONFIG", @@ -271,7 +286,6 @@ impl FalkorSyncClient { Ok(self.select_graph(new_graph_name)) } - #[cfg(feature = "redis")] /// Retrieves redis information pub fn redis_info( &self, diff --git a/src/client/builder.rs b/src/client/builder.rs index 279f274..b998fad 100644 --- a/src/client/builder.rs +++ b/src/client/builder.rs @@ -58,7 +58,6 @@ impl FalkorClientBuilder { .try_into() .map_err(|err| FalkorDBError::InvalidConnectionInfo(err.to_string()))?; Ok(match connection_info { - #[cfg(feature = "redis")] FalkorConnectionInfo::Redis(connection_info) => FalkorClientProvider::Redis { client: redis::Client::open(connection_info.clone()) .map_err(|err| FalkorDBError::RedisError(err.to_string()))?, @@ -92,7 +91,6 @@ impl FalkorClientBuilder<'S'> { let mut client = Self::get_client(connection_info.clone())?; - #[cfg(feature = "redis")] #[allow(irrefutable_let_patterns)] if let FalkorConnectionInfo::Redis(redis_conn_info) = &connection_info { if let Some(sentinel) = @@ -121,7 +119,7 @@ mod tests { } #[test] - #[cfg(feature = "redis")] + fn test_sync_builder_redis_fallback() { let client = FalkorClientBuilder::new().build(); assert!(client.is_ok()); diff --git a/src/client/mod.rs b/src/client/mod.rs index e5201b3..84e49f0 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -12,7 +12,7 @@ pub(crate) mod builder; pub(crate) enum FalkorClientProvider { #[allow(unused)] None, - #[cfg(feature = "redis")] + Redis { client: redis::Client, sentinel: Option, @@ -22,7 +22,6 @@ pub(crate) enum FalkorClientProvider { impl FalkorClientProvider { pub(crate) fn get_connection(&mut self) -> FalkorResult { Ok(match self { - #[cfg(feature = "redis")] FalkorClientProvider::Redis { sentinel: Some(sentinel), .. @@ -31,7 +30,7 @@ impl FalkorClientProvider { .get_connection() .map_err(|err| FalkorDBError::RedisError(err.to_string()))?, ), - #[cfg(feature = "redis")] + FalkorClientProvider::Redis { client, .. } => FalkorSyncConnection::Redis( client .get_connection() @@ -41,7 +40,6 @@ impl FalkorClientProvider { }) } - #[cfg(feature = "redis")] pub(crate) fn set_sentinel( &mut self, sentinel_client: redis::sentinel::SentinelClient, diff --git a/src/connection/blocking.rs b/src/connection/blocking.rs index 1f65536..bacbcec 100644 --- a/src/connection/blocking.rs +++ b/src/connection/blocking.rs @@ -3,7 +3,8 @@ * Licensed under the Server Side Public License v1 (SSPLv1). */ -use crate::{client::blocking::FalkorSyncClientInner, FalkorDBError, FalkorResult, FalkorValue}; +use crate::redis_ext::redis_value_as_string; +use crate::{client::blocking::FalkorSyncClientInner, FalkorDBError, FalkorResult}; use std::{ collections::HashMap, sync::{mpsc, Arc}, @@ -12,20 +13,22 @@ use std::{ pub(crate) enum FalkorSyncConnection { #[allow(unused)] None, - #[cfg(feature = "redis")] Redis(redis::Connection), } impl FalkorSyncConnection { + #[cfg_attr( + feature = "tracing", + tracing::instrument(name = "Connection Inner Execute Command", skip_all) + )] pub(crate) fn execute_command( &mut self, graph_name: Option<&str>, command: &str, subcommand: Option<&str>, params: Option<&[&str]>, - ) -> FalkorResult { + ) -> FalkorResult { match self { - #[cfg(feature = "redis")] FalkorSyncConnection::Redis(redis_conn) => { use redis::ConnectionLike as _; let mut cmd = redis::cmd(command); @@ -36,36 +39,40 @@ impl FalkorSyncConnection { cmd.arg(param.to_string()); } } - redis::FromRedisValue::from_owned_redis_value( - redis_conn - .req_command(&cmd) - .map_err(|err| match err.kind() { - redis::ErrorKind::IoError - | redis::ErrorKind::ClusterConnectionNotFound - | redis::ErrorKind::ClusterDown - | redis::ErrorKind::MasterDown => FalkorDBError::ConnectionDown, - _ => FalkorDBError::RedisError(err.to_string()), - })?, - ) - .map_err(|err| FalkorDBError::RedisParsingError(err.to_string())) + redis_conn + .req_command(&cmd) + .map_err(|err| match err.kind() { + redis::ErrorKind::IoError + | redis::ErrorKind::ClusterConnectionNotFound + | redis::ErrorKind::ClusterDown + | redis::ErrorKind::MasterDown => FalkorDBError::ConnectionDown, + _ => FalkorDBError::RedisError(err.to_string()), + }) } - FalkorSyncConnection::None => Ok(FalkorValue::None), + FalkorSyncConnection::None => Ok(redis::Value::Nil), } } - #[cfg(feature = "redis")] + #[cfg_attr( + feature = "tracing", + tracing::instrument(name = "Get Redis Info", skip_all) + )] pub(crate) fn get_redis_info( &mut self, section: Option<&str>, ) -> FalkorResult> { - Ok(self - .execute_command(None, "INFO", section, None)? - .into_string()? - .split("\r\n") - .map(|info_item| info_item.split(':').collect::>()) - .flat_map(TryInto::<[&str; 2]>::try_into) - .map(|[key, val]| (key.to_string(), val.to_string())) - .collect()) + self.execute_command(None, "INFO", section, None) + .and_then(|res| { + redis_value_as_string(res) + .map(|info| { + info.split("\r\n") + .map(|info_item| info_item.split(':').collect::>()) + .flat_map(TryInto::<[&str; 2]>::try_into) + .map(|[key, val]| (key.to_string(), val.to_string())) + .collect() + }) + .map_err(|_| FalkorDBError::ParsingFString) + }) } } @@ -96,13 +103,17 @@ impl BorrowedSyncConnection { self.conn.as_mut().ok_or(FalkorDBError::EmptyConnection) } + #[cfg_attr( + feature = "tracing", + tracing::instrument(name = "Borrowed Connection Execute Command", skip_all) + )] pub(crate) fn execute_command( &mut self, graph_name: Option<&str>, command: &str, subcommand: Option<&str>, params: Option<&[&str]>, - ) -> Result { + ) -> Result { match self .as_inner()? .execute_command(graph_name, command, subcommand, params) diff --git a/src/connection_info/mod.rs b/src/connection_info/mod.rs index 417cd6b..8f72886 100644 --- a/src/connection_info/mod.rs +++ b/src/connection_info/mod.rs @@ -9,14 +9,12 @@ use crate::{FalkorDBError, FalkorResult}; /// The different enum variants are enabled based on compilation features #[derive(Clone, Debug)] pub enum FalkorConnectionInfo { - #[cfg(feature = "redis")] /// A Redis database connection Redis(redis::ConnectionInfo), } impl FalkorConnectionInfo { fn fallback_provider(mut full_url: String) -> FalkorResult { - #[cfg(feature = "redis")] Ok(FalkorConnectionInfo::Redis({ if full_url.starts_with("falkor://") { full_url = full_url.replace("falkor://", "redis://"); @@ -34,7 +32,6 @@ impl FalkorConnectionInfo { /// A [`String`] representation of the address and port, or a UNIX socket path pub fn address(&self) -> String { match self { - #[cfg(feature = "redis")] FalkorConnectionInfo::Redis(redis_info) => redis_info.addr.to_string(), } } @@ -53,13 +50,10 @@ impl TryFrom<&str> for FalkorConnectionInfo { match url_schema { "redis" | "rediss" => { - #[cfg(feature = "redis")] return Ok(FalkorConnectionInfo::Redis( redis::IntoConnectionInfo::into_connection_info(value) .map_err(|err| FalkorDBError::InvalidConnectionInfo(err.to_string()))?, )); - #[cfg(not(feature = "redis"))] - return Err(FalkorDBError::UnavailableProvider); } _ => FalkorConnectionInfo::fallback_provider(url), } @@ -90,7 +84,7 @@ mod tests { use std::{mem, str::FromStr}; #[test] - #[cfg(feature = "redis")] + fn test_redis_fallback_provider() { let FalkorConnectionInfo::Redis(redis) = FalkorConnectionInfo::fallback_provider("redis://127.0.0.1:6379".to_string()).unwrap(); @@ -99,7 +93,7 @@ mod tests { } #[test] - #[cfg(feature = "redis")] + fn test_try_from_redis() { let res = FalkorConnectionInfo::try_from("redis://0.0.0.0:1234"); assert!(res.is_ok()); diff --git a/src/error/mod.rs b/src/error/mod.rs index c0f82ab..2d57fe4 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -57,7 +57,7 @@ pub enum FalkorDBError { ParsingError(String), /// Received malformed header. #[error("Could not parse header: {0}")] - ParsingHeader(String), + ParsingHeader(&'static str), /// The id received for this label/property/relationship was unknown. #[error("The id received for this label/property/relationship was unknown")] ParsingCompactIdUnknown, @@ -76,9 +76,9 @@ pub enum FalkorDBError { /// Element was not of type F64. #[error("Element was not of type F64")] ParsingF64, - /// Element was not of type FArray. - #[error("Element was not of type FArray")] - ParsingFArray, + /// Element was not of type Array. + #[error("Element was not of type Array")] + ParsingArray, /// Element was not of type FString. #[error("Element was not of type FString")] ParsingFString, @@ -92,8 +92,8 @@ pub enum FalkorDBError { #[error("Element was not of type FPath")] ParsingFPath, /// Element was not of type FMap. - #[error("Element was not of type FMap: {0}")] - ParsingFMap(String), + #[error("Element was not of type FMap")] + ParsingFMap, /// Element was not of type FPoint. #[error("Element was not of type FPoint")] ParsingFPoint, @@ -106,9 +106,9 @@ pub enum FalkorDBError { /// Both key id and type marker were not of type i64. #[error("Both key id and type marker were not of type i64")] ParsingKTVTypes, - /// Attempting to parse an FArray into a struct, but the array doesn't have the expected element count. - #[error("Attempting to parse an FArray into a struct, but the array doesn't have the expected element count: {0}")] - ParsingArrayToStructElementCount(String), + /// Attempting to parse an Array into a struct, but the array doesn't have the expected element count. + #[error("Attempting to parse an Array into a struct, but the array doesn't have the expected element count: {0}")] + ParsingArrayToStructElementCount(&'static str), /// Invalid enum string variant was encountered when parsing #[error("Invalid enum string variant was encountered when parsing: {0}")] InvalidEnumType(String), diff --git a/src/graph/blocking.rs b/src/graph/blocking.rs index 579809f..aaf6fd5 100644 --- a/src/graph/blocking.rs +++ b/src/graph/blocking.rs @@ -5,8 +5,8 @@ use crate::{ client::blocking::FalkorSyncClientInner, Constraint, ConstraintType, EntityType, ExecutionPlan, - FalkorIndex, FalkorResponse, FalkorResult, FalkorValue, GraphSchema, IndexType, LazyResultSet, - ProcedureQueryBuilder, QueryBuilder, SlowlogEntry, + FalkorDBError, FalkorIndex, FalkorResponse, FalkorResult, GraphSchema, IndexType, + LazyResultSet, ProcedureQueryBuilder, QueryBuilder, SlowlogEntry, }; use std::{collections::HashMap, fmt::Display, sync::Arc}; @@ -46,7 +46,7 @@ impl SyncGraph { command: &str, subcommand: Option<&str>, params: Option<&[&str]>, - ) -> FalkorResult { + ) -> FalkorResult { self.client .borrow_connection(self.client.clone())? .execute_command(Some(self.graph_name.as_str()), command, subcommand, params) @@ -65,15 +65,21 @@ impl SyncGraph { /// # Returns /// A [`Vec`] of [`SlowlogEntry`], providing information about each query. pub fn slowlog(&self) -> FalkorResult> { - let res = self - .execute_command("GRAPH.SLOWLOG", None, None)? - .into_vec()?; - - Ok(res.into_iter().flat_map(SlowlogEntry::try_from).collect()) + self.execute_command("GRAPH.SLOWLOG", None, None) + .and_then(|res| { + res.into_sequence() + .map(|as_vec| { + as_vec + .into_iter() + .flat_map(SlowlogEntry::try_from) + .collect() + }) + .map_err(|_| FalkorDBError::ParsingArray) + }) } /// Resets the slowlog, all query time data will be cleared. - pub fn slowlog_reset(&self) -> FalkorResult { + pub fn slowlog_reset(&self) -> FalkorResult { self.execute_command("GRAPH.SLOWLOG", None, Some(&["RESET"])) } @@ -290,7 +296,7 @@ impl SyncGraph { entity_type: EntityType, label: &str, properties: &[&str], - ) -> FalkorResult { + ) -> FalkorResult { let entity_type = entity_type.to_string(); let properties_count = properties.len().to_string(); @@ -318,7 +324,7 @@ impl SyncGraph { entity_type: EntityType, label: String, properties: &[&str], - ) -> FalkorResult { + ) -> FalkorResult { self.create_index( IndexType::Range, entity_type, @@ -356,7 +362,7 @@ impl SyncGraph { entity_type: EntityType, label: &str, properties: &[&str], - ) -> FalkorResult { + ) -> FalkorResult { let constraint_type = constraint_type.to_string(); let entity_type = entity_type.to_string(); let properties_count = properties.len().to_string(); diff --git a/src/graph/query_builder.rs b/src/graph/query_builder.rs index a8cf51a..47523eb 100644 --- a/src/graph/query_builder.rs +++ b/src/graph/query_builder.rs @@ -5,8 +5,7 @@ use crate::{ connection::blocking::BorrowedSyncConnection, Constraint, ExecutionPlan, FalkorDBError, - FalkorIndex, FalkorParsable, FalkorResponse, FalkorResult, FalkorValue, LazyResultSet, - SyncGraph, + FalkorIndex, FalkorResponse, FalkorResult, LazyResultSet, SyncGraph, }; use std::{collections::HashMap, fmt::Display, marker::PhantomData, ops::Not}; @@ -85,7 +84,11 @@ impl<'a, Output, T: Display> QueryBuilder<'a, Output, T> { } } - fn common_execute_steps(&mut self) -> FalkorResult { + #[cfg_attr( + feature = "tracing", + tracing::instrument(name = "Common Query Execution Steps", skip_all) + )] + fn common_execute_steps(&mut self) -> FalkorResult { let mut conn = self .graph .client @@ -107,14 +110,21 @@ impl<'a, Output, T: Display> QueryBuilder<'a, Output, T> { impl<'a, T: Display> QueryBuilder<'a, FalkorResponse>, T> { /// Executes the query, retuning a [`FalkorResponse`], with a [`LazyResultSet`] as its `data` member + #[cfg_attr( + feature = "tracing", + tracing::instrument(name = "Execute Lazy Result Set Query", skip_all) + )] pub fn execute(mut self) -> FalkorResult>> { - let res = self.common_execute_steps()?.into_vec()?; + let res = self + .common_execute_steps()? + .into_sequence() + .map_err(|_| FalkorDBError::ParsingArray)?; match res.len() { 1 => { let stats = res.into_iter().next().ok_or( FalkorDBError::ParsingArrayToStructElementCount( - "One element exist but using next() failed".to_string(), + "One element exist but using next() failed", ), )?; @@ -125,9 +135,9 @@ impl<'a, T: Display> QueryBuilder<'a, FalkorResponse>, T> { ) } 2 => { - let [header, stats]: [FalkorValue; 2] = res.try_into().map_err(|_| { + let [header, stats]: [redis::Value; 2] = res.try_into().map_err(|_| { FalkorDBError::ParsingArrayToStructElementCount( - "Two elements exist but couldn't be parsed to an array".to_string(), + "Two elements exist but couldn't be parsed to an array", ) })?; @@ -138,20 +148,24 @@ impl<'a, T: Display> QueryBuilder<'a, FalkorResponse>, T> { ) } 3 => { - let [header, data, stats]: [FalkorValue; 3] = res.try_into().map_err(|_| { + let [header, data, stats]: [redis::Value; 3] = res.try_into().map_err(|_| { FalkorDBError::ParsingArrayToStructElementCount( - "3 elements exist but couldn't be parsed to an array".to_string(), + "3 elements exist but couldn't be parsed to an array", ) })?; FalkorResponse::from_response( Some(header), - LazyResultSet::new(data.into_vec()?, &mut self.graph.graph_schema), + LazyResultSet::new( + data.into_sequence() + .map_err(|_| FalkorDBError::ParsingArray)?, + &mut self.graph.graph_schema, + ), stats, ) } _ => Err(FalkorDBError::ParsingArrayToStructElementCount( - "Invalid number of elements returned from query".to_string(), + "Invalid number of elements returned from query", ))?, } } @@ -273,7 +287,7 @@ impl<'a, Output> ProcedureQueryBuilder<'a, Output> { fn common_execute_steps( &mut self, conn: &mut BorrowedSyncConnection, - ) -> FalkorResult { + ) -> FalkorResult { let command = match self.readonly { true => "GRAPH.QUERY_RO", false => "GRAPH.QUERY", @@ -296,15 +310,31 @@ impl<'a> ProcedureQueryBuilder<'a, FalkorResponse>> { /// Executes the procedure call and return a [`FalkorResponse`] type containing a result set of [`FalkorIndex`]s /// This functions consumes self pub fn execute(mut self) -> FalkorResult>> { - FalkorParsable::from_falkor_value( - self.common_execute_steps( - &mut self - .graph - .client - .borrow_connection(self.graph.client.clone())?, - )?, - &mut self.graph.graph_schema, + self.common_execute_steps( + &mut self + .graph + .client + .borrow_connection(self.graph.client.clone())?, ) + .and_then(|res| res.into_sequence().map_err(|_| FalkorDBError::ParsingArray)) + .and_then(|res_vec| { + let [header, indices, stats]: [redis::Value; 3] = res_vec.try_into().map_err(|_| { + FalkorDBError::ParsingArrayToStructElementCount( + "Expected exactly 3 elements in query response", + ) + })?; + + FalkorResponse::from_response( + Some(header), + indices + .into_sequence() + .map_err(|_| FalkorDBError::ParsingArray)? + .into_iter() + .flat_map(FalkorIndex::parse) + .collect(), + stats, + ) + }) } } @@ -312,15 +342,29 @@ impl<'a> ProcedureQueryBuilder<'a, FalkorResponse>> { /// Executes the procedure call and return a [`FalkorResponse`] type containing a result set of [`Constraint`]s /// This functions consumes self pub fn execute(mut self) -> FalkorResult>> { - FalkorParsable::from_falkor_value( - self.common_execute_steps( - &mut self - .graph - .client - .borrow_connection(self.graph.client.clone())?, - )?, - &mut self.graph.graph_schema, + self.common_execute_steps( + &mut self + .graph + .client + .borrow_connection(self.graph.client.clone())?, ) + .and_then(|res| res.into_sequence().map_err(|_| FalkorDBError::ParsingArray)) + .and_then(|res_vec| { + let [header, indices, stats]: [redis::Value; 3] = res_vec.try_into().map_err(|_| { + FalkorDBError::ParsingArrayToStructElementCount( + "Expected exactly 3 elements in query response", + ) + })?; + + FalkorResponse::from_response( + Some(header), + indices + .into_sequence() + .map(|indices| indices.into_iter().flat_map(Constraint::parse).collect()) + .map_err(|_| FalkorDBError::ParsingArray)?, + stats, + ) + }) } } diff --git a/src/graph_schema/mod.rs b/src/graph_schema/mod.rs index 7c618a3..23b51e9 100644 --- a/src/graph_schema/mod.rs +++ b/src/graph_schema/mod.rs @@ -4,11 +4,12 @@ */ use crate::{ - client::blocking::FalkorSyncClientInner, value::utils::parse_type, FalkorDBError, FalkorResult, - FalkorValue, + client::blocking::FalkorSyncClientInner, + redis_ext::{redis_value_as_int, redis_value_as_string}, + value::utils::parse_type, + FalkorDBError, FalkorResult, FalkorValue, }; -use std::collections::HashMap; -use std::sync::Arc; +use std::{collections::HashMap, sync::Arc}; pub(crate) fn get_refresh_command(schema_type: SchemaType) -> &'static str { match schema_type { @@ -23,31 +24,31 @@ pub(crate) fn get_refresh_command(schema_type: SchemaType) -> &'static str { pub(crate) struct FKeyTypeVal { pub(crate) key: i64, pub(crate) type_marker: i64, - pub(crate) val: FalkorValue, + pub(crate) val: redis::Value, } -impl TryFrom for FKeyTypeVal { +impl TryFrom for FKeyTypeVal { type Error = FalkorDBError; - fn try_from(value: FalkorValue) -> FalkorResult { - let [key_raw, type_raw, val]: [FalkorValue; 3] = - value.into_vec()?.try_into().map_err(|_| { + fn try_from(value: redis::Value) -> FalkorResult { + let [key_raw, type_raw, val]: [redis::Value; 3] = value + .into_sequence() + .ok() + .and_then(|seq| seq.try_into().ok()) + .ok_or({ FalkorDBError::ParsingArrayToStructElementCount( - "Expected exactly 3 elements for key-type-value property".to_string(), + "Expected exactly 3 elements for key-type-value property", ) })?; - let key = key_raw.to_i64(); - let type_marker = type_raw.to_i64(); - - match (key, type_marker) { - (Some(key), Some(type_marker)) => Ok(FKeyTypeVal { + match (redis_value_as_int(key_raw), redis_value_as_int(type_raw)) { + (Ok(key), Ok(type_marker)) => Ok(FKeyTypeVal { key, type_marker, val, }), - (Some(_), None) => Err(FalkorDBError::ParsingTypeMarkerTypeMismatch)?, - (None, Some(_)) => Err(FalkorDBError::ParsingKeyIdTypeMismatch)?, + (Ok(_), Err(_)) => Err(FalkorDBError::ParsingTypeMarkerTypeMismatch)?, + (Err(_), Ok(_)) => Err(FalkorDBError::ParsingKeyIdTypeMismatch)?, _ => Err(FalkorDBError::ParsingKTVTypes)?, } } @@ -132,6 +133,10 @@ impl GraphSchema { } } + #[cfg_attr( + feature = "tracing", + tracing::instrument(name = "Refresh Schema Type", skip_all) + )] fn refresh( &mut self, schema_type: SchemaType, @@ -143,7 +148,7 @@ impl GraphSchema { }; // This is essentially the call_procedure(), but can be done here without access to the graph(which would cause ownership issues) - let [_, keys, _]: [FalkorValue; 3] = self + let [_, keys, _]: [redis::Value; 3] = self .client .borrow_connection(self.client.clone())? .execute_command( @@ -152,30 +157,30 @@ impl GraphSchema { None, Some(&[format!("CALL {}()", get_refresh_command(schema_type)).as_str()]), )? - .into_vec()? + .into_sequence().map_err(|_| FalkorDBError::ParsingArray)? .try_into() .map_err(|_| { FalkorDBError::ParsingArrayToStructElementCount( "Expected exactly 3 types for header-resultset-stats response from refresh query" - .to_string(), ) })?; let new_keys = keys - .into_vec()? + .into_sequence() + .map_err(|_| FalkorDBError::ParsingArray)? .into_iter() .enumerate() .flat_map(|(idx, item)| { FalkorResult::<(i64, String)>::Ok(( idx as i64, - item.into_vec()? - .into_iter() - .next() + item.into_sequence() + .ok() + .and_then(|item_seq| item_seq.into_iter().next()) .ok_or(FalkorDBError::ParsingError( "Expected new label/property to be the first element in an array" .to_string(), )) - .and_then(|item| item.into_string())?, + .and_then(redis_value_as_string)?, )) }) .collect::>(); @@ -184,16 +189,20 @@ impl GraphSchema { Ok(()) } + #[cfg_attr( + feature = "tracing", + tracing::instrument(name = "Parse ID Vec To String Vec", skip_all) + )] pub(crate) fn parse_id_vec( &mut self, - raw_ids: Vec, + raw_ids: Vec, schema_type: SchemaType, ) -> FalkorResult> { let raw_ids_len = raw_ids.len(); raw_ids .into_iter() .try_fold(Vec::with_capacity(raw_ids_len), |mut acc, raw_id| { - let id = raw_id.to_i64().ok_or(FalkorDBError::ParsingI64)?; + let id = redis_value_as_int(raw_id)?; let value = match self .get_id_map_by_schema_type(schema_type) .get(&id) @@ -213,31 +222,37 @@ impl GraphSchema { }) } + #[cfg_attr( + feature = "tracing", + tracing::instrument(name = "Parse Properties Map", skip_all) + )] pub(crate) fn parse_properties_map( &mut self, - value: FalkorValue, + value: redis::Value, ) -> FalkorResult> { - let raw_properties_vec = value.into_vec()?; - let mut out_map = HashMap::with_capacity(raw_properties_vec.len()); - - for item in raw_properties_vec { - let ktv = FKeyTypeVal::try_from(item)?; - let key = match self.properties.get(&ktv.key).cloned() { - None => { - // Refresh, but this time when we try again, throw an error on failure + let raw_properties_vec = value + .into_sequence() + .map_err(|_| FalkorDBError::ParsingArray)?; + let raw_properties_len = raw_properties_vec.len(); + raw_properties_vec.into_iter().try_fold( + HashMap::with_capacity(raw_properties_len), + |mut out_map, item| { + let ktv = FKeyTypeVal::try_from(item)?; + let key = if let Some(key) = self.properties.get(&ktv.key).cloned() { + key + } else { + // Refresh the schema and attempt to retrieve the key again self.refresh(SchemaType::Properties)?; self.properties .get(&ktv.key) .cloned() .ok_or(FalkorDBError::MissingSchemaId(SchemaType::Properties))? - } - Some(key) => key, - }; - - out_map.insert(key, parse_type(ktv.type_marker, ktv.val, self)?); - } + }; - Ok(out_map) + out_map.insert(key, parse_type(ktv.type_marker, ktv.val, self)?); + Ok(out_map) + }, + ) } } @@ -272,10 +287,10 @@ pub(crate) mod tests { #[test] fn test_label_not_exists() { let mut parser = GraphSchema::new("graph_name".to_string(), create_empty_inner_client()); - let input_value = FalkorValue::Array(vec![FalkorValue::Array(vec![ - FalkorValue::I64(1), - FalkorValue::I64(2), - FalkorValue::String("test".to_string()), + let input_value = redis::Value::Bulk(vec![redis::Value::Bulk(vec![ + redis::Value::Int(1), + redis::Value::Int(2), + redis::Value::Status("test".to_string()), ])]); let result = parser.parse_properties_map(input_value); @@ -292,21 +307,21 @@ pub(crate) mod tests { ]); // Create a FalkorValue to test - let input_value = FalkorValue::Array(vec![ - FalkorValue::Array(vec![ - FalkorValue::I64(1), - FalkorValue::I64(2), - FalkorValue::String("test".to_string()), + let input_value = redis::Value::Bulk(vec![ + redis::Value::Bulk(vec![ + redis::Value::Int(1), + redis::Value::Int(2), + redis::Value::Status("test".to_string()), ]), - FalkorValue::Array(vec![ - FalkorValue::I64(2), - FalkorValue::I64(3), - FalkorValue::I64(42), + redis::Value::Bulk(vec![ + redis::Value::Int(2), + redis::Value::Int(3), + redis::Value::Int(42), ]), - FalkorValue::Array(vec![ - FalkorValue::I64(3), - FalkorValue::I64(4), - FalkorValue::Bool(true), + redis::Value::Bulk(vec![ + redis::Value::Int(3), + redis::Value::Int(4), + redis::Value::Status("true".to_string()), ]), ]); @@ -333,8 +348,14 @@ pub(crate) mod tests { (3, "property3".to_string()), ]); - let labels_ok_res = - parser.parse_id_vec(vec![3.into(), 1.into(), 2.into()], SchemaType::Labels); + let labels_ok_res = parser.parse_id_vec( + vec![ + redis::Value::Int(3), + redis::Value::Int(1), + redis::Value::Int(2), + ], + SchemaType::Labels, + ); assert!(labels_ok_res.is_ok()); assert_eq!( labels_ok_res.unwrap(), @@ -343,7 +364,11 @@ pub(crate) mod tests { // Should fail, these are not relationships let labels_not_ok_res = parser.parse_id_vec( - vec![3.into(), 1.into(), 2.into()], + vec![ + redis::Value::Int(3), + redis::Value::Int(1), + redis::Value::Int(2), + ], SchemaType::Relationships, ); assert!(labels_not_ok_res.is_err()); @@ -357,7 +382,11 @@ pub(crate) mod tests { ]); let rels_ok_res = parser.parse_id_vec( - vec![3.into(), 1.into(), 2.into()], + vec![ + redis::Value::Int(3), + redis::Value::Int(1), + redis::Value::Int(2), + ], SchemaType::Relationships, ); assert!(rels_ok_res.is_ok()); diff --git a/src/lib.rs b/src/lib.rs index e4acb0c..df89bf4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,9 +7,6 @@ #![deny(rustdoc::broken_intra_doc_links)] #![doc = include_str!("../README.md")] -#[cfg(not(feature = "redis"))] -compile_error!("The `redis` feature must be enabled."); - mod client; mod connection; mod connection_info; @@ -20,7 +17,6 @@ mod parser; mod response; mod value; -#[cfg(feature = "redis")] mod redis_ext; /// A [`Result`] which only returns [`FalkorDBError`] as its E type diff --git a/src/parser/utils.rs b/src/parser/utils.rs index f02b6fc..5610b20 100644 --- a/src/parser/utils.rs +++ b/src/parser/utils.rs @@ -3,57 +3,57 @@ * Licensed under the Server Side Public License v1 (SSPLv1). */ -use crate::{FalkorDBError, FalkorResult, FalkorValue}; +use crate::redis_ext::redis_value_as_string; +use crate::{FalkorDBError, FalkorResult}; -pub(crate) fn string_vec_from_val(value: FalkorValue) -> FalkorResult> { - value.into_vec().map(|value_as_vec| { - value_as_vec - .into_iter() - .flat_map(FalkorValue::into_string) - .collect() - }) +pub(crate) fn string_vec_from_val(value: redis::Value) -> FalkorResult> { + value + .into_sequence() + .map(|as_vec| as_vec.into_iter().flat_map(redis_value_as_string).collect()) + .map_err(|_| FalkorDBError::ParsingArray) } -pub(crate) fn parse_header(header: FalkorValue) -> FalkorResult> { - let in_vec = header.into_vec()?; - let in_vec_len = in_vec.len(); - in_vec - .into_iter() - .try_fold(Vec::with_capacity(in_vec_len), |mut acc, item| { - let item_vec = item.into_vec()?; - - acc.push( - if item_vec.len() == 2 { - let [_, key]: [FalkorValue; 2] = item_vec.try_into().map_err(|_| { - FalkorDBError::ParsingHeader( - "Could not get 2-sized array despite there being 2 elements" - .to_string(), - ) - })?; - key - } else { - item_vec - .into_iter() - .next() - .ok_or(FalkorDBError::ParsingHeader( - "Expected at least one item in header vector".to_string(), - ))? - } - .into_string()?, - ); - Ok(acc) +pub(crate) fn parse_header(header: redis::Value) -> FalkorResult> { + header.into_sequence().map_err(|_| FalkorDBError::ParsingArray) + .and_then(|in_vec| { + let in_vec_len = in_vec.len(); + in_vec + .into_iter() + .try_fold(Vec::with_capacity(in_vec_len), |mut acc, item| { + item.into_sequence().map_err(|_| FalkorDBError::ParsingArray) + .and_then(|item_vec| { + acc.push( + redis_value_as_string(if item_vec.len() == 2 { + let [_, key]: [redis::Value; 2] = + item_vec.try_into().map_err(|_| { + FalkorDBError::ParsingHeader( + "Could not get 2-sized array despite there being 2 elements" + ) + })?; + key + } else { + item_vec.into_iter().next().ok_or( + FalkorDBError::ParsingHeader( + "Expected at least one item in header vector" + ), + )? + })? + ); + Ok(acc) + }) + }) }) } #[cfg(test)] mod tests { use super::*; - use crate::{FalkorDBError, FalkorValue}; + use crate::FalkorDBError; #[test] fn test_parse_header_valid_single_key() { - let header = FalkorValue::Array(vec![FalkorValue::Array(vec![FalkorValue::String( - "key1".to_string(), + let header = redis::Value::Bulk(vec![redis::Value::Bulk(vec![redis::Value::Data( + "key1".as_bytes().to_vec(), )])]); let result = parse_header(header); assert!(result.is_ok()); @@ -62,12 +62,12 @@ mod tests { #[test] fn test_parse_header_valid_multiple_keys() { - let header = FalkorValue::Array(vec![ - FalkorValue::Array(vec![ - FalkorValue::String("type".to_string()), - FalkorValue::String("header1".to_string()), + let header = redis::Value::Bulk(vec![ + redis::Value::Bulk(vec![ + redis::Value::Data("type".as_bytes().to_vec()), + redis::Value::Data("header1".as_bytes().to_vec()), ]), - FalkorValue::Array(vec![FalkorValue::String("key2".to_string())]), + redis::Value::Bulk(vec![redis::Value::Data("key2".as_bytes().to_vec())]), ]); let result = parse_header(header); assert!(result.is_ok()); @@ -79,7 +79,7 @@ mod tests { #[test] fn test_parse_header_empty_header() { - let header = FalkorValue::Array(vec![]); + let header = redis::Value::Bulk(vec![]); let result = parse_header(header); assert!(result.is_ok()); assert_eq!(result.unwrap(), Vec::::new()); @@ -87,21 +87,21 @@ mod tests { #[test] fn test_parse_header_empty_vec() { - let header = FalkorValue::Array(vec![FalkorValue::Array(vec![])]); + let header = redis::Value::Bulk(vec![redis::Value::Bulk(vec![])]); let result = parse_header(header); assert!(result.is_err()); assert_eq!( result.unwrap_err(), - FalkorDBError::ParsingHeader("Expected at least one item in header vector".to_string()) + FalkorDBError::ParsingHeader("Expected at least one item in header vector") ); } #[test] fn test_parse_header_many_elements() { - let header = FalkorValue::Array(vec![FalkorValue::Array(vec![ - FalkorValue::String("just_some_header".to_string()), - FalkorValue::String("header1".to_string()), - FalkorValue::String("extra".to_string()), + let header = redis::Value::Bulk(vec![redis::Value::Bulk(vec![ + redis::Value::Data("just_some_header".as_bytes().to_vec()), + redis::Value::Data("header1".as_bytes().to_vec()), + redis::Value::Data("extra".as_bytes().to_vec()), ])]); let result = parse_header(header); assert!(result.is_ok()); diff --git a/src/redis_ext.rs b/src/redis_ext.rs index 88feba0..644979c 100644 --- a/src/redis_ext.rs +++ b/src/redis_ext.rs @@ -3,7 +3,7 @@ * Licensed under the Server Side Public License v1 (SSPLv1). */ -use crate::{ConfigValue, FalkorDBError, FalkorValue}; +use crate::{ConfigValue, FalkorDBError, FalkorResult, FalkorValue}; use redis::{FromRedisValue, RedisResult, RedisWrite, ToRedisArgs}; impl ToRedisArgs for ConfigValue { @@ -33,6 +33,20 @@ impl TryFrom<&redis::Value> for ConfigValue { } } +impl TryFrom for ConfigValue { + type Error = FalkorDBError; + + fn try_from(value: redis::Value) -> Result { + Ok(match value { + redis::Value::Int(int_val) => ConfigValue::Int64(int_val), + redis::Value::Data(str_data) => ConfigValue::String( + String::from_utf8(str_data).map_err(|_| FalkorDBError::ParsingFString)?, + ), + _ => return Err(FalkorDBError::InvalidDataReceived), + }) + } +} + impl FromRedisValue for FalkorValue { fn from_redis_value(v: &redis::Value) -> RedisResult { Ok(match v { @@ -51,3 +65,33 @@ impl FromRedisValue for FalkorValue { }) } } + +pub(crate) fn redis_value_as_string(value: redis::Value) -> FalkorResult { + match value { + redis::Value::Data(data) => { + String::from_utf8(data).map_err(|_| FalkorDBError::ParsingFString) + } + redis::Value::Status(status) => Ok(status), + _ => Err(FalkorDBError::ParsingFString), + } +} + +pub(crate) fn redis_value_as_int(value: redis::Value) -> FalkorResult { + match value { + redis::Value::Int(int_val) => Ok(int_val), + _ => Err(FalkorDBError::ParsingI64), + } +} + +pub(crate) fn redis_value_as_bool(value: redis::Value) -> FalkorResult { + redis_value_as_string(value).and_then(|string_val| match string_val.as_str() { + "true" => Ok(true), + "false" => Ok(false), + _ => Err(FalkorDBError::ParsingBool), + }) +} + +pub(crate) fn redis_value_as_double(value: redis::Value) -> FalkorResult { + redis_value_as_string(value) + .and_then(|string_val| string_val.parse().map_err(|_| FalkorDBError::ParsingF64)) +} diff --git a/src/response/constraint.rs b/src/response/constraint.rs index 8d1aadf..aeac04d 100644 --- a/src/response/constraint.rs +++ b/src/response/constraint.rs @@ -3,10 +3,8 @@ * Licensed under the Server Side Public License v1 (SSPLv1). */ -use crate::{ - value::utils::parse_type, EntityType, FalkorDBError, FalkorParsable, FalkorResult, FalkorValue, - GraphSchema, -}; +use crate::redis_ext::redis_value_as_string; +use crate::{EntityType, FalkorDBError, FalkorResult}; /// The type of restriction to apply for the property #[derive(Copy, Clone, Debug, Eq, PartialEq, strum::EnumString, strum::Display)] @@ -47,26 +45,24 @@ pub struct Constraint { pub status: ConstraintStatus, } -impl FalkorParsable for Constraint { - fn from_falkor_value( - value: FalkorValue, - graph_schema: &mut GraphSchema, - ) -> FalkorResult { - let value_vec = parse_type(6, value, graph_schema)?.into_vec()?; +impl Constraint { + pub(crate) fn parse(value: redis::Value) -> FalkorResult { + let [constraint_type_raw, label_raw, properties_raw, entity_type_raw, status_raw]: [redis::Value; 5] = value.into_sequence() + .map_err(|_| FalkorDBError::ParsingArray) + .and_then(|res| res.try_into() + .map_err(|_| FalkorDBError::ParsingArrayToStructElementCount("Expected exactly 5 elements in constraint object")))?; - let [constraint_type_raw, label_raw, properties_raw, entity_type_raw, status_raw]: [FalkorValue; 5] = value_vec.try_into() - .map_err(|_| FalkorDBError::ParsingArrayToStructElementCount("Expected exactly 5 elements in constraint object".to_string()))?; - - Ok(Constraint { - constraint_type: constraint_type_raw.into_string()?.as_str().try_into()?, - label: label_raw.into_string()?, + Ok(Self { + constraint_type: ConstraintType::try_from( + redis_value_as_string(constraint_type_raw)?.as_str(), + )?, + label: redis_value_as_string(label_raw)?, properties: properties_raw - .into_vec()? - .into_iter() - .flat_map(FalkorValue::into_string) - .collect(), - entity_type: entity_type_raw.into_string()?.as_str().try_into()?, - status: status_raw.into_string()?.as_str().try_into()?, + .into_sequence() + .map(|data| data.into_iter().flat_map(redis_value_as_string).collect()) + .map_err(|_| FalkorDBError::ParsingArray)?, + entity_type: EntityType::try_from(redis_value_as_string(entity_type_raw)?.as_str())?, + status: ConstraintStatus::try_from(redis_value_as_string(status_raw)?.as_str())?, }) } } diff --git a/src/response/execution_plan.rs b/src/response/execution_plan.rs index d410f78..96c7641 100644 --- a/src/response/execution_plan.rs +++ b/src/response/execution_plan.rs @@ -3,7 +3,8 @@ * Licensed under the Server Side Public License v1 (SSPLv1). */ -use crate::{FalkorDBError, FalkorResult, FalkorValue}; +use crate::redis_ext::redis_value_as_string; +use crate::{FalkorDBError, FalkorResult}; use regex::Regex; use std::cell::RefCell; use std::cmp::Ordering; @@ -168,26 +169,24 @@ impl ExecutionPlan { } } -impl TryFrom for ExecutionPlan { +impl TryFrom for ExecutionPlan { type Error = FalkorDBError; - fn try_from(value: FalkorValue) -> Result { - let execution_plan_operations: Vec<_> = value - .into_vec()? - .into_iter() - .flat_map(FalkorValue::into_string) - .collect(); + fn try_from(value: redis::Value) -> Result { + let redis_value_vec = value + .into_sequence() + .map_err(|_| FalkorDBError::ParsingArray)?; - let string_representation = ["".to_string()] - .into_iter() - .chain(execution_plan_operations.iter().cloned()) - .collect::>() - .join("\n"); + let mut string_representation = Vec::with_capacity(redis_value_vec.len() + 1); + string_representation.push("".to_string()); + let execution_plan_operations = Vec::with_capacity(redis_value_vec.len()); let mut current_traversal_stack = vec![]; - for node in execution_plan_operations.iter().map(String::as_str) { - let depth = node.matches(" ").count(); - let node = node.trim(); + for node in redis_value_vec { + let node_string = redis_value_as_string(node)?; + + let depth = node_string.matches(" ").count(); + let node = node_string.trim(); let current_node = match current_traversal_stack.last().cloned() { None => { @@ -231,6 +230,8 @@ impl TryFrom for ExecutionPlan { current_node.borrow_mut().children.push(new_node); } } + + string_representation.push(node_string); } // Must drop traversal stack first @@ -244,7 +245,7 @@ impl TryFrom for ExecutionPlan { Self::operations_map_from_tree(&operation_tree, &mut operations); Ok(ExecutionPlan { - string_representation, + string_representation: string_representation.join("\n"), plan: execution_plan_operations, operations, operation_tree, diff --git a/src/response/index.rs b/src/response/index.rs index ea391dd..0867634 100644 --- a/src/response/index.rs +++ b/src/response/index.rs @@ -3,10 +3,8 @@ * Licensed under the Server Side Public License v1 (SSPLv1). */ -use crate::{ - value::utils::{parse_type, parse_vec, type_val_from_value}, - EntityType, FalkorDBError, FalkorParsable, FalkorValue, GraphSchema, -}; +use crate::redis_ext::redis_value_as_string; +use crate::{EntityType, FalkorDBError}; use std::collections::HashMap; /// The status of this index @@ -33,21 +31,26 @@ pub enum IndexType { Fulltext, } -fn parse_types_map(value: FalkorValue) -> Result>, FalkorDBError> { - let value: HashMap = value.try_into()?; +fn parse_types_map(value: redis::Value) -> Result>, FalkorDBError> { + Ok(value + .into_map_iter() + .map_err(|_| FalkorDBError::ParsingFMap)? + .into_iter() + .filter_map(|(key, val)| { + let key_str = redis_value_as_string(key).ok()?; + let val_seq = val.into_sequence().ok()?; - let mut out_map = HashMap::with_capacity(value.len()); - for (key, val) in value.into_iter() { - let val = val.into_vec()?; - let mut field_types = Vec::with_capacity(val.len()); - for field_type in val { - field_types.push(field_type.into_string()?.as_str().try_into()?); - } + let index_types = val_seq + .into_iter() + .filter_map(|index_type| { + let index_str = redis_value_as_string(index_type).ok()?; + IndexType::try_from(index_str.as_str()).ok() + }) + .collect::>(); - out_map.insert(key, field_types); - } - - Ok(out_map) + Some((key_str, index_types)) + }) + .collect()) } /// Contains all the info regarding an index on the database @@ -68,40 +71,55 @@ pub struct FalkorIndex { /// Words to avoid indexing as they are very common and will just be a waste of resources pub stopwords: Vec, /// Various other information for querying by the user - pub info: HashMap, + pub info: HashMap, } impl FalkorIndex { - fn from_raw_values(items: Vec) -> Result { - let [label, fields, field_types, language, stopwords, entity_type, status, info]: [FalkorValue; 8] = items.try_into().map_err(|_| FalkorDBError::ParsingArrayToStructElementCount("Expected exactly 8 elements in index object".to_string()))?; + pub(crate) fn parse(value: redis::Value) -> Result { + let [label, fields, field_types, language, stopwords, entity_type, status, info] = value + .into_sequence() + .map_err(|_| FalkorDBError::ParsingArray) + .and_then(|as_vec| { + as_vec.try_into().map_err(|_| { + FalkorDBError::ParsingArrayToStructElementCount( + "Expected exactly 8 elements in index object", + ) + }) + })?; + + eprintln!("Got here: {label:?} \n{fields:?} \n{field_types:?} \n{language:?} \n{stopwords:?} \n{entity_type:?} \n{status:?} \n{info:?}"); Ok(Self { - entity_type: entity_type.into_string()?.as_str().try_into()?, - status: status.into_string()?.as_str().try_into()?, - index_label: label.try_into()?, - fields: parse_vec(fields)?, + entity_type: EntityType::try_from(redis_value_as_string(entity_type)?.as_str())?, + status: IndexStatus::try_from(redis_value_as_string(status)?.as_str())?, + index_label: redis_value_as_string(label)?, + fields: fields + .into_sequence() + .map(|fields| fields.into_iter().flat_map(redis_value_as_string).collect()) + .map_err(|_| FalkorDBError::ParsingArray)?, field_types: parse_types_map(field_types)?, - language: language.try_into()?, - stopwords: parse_vec(stopwords)?, - info: info.try_into()?, + language: redis_value_as_string(language)?, + stopwords: stopwords + .into_sequence() + .map(|stopwords| { + stopwords + .into_iter() + .flat_map(redis_value_as_string) + .collect() + }) + .map_err(|_| FalkorDBError::ParsingArray)?, + info: info + .into_map_iter() + .map(|map_iter| { + map_iter + .into_iter() + .flat_map(|(key, val)| { + redis_value_as_string(key) + .and_then(|key| redis_value_as_string(val).map(|val| (key, val))) + }) + .collect() + }) + .map_err(|_| FalkorDBError::ParsingFMap)?, }) } } - -impl FalkorParsable for FalkorIndex { - fn from_falkor_value( - value: FalkorValue, - graph_schema: &mut GraphSchema, - ) -> Result { - let semi_parsed_items = value - .into_vec()? - .into_iter() - .flat_map(|item| { - let (type_marker, val) = type_val_from_value(item)?; - parse_type(type_marker, val, graph_schema) - }) - .collect::>(); - - Self::from_raw_values(semi_parsed_items) - } -} diff --git a/src/response/lazy_result_set.rs b/src/response/lazy_result_set.rs index 6f13667..d3139a6 100644 --- a/src/response/lazy_result_set.rs +++ b/src/response/lazy_result_set.rs @@ -9,13 +9,17 @@ use std::collections::VecDeque; /// A wrapper around the returned raw data, allowing parsing on demand of each result /// This implements Iterator, so can simply be collect()'ed into any desired container pub struct LazyResultSet<'a> { - data: VecDeque, + data: VecDeque, graph_schema: &'a mut GraphSchema, } impl<'a> LazyResultSet<'a> { + #[cfg_attr( + feature = "tracing", + tracing::instrument(name = "Create New Lazy Result Set", skip_all) + )] pub(crate) fn new( - data: Vec, + data: Vec, graph_schema: &'a mut GraphSchema, ) -> Self { Self { @@ -38,6 +42,10 @@ impl<'a> LazyResultSet<'a> { impl<'a> Iterator for LazyResultSet<'a> { type Item = Vec; + #[cfg_attr( + feature = "tracing", + tracing::instrument(name = "Parse Next Result", skip_all) + )] fn next(&mut self) -> Option { self.data.pop_front().map(|current_result| { parse_type(6, current_result, self.graph_schema) diff --git a/src/response/mod.rs b/src/response/mod.rs index 031a2f3..7fb1063 100644 --- a/src/response/mod.rs +++ b/src/response/mod.rs @@ -5,7 +5,7 @@ use crate::{ parser::utils::{parse_header, string_vec_from_val}, - FalkorDBError, FalkorParsable, FalkorResult, FalkorValue, GraphSchema, + FalkorResult, }; pub(crate) mod constraint; @@ -29,13 +29,13 @@ impl FalkorResponse { /// Creates a [`FalkorResponse`] from the specified data, and raw stats, where raw headers are optional /// /// # Arguments - /// * `headers`: a [`FalkorValue`] that is expected to be of variant [`FalkorValue::Array`], where each element is expected to be of variant [`FalkorValue::String`] + /// * `headers`: a [`redis::Value`] that is expected to be of variant [`redis::Value::Bulk`], where each element is expected to be of variant [`redis::Value::Data`] or [`redis::Value::Status`] /// * `data`: The actual data - /// * `stats`: a [`FalkorValue`] that is expected to be of variant [`FalkorValue::Array`], where each element is expected to be of variant [`FalkorValue::String`] + /// * `stats`: a [`redis::Value`] that is expected to be of variant [`redis::Value::Bulk`], where each element is expected to be of variant [`redis::Value::Data`] or [`redis::Value::Status`] pub fn from_response( - headers: Option, + headers: Option, data: T, - stats: FalkorValue, + stats: redis::Value, ) -> FalkorResult { Ok(Self { header: match headers { @@ -47,27 +47,3 @@ impl FalkorResponse { }) } } - -impl FalkorParsable for FalkorResponse> { - fn from_falkor_value( - value: FalkorValue, - graph_schema: &mut GraphSchema, - ) -> FalkorResult { - let [header, indices, stats]: [FalkorValue; 3] = - value.into_vec()?.try_into().map_err(|_| { - FalkorDBError::ParsingArrayToStructElementCount( - "Expected exactly 3 elements in query response".to_string(), - ) - })?; - - FalkorResponse::from_response( - Some(header), - indices - .into_vec()? - .into_iter() - .flat_map(|index| FalkorParsable::from_falkor_value(index, graph_schema)) - .collect(), - stats, - ) - } -} diff --git a/src/response/slowlog_entry.rs b/src/response/slowlog_entry.rs index f425e2f..193bc5f 100644 --- a/src/response/slowlog_entry.rs +++ b/src/response/slowlog_entry.rs @@ -3,6 +3,7 @@ * Licensed under the Server Side Public License v1 (SSPLv1). */ +use crate::redis_ext::redis_value_as_string; use crate::{FalkorDBError, FalkorValue}; /// A slowlog entry, representing one of the N slowest queries in the current log @@ -25,7 +26,7 @@ impl TryFrom for SlowlogEntry { let [timestamp, command, arguments, time_taken] = value.into_vec()?.try_into().map_err(|_| { FalkorDBError::ParsingArrayToStructElementCount( - "Expected exactly 4 elements of slowlog entry".to_string(), + "Expected exactly 4 elements of slowlog entry", ) })?; @@ -43,3 +44,33 @@ impl TryFrom for SlowlogEntry { }) } } + +impl TryFrom for SlowlogEntry { + type Error = FalkorDBError; + + fn try_from(value: redis::Value) -> Result { + let [timestamp, command, arguments, time_taken]: [redis::Value; 4] = value + .into_sequence() + .map_err(|_| FalkorDBError::ParsingArray) + .and_then(|as_vec| { + as_vec.try_into().map_err(|_| { + FalkorDBError::ParsingArrayToStructElementCount( + "Expected exactly 4 elements of slowlog entry", + ) + }) + })?; + + Ok(Self { + timestamp: redis_value_as_string(timestamp) + .ok() + .and_then(|timestamp| timestamp.parse().ok()) + .ok_or(FalkorDBError::ParsingI64)?, + command: redis_value_as_string(command)?, + arguments: redis_value_as_string(arguments)?, + time_taken: redis_value_as_string(time_taken) + .ok() + .and_then(|time_taken| time_taken.parse().ok()) + .ok_or(FalkorDBError::ParsingF64)?, + }) + } +} diff --git a/src/value/graph_entities.rs b/src/value/graph_entities.rs index ddd742f..3531e58 100644 --- a/src/value/graph_entities.rs +++ b/src/value/graph_entities.rs @@ -3,8 +3,9 @@ * Licensed under the Server Side Public License v1 (SSPLv1). */ -use crate::{FalkorDBError, FalkorParsable, FalkorResult, FalkorValue, GraphSchema, SchemaType}; -use std::collections::{HashMap, HashSet}; +use crate::redis_ext::redis_value_as_int; +use crate::{FalkorDBError, FalkorResult, FalkorValue, GraphSchema, SchemaType}; +use std::collections::HashMap; /// Whether this element is a node or edge in the graph #[derive(Copy, Clone, Debug, Eq, PartialEq, strum::EnumString, strum::Display)] @@ -28,30 +29,32 @@ pub struct Node { pub properties: HashMap, } -impl FalkorParsable for Node { - fn from_falkor_value( - value: FalkorValue, +impl Node { + #[cfg_attr( + feature = "tracing", + tracing::instrument(name = "Parse Node", skip_all) + )] + pub(crate) fn parse( + value: redis::Value, graph_schema: &mut GraphSchema, ) -> FalkorResult { - let [entity_id, labels, properties]: [FalkorValue; 3] = - value.into_vec()?.try_into().map_err(|_| { + let [entity_id, labels, properties]: [redis::Value; 3] = value + .into_sequence() + .map_err(|_| FalkorDBError::ParsingArray)? + .try_into() + .map_err(|_| { FalkorDBError::ParsingArrayToStructElementCount( - "Expected exactly 3 elements in node object".to_string(), + "Expected exactly 3 elements in node object", ) })?; - let labels = labels.into_vec()?; - - let mut ids_hashset = HashSet::with_capacity(labels.len()); - for label in labels.iter() { - ids_hashset.insert( - label - .to_i64() - .ok_or(FalkorDBError::ParsingCompactIdUnknown)?, - ); - } Ok(Node { - entity_id: entity_id.to_i64().ok_or(FalkorDBError::ParsingI64)?, - labels: graph_schema.parse_id_vec(labels, SchemaType::Labels)?, + entity_id: redis_value_as_int(entity_id)?, + labels: graph_schema.parse_id_vec( + labels + .into_sequence() + .map_err(|_| FalkorDBError::ParsingArray)?, + SchemaType::Labels, + )?, properties: graph_schema.parse_properties_map(properties)?, }) } @@ -72,29 +75,36 @@ pub struct Edge { pub properties: HashMap, } -impl FalkorParsable for Edge { - fn from_falkor_value( - value: FalkorValue, +impl Edge { + #[cfg_attr( + feature = "tracing", + tracing::instrument(name = "Parse Edge", skip_all) + )] + pub(crate) fn parse( + value: redis::Value, graph_schema: &mut GraphSchema, ) -> FalkorResult { - let [entity_id, relations, src_node_id, dst_node_id, properties]: [FalkorValue; 5] = - value.into_vec()?.try_into().map_err(|_| { + let [entity_id, relationship_id_raw, src_node_id, dst_node_id, properties]: [redis::Value; + 5] = value + .into_sequence() + .map_err(|_| FalkorDBError::ParsingArray)? + .try_into() + .map_err(|_| { FalkorDBError::ParsingArrayToStructElementCount( - "Expected exactly 5 elements in edge object".to_string(), + "Expected exactly 5 elements in edge object", ) })?; - let relation = relations.to_i64().ok_or(FalkorDBError::ParsingI64)?; let relationship = graph_schema .relationships() - .get(&relation) + .get(&redis_value_as_int(relationship_id_raw)?) .ok_or(FalkorDBError::MissingSchemaId(SchemaType::Relationships))?; Ok(Edge { - entity_id: entity_id.to_i64().ok_or(FalkorDBError::ParsingI64)?, + entity_id: redis_value_as_int(entity_id)?, relationship_type: relationship.to_string(), - src_node_id: src_node_id.to_i64().ok_or(FalkorDBError::ParsingI64)?, - dst_node_id: dst_node_id.to_i64().ok_or(FalkorDBError::ParsingI64)?, + src_node_id: redis_value_as_int(src_node_id)?, + dst_node_id: redis_value_as_int(dst_node_id)?, properties: graph_schema.parse_properties_map(properties)?, }) } diff --git a/src/value/map.rs b/src/value/map.rs deleted file mode 100644 index e6fd140..0000000 --- a/src/value/map.rs +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright FalkorDB Ltd. 2023 - present - * Licensed under the Server Side Public License v1 (SSPLv1). - */ - -use crate::{ - value::utils::parse_type, FalkorDBError, FalkorParsable, FalkorResult, FalkorValue, GraphSchema, -}; -use std::collections::HashMap; - -impl FalkorParsable for HashMap { - fn from_falkor_value( - value: FalkorValue, - graph_schema: &mut GraphSchema, - ) -> FalkorResult { - let val_vec = value.into_vec()?; - if val_vec.len() % 2 != 0 { - Err(FalkorDBError::ParsingFMap( - "Map should have an even amount of elements".to_string(), - ))?; - } - - let mut out_map = HashMap::with_capacity(val_vec.len()); - for pair in val_vec.chunks_exact(2) { - let [key, val]: [FalkorValue; 2] = pair.to_vec().try_into().map_err(|_| { - FalkorDBError::ParsingFMap( - "The vec returned from using chunks_exact(2) should be comprised of 2 elements" - .to_string(), - ) - })?; - - let [type_marker, val]: [FalkorValue; 2] = - val.into_vec()?.try_into().map_err(|_| { - FalkorDBError::ParsingFMap( - "The value in a map should be comprised of a type marker and value" - .to_string(), - ) - })?; - - out_map.insert( - key.into_string()?, - parse_type( - type_marker.to_i64().ok_or(FalkorDBError::ParsingKTVTypes)?, - val, - graph_schema, - )?, - ); - } - - Ok(out_map) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{client::blocking::create_empty_inner_client, GraphSchema}; - - #[test] - fn test_not_a_vec() { - let mut graph_schema = GraphSchema::new("test_graph", create_empty_inner_client()); - - let res: FalkorResult> = FalkorParsable::from_falkor_value( - FalkorValue::String("Hello".to_string()), - &mut graph_schema, - ); - - assert!(res.is_err()) - } - - #[test] - fn test_vec_odd_element_count() { - let mut graph_schema = GraphSchema::new("test_graph", create_empty_inner_client()); - - let res: FalkorResult> = FalkorParsable::from_falkor_value( - FalkorValue::Array(vec![FalkorValue::None; 7]), - &mut graph_schema, - ); - - assert!(res.is_err()) - } - - #[test] - fn test_val_element_is_not_array() { - let mut graph_schema = GraphSchema::new("test_graph", create_empty_inner_client()); - - let res: FalkorResult> = FalkorParsable::from_falkor_value( - FalkorValue::Array(vec![ - FalkorValue::String("Key".to_string()), - FalkorValue::Bool(false), - ]), - &mut graph_schema, - ); - - assert!(res.is_err()) - } - - #[test] - fn test_val_element_has_only_1_element() { - let mut graph_schema = GraphSchema::new("test_graph", create_empty_inner_client()); - - let res: FalkorResult> = FalkorParsable::from_falkor_value( - FalkorValue::Array(vec![ - FalkorValue::String("Key".to_string()), - FalkorValue::Array(vec![FalkorValue::I64(7)]), - ]), - &mut graph_schema, - ); - - assert!(res.is_err()) - } - - #[test] - fn test_val_element_has_ge_2_elements() { - let mut graph_schema = GraphSchema::new("test_graph", create_empty_inner_client()); - - let res: FalkorResult> = FalkorParsable::from_falkor_value( - FalkorValue::Array(vec![ - FalkorValue::String("Key".to_string()), - FalkorValue::Array(vec![FalkorValue::I64(3); 3]), - ]), - &mut graph_schema, - ); - - assert!(res.is_err()) - } - - #[test] - fn test_val_element_mismatch_type_marker() { - let mut graph_schema = GraphSchema::new("test_graph", create_empty_inner_client()); - - let res: FalkorResult> = FalkorParsable::from_falkor_value( - FalkorValue::Array(vec![ - FalkorValue::String("Key".to_string()), - FalkorValue::Array(vec![FalkorValue::I64(3), FalkorValue::Bool(true)]), - ]), - &mut graph_schema, - ); - - assert!(res.is_err()) - } - - #[test] - fn test_ok_values() { - let mut graph_schema = GraphSchema::new("test_graph", create_empty_inner_client()); - - let res: HashMap = FalkorParsable::from_falkor_value( - FalkorValue::Array(vec![ - FalkorValue::String("IntKey".to_string()), - FalkorValue::Array(vec![FalkorValue::I64(3), FalkorValue::I64(1)]), - FalkorValue::String("BoolKey".to_string()), - FalkorValue::Array(vec![FalkorValue::I64(4), FalkorValue::Bool(true)]), - ]), - &mut graph_schema, - ) - .expect("Could not parse map"); - - assert_eq!(res.get("IntKey"), Some(FalkorValue::I64(1)).as_ref()); - assert_eq!(res.get("BoolKey"), Some(FalkorValue::Bool(true)).as_ref()); - } -} diff --git a/src/value/mod.rs b/src/value/mod.rs index 8527b4f..b24221f 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -11,7 +11,6 @@ use std::{collections::HashMap, fmt::Debug}; pub(crate) mod config; pub(crate) mod graph_entities; -pub(crate) mod map; pub(crate) mod path; pub(crate) mod point; pub(crate) mod utils; @@ -94,7 +93,7 @@ impl TryFrom for Vec { fn try_from(value: FalkorValue) -> FalkorResult { match value { FalkorValue::Array(val) => Ok(val), - _ => Err(FalkorDBError::ParsingFArray), + _ => Err(FalkorDBError::ParsingArray), } } } @@ -161,9 +160,7 @@ impl TryFrom for HashMap { fn try_from(value: FalkorValue) -> FalkorResult { match value { FalkorValue::Map(map) => Ok(map), - _ => Err(FalkorDBError::ParsingFMap( - "Attempting to get a non-map element as a map".to_string(), - )), + _ => Err(FalkorDBError::ParsingFMap), } } } @@ -180,7 +177,7 @@ impl TryFrom for Point { } impl FalkorValue { - /// Returns a reference to the internal [`Vec`] if this is an FArray variant. + /// Returns a reference to the internal [`Vec`] if this is an Array variant. /// /// # Returns /// A reference to the internal [`Vec`] @@ -295,7 +292,7 @@ impl FalkorValue { } } - /// Consumes itself and returns the inner [`Vec`] if this is an FArray variant + /// Consumes itself and returns the inner [`Vec`] if this is an Array variant /// /// # Returns /// The inner [`Vec`] diff --git a/src/value/path.rs b/src/value/path.rs index 6f499fe..8e561a7 100644 --- a/src/value/path.rs +++ b/src/value/path.rs @@ -3,7 +3,7 @@ * Licensed under the Server Side Public License v1 (SSPLv1). */ -use crate::{Edge, FalkorDBError, FalkorParsable, FalkorResult, FalkorValue, GraphSchema, Node}; +use crate::{Edge, FalkorDBError, FalkorResult, GraphSchema, Node}; /// Represents a path between two nodes, contains all the nodes, and the relationships between them along the path #[derive(Clone, Debug, Default, PartialEq)] @@ -14,28 +14,33 @@ pub struct Path { pub relationships: Vec, } -impl FalkorParsable for Path { - fn from_falkor_value( - value: FalkorValue, +impl Path { + pub(crate) fn parse( + value: redis::Value, graph_schema: &mut GraphSchema, ) -> FalkorResult { - let [nodes, relationships]: [FalkorValue; 2] = - value.into_vec()?.try_into().map_err(|_| { + let [nodes, relationships]: [redis::Value; 2] = value + .into_sequence() + .map_err(|_| FalkorDBError::ParsingArray)? + .try_into() + .map_err(|_| { FalkorDBError::ParsingArrayToStructElementCount( - "Expected exactly 2 elements for path".to_string(), + "Expected exactly 2 elements for path", ) })?; Ok(Self { nodes: nodes - .into_vec()? + .into_sequence() + .map_err(|_| FalkorDBError::ParsingArray)? .into_iter() - .flat_map(|node| Node::from_falkor_value(node, graph_schema)) + .flat_map(|node| Node::parse(node, graph_schema)) .collect(), relationships: relationships - .into_vec()? + .into_sequence() + .map_err(|_| FalkorDBError::ParsingArray)? .into_iter() - .flat_map(|edge| Edge::from_falkor_value(edge, graph_schema)) + .flat_map(|edge| Edge::parse(edge, graph_schema)) .collect(), }) } diff --git a/src/value/point.rs b/src/value/point.rs index afa1a54..ea56730 100644 --- a/src/value/point.rs +++ b/src/value/point.rs @@ -3,7 +3,8 @@ * Licensed under the Server Side Public License v1 (SSPLv1). */ -use crate::{FalkorDBError, FalkorResult, FalkorValue}; +use crate::redis_ext::redis_value_as_double; +use crate::{FalkorDBError, FalkorResult}; /// A point in the world. #[derive(Clone, Debug, Default, PartialEq)] @@ -15,7 +16,7 @@ pub struct Point { } impl Point { - /// Parses a point from a FalkorValue::Array, + /// Parses a point from a redis::Value::Bulk, /// taking the first element as an f64 latitude, and second element as an f64 longitude /// /// # Arguments @@ -23,16 +24,20 @@ impl Point { /// /// # Returns /// Self, if successful - pub fn parse(value: FalkorValue) -> FalkorResult { - let [lat, long]: [FalkorValue; 2] = value.into_vec()?.try_into().map_err(|_| { - FalkorDBError::ParsingArrayToStructElementCount( - "Expected exactly 2 element in point - latitude and longitude".to_string(), - ) - })?; + pub fn parse(value: redis::Value) -> FalkorResult { + let [lat, long]: [redis::Value; 2] = value + .into_sequence() + .map_err(|_| FalkorDBError::ParsingArray)? + .try_into() + .map_err(|_| { + FalkorDBError::ParsingArrayToStructElementCount( + "Expected exactly 2 element in point - latitude and longitude", + ) + })?; Ok(Point { - latitude: lat.to_f64().ok_or(FalkorDBError::ParsingF64)?, - longitude: long.to_f64().ok_or(FalkorDBError::ParsingF64)?, + latitude: redis_value_as_double(lat)?, + longitude: redis_value_as_double(long)?, }) } } @@ -43,7 +48,10 @@ mod tests { #[test] fn test_parse_valid_point() { - let value = FalkorValue::Array(vec![FalkorValue::F64(45.0), FalkorValue::F64(90.0)]); + let value = redis::Value::Bulk(vec![ + redis::Value::Status("45.0".to_string()), + redis::Value::Status("90.0".to_string()), + ]); let result = Point::parse(value); assert!(result.is_ok()); let point = result.unwrap(); @@ -53,7 +61,7 @@ mod tests { #[test] fn test_parse_invalid_point_missing_elements() { - let value = FalkorValue::Array(vec![FalkorValue::F64(45.0)]); + let value = redis::Value::Bulk(vec![redis::Value::Status("45.0".to_string())]); let result = Point::parse(value); assert!(result.is_err()); match result { @@ -69,10 +77,10 @@ mod tests { #[test] fn test_parse_invalid_point_extra_elements() { - let value = FalkorValue::Array(vec![ - FalkorValue::F64(45.0), - FalkorValue::F64(90.0), - FalkorValue::F64(30.0), + let value = redis::Value::Bulk(vec![ + redis::Value::Status("45.0".to_string()), + redis::Value::Status("90.0".to_string()), + redis::Value::Status("30.0".to_string()), ]); let result = Point::parse(value); assert!(result.is_err()); @@ -87,23 +95,9 @@ mod tests { } } - #[test] - fn test_parse_invalid_point_non_f64_elements() { - let value = FalkorValue::Array(vec![ - FalkorValue::String("45.0".to_string()), - FalkorValue::String("90.0".to_string()), - ]); - let result = Point::parse(value); - assert!(result.is_err()); - match result { - Err(FalkorDBError::ParsingF64) => {} - _ => panic!("Expected ParsingF64 error"), - } - } - #[test] fn test_parse_invalid_point_not_an_array() { - let value = FalkorValue::String("not an array".to_string()); + let value = redis::Value::Status("not an array".to_string()); let result = Point::parse(value); assert!(result.is_err()); // Check for the specific error type if needed diff --git a/src/value/utils.rs b/src/value/utils.rs index cefd39d..c137004 100644 --- a/src/value/utils.rs +++ b/src/value/utils.rs @@ -3,32 +3,68 @@ * Licensed under the Server Side Public License v1 (SSPLv1). */ -use crate::{FalkorDBError, FalkorParsable, FalkorValue, GraphSchema, Point}; +use crate::redis_ext::{ + redis_value_as_bool, redis_value_as_double, redis_value_as_int, redis_value_as_string, +}; +use crate::{Edge, FalkorDBError, FalkorResult, FalkorValue, GraphSchema, Node, Path, Point}; +use std::collections::HashMap; -pub(crate) fn type_val_from_value(value: FalkorValue) -> Result<(i64, FalkorValue), FalkorDBError> { - let [type_marker, val]: [FalkorValue; 2] = value.into_vec()?.try_into().map_err(|_| { - FalkorDBError::ParsingArrayToStructElementCount( - "Expected exactly 2 elements: type marker, and value".to_string(), - ) - })?; - let type_marker = type_marker.to_i64().ok_or(FalkorDBError::ParsingI64)?; +pub(crate) fn type_val_from_value( + value: redis::Value +) -> Result<(i64, redis::Value), FalkorDBError> { + let [type_marker, val]: [redis::Value; 2] = value + .into_sequence() + .map_err(|_| FalkorDBError::ParsingArray)? + .try_into() + .map_err(|_| { + FalkorDBError::ParsingArrayToStructElementCount( + "Expected exactly 2 elements: type marker, and value", + ) + })?; + + Ok((redis_value_as_int(type_marker)?, val)) +} + +fn parse_regular_falkor_map( + value: redis::Value, + graph_schema: &mut GraphSchema, +) -> FalkorResult> { + value + .into_map_iter() + .map_err(|_| FalkorDBError::ParsingFMap)? + .try_fold(HashMap::new(), |mut out_map, (key, val)| { + let [type_marker, val]: [redis::Value; 2] = val + .into_sequence() + .ok() + .and_then(|val_seq| val_seq.try_into().ok()) + .ok_or(FalkorDBError::ParsingFMap)?; - Ok((type_marker, val)) + out_map.insert( + redis_value_as_string(key)?, + parse_type(redis_value_as_int(type_marker)?, val, graph_schema)?, + ); + Ok(out_map) + }) } +#[cfg_attr( + feature = "tracing", + tracing::instrument(name = "Parse Element With Type Marker", skip_all) +)] pub(crate) fn parse_type( type_marker: i64, - val: FalkorValue, + val: redis::Value, graph_schema: &mut GraphSchema, ) -> Result { let res = match type_marker { 1 => FalkorValue::None, - 2 => FalkorValue::String(val.into_string()?), - 3 => FalkorValue::I64(val.to_i64().ok_or(FalkorDBError::ParsingI64)?), - 4 => FalkorValue::Bool(val.to_bool().ok_or(FalkorDBError::ParsingBool)?), - 5 => FalkorValue::F64(val.try_into()?), + 2 => FalkorValue::String(redis_value_as_string(val)?), + 3 => FalkorValue::I64(redis_value_as_int(val)?), + 4 => FalkorValue::Bool(redis_value_as_bool(val)?), + 5 => FalkorValue::F64(redis_value_as_double(val)?), 6 => FalkorValue::Array( - val.into_vec()? + val.into_sequence() + .map_err(|_| FalkorDBError::ParsingArray)? .into_iter() .flat_map(|item| { type_val_from_value(item) @@ -37,10 +73,10 @@ pub(crate) fn parse_type( .collect(), ), // The following types are sent as an array and require specific parsing functions - 7 => FalkorValue::Edge(FalkorParsable::from_falkor_value(val, graph_schema)?), - 8 => FalkorValue::Node(FalkorParsable::from_falkor_value(val, graph_schema)?), - 9 => FalkorValue::Path(FalkorParsable::from_falkor_value(val, graph_schema)?), - 10 => FalkorValue::Map(FalkorParsable::from_falkor_value(val, graph_schema)?), + 7 => FalkorValue::Edge(Edge::parse(val, graph_schema)?), + 8 => FalkorValue::Node(Node::parse(val, graph_schema)?), + 9 => FalkorValue::Path(Path::parse(val, graph_schema)?), + 10 => FalkorValue::Map(parse_regular_falkor_map(val, graph_schema)?), 11 => FalkorValue::Point(Point::parse(val)?), _ => Err(FalkorDBError::ParsingUnknownType)?, }; @@ -48,20 +84,13 @@ pub(crate) fn parse_type( Ok(res) } -pub(crate) fn parse_vec>( - value: FalkorValue -) -> Result, FalkorDBError> { - Ok(value - .into_vec()? - .into_iter() - .flat_map(TryFrom::try_from) - .collect()) -} - #[cfg(test)] mod tests { use super::*; - use crate::graph_schema::tests::open_readonly_graph_with_modified_schema; + use crate::{ + client::blocking::create_empty_inner_client, + graph_schema::tests::open_readonly_graph_with_modified_schema, + }; #[test] fn test_parse_edge() { @@ -69,21 +98,21 @@ mod tests { let res = parse_type( 7, - FalkorValue::Array(vec![ - FalkorValue::I64(100), // edge id - FalkorValue::I64(0), // edge type - FalkorValue::I64(51), // src node - FalkorValue::I64(52), // dst node - FalkorValue::Array(vec![ - FalkorValue::Array(vec![ - FalkorValue::I64(0), - FalkorValue::I64(3), - FalkorValue::I64(20), + redis::Value::Bulk(vec![ + redis::Value::Int(100), // edge id + redis::Value::Int(0), // edge type + redis::Value::Int(51), // src node + redis::Value::Int(52), // dst node + redis::Value::Bulk(vec![ + redis::Value::Bulk(vec![ + redis::Value::Int(0), + redis::Value::Int(3), + redis::Value::Int(20), ]), - FalkorValue::Array(vec![ - FalkorValue::I64(1), - FalkorValue::I64(4), - FalkorValue::Bool(false), + redis::Value::Bulk(vec![ + redis::Value::Int(1), + redis::Value::Int(4), + redis::Value::Status("false".to_string()), ]), ]), ]), @@ -105,7 +134,7 @@ mod tests { assert_eq!(edge.properties.get("age"), Some(&FalkorValue::I64(20))); assert_eq!( edge.properties.get("is_boring"), - Some(&FalkorValue::Bool(false)) + Some(&FalkorValue::String("false".to_string())) ); } @@ -115,24 +144,24 @@ mod tests { let res = parse_type( 8, - FalkorValue::Array(vec![ - FalkorValue::I64(51), // node id - FalkorValue::Array(vec![FalkorValue::I64(0), FalkorValue::I64(1)]), // node type - FalkorValue::Array(vec![ - FalkorValue::Array(vec![ - FalkorValue::I64(0), - FalkorValue::I64(3), - FalkorValue::I64(15), + redis::Value::Bulk(vec![ + redis::Value::Int(51), // node id + redis::Value::Bulk(vec![redis::Value::Int(0), redis::Value::Int(1)]), // node type + redis::Value::Bulk(vec![ + redis::Value::Bulk(vec![ + redis::Value::Int(0), + redis::Value::Int(3), + redis::Value::Int(15), ]), - FalkorValue::Array(vec![ - FalkorValue::I64(2), - FalkorValue::I64(2), - FalkorValue::String("the something".to_string()), + redis::Value::Bulk(vec![ + redis::Value::Int(2), + redis::Value::Int(2), + redis::Value::Status("the something".to_string()), ]), - FalkorValue::Array(vec![ - FalkorValue::I64(3), - FalkorValue::I64(5), - FalkorValue::F64(105.5), + redis::Value::Bulk(vec![ + redis::Value::Int(3), + redis::Value::Int(5), + redis::Value::Status("105.5".to_string()), ]), ]), ]), @@ -155,7 +184,7 @@ mod tests { ); assert_eq!( node.properties.get("secs_since_login"), - Some(&FalkorValue::F64(105.5)) + Some(&FalkorValue::String("105.5".to_string())) ); } @@ -165,38 +194,38 @@ mod tests { let res = parse_type( 9, - FalkorValue::Array(vec![ - FalkorValue::Array(vec![ - FalkorValue::Array(vec![ - FalkorValue::I64(51), - FalkorValue::Array(vec![FalkorValue::I64(0)]), - FalkorValue::Array(vec![]), + redis::Value::Bulk(vec![ + redis::Value::Bulk(vec![ + redis::Value::Bulk(vec![ + redis::Value::Int(51), + redis::Value::Bulk(vec![redis::Value::Int(0)]), + redis::Value::Bulk(vec![]), ]), - FalkorValue::Array(vec![ - FalkorValue::I64(52), - FalkorValue::Array(vec![FalkorValue::I64(0)]), - FalkorValue::Array(vec![]), + redis::Value::Bulk(vec![ + redis::Value::Int(52), + redis::Value::Bulk(vec![redis::Value::Int(0)]), + redis::Value::Bulk(vec![]), ]), - FalkorValue::Array(vec![ - FalkorValue::I64(53), - FalkorValue::Array(vec![FalkorValue::I64(0)]), - FalkorValue::Array(vec![]), + redis::Value::Bulk(vec![ + redis::Value::Int(53), + redis::Value::Bulk(vec![redis::Value::Int(0)]), + redis::Value::Bulk(vec![]), ]), ]), - FalkorValue::Array(vec![ - FalkorValue::Array(vec![ - FalkorValue::I64(100), - FalkorValue::I64(0), - FalkorValue::I64(51), - FalkorValue::I64(52), - FalkorValue::Array(vec![]), + redis::Value::Bulk(vec![ + redis::Value::Bulk(vec![ + redis::Value::Int(100), + redis::Value::Int(0), + redis::Value::Int(51), + redis::Value::Int(52), + redis::Value::Bulk(vec![]), ]), - FalkorValue::Array(vec![ - FalkorValue::I64(101), - FalkorValue::I64(1), - FalkorValue::I64(52), - FalkorValue::I64(53), - FalkorValue::Array(vec![]), + redis::Value::Bulk(vec![ + redis::Value::Int(101), + redis::Value::Int(1), + redis::Value::Int(52), + redis::Value::Int(53), + redis::Value::Bulk(vec![]), ]), ]), ]), @@ -231,16 +260,19 @@ mod tests { let res = parse_type( 10, - FalkorValue::Array(vec![ - FalkorValue::String("key0".to_string()), - FalkorValue::Array(vec![ - FalkorValue::I64(2), - FalkorValue::String("val0".to_string()), + redis::Value::Bulk(vec![ + redis::Value::Status("key0".to_string()), + redis::Value::Bulk(vec![ + redis::Value::Int(2), + redis::Value::Status("val0".to_string()), + ]), + redis::Value::Status("key1".to_string()), + redis::Value::Bulk(vec![redis::Value::Int(3), redis::Value::Int(1)]), + redis::Value::Status("key2".to_string()), + redis::Value::Bulk(vec![ + redis::Value::Int(4), + redis::Value::Status("true".to_string()), ]), - FalkorValue::String("key1".to_string()), - FalkorValue::Array(vec![FalkorValue::I64(3), FalkorValue::I64(1)]), - FalkorValue::String("key2".to_string()), - FalkorValue::Array(vec![FalkorValue::I64(4), FalkorValue::Bool(true)]), ]), &mut graph.graph_schema, ); @@ -257,7 +289,10 @@ mod tests { Some(&FalkorValue::String("val0".to_string())) ); assert_eq!(map.get("key1"), Some(&FalkorValue::I64(1))); - assert_eq!(map.get("key2"), Some(&FalkorValue::Bool(true))); + assert_eq!( + map.get("key2"), + Some(&FalkorValue::String("true".to_string())) + ); } #[test] @@ -266,7 +301,10 @@ mod tests { let res = parse_type( 11, - FalkorValue::Array(vec![FalkorValue::F64(102.0), FalkorValue::F64(15.2)]), + redis::Value::Bulk(vec![ + redis::Value::Status("102.0".to_string()), + redis::Value::Status("15.2".to_string()), + ]), &mut graph.graph_schema, ); assert!(res.is_ok()); @@ -278,4 +316,114 @@ mod tests { assert_eq!(point.latitude, 102.0); assert_eq!(point.longitude, 15.2); } + + #[test] + fn test_map_not_a_vec() { + let mut graph_schema = GraphSchema::new("test_graph", create_empty_inner_client()); + + let res = + parse_regular_falkor_map(redis::Value::Status("Hello".to_string()), &mut graph_schema); + + assert!(res.is_err()) + } + + #[test] + fn test_map_vec_odd_element_count() { + let mut graph_schema = GraphSchema::new("test_graph", create_empty_inner_client()); + + let res = parse_regular_falkor_map( + redis::Value::Bulk(vec![redis::Value::Nil; 7]), + &mut graph_schema, + ); + + assert!(res.is_err()) + } + + #[test] + fn test_map_val_element_is_not_array() { + let mut graph_schema = GraphSchema::new("test_graph", create_empty_inner_client()); + + let res = parse_regular_falkor_map( + redis::Value::Bulk(vec![ + redis::Value::Status("Key".to_string()), + redis::Value::Status("false".to_string()), + ]), + &mut graph_schema, + ); + + assert!(res.is_err()) + } + + #[test] + fn test_map_val_element_has_only_1_element() { + let mut graph_schema = GraphSchema::new("test_graph", create_empty_inner_client()); + + let res = parse_regular_falkor_map( + redis::Value::Bulk(vec![ + redis::Value::Status("Key".to_string()), + redis::Value::Bulk(vec![redis::Value::Int(7)]), + ]), + &mut graph_schema, + ); + + assert!(res.is_err()) + } + + #[test] + fn test_map_val_element_has_ge_2_elements() { + let mut graph_schema = GraphSchema::new("test_graph", create_empty_inner_client()); + + let res = parse_regular_falkor_map( + redis::Value::Bulk(vec![ + redis::Value::Status("Key".to_string()), + redis::Value::Bulk(vec![redis::Value::Int(3); 3]), + ]), + &mut graph_schema, + ); + + assert!(res.is_err()) + } + + #[test] + fn test_map_val_element_mismatch_type_marker() { + let mut graph_schema = GraphSchema::new("test_graph", create_empty_inner_client()); + + let res = parse_regular_falkor_map( + redis::Value::Bulk(vec![ + redis::Value::Status("Key".to_string()), + redis::Value::Bulk(vec![ + redis::Value::Int(3), + redis::Value::Status("true".to_string()), + ]), + ]), + &mut graph_schema, + ); + + assert!(res.is_err()) + } + + #[test] + fn test_map_ok_values() { + let mut graph_schema = GraphSchema::new("test_graph", create_empty_inner_client()); + + let res = parse_regular_falkor_map( + redis::Value::Bulk(vec![ + redis::Value::Status("IntKey".to_string()), + redis::Value::Bulk(vec![redis::Value::Int(3), redis::Value::Int(1)]), + redis::Value::Status("BoolKey".to_string()), + redis::Value::Bulk(vec![ + redis::Value::Int(4), + redis::Value::Status("true".to_string()), + ]), + ]), + &mut graph_schema, + ) + .expect("Could not parse map"); + + assert_eq!(res.get("IntKey"), Some(FalkorValue::I64(1)).as_ref()); + assert_eq!( + res.get("BoolKey"), + Some(FalkorValue::String("true".to_string())).as_ref() + ); + } } From 7dc0087ab680bceeceeea9797f82b604aa58c663 Mon Sep 17 00:00:00 2001 From: Emily Matheys Date: Wed, 12 Jun 2024 22:46:35 +0300 Subject: [PATCH 02/11] Honestly its kinda fast now --- src/client/blocking.rs | 4 +- src/connection_info/mod.rs | 10 ++- src/graph/query_builder.rs | 13 +++- src/response/constraint.rs | 42 ++++++++---- src/response/execution_plan.rs | 29 ++++---- src/response/index.rs | 113 ++++++++++++++++++-------------- src/response/lazy_result_set.rs | 6 +- src/value/mod.rs | 23 ++++++- src/value/utils.rs | 35 ++++------ 9 files changed, 162 insertions(+), 113 deletions(-) diff --git a/src/client/blocking.rs b/src/client/blocking.rs index 1017829..a68461c 100644 --- a/src/client/blocking.rs +++ b/src/client/blocking.rs @@ -3,11 +3,11 @@ * Licensed under the Server Side Public License v1 (SSPLv1). */ -use crate::redis_ext::redis_value_as_string; use crate::{ client::FalkorClientProvider, connection::blocking::{BorrowedSyncConnection, FalkorSyncConnection}, parser::utils::string_vec_from_val, + redis_ext::redis_value_as_string, ConfigValue, FalkorConnectionInfo, FalkorDBError, FalkorResult, SyncGraph, }; use parking_lot::{Mutex, RwLock}; @@ -84,7 +84,7 @@ pub(crate) fn get_sentinel_client( .and_then(|master| master.into_sequence().ok()) .ok_or(FalkorDBError::SentinelMastersCount)? .chunks_exact(2) - .flat_map(|chunk| TryInto::<&[redis::Value; 2]>::try_into(chunk)) // TODO: check if this can be done with no copying + .flat_map(TryInto::<&[redis::Value; 2]>::try_into) // TODO: check if this can be done with no copying .flat_map(|[key, val]| { redis_value_as_string(key.to_owned()) .and_then(|key| redis_value_as_string(val.to_owned()).map(|val| (key, val))) diff --git a/src/connection_info/mod.rs b/src/connection_info/mod.rs index 8f72886..719ff25 100644 --- a/src/connection_info/mod.rs +++ b/src/connection_info/mod.rs @@ -49,12 +49,10 @@ impl TryFrom<&str> for FalkorConnectionInfo { .unwrap_or((format!("falkor://{value}"), "falkor")); match url_schema { - "redis" | "rediss" => { - return Ok(FalkorConnectionInfo::Redis( - redis::IntoConnectionInfo::into_connection_info(value) - .map_err(|err| FalkorDBError::InvalidConnectionInfo(err.to_string()))?, - )); - } + "redis" | "rediss" => Ok(FalkorConnectionInfo::Redis( + redis::IntoConnectionInfo::into_connection_info(value) + .map_err(|err| FalkorDBError::InvalidConnectionInfo(err.to_string()))?, + )), _ => FalkorConnectionInfo::fallback_provider(url), } } diff --git a/src/graph/query_builder.rs b/src/graph/query_builder.rs index 47523eb..4baf0a1 100644 --- a/src/graph/query_builder.rs +++ b/src/graph/query_builder.rs @@ -176,7 +176,7 @@ impl<'a, T: Display> QueryBuilder<'a, ExecutionPlan, T> { pub fn execute(mut self) -> FalkorResult { let res = self.common_execute_steps()?; - ExecutionPlan::try_from(res) + ExecutionPlan::parse(res) } } @@ -330,7 +330,7 @@ impl<'a> ProcedureQueryBuilder<'a, FalkorResponse>> { .into_sequence() .map_err(|_| FalkorDBError::ParsingArray)? .into_iter() - .flat_map(FalkorIndex::parse) + .flat_map(|index| FalkorIndex::parse(index, &mut self.graph.graph_schema)) .collect(), stats, ) @@ -360,7 +360,14 @@ impl<'a> ProcedureQueryBuilder<'a, FalkorResponse>> { Some(header), indices .into_sequence() - .map(|indices| indices.into_iter().flat_map(Constraint::parse).collect()) + .map(|indices| { + indices + .into_iter() + .flat_map(|constraint| { + Constraint::parse(constraint, &mut self.graph.graph_schema) + }) + .collect() + }) .map_err(|_| FalkorDBError::ParsingArray)?, stats, ) diff --git a/src/response/constraint.rs b/src/response/constraint.rs index aeac04d..43c334c 100644 --- a/src/response/constraint.rs +++ b/src/response/constraint.rs @@ -3,8 +3,10 @@ * Licensed under the Server Side Public License v1 (SSPLv1). */ -use crate::redis_ext::redis_value_as_string; -use crate::{EntityType, FalkorDBError, FalkorResult}; +use crate::{ + value::utils::parse_raw_redis_value, EntityType, FalkorDBError, FalkorResult, FalkorValue, + GraphSchema, +}; /// The type of restriction to apply for the property #[derive(Copy, Clone, Debug, Eq, PartialEq, strum::EnumString, strum::Display)] @@ -46,7 +48,10 @@ pub struct Constraint { } impl Constraint { - pub(crate) fn parse(value: redis::Value) -> FalkorResult { + pub(crate) fn parse( + value: redis::Value, + graph_schema: &mut GraphSchema, + ) -> FalkorResult { let [constraint_type_raw, label_raw, properties_raw, entity_type_raw, status_raw]: [redis::Value; 5] = value.into_sequence() .map_err(|_| FalkorDBError::ParsingArray) .and_then(|res| res.try_into() @@ -54,15 +59,30 @@ impl Constraint { Ok(Self { constraint_type: ConstraintType::try_from( - redis_value_as_string(constraint_type_raw)?.as_str(), + parse_raw_redis_value(constraint_type_raw, graph_schema) + .and_then(|parsed_constraint_type| parsed_constraint_type.into_string())? + .as_str(), + )?, + label: parse_raw_redis_value(label_raw, graph_schema) + .and_then(FalkorValue::into_string)?, + properties: parse_raw_redis_value(properties_raw, graph_schema) + .and_then(|properties_parsed| properties_parsed.into_vec()) + .map(|properties_vec| { + properties_vec + .into_iter() + .flat_map(FalkorValue::into_string) + .collect() + })?, + entity_type: EntityType::try_from( + parse_raw_redis_value(entity_type_raw, graph_schema) + .and_then(|parsed_entity_type| parsed_entity_type.into_string())? + .as_str(), + )?, + status: ConstraintStatus::try_from( + parse_raw_redis_value(status_raw, graph_schema) + .and_then(|parsed_status| parsed_status.into_string())? + .as_str(), )?, - label: redis_value_as_string(label_raw)?, - properties: properties_raw - .into_sequence() - .map(|data| data.into_iter().flat_map(redis_value_as_string).collect()) - .map_err(|_| FalkorDBError::ParsingArray)?, - entity_type: EntityType::try_from(redis_value_as_string(entity_type_raw)?.as_str())?, - status: ConstraintStatus::try_from(redis_value_as_string(status_raw)?.as_str())?, }) } } diff --git a/src/response/execution_plan.rs b/src/response/execution_plan.rs index 96c7641..4267d31 100644 --- a/src/response/execution_plan.rs +++ b/src/response/execution_plan.rs @@ -3,14 +3,15 @@ * Licensed under the Server Side Public License v1 (SSPLv1). */ -use crate::redis_ext::redis_value_as_string; -use crate::{FalkorDBError, FalkorResult}; +use crate::{redis_ext::redis_value_as_string, FalkorDBError, FalkorResult}; use regex::Regex; -use std::cell::RefCell; -use std::cmp::Ordering; -use std::collections::{HashMap, VecDeque}; -use std::ops::Not; -use std::rc::Rc; +use std::{ + cell::RefCell, + cmp::Ordering, + collections::{HashMap, VecDeque}, + ops::Not, + rc::Rc, +}; #[derive(Debug)] struct IntermediateOperation { @@ -167,20 +168,13 @@ impl ExecutionPlan { Self::operations_map_from_tree(child, map); } } -} - -impl TryFrom for ExecutionPlan { - type Error = FalkorDBError; - fn try_from(value: redis::Value) -> Result { + pub(crate) fn parse(value: redis::Value) -> FalkorResult { let redis_value_vec = value .into_sequence() .map_err(|_| FalkorDBError::ParsingArray)?; let mut string_representation = Vec::with_capacity(redis_value_vec.len() + 1); - string_representation.push("".to_string()); - - let execution_plan_operations = Vec::with_capacity(redis_value_vec.len()); let mut current_traversal_stack = vec![]; for node in redis_value_vec { let node_string = redis_value_as_string(node)?; @@ -193,6 +187,7 @@ impl TryFrom for ExecutionPlan { current_traversal_stack.push(Rc::new(RefCell::new( IntermediateOperation::new(depth, node)?, ))); + string_representation.push(node_string); continue; } Some(current_node) => current_node, @@ -245,8 +240,8 @@ impl TryFrom for ExecutionPlan { Self::operations_map_from_tree(&operation_tree, &mut operations); Ok(ExecutionPlan { - string_representation: string_representation.join("\n"), - plan: execution_plan_operations, + string_representation: format!("\n{}", string_representation.join("\n")), + plan: string_representation, operations, operation_tree, }) diff --git a/src/response/index.rs b/src/response/index.rs index 0867634..6449764 100644 --- a/src/response/index.rs +++ b/src/response/index.rs @@ -3,8 +3,9 @@ * Licensed under the Server Side Public License v1 (SSPLv1). */ -use crate::redis_ext::redis_value_as_string; -use crate::{EntityType, FalkorDBError}; +use crate::{ + value::utils::parse_raw_redis_value, EntityType, FalkorDBError, FalkorValue, GraphSchema, +}; use std::collections::HashMap; /// The status of this index @@ -31,26 +32,32 @@ pub enum IndexType { Fulltext, } -fn parse_types_map(value: redis::Value) -> Result>, FalkorDBError> { - Ok(value - .into_map_iter() - .map_err(|_| FalkorDBError::ParsingFMap)? - .into_iter() - .filter_map(|(key, val)| { - let key_str = redis_value_as_string(key).ok()?; - let val_seq = val.into_sequence().ok()?; - - let index_types = val_seq +fn parse_types_map( + value: redis::Value, + graph_schema: &mut GraphSchema, +) -> Result>, FalkorDBError> { + parse_raw_redis_value(value, graph_schema) + .and_then(|map_val| map_val.into_map()) + .map(|map_val| { + map_val .into_iter() - .filter_map(|index_type| { - let index_str = redis_value_as_string(index_type).ok()?; - IndexType::try_from(index_str.as_str()).ok() + .flat_map(|(key, val)| { + val.into_vec().map(|as_vec| { + ( + key, + as_vec + .into_iter() + .flat_map(|item| { + item.into_string().and_then(|as_str| { + IndexType::try_from(as_str.as_str()).map_err(Into::into) + }) + }) + .collect(), + ) + }) }) - .collect::>(); - - Some((key_str, index_types)) + .collect() }) - .collect()) } /// Contains all the info regarding an index on the database @@ -75,7 +82,10 @@ pub struct FalkorIndex { } impl FalkorIndex { - pub(crate) fn parse(value: redis::Value) -> Result { + pub(crate) fn parse( + value: redis::Value, + graph_schema: &mut GraphSchema, + ) -> Result { let [label, fields, field_types, language, stopwords, entity_type, status, info] = value .into_sequence() .map_err(|_| FalkorDBError::ParsingArray) @@ -87,39 +97,46 @@ impl FalkorIndex { }) })?; - eprintln!("Got here: {label:?} \n{fields:?} \n{field_types:?} \n{language:?} \n{stopwords:?} \n{entity_type:?} \n{status:?} \n{info:?}"); - Ok(Self { - entity_type: EntityType::try_from(redis_value_as_string(entity_type)?.as_str())?, - status: IndexStatus::try_from(redis_value_as_string(status)?.as_str())?, - index_label: redis_value_as_string(label)?, - fields: fields - .into_sequence() - .map(|fields| fields.into_iter().flat_map(redis_value_as_string).collect()) - .map_err(|_| FalkorDBError::ParsingArray)?, - field_types: parse_types_map(field_types)?, - language: redis_value_as_string(language)?, - stopwords: stopwords - .into_sequence() - .map(|stopwords| { - stopwords + entity_type: EntityType::try_from( + parse_raw_redis_value(entity_type, graph_schema) + .and_then(|parsed_entity_type| parsed_entity_type.into_string())? + .as_str(), + )?, + status: IndexStatus::try_from( + parse_raw_redis_value(status, graph_schema) + .and_then(|parsed_status| parsed_status.into_string())? + .as_str(), + )?, + index_label: parse_raw_redis_value(label, graph_schema) + .and_then(|parsed_entity_type| parsed_entity_type.into_string())?, + fields: parse_raw_redis_value(fields, graph_schema) + .and_then(|parsed_fields| parsed_fields.into_vec()) + .map(|parsed_fields_vec| { + parsed_fields_vec .into_iter() - .flat_map(redis_value_as_string) + .flat_map(FalkorValue::into_string) .collect() - }) - .map_err(|_| FalkorDBError::ParsingArray)?, - info: info - .into_map_iter() - .map(|map_iter| { - map_iter + })?, + field_types: parse_types_map(field_types, graph_schema)?, + language: parse_raw_redis_value(language, graph_schema) + .and_then(FalkorValue::into_string)?, + stopwords: parse_raw_redis_value(stopwords, graph_schema) + .and_then(|stopwords_raw| stopwords_raw.into_vec()) + .map(|stopwords_vec| { + stopwords_vec .into_iter() - .flat_map(|(key, val)| { - redis_value_as_string(key) - .and_then(|key| redis_value_as_string(val).map(|val| (key, val))) - }) + .flat_map(FalkorValue::into_string) .collect() - }) - .map_err(|_| FalkorDBError::ParsingFMap)?, + })?, + info: parse_raw_redis_value(info, graph_schema) + .and_then(FalkorValue::into_map) + .map(|as_map| { + as_map + .into_iter() + .flat_map(|(key, val)| val.into_string().map(|val_str| (key, val_str))) + .collect() + })?, }) } } diff --git a/src/response/lazy_result_set.rs b/src/response/lazy_result_set.rs index d3139a6..c391f29 100644 --- a/src/response/lazy_result_set.rs +++ b/src/response/lazy_result_set.rs @@ -3,7 +3,7 @@ * Licensed under the Server Side Public License v1 (SSPLv1). */ -use crate::{value::utils::parse_type, FalkorValue, GraphSchema}; +use crate::{value::utils::parse_raw_redis_value, FalkorValue, GraphSchema}; use std::collections::VecDeque; /// A wrapper around the returned raw data, allowing parsing on demand of each result @@ -48,8 +48,8 @@ impl<'a> Iterator for LazyResultSet<'a> { )] fn next(&mut self) -> Option { self.data.pop_front().map(|current_result| { - parse_type(6, current_result, self.graph_schema) - .and_then(|parsed_result| parsed_result.into_vec()) + parse_raw_redis_value(current_result, self.graph_schema) + .and_then(FalkorValue::into_vec) .unwrap_or(vec![FalkorValue::Unparseable]) }) } diff --git a/src/value/mod.rs b/src/value/mod.rs index b24221f..6f9d644 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -297,7 +297,11 @@ impl FalkorValue { /// # Returns /// The inner [`Vec`] pub fn into_vec(self) -> FalkorResult> { - self.try_into() + if let FalkorValue::Array(array) = self { + Ok(array) + } else { + Err(FalkorDBError::ParsingFMap) + } } /// Consumes itself and returns the inner [`String`] if this is an FString variant @@ -305,7 +309,22 @@ impl FalkorValue { /// # Returns /// The inner [`String`] pub fn into_string(self) -> FalkorResult { - self.try_into() + if let FalkorValue::String(string) = self { + Ok(string) + } else { + Err(FalkorDBError::ParsingFString) + } + } + /// Consumes itself and returns the inner [`HashMap`] if this is a Map variant + /// + /// # Returns + /// The inner [`HashMap`] + pub fn into_map(self) -> FalkorResult> { + if let FalkorValue::Map(map) = self { + Ok(map) + } else { + Err(FalkorDBError::ParsingFMap) + } } } diff --git a/src/value/utils.rs b/src/value/utils.rs index c137004..a5b64ea 100644 --- a/src/value/utils.rs +++ b/src/value/utils.rs @@ -9,6 +9,14 @@ use crate::redis_ext::{ use crate::{Edge, FalkorDBError, FalkorResult, FalkorValue, GraphSchema, Node, Path, Point}; use std::collections::HashMap; +pub(crate) fn parse_raw_redis_value( + value: redis::Value, + graph_schema: &mut GraphSchema, +) -> FalkorResult { + type_val_from_value(value) + .and_then(|(type_marker, val)| parse_type(type_marker, val, graph_schema)) +} + pub(crate) fn type_val_from_value( value: redis::Value ) -> Result<(i64, redis::Value), FalkorDBError> { @@ -33,15 +41,9 @@ fn parse_regular_falkor_map( .into_map_iter() .map_err(|_| FalkorDBError::ParsingFMap)? .try_fold(HashMap::new(), |mut out_map, (key, val)| { - let [type_marker, val]: [redis::Value; 2] = val - .into_sequence() - .ok() - .and_then(|val_seq| val_seq.try_into().ok()) - .ok_or(FalkorDBError::ParsingFMap)?; - out_map.insert( redis_value_as_string(key)?, - parse_type(redis_value_as_int(type_marker)?, val, graph_schema)?, + parse_raw_redis_value(val, graph_schema)?, ); Ok(out_map) }) @@ -66,10 +68,7 @@ pub(crate) fn parse_type( val.into_sequence() .map_err(|_| FalkorDBError::ParsingArray)? .into_iter() - .flat_map(|item| { - type_val_from_value(item) - .and_then(|(type_marker, val)| parse_type(type_marker, val, graph_schema)) - }) + .flat_map(|item| parse_raw_redis_value(item, graph_schema)) .collect(), ), // The following types are sent as an array and require specific parsing functions @@ -134,7 +133,7 @@ mod tests { assert_eq!(edge.properties.get("age"), Some(&FalkorValue::I64(20))); assert_eq!( edge.properties.get("is_boring"), - Some(&FalkorValue::String("false".to_string())) + Some(&FalkorValue::Bool(false)) ); } @@ -184,7 +183,7 @@ mod tests { ); assert_eq!( node.properties.get("secs_since_login"), - Some(&FalkorValue::String("105.5".to_string())) + Some(&FalkorValue::F64(105.5)) ); } @@ -289,10 +288,7 @@ mod tests { Some(&FalkorValue::String("val0".to_string())) ); assert_eq!(map.get("key1"), Some(&FalkorValue::I64(1))); - assert_eq!( - map.get("key2"), - Some(&FalkorValue::String("true".to_string())) - ); + assert_eq!(map.get("key2"), Some(&FalkorValue::Bool(true))); } #[test] @@ -421,9 +417,6 @@ mod tests { .expect("Could not parse map"); assert_eq!(res.get("IntKey"), Some(FalkorValue::I64(1)).as_ref()); - assert_eq!( - res.get("BoolKey"), - Some(FalkorValue::String("true".to_string())).as_ref() - ); + assert_eq!(res.get("BoolKey"), Some(FalkorValue::Bool(true)).as_ref()); } } From de190d6f62dd4bfc577e6e37b7edd1520fe68c05 Mon Sep 17 00:00:00 2001 From: Emily Matheys Date: Wed, 12 Jun 2024 22:58:26 +0300 Subject: [PATCH 03/11] More cleanup --- src/client/blocking.rs | 2 +- src/graph_schema/mod.rs | 2 +- src/lib.rs | 1 - src/parser/mod.rs | 530 +++++++++++++++++++++++++++++++- src/parser/utils.rs | 110 ------- src/response/constraint.rs | 2 +- src/response/index.rs | 4 +- src/response/lazy_result_set.rs | 2 +- src/response/mod.rs | 2 +- src/value/mod.rs | 12 +- src/value/utils.rs | 422 ------------------------- 11 files changed, 528 insertions(+), 561 deletions(-) delete mode 100644 src/parser/utils.rs delete mode 100644 src/value/utils.rs diff --git a/src/client/blocking.rs b/src/client/blocking.rs index a68461c..e5782fa 100644 --- a/src/client/blocking.rs +++ b/src/client/blocking.rs @@ -6,7 +6,7 @@ use crate::{ client::FalkorClientProvider, connection::blocking::{BorrowedSyncConnection, FalkorSyncConnection}, - parser::utils::string_vec_from_val, + parser::string_vec_from_val, redis_ext::redis_value_as_string, ConfigValue, FalkorConnectionInfo, FalkorDBError, FalkorResult, SyncGraph, }; diff --git a/src/graph_schema/mod.rs b/src/graph_schema/mod.rs index 23b51e9..a0b9f32 100644 --- a/src/graph_schema/mod.rs +++ b/src/graph_schema/mod.rs @@ -5,8 +5,8 @@ use crate::{ client::blocking::FalkorSyncClientInner, + parser::parse_type, redis_ext::{redis_value_as_int, redis_value_as_string}, - value::utils::parse_type, FalkorDBError, FalkorResult, FalkorValue, }; use std::{collections::HashMap, sync::Arc}; diff --git a/src/lib.rs b/src/lib.rs index df89bf4..f9860fb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,7 +30,6 @@ pub use graph::{ query_builder::{ProcedureQueryBuilder, QueryBuilder}, }; pub use graph_schema::{GraphSchema, SchemaType}; -pub use parser::FalkorParsable; pub use response::{ constraint::{Constraint, ConstraintStatus, ConstraintType}, execution_plan::ExecutionPlan, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 2c1edb1..774361f 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -3,15 +3,527 @@ * Licensed under the Server Side Public License v1 (SSPLv1). */ -pub mod utils; +use crate::{ + redis_ext::{ + redis_value_as_bool, redis_value_as_double, redis_value_as_int, redis_value_as_string, + }, + Edge, FalkorDBError, FalkorResult, FalkorValue, GraphSchema, Node, Path, Point, +}; +use std::collections::HashMap; -use crate::{FalkorResult, FalkorValue, GraphSchema}; +pub(crate) fn string_vec_from_val(value: redis::Value) -> FalkorResult> { + value + .into_sequence() + .map(|as_vec| as_vec.into_iter().flat_map(redis_value_as_string).collect()) + .map_err(|_| FalkorDBError::ParsingArray) +} + +pub(crate) fn parse_header(header: redis::Value) -> FalkorResult> { + // Convert the header into a sequence + let header_sequence = header + .into_sequence() + .map_err(|_| FalkorDBError::ParsingArray)?; + + // Initialize a vector with the capacity of the header sequence length + let header_sequence_len = header_sequence.len(); + + header_sequence.into_iter().try_fold( + Vec::with_capacity(header_sequence_len), + |mut result, item| { + // Convert the item into a sequence + let item_sequence = item + .into_sequence() + .map_err(|_| FalkorDBError::ParsingArray)?; + + // Determine the key based on the length of the item sequence + let key = if item_sequence.len() == 2 { + // Extract the key from a 2-element array + let [_val, key]: [redis::Value; 2] = item_sequence.try_into().map_err(|_| { + FalkorDBError::ParsingHeader( + "Could not get 2-sized array despite there being 2 elements", + ) + })?; + key + } else { + // Get the first element from the item sequence + item_sequence.into_iter().next().ok_or_else(|| { + FalkorDBError::ParsingHeader("Expected at least one item in header vector") + })? + }; + + // Convert the key to a string and push it to the result vector + result.push(redis_value_as_string(key)?); + Ok(result) + }, + ) +} + +pub(crate) fn parse_raw_redis_value( + value: redis::Value, + graph_schema: &mut GraphSchema, +) -> FalkorResult { + type_val_from_value(value) + .and_then(|(type_marker, val)| parse_type(type_marker, val, graph_schema)) +} + +pub(crate) fn type_val_from_value( + value: redis::Value +) -> Result<(i64, redis::Value), FalkorDBError> { + let [type_marker, val]: [redis::Value; 2] = value + .into_sequence() + .map_err(|_| FalkorDBError::ParsingArray)? + .try_into() + .map_err(|_| { + FalkorDBError::ParsingArrayToStructElementCount( + "Expected exactly 2 elements: type marker, and value", + ) + })?; + + Ok((redis_value_as_int(type_marker)?, val)) +} + +fn parse_regular_falkor_map( + value: redis::Value, + graph_schema: &mut GraphSchema, +) -> FalkorResult> { + value + .into_map_iter() + .map_err(|_| FalkorDBError::ParsingFMap)? + .try_fold(HashMap::new(), |mut out_map, (key, val)| { + out_map.insert( + redis_value_as_string(key)?, + parse_raw_redis_value(val, graph_schema)?, + ); + Ok(out_map) + }) +} + +#[cfg_attr( + feature = "tracing", + tracing::instrument(name = "Parse Element With Type Marker", skip_all) +)] +pub(crate) fn parse_type( + type_marker: i64, + val: redis::Value, + graph_schema: &mut GraphSchema, +) -> Result { + let res = match type_marker { + 1 => FalkorValue::None, + 2 => FalkorValue::String(redis_value_as_string(val)?), + 3 => FalkorValue::I64(redis_value_as_int(val)?), + 4 => FalkorValue::Bool(redis_value_as_bool(val)?), + 5 => FalkorValue::F64(redis_value_as_double(val)?), + 6 => FalkorValue::Array( + val.into_sequence() + .map_err(|_| FalkorDBError::ParsingArray)? + .into_iter() + .flat_map(|item| parse_raw_redis_value(item, graph_schema)) + .collect(), + ), + // The following types are sent as an array and require specific parsing functions + 7 => FalkorValue::Edge(Edge::parse(val, graph_schema)?), + 8 => FalkorValue::Node(Node::parse(val, graph_schema)?), + 9 => FalkorValue::Path(Path::parse(val, graph_schema)?), + 10 => FalkorValue::Map(parse_regular_falkor_map(val, graph_schema)?), + 11 => FalkorValue::Point(Point::parse(val)?), + _ => Err(FalkorDBError::ParsingUnknownType)?, + }; + + Ok(res) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + client::blocking::create_empty_inner_client, + graph_schema::tests::open_readonly_graph_with_modified_schema, FalkorDBError, + }; + + #[test] + fn test_parse_header_valid_single_key() { + let header = redis::Value::Bulk(vec![redis::Value::Bulk(vec![redis::Value::Data( + "key1".as_bytes().to_vec(), + )])]); + let result = parse_header(header); + assert!(result.is_ok()); + assert_eq!(result.unwrap(), vec!["key1".to_string()]); + } + + #[test] + fn test_parse_header_valid_multiple_keys() { + let header = redis::Value::Bulk(vec![ + redis::Value::Bulk(vec![ + redis::Value::Data("type".as_bytes().to_vec()), + redis::Value::Data("header1".as_bytes().to_vec()), + ]), + redis::Value::Bulk(vec![redis::Value::Data("key2".as_bytes().to_vec())]), + ]); + let result = parse_header(header); + assert!(result.is_ok()); + assert_eq!( + result.unwrap(), + vec!["header1".to_string(), "key2".to_string()] + ); + } + + #[test] + fn test_parse_header_empty_header() { + let header = redis::Value::Bulk(vec![]); + let result = parse_header(header); + assert!(result.is_ok()); + assert_eq!(result.unwrap(), Vec::::new()); + } + + #[test] + fn test_parse_header_empty_vec() { + let header = redis::Value::Bulk(vec![redis::Value::Bulk(vec![])]); + let result = parse_header(header); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err(), + FalkorDBError::ParsingHeader("Expected at least one item in header vector") + ); + } + + #[test] + fn test_parse_header_many_elements() { + let header = redis::Value::Bulk(vec![redis::Value::Bulk(vec![ + redis::Value::Data("just_some_header".as_bytes().to_vec()), + redis::Value::Data("header1".as_bytes().to_vec()), + redis::Value::Data("extra".as_bytes().to_vec()), + ])]); + let result = parse_header(header); + assert!(result.is_ok()); + assert_eq!(result.unwrap()[0], "just_some_header"); + } + + #[test] + fn test_parse_edge() { + let mut graph = open_readonly_graph_with_modified_schema(); + + let res = parse_type( + 7, + redis::Value::Bulk(vec![ + redis::Value::Int(100), // edge id + redis::Value::Int(0), // edge type + redis::Value::Int(51), // src node + redis::Value::Int(52), // dst node + redis::Value::Bulk(vec![ + redis::Value::Bulk(vec![ + redis::Value::Int(0), + redis::Value::Int(3), + redis::Value::Int(20), + ]), + redis::Value::Bulk(vec![ + redis::Value::Int(1), + redis::Value::Int(4), + redis::Value::Status("false".to_string()), + ]), + ]), + ]), + &mut graph.graph_schema, + ); + assert!(res.is_ok()); + + let falkor_edge = res.unwrap(); + + let FalkorValue::Edge(edge) = falkor_edge else { + panic!("Was not of type edge") + }; + assert_eq!(edge.entity_id, 100); + assert_eq!(edge.relationship_type, "very".to_string()); + assert_eq!(edge.src_node_id, 51); + assert_eq!(edge.dst_node_id, 52); + + assert_eq!(edge.properties.len(), 2); + assert_eq!(edge.properties.get("age"), Some(&FalkorValue::I64(20))); + assert_eq!( + edge.properties.get("is_boring"), + Some(&FalkorValue::Bool(false)) + ); + } + + #[test] + fn test_parse_node() { + let mut graph = open_readonly_graph_with_modified_schema(); + + let res = parse_type( + 8, + redis::Value::Bulk(vec![ + redis::Value::Int(51), // node id + redis::Value::Bulk(vec![redis::Value::Int(0), redis::Value::Int(1)]), // node type + redis::Value::Bulk(vec![ + redis::Value::Bulk(vec![ + redis::Value::Int(0), + redis::Value::Int(3), + redis::Value::Int(15), + ]), + redis::Value::Bulk(vec![ + redis::Value::Int(2), + redis::Value::Int(2), + redis::Value::Status("the something".to_string()), + ]), + redis::Value::Bulk(vec![ + redis::Value::Int(3), + redis::Value::Int(5), + redis::Value::Status("105.5".to_string()), + ]), + ]), + ]), + &mut graph.graph_schema, + ); + assert!(res.is_ok()); + + let falkor_node = res.unwrap(); + let FalkorValue::Node(node) = falkor_node else { + panic!("Was not of type node") + }; + + assert_eq!(node.entity_id, 51); + assert_eq!(node.labels, vec!["much".to_string(), "actor".to_string()]); + assert_eq!(node.properties.len(), 3); + assert_eq!(node.properties.get("age"), Some(&FalkorValue::I64(15))); + assert_eq!( + node.properties.get("something_else"), + Some(&FalkorValue::String("the something".to_string())) + ); + assert_eq!( + node.properties.get("secs_since_login"), + Some(&FalkorValue::F64(105.5)) + ); + } + + #[test] + fn test_parse_path() { + let mut graph = open_readonly_graph_with_modified_schema(); + + let res = parse_type( + 9, + redis::Value::Bulk(vec![ + redis::Value::Bulk(vec![ + redis::Value::Bulk(vec![ + redis::Value::Int(51), + redis::Value::Bulk(vec![redis::Value::Int(0)]), + redis::Value::Bulk(vec![]), + ]), + redis::Value::Bulk(vec![ + redis::Value::Int(52), + redis::Value::Bulk(vec![redis::Value::Int(0)]), + redis::Value::Bulk(vec![]), + ]), + redis::Value::Bulk(vec![ + redis::Value::Int(53), + redis::Value::Bulk(vec![redis::Value::Int(0)]), + redis::Value::Bulk(vec![]), + ]), + ]), + redis::Value::Bulk(vec![ + redis::Value::Bulk(vec![ + redis::Value::Int(100), + redis::Value::Int(0), + redis::Value::Int(51), + redis::Value::Int(52), + redis::Value::Bulk(vec![]), + ]), + redis::Value::Bulk(vec![ + redis::Value::Int(101), + redis::Value::Int(1), + redis::Value::Int(52), + redis::Value::Int(53), + redis::Value::Bulk(vec![]), + ]), + ]), + ]), + &mut graph.graph_schema, + ); + assert!(res.is_ok()); + + let falkor_path = res.unwrap(); + let FalkorValue::Path(path) = falkor_path else { + panic!("Is not of type path") + }; + + assert_eq!(path.nodes.len(), 3); + assert_eq!(path.nodes[0].entity_id, 51); + assert_eq!(path.nodes[1].entity_id, 52); + assert_eq!(path.nodes[2].entity_id, 53); + + assert_eq!(path.relationships.len(), 2); + assert_eq!(path.relationships[0].entity_id, 100); + assert_eq!(path.relationships[1].entity_id, 101); + + assert_eq!(path.relationships[0].src_node_id, 51); + assert_eq!(path.relationships[0].dst_node_id, 52); + + assert_eq!(path.relationships[1].src_node_id, 52); + assert_eq!(path.relationships[1].dst_node_id, 53); + } + + #[test] + fn test_parse_map() { + let mut graph = open_readonly_graph_with_modified_schema(); + + let res = parse_type( + 10, + redis::Value::Bulk(vec![ + redis::Value::Status("key0".to_string()), + redis::Value::Bulk(vec![ + redis::Value::Int(2), + redis::Value::Status("val0".to_string()), + ]), + redis::Value::Status("key1".to_string()), + redis::Value::Bulk(vec![redis::Value::Int(3), redis::Value::Int(1)]), + redis::Value::Status("key2".to_string()), + redis::Value::Bulk(vec![ + redis::Value::Int(4), + redis::Value::Status("true".to_string()), + ]), + ]), + &mut graph.graph_schema, + ); + assert!(res.is_ok()); + + let falkor_map = res.unwrap(); + let FalkorValue::Map(map) = falkor_map else { + panic!("Is not of type map") + }; + + assert_eq!(map.len(), 3); + assert_eq!( + map.get("key0"), + Some(&FalkorValue::String("val0".to_string())) + ); + assert_eq!(map.get("key1"), Some(&FalkorValue::I64(1))); + assert_eq!(map.get("key2"), Some(&FalkorValue::Bool(true))); + } + + #[test] + fn test_parse_point() { + let mut graph = open_readonly_graph_with_modified_schema(); + + let res = parse_type( + 11, + redis::Value::Bulk(vec![ + redis::Value::Status("102.0".to_string()), + redis::Value::Status("15.2".to_string()), + ]), + &mut graph.graph_schema, + ); + assert!(res.is_ok()); + + let falkor_point = res.unwrap(); + let FalkorValue::Point(point) = falkor_point else { + panic!("Is not of type point") + }; + assert_eq!(point.latitude, 102.0); + assert_eq!(point.longitude, 15.2); + } + + #[test] + fn test_map_not_a_vec() { + let mut graph_schema = GraphSchema::new("test_graph", create_empty_inner_client()); + + let res = + parse_regular_falkor_map(redis::Value::Status("Hello".to_string()), &mut graph_schema); + + assert!(res.is_err()) + } + + #[test] + fn test_map_vec_odd_element_count() { + let mut graph_schema = GraphSchema::new("test_graph", create_empty_inner_client()); + + let res = parse_regular_falkor_map( + redis::Value::Bulk(vec![redis::Value::Nil; 7]), + &mut graph_schema, + ); + + assert!(res.is_err()) + } + + #[test] + fn test_map_val_element_is_not_array() { + let mut graph_schema = GraphSchema::new("test_graph", create_empty_inner_client()); + + let res = parse_regular_falkor_map( + redis::Value::Bulk(vec![ + redis::Value::Status("Key".to_string()), + redis::Value::Status("false".to_string()), + ]), + &mut graph_schema, + ); + + assert!(res.is_err()) + } + + #[test] + fn test_map_val_element_has_only_1_element() { + let mut graph_schema = GraphSchema::new("test_graph", create_empty_inner_client()); + + let res = parse_regular_falkor_map( + redis::Value::Bulk(vec![ + redis::Value::Status("Key".to_string()), + redis::Value::Bulk(vec![redis::Value::Int(7)]), + ]), + &mut graph_schema, + ); + + assert!(res.is_err()) + } + + #[test] + fn test_map_val_element_has_ge_2_elements() { + let mut graph_schema = GraphSchema::new("test_graph", create_empty_inner_client()); + + let res = parse_regular_falkor_map( + redis::Value::Bulk(vec![ + redis::Value::Status("Key".to_string()), + redis::Value::Bulk(vec![redis::Value::Int(3); 3]), + ]), + &mut graph_schema, + ); + + assert!(res.is_err()) + } + + #[test] + fn test_map_val_element_mismatch_type_marker() { + let mut graph_schema = GraphSchema::new("test_graph", create_empty_inner_client()); + + let res = parse_regular_falkor_map( + redis::Value::Bulk(vec![ + redis::Value::Status("Key".to_string()), + redis::Value::Bulk(vec![ + redis::Value::Int(3), + redis::Value::Status("true".to_string()), + ]), + ]), + &mut graph_schema, + ); + + assert!(res.is_err()) + } + + #[test] + fn test_map_ok_values() { + let mut graph_schema = GraphSchema::new("test_graph", create_empty_inner_client()); + + let res = parse_regular_falkor_map( + redis::Value::Bulk(vec![ + redis::Value::Status("IntKey".to_string()), + redis::Value::Bulk(vec![redis::Value::Int(3), redis::Value::Int(1)]), + redis::Value::Status("BoolKey".to_string()), + redis::Value::Bulk(vec![ + redis::Value::Int(4), + redis::Value::Status("true".to_string()), + ]), + ]), + &mut graph_schema, + ) + .expect("Could not parse map"); -/// This trait allows implementing a parser from the table-style result sent by the database, to any other struct -pub trait FalkorParsable: Sized { - /// Parse the following value, using the graph schem owned by the graph object, and the connection used to make the request - fn from_falkor_value( - value: FalkorValue, - graph_schema: &mut GraphSchema, - ) -> FalkorResult; + assert_eq!(res.get("IntKey"), Some(FalkorValue::I64(1)).as_ref()); + assert_eq!(res.get("BoolKey"), Some(FalkorValue::Bool(true)).as_ref()); + } } diff --git a/src/parser/utils.rs b/src/parser/utils.rs deleted file mode 100644 index 5610b20..0000000 --- a/src/parser/utils.rs +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright FalkorDB Ltd. 2023 - present - * Licensed under the Server Side Public License v1 (SSPLv1). - */ - -use crate::redis_ext::redis_value_as_string; -use crate::{FalkorDBError, FalkorResult}; - -pub(crate) fn string_vec_from_val(value: redis::Value) -> FalkorResult> { - value - .into_sequence() - .map(|as_vec| as_vec.into_iter().flat_map(redis_value_as_string).collect()) - .map_err(|_| FalkorDBError::ParsingArray) -} - -pub(crate) fn parse_header(header: redis::Value) -> FalkorResult> { - header.into_sequence().map_err(|_| FalkorDBError::ParsingArray) - .and_then(|in_vec| { - let in_vec_len = in_vec.len(); - in_vec - .into_iter() - .try_fold(Vec::with_capacity(in_vec_len), |mut acc, item| { - item.into_sequence().map_err(|_| FalkorDBError::ParsingArray) - .and_then(|item_vec| { - acc.push( - redis_value_as_string(if item_vec.len() == 2 { - let [_, key]: [redis::Value; 2] = - item_vec.try_into().map_err(|_| { - FalkorDBError::ParsingHeader( - "Could not get 2-sized array despite there being 2 elements" - ) - })?; - key - } else { - item_vec.into_iter().next().ok_or( - FalkorDBError::ParsingHeader( - "Expected at least one item in header vector" - ), - )? - })? - ); - Ok(acc) - }) - }) - }) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::FalkorDBError; - - #[test] - fn test_parse_header_valid_single_key() { - let header = redis::Value::Bulk(vec![redis::Value::Bulk(vec![redis::Value::Data( - "key1".as_bytes().to_vec(), - )])]); - let result = parse_header(header); - assert!(result.is_ok()); - assert_eq!(result.unwrap(), vec!["key1".to_string()]); - } - - #[test] - fn test_parse_header_valid_multiple_keys() { - let header = redis::Value::Bulk(vec![ - redis::Value::Bulk(vec![ - redis::Value::Data("type".as_bytes().to_vec()), - redis::Value::Data("header1".as_bytes().to_vec()), - ]), - redis::Value::Bulk(vec![redis::Value::Data("key2".as_bytes().to_vec())]), - ]); - let result = parse_header(header); - assert!(result.is_ok()); - assert_eq!( - result.unwrap(), - vec!["header1".to_string(), "key2".to_string()] - ); - } - - #[test] - fn test_parse_header_empty_header() { - let header = redis::Value::Bulk(vec![]); - let result = parse_header(header); - assert!(result.is_ok()); - assert_eq!(result.unwrap(), Vec::::new()); - } - - #[test] - fn test_parse_header_empty_vec() { - let header = redis::Value::Bulk(vec![redis::Value::Bulk(vec![])]); - let result = parse_header(header); - assert!(result.is_err()); - assert_eq!( - result.unwrap_err(), - FalkorDBError::ParsingHeader("Expected at least one item in header vector") - ); - } - - #[test] - fn test_parse_header_many_elements() { - let header = redis::Value::Bulk(vec![redis::Value::Bulk(vec![ - redis::Value::Data("just_some_header".as_bytes().to_vec()), - redis::Value::Data("header1".as_bytes().to_vec()), - redis::Value::Data("extra".as_bytes().to_vec()), - ])]); - let result = parse_header(header); - assert!(result.is_ok()); - assert_eq!(result.unwrap()[0], "just_some_header"); - } -} diff --git a/src/response/constraint.rs b/src/response/constraint.rs index 43c334c..a8c4aaa 100644 --- a/src/response/constraint.rs +++ b/src/response/constraint.rs @@ -4,7 +4,7 @@ */ use crate::{ - value::utils::parse_raw_redis_value, EntityType, FalkorDBError, FalkorResult, FalkorValue, + parser::parse_raw_redis_value, EntityType, FalkorDBError, FalkorResult, FalkorValue, GraphSchema, }; diff --git a/src/response/index.rs b/src/response/index.rs index 6449764..5b61e99 100644 --- a/src/response/index.rs +++ b/src/response/index.rs @@ -3,9 +3,7 @@ * Licensed under the Server Side Public License v1 (SSPLv1). */ -use crate::{ - value::utils::parse_raw_redis_value, EntityType, FalkorDBError, FalkorValue, GraphSchema, -}; +use crate::{parser::parse_raw_redis_value, EntityType, FalkorDBError, FalkorValue, GraphSchema}; use std::collections::HashMap; /// The status of this index diff --git a/src/response/lazy_result_set.rs b/src/response/lazy_result_set.rs index c391f29..4a7a337 100644 --- a/src/response/lazy_result_set.rs +++ b/src/response/lazy_result_set.rs @@ -3,7 +3,7 @@ * Licensed under the Server Side Public License v1 (SSPLv1). */ -use crate::{value::utils::parse_raw_redis_value, FalkorValue, GraphSchema}; +use crate::{parser::parse_raw_redis_value, FalkorValue, GraphSchema}; use std::collections::VecDeque; /// A wrapper around the returned raw data, allowing parsing on demand of each result diff --git a/src/response/mod.rs b/src/response/mod.rs index 7fb1063..8649c43 100644 --- a/src/response/mod.rs +++ b/src/response/mod.rs @@ -4,7 +4,7 @@ */ use crate::{ - parser::utils::{parse_header, string_vec_from_val}, + parser::{parse_header, string_vec_from_val}, FalkorResult, }; diff --git a/src/value/mod.rs b/src/value/mod.rs index 6f9d644..8bb6910 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -3,7 +3,7 @@ * Licensed under the Server Side Public License v1 (SSPLv1). */ -use crate::{FalkorDBError, FalkorParsable, FalkorResult, GraphSchema}; +use crate::{FalkorDBError, FalkorResult}; use graph_entities::{Edge, Node}; use path::Path; use point::Point; @@ -13,7 +13,6 @@ pub(crate) mod config; pub(crate) mod graph_entities; pub(crate) mod path; pub(crate) mod point; -pub(crate) mod utils; /// An enum of all the supported Falkor types #[derive(Clone, Debug, PartialEq)] @@ -328,15 +327,6 @@ impl FalkorValue { } } -impl FalkorParsable for FalkorValue { - fn from_falkor_value( - value: FalkorValue, - _: &mut GraphSchema, - ) -> FalkorResult { - Ok(value) - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/src/value/utils.rs b/src/value/utils.rs deleted file mode 100644 index a5b64ea..0000000 --- a/src/value/utils.rs +++ /dev/null @@ -1,422 +0,0 @@ -/* - * Copyright FalkorDB Ltd. 2023 - present - * Licensed under the Server Side Public License v1 (SSPLv1). - */ - -use crate::redis_ext::{ - redis_value_as_bool, redis_value_as_double, redis_value_as_int, redis_value_as_string, -}; -use crate::{Edge, FalkorDBError, FalkorResult, FalkorValue, GraphSchema, Node, Path, Point}; -use std::collections::HashMap; - -pub(crate) fn parse_raw_redis_value( - value: redis::Value, - graph_schema: &mut GraphSchema, -) -> FalkorResult { - type_val_from_value(value) - .and_then(|(type_marker, val)| parse_type(type_marker, val, graph_schema)) -} - -pub(crate) fn type_val_from_value( - value: redis::Value -) -> Result<(i64, redis::Value), FalkorDBError> { - let [type_marker, val]: [redis::Value; 2] = value - .into_sequence() - .map_err(|_| FalkorDBError::ParsingArray)? - .try_into() - .map_err(|_| { - FalkorDBError::ParsingArrayToStructElementCount( - "Expected exactly 2 elements: type marker, and value", - ) - })?; - - Ok((redis_value_as_int(type_marker)?, val)) -} - -fn parse_regular_falkor_map( - value: redis::Value, - graph_schema: &mut GraphSchema, -) -> FalkorResult> { - value - .into_map_iter() - .map_err(|_| FalkorDBError::ParsingFMap)? - .try_fold(HashMap::new(), |mut out_map, (key, val)| { - out_map.insert( - redis_value_as_string(key)?, - parse_raw_redis_value(val, graph_schema)?, - ); - Ok(out_map) - }) -} - -#[cfg_attr( - feature = "tracing", - tracing::instrument(name = "Parse Element With Type Marker", skip_all) -)] -pub(crate) fn parse_type( - type_marker: i64, - val: redis::Value, - graph_schema: &mut GraphSchema, -) -> Result { - let res = match type_marker { - 1 => FalkorValue::None, - 2 => FalkorValue::String(redis_value_as_string(val)?), - 3 => FalkorValue::I64(redis_value_as_int(val)?), - 4 => FalkorValue::Bool(redis_value_as_bool(val)?), - 5 => FalkorValue::F64(redis_value_as_double(val)?), - 6 => FalkorValue::Array( - val.into_sequence() - .map_err(|_| FalkorDBError::ParsingArray)? - .into_iter() - .flat_map(|item| parse_raw_redis_value(item, graph_schema)) - .collect(), - ), - // The following types are sent as an array and require specific parsing functions - 7 => FalkorValue::Edge(Edge::parse(val, graph_schema)?), - 8 => FalkorValue::Node(Node::parse(val, graph_schema)?), - 9 => FalkorValue::Path(Path::parse(val, graph_schema)?), - 10 => FalkorValue::Map(parse_regular_falkor_map(val, graph_schema)?), - 11 => FalkorValue::Point(Point::parse(val)?), - _ => Err(FalkorDBError::ParsingUnknownType)?, - }; - - Ok(res) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - client::blocking::create_empty_inner_client, - graph_schema::tests::open_readonly_graph_with_modified_schema, - }; - - #[test] - fn test_parse_edge() { - let mut graph = open_readonly_graph_with_modified_schema(); - - let res = parse_type( - 7, - redis::Value::Bulk(vec![ - redis::Value::Int(100), // edge id - redis::Value::Int(0), // edge type - redis::Value::Int(51), // src node - redis::Value::Int(52), // dst node - redis::Value::Bulk(vec![ - redis::Value::Bulk(vec![ - redis::Value::Int(0), - redis::Value::Int(3), - redis::Value::Int(20), - ]), - redis::Value::Bulk(vec![ - redis::Value::Int(1), - redis::Value::Int(4), - redis::Value::Status("false".to_string()), - ]), - ]), - ]), - &mut graph.graph_schema, - ); - assert!(res.is_ok()); - - let falkor_edge = res.unwrap(); - - let FalkorValue::Edge(edge) = falkor_edge else { - panic!("Was not of type edge") - }; - assert_eq!(edge.entity_id, 100); - assert_eq!(edge.relationship_type, "very".to_string()); - assert_eq!(edge.src_node_id, 51); - assert_eq!(edge.dst_node_id, 52); - - assert_eq!(edge.properties.len(), 2); - assert_eq!(edge.properties.get("age"), Some(&FalkorValue::I64(20))); - assert_eq!( - edge.properties.get("is_boring"), - Some(&FalkorValue::Bool(false)) - ); - } - - #[test] - fn test_parse_node() { - let mut graph = open_readonly_graph_with_modified_schema(); - - let res = parse_type( - 8, - redis::Value::Bulk(vec![ - redis::Value::Int(51), // node id - redis::Value::Bulk(vec![redis::Value::Int(0), redis::Value::Int(1)]), // node type - redis::Value::Bulk(vec![ - redis::Value::Bulk(vec![ - redis::Value::Int(0), - redis::Value::Int(3), - redis::Value::Int(15), - ]), - redis::Value::Bulk(vec![ - redis::Value::Int(2), - redis::Value::Int(2), - redis::Value::Status("the something".to_string()), - ]), - redis::Value::Bulk(vec![ - redis::Value::Int(3), - redis::Value::Int(5), - redis::Value::Status("105.5".to_string()), - ]), - ]), - ]), - &mut graph.graph_schema, - ); - assert!(res.is_ok()); - - let falkor_node = res.unwrap(); - let FalkorValue::Node(node) = falkor_node else { - panic!("Was not of type node") - }; - - assert_eq!(node.entity_id, 51); - assert_eq!(node.labels, vec!["much".to_string(), "actor".to_string()]); - assert_eq!(node.properties.len(), 3); - assert_eq!(node.properties.get("age"), Some(&FalkorValue::I64(15))); - assert_eq!( - node.properties.get("something_else"), - Some(&FalkorValue::String("the something".to_string())) - ); - assert_eq!( - node.properties.get("secs_since_login"), - Some(&FalkorValue::F64(105.5)) - ); - } - - #[test] - fn test_parse_path() { - let mut graph = open_readonly_graph_with_modified_schema(); - - let res = parse_type( - 9, - redis::Value::Bulk(vec![ - redis::Value::Bulk(vec![ - redis::Value::Bulk(vec![ - redis::Value::Int(51), - redis::Value::Bulk(vec![redis::Value::Int(0)]), - redis::Value::Bulk(vec![]), - ]), - redis::Value::Bulk(vec![ - redis::Value::Int(52), - redis::Value::Bulk(vec![redis::Value::Int(0)]), - redis::Value::Bulk(vec![]), - ]), - redis::Value::Bulk(vec![ - redis::Value::Int(53), - redis::Value::Bulk(vec![redis::Value::Int(0)]), - redis::Value::Bulk(vec![]), - ]), - ]), - redis::Value::Bulk(vec![ - redis::Value::Bulk(vec![ - redis::Value::Int(100), - redis::Value::Int(0), - redis::Value::Int(51), - redis::Value::Int(52), - redis::Value::Bulk(vec![]), - ]), - redis::Value::Bulk(vec![ - redis::Value::Int(101), - redis::Value::Int(1), - redis::Value::Int(52), - redis::Value::Int(53), - redis::Value::Bulk(vec![]), - ]), - ]), - ]), - &mut graph.graph_schema, - ); - assert!(res.is_ok()); - - let falkor_path = res.unwrap(); - let FalkorValue::Path(path) = falkor_path else { - panic!("Is not of type path") - }; - - assert_eq!(path.nodes.len(), 3); - assert_eq!(path.nodes[0].entity_id, 51); - assert_eq!(path.nodes[1].entity_id, 52); - assert_eq!(path.nodes[2].entity_id, 53); - - assert_eq!(path.relationships.len(), 2); - assert_eq!(path.relationships[0].entity_id, 100); - assert_eq!(path.relationships[1].entity_id, 101); - - assert_eq!(path.relationships[0].src_node_id, 51); - assert_eq!(path.relationships[0].dst_node_id, 52); - - assert_eq!(path.relationships[1].src_node_id, 52); - assert_eq!(path.relationships[1].dst_node_id, 53); - } - - #[test] - fn test_parse_map() { - let mut graph = open_readonly_graph_with_modified_schema(); - - let res = parse_type( - 10, - redis::Value::Bulk(vec![ - redis::Value::Status("key0".to_string()), - redis::Value::Bulk(vec![ - redis::Value::Int(2), - redis::Value::Status("val0".to_string()), - ]), - redis::Value::Status("key1".to_string()), - redis::Value::Bulk(vec![redis::Value::Int(3), redis::Value::Int(1)]), - redis::Value::Status("key2".to_string()), - redis::Value::Bulk(vec![ - redis::Value::Int(4), - redis::Value::Status("true".to_string()), - ]), - ]), - &mut graph.graph_schema, - ); - assert!(res.is_ok()); - - let falkor_map = res.unwrap(); - let FalkorValue::Map(map) = falkor_map else { - panic!("Is not of type map") - }; - - assert_eq!(map.len(), 3); - assert_eq!( - map.get("key0"), - Some(&FalkorValue::String("val0".to_string())) - ); - assert_eq!(map.get("key1"), Some(&FalkorValue::I64(1))); - assert_eq!(map.get("key2"), Some(&FalkorValue::Bool(true))); - } - - #[test] - fn test_parse_point() { - let mut graph = open_readonly_graph_with_modified_schema(); - - let res = parse_type( - 11, - redis::Value::Bulk(vec![ - redis::Value::Status("102.0".to_string()), - redis::Value::Status("15.2".to_string()), - ]), - &mut graph.graph_schema, - ); - assert!(res.is_ok()); - - let falkor_point = res.unwrap(); - let FalkorValue::Point(point) = falkor_point else { - panic!("Is not of type point") - }; - assert_eq!(point.latitude, 102.0); - assert_eq!(point.longitude, 15.2); - } - - #[test] - fn test_map_not_a_vec() { - let mut graph_schema = GraphSchema::new("test_graph", create_empty_inner_client()); - - let res = - parse_regular_falkor_map(redis::Value::Status("Hello".to_string()), &mut graph_schema); - - assert!(res.is_err()) - } - - #[test] - fn test_map_vec_odd_element_count() { - let mut graph_schema = GraphSchema::new("test_graph", create_empty_inner_client()); - - let res = parse_regular_falkor_map( - redis::Value::Bulk(vec![redis::Value::Nil; 7]), - &mut graph_schema, - ); - - assert!(res.is_err()) - } - - #[test] - fn test_map_val_element_is_not_array() { - let mut graph_schema = GraphSchema::new("test_graph", create_empty_inner_client()); - - let res = parse_regular_falkor_map( - redis::Value::Bulk(vec![ - redis::Value::Status("Key".to_string()), - redis::Value::Status("false".to_string()), - ]), - &mut graph_schema, - ); - - assert!(res.is_err()) - } - - #[test] - fn test_map_val_element_has_only_1_element() { - let mut graph_schema = GraphSchema::new("test_graph", create_empty_inner_client()); - - let res = parse_regular_falkor_map( - redis::Value::Bulk(vec![ - redis::Value::Status("Key".to_string()), - redis::Value::Bulk(vec![redis::Value::Int(7)]), - ]), - &mut graph_schema, - ); - - assert!(res.is_err()) - } - - #[test] - fn test_map_val_element_has_ge_2_elements() { - let mut graph_schema = GraphSchema::new("test_graph", create_empty_inner_client()); - - let res = parse_regular_falkor_map( - redis::Value::Bulk(vec![ - redis::Value::Status("Key".to_string()), - redis::Value::Bulk(vec![redis::Value::Int(3); 3]), - ]), - &mut graph_schema, - ); - - assert!(res.is_err()) - } - - #[test] - fn test_map_val_element_mismatch_type_marker() { - let mut graph_schema = GraphSchema::new("test_graph", create_empty_inner_client()); - - let res = parse_regular_falkor_map( - redis::Value::Bulk(vec![ - redis::Value::Status("Key".to_string()), - redis::Value::Bulk(vec![ - redis::Value::Int(3), - redis::Value::Status("true".to_string()), - ]), - ]), - &mut graph_schema, - ); - - assert!(res.is_err()) - } - - #[test] - fn test_map_ok_values() { - let mut graph_schema = GraphSchema::new("test_graph", create_empty_inner_client()); - - let res = parse_regular_falkor_map( - redis::Value::Bulk(vec![ - redis::Value::Status("IntKey".to_string()), - redis::Value::Bulk(vec![redis::Value::Int(3), redis::Value::Int(1)]), - redis::Value::Status("BoolKey".to_string()), - redis::Value::Bulk(vec![ - redis::Value::Int(4), - redis::Value::Status("true".to_string()), - ]), - ]), - &mut graph_schema, - ) - .expect("Could not parse map"); - - assert_eq!(res.get("IntKey"), Some(FalkorValue::I64(1)).as_ref()); - assert_eq!(res.get("BoolKey"), Some(FalkorValue::Bool(true)).as_ref()); - } -} From dd025fb3707d8a785e2af917e508a0fcaaf9e447 Mon Sep 17 00:00:00 2001 From: Emily Matheys Date: Wed, 12 Jun 2024 23:19:40 +0300 Subject: [PATCH 04/11] Done now I think --- src/client/blocking.rs | 4 ++-- src/parser/mod.rs | 30 ++++++++++++++++++++++++++++-- src/response/constraint.rs | 31 ++++++------------------------- src/response/index.rs | 31 +++++-------------------------- src/response/mod.rs | 4 ++-- 5 files changed, 43 insertions(+), 57 deletions(-) diff --git a/src/client/blocking.rs b/src/client/blocking.rs index e5782fa..5cac98c 100644 --- a/src/client/blocking.rs +++ b/src/client/blocking.rs @@ -6,7 +6,7 @@ use crate::{ client::FalkorClientProvider, connection::blocking::{BorrowedSyncConnection, FalkorSyncConnection}, - parser::string_vec_from_val, + parser::string_vec_from_untyped_val, redis_ext::redis_value_as_string, ConfigValue, FalkorConnectionInfo, FalkorDBError, FalkorResult, SyncGraph, }; @@ -176,7 +176,7 @@ impl FalkorSyncClient { pub fn list_graphs(&self) -> FalkorResult> { let mut conn = self.borrow_connection()?; conn.execute_command(None, "GRAPH.LIST", None, None) - .and_then(string_vec_from_val) + .and_then(string_vec_from_untyped_val) } /// Return the current value of a configuration option in the database. diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 774361f..f5d6755 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -11,7 +11,33 @@ use crate::{ }; use std::collections::HashMap; -pub(crate) fn string_vec_from_val(value: redis::Value) -> FalkorResult> { +pub(crate) fn parse_falkor_enum TryFrom<&'a str, Error = impl ToString>>( + val: redis::Value, + graph_schema: &mut GraphSchema, +) -> FalkorResult { + T::try_from( + parse_raw_redis_value(val, graph_schema) + .and_then(FalkorValue::into_string)? + .as_str(), + ) + .map_err(|err| FalkorDBError::InvalidEnumType(err.to_string())) +} + +pub(crate) fn string_vec_from_val( + value: redis::Value, + graph_schema: &mut GraphSchema, +) -> FalkorResult> { + parse_raw_redis_value(value, graph_schema) + .and_then(|parsed_value| parsed_value.into_vec()) + .map(|parsed_value_vec| { + parsed_value_vec + .into_iter() + .flat_map(FalkorValue::into_string) + .collect() + }) +} + +pub(crate) fn string_vec_from_untyped_val(value: redis::Value) -> FalkorResult> { value .into_sequence() .map(|as_vec| as_vec.into_iter().flat_map(redis_value_as_string).collect()) @@ -46,7 +72,7 @@ pub(crate) fn parse_header(header: redis::Value) -> FalkorResult> { key } else { // Get the first element from the item sequence - item_sequence.into_iter().next().ok_or_else(|| { + item_sequence.into_iter().next().ok_or({ FalkorDBError::ParsingHeader("Expected at least one item in header vector") })? }; diff --git a/src/response/constraint.rs b/src/response/constraint.rs index a8c4aaa..566eaff 100644 --- a/src/response/constraint.rs +++ b/src/response/constraint.rs @@ -4,8 +4,8 @@ */ use crate::{ - parser::parse_raw_redis_value, EntityType, FalkorDBError, FalkorResult, FalkorValue, - GraphSchema, + parser::{parse_falkor_enum, parse_raw_redis_value, string_vec_from_val}, + EntityType, FalkorDBError, FalkorResult, FalkorValue, GraphSchema, }; /// The type of restriction to apply for the property @@ -58,31 +58,12 @@ impl Constraint { .map_err(|_| FalkorDBError::ParsingArrayToStructElementCount("Expected exactly 5 elements in constraint object")))?; Ok(Self { - constraint_type: ConstraintType::try_from( - parse_raw_redis_value(constraint_type_raw, graph_schema) - .and_then(|parsed_constraint_type| parsed_constraint_type.into_string())? - .as_str(), - )?, + constraint_type: parse_falkor_enum(constraint_type_raw, graph_schema)?, label: parse_raw_redis_value(label_raw, graph_schema) .and_then(FalkorValue::into_string)?, - properties: parse_raw_redis_value(properties_raw, graph_schema) - .and_then(|properties_parsed| properties_parsed.into_vec()) - .map(|properties_vec| { - properties_vec - .into_iter() - .flat_map(FalkorValue::into_string) - .collect() - })?, - entity_type: EntityType::try_from( - parse_raw_redis_value(entity_type_raw, graph_schema) - .and_then(|parsed_entity_type| parsed_entity_type.into_string())? - .as_str(), - )?, - status: ConstraintStatus::try_from( - parse_raw_redis_value(status_raw, graph_schema) - .and_then(|parsed_status| parsed_status.into_string())? - .as_str(), - )?, + properties: string_vec_from_val(properties_raw, graph_schema)?, + entity_type: parse_falkor_enum(entity_type_raw, graph_schema)?, + status: parse_falkor_enum(status_raw, graph_schema)?, }) } } diff --git a/src/response/index.rs b/src/response/index.rs index 5b61e99..4e2e1a8 100644 --- a/src/response/index.rs +++ b/src/response/index.rs @@ -3,6 +3,7 @@ * Licensed under the Server Side Public License v1 (SSPLv1). */ +use crate::parser::{parse_falkor_enum, string_vec_from_val}; use crate::{parser::parse_raw_redis_value, EntityType, FalkorDBError, FalkorValue, GraphSchema}; use std::collections::HashMap; @@ -96,37 +97,15 @@ impl FalkorIndex { })?; Ok(Self { - entity_type: EntityType::try_from( - parse_raw_redis_value(entity_type, graph_schema) - .and_then(|parsed_entity_type| parsed_entity_type.into_string())? - .as_str(), - )?, - status: IndexStatus::try_from( - parse_raw_redis_value(status, graph_schema) - .and_then(|parsed_status| parsed_status.into_string())? - .as_str(), - )?, + entity_type: parse_falkor_enum(entity_type, graph_schema)?, + status: parse_falkor_enum(status, graph_schema)?, index_label: parse_raw_redis_value(label, graph_schema) .and_then(|parsed_entity_type| parsed_entity_type.into_string())?, - fields: parse_raw_redis_value(fields, graph_schema) - .and_then(|parsed_fields| parsed_fields.into_vec()) - .map(|parsed_fields_vec| { - parsed_fields_vec - .into_iter() - .flat_map(FalkorValue::into_string) - .collect() - })?, + fields: string_vec_from_val(fields, graph_schema)?, field_types: parse_types_map(field_types, graph_schema)?, language: parse_raw_redis_value(language, graph_schema) .and_then(FalkorValue::into_string)?, - stopwords: parse_raw_redis_value(stopwords, graph_schema) - .and_then(|stopwords_raw| stopwords_raw.into_vec()) - .map(|stopwords_vec| { - stopwords_vec - .into_iter() - .flat_map(FalkorValue::into_string) - .collect() - })?, + stopwords: string_vec_from_val(stopwords, graph_schema)?, info: parse_raw_redis_value(info, graph_schema) .and_then(FalkorValue::into_map) .map(|as_map| { diff --git a/src/response/mod.rs b/src/response/mod.rs index 8649c43..0f36355 100644 --- a/src/response/mod.rs +++ b/src/response/mod.rs @@ -4,7 +4,7 @@ */ use crate::{ - parser::{parse_header, string_vec_from_val}, + parser::{parse_header, string_vec_from_untyped_val}, FalkorResult, }; @@ -43,7 +43,7 @@ impl FalkorResponse { None => vec![], }, data, - stats: string_vec_from_val(stats)?, + stats: string_vec_from_untyped_val(stats)?, }) } } From 6eea51b9a1b55c1ea896f273a2831b1c271145b9 Mon Sep 17 00:00:00 2001 From: Emily Matheys Date: Wed, 12 Jun 2024 23:51:58 +0300 Subject: [PATCH 05/11] Fully Instrumented --- src/client/blocking.rs | 45 +++++++++++- src/connection/blocking.rs | 10 ++- src/graph/blocking.rs | 51 +++++++++++-- src/graph/query_builder.rs | 20 +++++- src/graph_schema/mod.rs | 10 ++- src/parser/mod.rs | 31 +++++++- src/response/constraint.rs | 4 ++ src/response/execution_plan.rs | 20 ++++++ src/response/index.rs | 4 ++ src/response/lazy_result_set.rs | 4 -- src/response/mod.rs | 4 ++ src/response/slowlog_entry.rs | 39 ++-------- src/value/graph_entities.rs | 10 +-- src/value/mod.rs | 124 +++----------------------------- src/value/path.rs | 4 ++ src/value/point.rs | 4 ++ 16 files changed, 210 insertions(+), 174 deletions(-) diff --git a/src/client/blocking.rs b/src/client/blocking.rs index 5cac98c..3ee20d1 100644 --- a/src/client/blocking.rs +++ b/src/client/blocking.rs @@ -27,7 +27,11 @@ pub(crate) struct FalkorSyncClientInner { impl FalkorSyncClientInner { #[cfg_attr( feature = "tracing", - tracing::instrument(name = "Borrow Connection From Connection Pool", skip_all) + tracing::instrument( + name = "Borrow Connection From Connection Pool", + skip_all, + level = "debug" + ) )] pub(crate) fn borrow_connection( &self, @@ -45,13 +49,20 @@ impl FalkorSyncClientInner { #[cfg_attr( feature = "tracing", - tracing::instrument(name = "Get New Sync Connection From Client", skip_all) + tracing::instrument( + name = "Get New Sync Connection From Client", + skip_all, + level = "info" + ) )] pub(crate) fn get_connection(&self) -> FalkorResult { self._inner.lock().get_connection() } } - +#[cfg_attr( + feature = "tracing", + tracing::instrument(name = "Check Is Sentinel", skip_all, level = "info") +)] fn is_sentinel(conn: &mut FalkorSyncConnection) -> FalkorResult { let info_map = conn.get_redis_info(Some("server"))?; Ok(info_map @@ -60,6 +71,10 @@ fn is_sentinel(conn: &mut FalkorSyncConnection) -> FalkorResult { .unwrap_or_default()) } +#[cfg_attr( + feature = "tracing", + tracing::instrument(name = "Get Sentinel Client", skip_all, level = "info") +)] pub(crate) fn get_sentinel_client( client: &mut FalkorClientProvider, connection_info: &redis::ConnectionInfo, @@ -131,6 +146,10 @@ pub struct FalkorSyncClient { } impl FalkorSyncClient { + #[cfg_attr( + feature = "tracing", + tracing::instrument(name = "Create Sync Client", skip_all, level = "info") + )] pub(crate) fn create( mut client: FalkorClientProvider, connection_info: FalkorConnectionInfo, @@ -173,6 +192,10 @@ impl FalkorSyncClient { /// /// # Returns /// A [`Vec`] of [`String`]s, containing the names of available graphs + #[cfg_attr( + feature = "tracing", + tracing::instrument(name = "List Graphs", skip_all, level = "info") + )] pub fn list_graphs(&self) -> FalkorResult> { let mut conn = self.borrow_connection()?; conn.execute_command(None, "GRAPH.LIST", None, None) @@ -187,6 +210,10 @@ impl FalkorSyncClient { /// /// # Returns /// A [`HashMap`] comprised of [`String`] keys, and [`ConfigValue`] values. + #[cfg_attr( + feature = "tracing", + tracing::instrument(name = "Get Config Value", skip_all, level = "info") + )] pub fn config_get( &self, config_key: &str, @@ -237,6 +264,10 @@ impl FalkorSyncClient { /// * `config_Key`: A [`String`] representation of a configuration's key. /// The config key can also be "*", which will return ALL the configuration options. /// * `value`: The new value to set, which is anything that can be converted into a [`ConfigValue`], namely string types and i64. + #[cfg_attr( + feature = "tracing", + tracing::instrument(name = "Set Config Value", skip_all, level = "info") + )] pub fn config_set>( &self, config_key: &str, @@ -272,6 +303,10 @@ impl FalkorSyncClient { /// /// # Returns /// If successful, will return the new [`SyncGraph`] object. + #[cfg_attr( + feature = "tracing", + tracing::instrument(name = "Copy Graph", skip_all, level = "info") + )] pub fn copy_graph( &self, graph_to_clone: &str, @@ -287,6 +322,10 @@ impl FalkorSyncClient { } /// Retrieves redis information + #[cfg_attr( + feature = "tracing", + tracing::instrument(name = "Client Get Redis Info", skip_all, level = "info") + )] pub fn redis_info( &self, section: Option<&str>, diff --git a/src/connection/blocking.rs b/src/connection/blocking.rs index bacbcec..fb75a05 100644 --- a/src/connection/blocking.rs +++ b/src/connection/blocking.rs @@ -19,7 +19,7 @@ pub(crate) enum FalkorSyncConnection { impl FalkorSyncConnection { #[cfg_attr( feature = "tracing", - tracing::instrument(name = "Connection Inner Execute Command", skip_all) + tracing::instrument(name = "Connection Inner Execute Command", skip_all, level = "debug") )] pub(crate) fn execute_command( &mut self, @@ -55,7 +55,7 @@ impl FalkorSyncConnection { #[cfg_attr( feature = "tracing", - tracing::instrument(name = "Get Redis Info", skip_all) + tracing::instrument(name = "Connection Get Redis Info", skip_all, level = "info") )] pub(crate) fn get_redis_info( &mut self, @@ -105,7 +105,11 @@ impl BorrowedSyncConnection { #[cfg_attr( feature = "tracing", - tracing::instrument(name = "Borrowed Connection Execute Command", skip_all) + tracing::instrument( + name = "Borrowed Connection Execute Command", + skip_all, + level = "trace" + ) )] pub(crate) fn execute_command( &mut self, diff --git a/src/graph/blocking.rs b/src/graph/blocking.rs index aaf6fd5..3c538c9 100644 --- a/src/graph/blocking.rs +++ b/src/graph/blocking.rs @@ -41,6 +41,10 @@ impl SyncGraph { self.graph_name.as_str() } + #[cfg_attr( + feature = "tracing", + tracing::instrument(name = "Graph Execute Command", skip_all, level = "info") + )] fn execute_command( &self, command: &str, @@ -54,6 +58,10 @@ impl SyncGraph { /// Deletes the graph stored in the database, and drop all the schema caches. /// NOTE: This still maintains the graph API, operations are still viable. + #[cfg_attr( + feature = "tracing", + tracing::instrument(name = "Delete Graph", skip_all, level = "info") + )] pub fn delete(&mut self) -> FalkorResult<()> { self.execute_command("GRAPH.DELETE", None, None)?; self.graph_schema.clear(); @@ -64,21 +72,24 @@ impl SyncGraph { /// /// # Returns /// A [`Vec`] of [`SlowlogEntry`], providing information about each query. + #[cfg_attr( + feature = "tracing", + tracing::instrument(name = "Get Graph Slowlog", skip_all, level = "info") + )] pub fn slowlog(&self) -> FalkorResult> { self.execute_command("GRAPH.SLOWLOG", None, None) .and_then(|res| { res.into_sequence() - .map(|as_vec| { - as_vec - .into_iter() - .flat_map(SlowlogEntry::try_from) - .collect() - }) + .map(|as_vec| as_vec.into_iter().flat_map(SlowlogEntry::parse).collect()) .map_err(|_| FalkorDBError::ParsingArray) }) } /// Resets the slowlog, all query time data will be cleared. + #[cfg_attr( + feature = "tracing", + tracing::instrument(name = "Reset Graph Slowlog", skip_all, level = "info") + )] pub fn slowlog_reset(&self) -> FalkorResult { self.execute_command("GRAPH.SLOWLOG", None, Some(&["RESET"])) } @@ -180,6 +191,10 @@ impl SyncGraph { /// /// # Returns /// A [`Vec`] of [`FalkorIndex`] + #[cfg_attr( + feature = "tracing", + tracing::instrument(name = "List Graph Indices", skip_all, level = "info") + )] pub fn list_indices(&mut self) -> FalkorResult>> { ProcedureQueryBuilder::>>::new(self, "DB.INDEXES").execute() } @@ -195,6 +210,10 @@ impl SyncGraph { /// /// # Returns /// A [`LazyResultSet`] containing information on the created index + #[cfg_attr( + feature = "tracing", + tracing::instrument(name = "Graph Create Index", skip_all, level = "info") + )] pub fn create_index( &mut self, index_field_type: IndexType, @@ -244,6 +263,10 @@ impl SyncGraph { /// /// # Arguments /// * `index_field_type` + #[cfg_attr( + feature = "tracing", + tracing::instrument(name = "Graph Drop Index", skip_all, level = "info") + )] pub fn drop_index( &mut self, index_field_type: IndexType, @@ -280,6 +303,10 @@ impl SyncGraph { /// /// # Returns /// A tuple where the first element is a [`Vec`] of [`Constraint`]s, and the second element is a [`Vec`] of stats as [`String`]s + #[cfg_attr( + feature = "tracing", + tracing::instrument(name = "List Graph Constraints", skip_all, level = "info") + )] pub fn list_constraints(&mut self) -> FalkorResult>> { ProcedureQueryBuilder::>>::new(self, "DB.CONSTRAINTS") .execute() @@ -291,6 +318,10 @@ impl SyncGraph { /// * `entity_type`: Whether to apply this constraint on nodes or relationships. /// * `label`: Entities with this label will have this constraint applied to them. /// * `properties`: A slice of the names of properties this constraint will apply to. + #[cfg_attr( + feature = "tracing", + tracing::instrument(name = "Create Graph Mandatory Constraint", skip_all, level = "info") + )] pub fn create_mandatory_constraint( &self, entity_type: EntityType, @@ -319,6 +350,10 @@ impl SyncGraph { /// * `entity_type`: Whether to apply this constraint on nodes or relationships. /// * `label`: Entities with this label will have this constraint applied to them. /// * `properties`: A slice of the names of properties this constraint will apply to. + #[cfg_attr( + feature = "tracing", + tracing::instrument(name = "Create Graph Unique Constraint", skip_all, level = "info") + )] pub fn create_unique_constraint( &mut self, entity_type: EntityType, @@ -356,6 +391,10 @@ impl SyncGraph { /// * `entity_type`: Whether this constraint exists on nodes or relationships. /// * `label`: Remove the constraint from entities with this label. /// * `properties`: A slice of the names of properties to remove the constraint from. + #[cfg_attr( + feature = "tracing", + tracing::instrument(name = "Drop Graph Constraint", skip_all, level = "info") + )] pub fn drop_constraint( &self, constraint_type: ConstraintType, diff --git a/src/graph/query_builder.rs b/src/graph/query_builder.rs index 4baf0a1..6907efb 100644 --- a/src/graph/query_builder.rs +++ b/src/graph/query_builder.rs @@ -9,6 +9,10 @@ use crate::{ }; use std::{collections::HashMap, fmt::Display, marker::PhantomData, ops::Not}; +#[cfg_attr( + feature = "tracing", + tracing::instrument(name = "Construct Query", skip_all, level = "trace") +)] pub(crate) fn construct_query( query_str: Q, params: Option<&HashMap>, @@ -86,7 +90,7 @@ impl<'a, Output, T: Display> QueryBuilder<'a, Output, T> { #[cfg_attr( feature = "tracing", - tracing::instrument(name = "Common Query Execution Steps", skip_all) + tracing::instrument(name = "Common Query Execution Steps", skip_all, level = "trace") )] fn common_execute_steps(&mut self) -> FalkorResult { let mut conn = self @@ -112,7 +116,7 @@ impl<'a, T: Display> QueryBuilder<'a, FalkorResponse>, T> { /// Executes the query, retuning a [`FalkorResponse`], with a [`LazyResultSet`] as its `data` member #[cfg_attr( feature = "tracing", - tracing::instrument(name = "Execute Lazy Result Set Query", skip_all) + tracing::instrument(name = "Execute Lazy Result Set Query", skip_all, level = "info") )] pub fn execute(mut self) -> FalkorResult>> { let res = self @@ -180,6 +184,10 @@ impl<'a, T: Display> QueryBuilder<'a, ExecutionPlan, T> { } } +#[cfg_attr( + feature = "tracing", + tracing::instrument(name = "Generate Procedure Call", skip_all, level = "trace") +)] pub(crate) fn generate_procedure_call( procedure: P, args: Option<&[T]>, @@ -309,6 +317,10 @@ impl<'a, Output> ProcedureQueryBuilder<'a, Output> { impl<'a> ProcedureQueryBuilder<'a, FalkorResponse>> { /// Executes the procedure call and return a [`FalkorResponse`] type containing a result set of [`FalkorIndex`]s /// This functions consumes self + #[cfg_attr( + feature = "tracing", + tracing::instrument(name = "Execute FalkorIndex Query", skip_all, level = "info") + )] pub fn execute(mut self) -> FalkorResult>> { self.common_execute_steps( &mut self @@ -341,6 +353,10 @@ impl<'a> ProcedureQueryBuilder<'a, FalkorResponse>> { impl<'a> ProcedureQueryBuilder<'a, FalkorResponse>> { /// Executes the procedure call and return a [`FalkorResponse`] type containing a result set of [`Constraint`]s /// This functions consumes self + #[cfg_attr( + feature = "tracing", + tracing::instrument(name = "Execute Constraint Procedure Call", skip_all, level = "info") + )] pub fn execute(mut self) -> FalkorResult>> { self.common_execute_steps( &mut self diff --git a/src/graph_schema/mod.rs b/src/graph_schema/mod.rs index a0b9f32..8b8577b 100644 --- a/src/graph_schema/mod.rs +++ b/src/graph_schema/mod.rs @@ -30,6 +30,10 @@ pub(crate) struct FKeyTypeVal { impl TryFrom for FKeyTypeVal { type Error = FalkorDBError; + #[cfg_attr( + feature = "tracing", + tracing::instrument(name = "New KeyTypeValue", skip_all, level = "trace") + )] fn try_from(value: redis::Value) -> FalkorResult { let [key_raw, type_raw, val]: [redis::Value; 3] = value .into_sequence() @@ -135,7 +139,7 @@ impl GraphSchema { #[cfg_attr( feature = "tracing", - tracing::instrument(name = "Refresh Schema Type", skip_all) + tracing::instrument(name = "Refresh Schema Type", skip_all, level = "info") )] fn refresh( &mut self, @@ -191,7 +195,7 @@ impl GraphSchema { #[cfg_attr( feature = "tracing", - tracing::instrument(name = "Parse ID Vec To String Vec", skip_all) + tracing::instrument(name = "Parse ID Vec To String Vec", skip_all, level = "debug") )] pub(crate) fn parse_id_vec( &mut self, @@ -224,7 +228,7 @@ impl GraphSchema { #[cfg_attr( feature = "tracing", - tracing::instrument(name = "Parse Properties Map", skip_all) + tracing::instrument(name = "Parse Properties Map", skip_all, level = "debug") )] pub(crate) fn parse_properties_map( &mut self, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index f5d6755..bd4fb8e 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -11,6 +11,10 @@ use crate::{ }; use std::collections::HashMap; +#[cfg_attr( + feature = "tracing", + tracing::instrument(name = "Parse Falkor Enum", skip_all, level = "trace") +)] pub(crate) fn parse_falkor_enum TryFrom<&'a str, Error = impl ToString>>( val: redis::Value, graph_schema: &mut GraphSchema, @@ -23,6 +27,10 @@ pub(crate) fn parse_falkor_enum TryFrom<&'a str, Error = impl ToStrin .map_err(|err| FalkorDBError::InvalidEnumType(err.to_string())) } +#[cfg_attr( + feature = "tracing", + tracing::instrument(name = "String Vec From Value", skip_all, level = "debug") +)] pub(crate) fn string_vec_from_val( value: redis::Value, graph_schema: &mut GraphSchema, @@ -37,6 +45,10 @@ pub(crate) fn string_vec_from_val( }) } +#[cfg_attr( + feature = "tracing", + tracing::instrument(name = "String Vec From Untyped Value", skip_all, level = "trace") +)] pub(crate) fn string_vec_from_untyped_val(value: redis::Value) -> FalkorResult> { value .into_sequence() @@ -44,6 +56,10 @@ pub(crate) fn string_vec_from_untyped_val(value: redis::Value) -> FalkorResult FalkorResult> { // Convert the header into a sequence let header_sequence = header @@ -83,7 +99,10 @@ pub(crate) fn parse_header(header: redis::Value) -> FalkorResult> { }, ) } - +#[cfg_attr( + feature = "tracing", + tracing::instrument(name = "Parse Raw Redis Value", skip_all, level = "debug") +)] pub(crate) fn parse_raw_redis_value( value: redis::Value, graph_schema: &mut GraphSchema, @@ -92,6 +111,10 @@ pub(crate) fn parse_raw_redis_value( .and_then(|(type_marker, val)| parse_type(type_marker, val, graph_schema)) } +#[cfg_attr( + feature = "tracing", + tracing::instrument(name = "TypeVal From Value", skip_all, level = "trace") +)] pub(crate) fn type_val_from_value( value: redis::Value ) -> Result<(i64, redis::Value), FalkorDBError> { @@ -108,6 +131,10 @@ pub(crate) fn type_val_from_value( Ok((redis_value_as_int(type_marker)?, val)) } +#[cfg_attr( + feature = "tracing", + tracing::instrument(name = "Parse Regular Falkor Map", skip_all, level = "debug") +)] fn parse_regular_falkor_map( value: redis::Value, graph_schema: &mut GraphSchema, @@ -126,7 +153,7 @@ fn parse_regular_falkor_map( #[cfg_attr( feature = "tracing", - tracing::instrument(name = "Parse Element With Type Marker", skip_all) + tracing::instrument(name = "Parse Element With Type Marker", skip_all, level = "trace") )] pub(crate) fn parse_type( type_marker: i64, diff --git a/src/response/constraint.rs b/src/response/constraint.rs index 566eaff..9332495 100644 --- a/src/response/constraint.rs +++ b/src/response/constraint.rs @@ -48,6 +48,10 @@ pub struct Constraint { } impl Constraint { + #[cfg_attr( + feature = "tracing", + tracing::instrument(name = "Parse Constraint", skip_all, level = "info") + )] pub(crate) fn parse( value: redis::Value, graph_schema: &mut GraphSchema, diff --git a/src/response/execution_plan.rs b/src/response/execution_plan.rs index 4267d31..c3b76f8 100644 --- a/src/response/execution_plan.rs +++ b/src/response/execution_plan.rs @@ -24,6 +24,10 @@ struct IntermediateOperation { } impl IntermediateOperation { + #[cfg_attr( + feature = "tracing", + tracing::instrument(name = "Create New Operation", skip_all, level = "trace") + )] fn new( depth: usize, operation_string: &str, @@ -118,6 +122,10 @@ impl ExecutionPlan { self.string_representation.as_str() } + #[cfg_attr( + feature = "tracing", + tracing::instrument(name = "Create Node", skip_all, level = "debug") + )] fn create_node( depth: usize, operation_string: &str, @@ -132,6 +140,10 @@ impl ExecutionPlan { Ok(()) } + #[cfg_attr( + feature = "tracing", + tracing::instrument(name = "Finalize Operation", skip_all, level = "debug") + )] fn finalize_operation( current_refcell: Rc> ) -> FalkorResult> { @@ -156,6 +168,10 @@ impl ExecutionPlan { })) } + #[cfg_attr( + feature = "tracing", + tracing::instrument(name = "Parse Operation Tree To Map", skip_all, level = "trace") + )] fn operations_map_from_tree( current_branch: &Rc, map: &mut HashMap>>, @@ -169,6 +185,10 @@ impl ExecutionPlan { } } + #[cfg_attr( + feature = "tracing", + tracing::instrument(name = "Parse Execution Plan", skip_all, level = "info") + )] pub(crate) fn parse(value: redis::Value) -> FalkorResult { let redis_value_vec = value .into_sequence() diff --git a/src/response/index.rs b/src/response/index.rs index 4e2e1a8..7eef248 100644 --- a/src/response/index.rs +++ b/src/response/index.rs @@ -81,6 +81,10 @@ pub struct FalkorIndex { } impl FalkorIndex { + #[cfg_attr( + feature = "tracing", + tracing::instrument(name = "Parse Index", skip_all, level = "info") + )] pub(crate) fn parse( value: redis::Value, graph_schema: &mut GraphSchema, diff --git a/src/response/lazy_result_set.rs b/src/response/lazy_result_set.rs index 4a7a337..b1e5e73 100644 --- a/src/response/lazy_result_set.rs +++ b/src/response/lazy_result_set.rs @@ -14,10 +14,6 @@ pub struct LazyResultSet<'a> { } impl<'a> LazyResultSet<'a> { - #[cfg_attr( - feature = "tracing", - tracing::instrument(name = "Create New Lazy Result Set", skip_all) - )] pub(crate) fn new( data: Vec, graph_schema: &'a mut GraphSchema, diff --git a/src/response/mod.rs b/src/response/mod.rs index 0f36355..a7ce8c3 100644 --- a/src/response/mod.rs +++ b/src/response/mod.rs @@ -32,6 +32,10 @@ impl FalkorResponse { /// * `headers`: a [`redis::Value`] that is expected to be of variant [`redis::Value::Bulk`], where each element is expected to be of variant [`redis::Value::Data`] or [`redis::Value::Status`] /// * `data`: The actual data /// * `stats`: a [`redis::Value`] that is expected to be of variant [`redis::Value::Bulk`], where each element is expected to be of variant [`redis::Value::Data`] or [`redis::Value::Status`] + #[cfg_attr( + feature = "tracing", + tracing::instrument(name = "New Falkor Response", skip_all, level = "trace") + )] pub fn from_response( headers: Option, data: T, diff --git a/src/response/slowlog_entry.rs b/src/response/slowlog_entry.rs index 193bc5f..f13ef8b 100644 --- a/src/response/slowlog_entry.rs +++ b/src/response/slowlog_entry.rs @@ -3,8 +3,7 @@ * Licensed under the Server Side Public License v1 (SSPLv1). */ -use crate::redis_ext::redis_value_as_string; -use crate::{FalkorDBError, FalkorValue}; +use crate::{redis_ext::redis_value_as_string, FalkorDBError, FalkorResult}; /// A slowlog entry, representing one of the N slowest queries in the current log #[derive(Clone, Debug, PartialEq)] @@ -19,36 +18,12 @@ pub struct SlowlogEntry { pub time_taken: f64, } -impl TryFrom for SlowlogEntry { - type Error = FalkorDBError; - - fn try_from(value: FalkorValue) -> Result { - let [timestamp, command, arguments, time_taken] = - value.into_vec()?.try_into().map_err(|_| { - FalkorDBError::ParsingArrayToStructElementCount( - "Expected exactly 4 elements of slowlog entry", - ) - })?; - - Ok(Self { - timestamp: timestamp - .into_string()? - .parse() - .map_err(|_| FalkorDBError::ParsingI64)?, - command: command.into_string()?, - arguments: arguments.into_string()?, - time_taken: time_taken - .into_string()? - .parse() - .map_err(|_| FalkorDBError::ParsingF64)?, - }) - } -} - -impl TryFrom for SlowlogEntry { - type Error = FalkorDBError; - - fn try_from(value: redis::Value) -> Result { +impl SlowlogEntry { + #[cfg_attr( + feature = "tracing", + tracing::instrument(name = "Parse Slowlog Entry", skip_all, level = "info") + )] + pub(crate) fn parse(value: redis::Value) -> FalkorResult { let [timestamp, command, arguments, time_taken]: [redis::Value; 4] = value .into_sequence() .map_err(|_| FalkorDBError::ParsingArray) diff --git a/src/value/graph_entities.rs b/src/value/graph_entities.rs index 3531e58..f2ca08a 100644 --- a/src/value/graph_entities.rs +++ b/src/value/graph_entities.rs @@ -3,8 +3,10 @@ * Licensed under the Server Side Public License v1 (SSPLv1). */ -use crate::redis_ext::redis_value_as_int; -use crate::{FalkorDBError, FalkorResult, FalkorValue, GraphSchema, SchemaType}; +use crate::{ + redis_ext::redis_value_as_int, FalkorDBError, FalkorResult, FalkorValue, GraphSchema, + SchemaType, +}; use std::collections::HashMap; /// Whether this element is a node or edge in the graph @@ -32,7 +34,7 @@ pub struct Node { impl Node { #[cfg_attr( feature = "tracing", - tracing::instrument(name = "Parse Node", skip_all) + tracing::instrument(name = "Parse Node", skip_all, level = "debug") )] pub(crate) fn parse( value: redis::Value, @@ -78,7 +80,7 @@ pub struct Edge { impl Edge { #[cfg_attr( feature = "tracing", - tracing::instrument(name = "Parse Edge", skip_all) + tracing::instrument(name = "Parse Edge", skip_all, level = "debug") )] pub(crate) fn parse( value: redis::Value, diff --git a/src/value/mod.rs b/src/value/mod.rs index 8bb6910..749d95a 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -72,109 +72,6 @@ impl From<&str> for FalkorValue { } } -impl From> for FalkorValue -where - FalkorValue: From, -{ - fn from(value: Vec) -> Self { - Self::Array( - value - .into_iter() - .map(|element| FalkorValue::from(element)) - .collect(), - ) - } -} - -impl TryFrom for Vec { - type Error = FalkorDBError; - - fn try_from(value: FalkorValue) -> FalkorResult { - match value { - FalkorValue::Array(val) => Ok(val), - _ => Err(FalkorDBError::ParsingArray), - } - } -} - -impl TryFrom for f64 { - type Error = FalkorDBError; - - fn try_from(value: FalkorValue) -> FalkorResult { - match value { - FalkorValue::String(f64_str) => f64_str.parse().map_err(|_| FalkorDBError::ParsingF64), - FalkorValue::F64(f64_val) => Ok(f64_val), - _ => Err(FalkorDBError::ParsingF64), - } - } -} - -impl TryFrom for String { - type Error = FalkorDBError; - - fn try_from(value: FalkorValue) -> FalkorResult { - match value { - FalkorValue::String(val) => Ok(val), - _ => Err(FalkorDBError::ParsingFString), - } - } -} - -impl TryFrom for Edge { - type Error = FalkorDBError; - - fn try_from(value: FalkorValue) -> FalkorResult { - match value { - FalkorValue::Edge(edge) => Ok(edge), - _ => Err(FalkorDBError::ParsingFEdge), - } - } -} - -impl TryFrom for Node { - type Error = FalkorDBError; - - fn try_from(value: FalkorValue) -> FalkorResult { - match value { - FalkorValue::Node(node) => Ok(node), - _ => Err(FalkorDBError::ParsingFNode), - } - } -} - -impl TryFrom for Path { - type Error = FalkorDBError; - - fn try_from(value: FalkorValue) -> FalkorResult { - match value { - FalkorValue::Path(path) => Ok(path), - _ => Err(FalkorDBError::ParsingFPath), - } - } -} - -impl TryFrom for HashMap { - type Error = FalkorDBError; - - fn try_from(value: FalkorValue) -> FalkorResult { - match value { - FalkorValue::Map(map) => Ok(map), - _ => Err(FalkorDBError::ParsingFMap), - } - } -} - -impl TryFrom for Point { - type Error = FalkorDBError; - - fn try_from(value: FalkorValue) -> FalkorResult { - match value { - FalkorValue::Point(point) => Ok(point), - _ => Err(FalkorDBError::ParsingFPoint), - } - } -} - impl FalkorValue { /// Returns a reference to the internal [`Vec`] if this is an Array variant. /// @@ -296,10 +193,9 @@ impl FalkorValue { /// # Returns /// The inner [`Vec`] pub fn into_vec(self) -> FalkorResult> { - if let FalkorValue::Array(array) = self { - Ok(array) - } else { - Err(FalkorDBError::ParsingFMap) + match self { + FalkorValue::Array(array) => Ok(array), + _ => Err(FalkorDBError::ParsingArray), } } @@ -308,10 +204,9 @@ impl FalkorValue { /// # Returns /// The inner [`String`] pub fn into_string(self) -> FalkorResult { - if let FalkorValue::String(string) = self { - Ok(string) - } else { - Err(FalkorDBError::ParsingFString) + match self { + FalkorValue::String(string) => Ok(string), + _ => Err(FalkorDBError::ParsingFString), } } /// Consumes itself and returns the inner [`HashMap`] if this is a Map variant @@ -319,10 +214,9 @@ impl FalkorValue { /// # Returns /// The inner [`HashMap`] pub fn into_map(self) -> FalkorResult> { - if let FalkorValue::Map(map) = self { - Ok(map) - } else { - Err(FalkorDBError::ParsingFMap) + match self { + FalkorValue::Map(map) => Ok(map), + _ => Err(FalkorDBError::ParsingFMap), } } } diff --git a/src/value/path.rs b/src/value/path.rs index 8e561a7..4da0d5a 100644 --- a/src/value/path.rs +++ b/src/value/path.rs @@ -15,6 +15,10 @@ pub struct Path { } impl Path { + #[cfg_attr( + feature = "tracing", + tracing::instrument(name = "Parse Path", skip_all, level = "debug") + )] pub(crate) fn parse( value: redis::Value, graph_schema: &mut GraphSchema, diff --git a/src/value/point.rs b/src/value/point.rs index ea56730..da93e59 100644 --- a/src/value/point.rs +++ b/src/value/point.rs @@ -24,6 +24,10 @@ impl Point { /// /// # Returns /// Self, if successful + #[cfg_attr( + feature = "tracing", + tracing::instrument(name = "Parse Point", skip_all, level = "trace") + )] pub fn parse(value: redis::Value) -> FalkorResult { let [lat, long]: [redis::Value; 2] = value .into_sequence() From b064efaf1cd8146831cb07cd6936e474730dff5c Mon Sep 17 00:00:00 2001 From: Emily Matheys Date: Wed, 12 Jun 2024 23:52:29 +0300 Subject: [PATCH 06/11] Fully Instrumented --- Cargo.toml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f9850d8..a73da46 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,10 +15,6 @@ strum = { version = "0.26.2", default-features = false, features = ["std", "deri thiserror = "1.0.61" tracing = { version = "0.1.40", default-features = false, features = ["std", "attributes"], optional = true } -[dev-dependencies] -tracing-tracy = { version = "0.11.0" } -tracing-subscriber = { version = "0.3.18", default-features = false, features = ["std"] } - [features] default = [] From ce11a4c80a02b945170faa5cf57a78eb56dd9faa Mon Sep 17 00:00:00 2001 From: Emily Matheys Date: Thu, 13 Jun 2024 10:17:39 +0300 Subject: [PATCH 07/11] Remove test crates --- Cargo.lock | 196 +---------------------------------------------------- 1 file changed, 3 insertions(+), 193 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f265f67..d6f927d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -130,8 +130,6 @@ dependencies = [ "strum", "thiserror", "tracing", - "tracing-subscriber", - "tracing-tracy", ] [[package]] @@ -170,20 +168,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "generator" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186014d53bc231d0090ef8d6f03e0920c54d85a5ed22f4f2f74315ec56cf83fb" -dependencies = [ - "cc", - "cfg-if", - "libc", - "log", - "rustversion", - "windows", -] - [[package]] name = "getrandom" version = "0.2.15" @@ -273,28 +257,6 @@ version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" -[[package]] -name = "loom" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" -dependencies = [ - "cfg-if", - "generator", - "scoped-tls", - "tracing", - "tracing-subscriber", -] - -[[package]] -name = "matchers" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" -dependencies = [ - "regex-automata 0.1.10", -] - [[package]] name = "memchr" version = "2.7.2" @@ -328,16 +290,6 @@ dependencies = [ "tempfile", ] -[[package]] -name = "nu-ansi-term" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" -dependencies = [ - "overload", - "winapi", -] - [[package]] name = "object" version = "0.32.2" @@ -397,12 +349,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "overload" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" - [[package]] name = "parking_lot" version = "0.12.3" @@ -547,17 +493,8 @@ checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.6", - "regex-syntax 0.8.3", -] - -[[package]] -name = "regex-automata" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" -dependencies = [ - "regex-syntax 0.6.29", + "regex-automata", + "regex-syntax", ] [[package]] @@ -568,15 +505,9 @@ checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.3", + "regex-syntax", ] -[[package]] -name = "regex-syntax" -version = "0.6.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" - [[package]] name = "regex-syntax" version = "0.8.3" @@ -692,12 +623,6 @@ dependencies = [ "windows-sys", ] -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - [[package]] name = "scopeguard" version = "1.2.0" @@ -727,15 +652,6 @@ dependencies = [ "libc", ] -[[package]] -name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - [[package]] name = "smallvec" version = "1.13.2" @@ -829,16 +745,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "thread_local" -version = "1.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" -dependencies = [ - "cfg-if", - "once_cell", -] - [[package]] name = "tinyvec" version = "1.6.0" @@ -883,67 +789,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", - "valuable", -] - -[[package]] -name = "tracing-log" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" -dependencies = [ - "matchers", - "nu-ansi-term", - "once_cell", - "regex", - "sharded-slab", - "smallvec", - "thread_local", - "tracing", - "tracing-core", - "tracing-log", -] - -[[package]] -name = "tracing-tracy" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6024d04f84a69fd0d1dc1eee3a2b070bd246530a0582f9982ae487cb6c703614" -dependencies = [ - "tracing-core", - "tracing-subscriber", - "tracy-client", -] - -[[package]] -name = "tracy-client" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59fb931a64ff88984f86d3e9bcd1ae8843aa7fe44dd0f8097527bc172351741d" -dependencies = [ - "loom", - "once_cell", - "tracy-client-sys", -] - -[[package]] -name = "tracy-client-sys" -version = "0.22.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d104d610dfa9dd154535102cc9c6164ae1fa37842bc2d9e83f9ac82b0ae0882" -dependencies = [ - "cc", ] [[package]] @@ -984,12 +829,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "valuable" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" - [[package]] name = "vcpkg" version = "0.2.15" @@ -1024,35 +863,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows" -version = "0.54.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49" -dependencies = [ - "windows-core", - "windows-targets", -] - -[[package]] -name = "windows-core" -version = "0.54.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65" -dependencies = [ - "windows-result", - "windows-targets", -] - -[[package]] -name = "windows-result" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" -dependencies = [ - "windows-targets", -] - [[package]] name = "windows-sys" version = "0.52.0" From 7ce7b2c56a70d08a8d92dd9575cf9f7b2c0add99 Mon Sep 17 00:00:00 2001 From: Emily Matheys Date: Thu, 13 Jun 2024 10:31:29 +0300 Subject: [PATCH 08/11] Update README --- README.md | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2023efc..824b44c 100644 --- a/README.md +++ b/README.md @@ -11,16 +11,16 @@ [![Try Free](https://img.shields.io/badge/Try%20Free-FalkorDB%20Cloud-FF8101?labelColor=FDE900&style=for-the-badge&link=https://app.falkordb.cloud)](https://app.falkordb.cloud) -FalkorDB Rust client +### FalkorDB Rust client ## Usage ### Installation -Just add it to your `Cargo.toml`, like so +Just add it to your `Cargo.toml`, like so: ```toml -falkordb = { version = "0.1.0" } +falkordb = { version = "*" } ``` ### Run FalkorDB instance @@ -55,3 +55,30 @@ for n in nodes.data { println!("{:?}", n[0]); } ``` + +## Features + +### SSL/TLS Support + +This client is currently built upon the [`redis`](https://docs.rs/redis/latest/redis/) crate, and therefor supports TLS using +its implementation, which uses either [`rustls`](https://docs.rs/rustls/latest/rustls/) or [`native_tls`](https://docs.rs/native-tls/latest/native_tls/). +This is not enabled by default, and the user ust opt-in by enabling the respective features: `"rustls"`/`"native-tls"`.\ + +For Rustls: +```toml +falkordb = { version = "*", features = ["rustls"] } +``` + +For NativeTLS: +```toml +falkordb = { version = "*", features = ["native-tls"] } +``` + +### Tracing + +This crate fully supports instrumentation using the [`tracing`](https://docs.rs/tracing/latest/tracing/) crate, to use it, simply, enable the `tracing` feature: +```toml +falkordb = { version = "*", features = ["tracing"] } +``` + +Note that different functions use different filtration levels, to avoid spamming your tests, be sure to enable the correct level as you desire it. \ No newline at end of file From 3976e114ff014bee02afbc53e625ff7e81f27de6 Mon Sep 17 00:00:00 2001 From: Emily Matheys Date: Thu, 13 Jun 2024 11:33:57 +0300 Subject: [PATCH 09/11] As optimized as it will get tbh --- src/client/blocking.rs | 47 ++++++----- src/connection/blocking.rs | 6 +- src/graph/blocking.rs | 9 +-- src/graph/query_builder.rs | 79 +++++++++---------- src/graph_schema/mod.rs | 79 +++++++++---------- src/lib.rs | 2 - src/parser/mod.rs | 139 +++++++++++++++++++++------------ src/redis_ext.rs | 97 ----------------------- src/response/constraint.rs | 16 ++-- src/response/execution_plan.rs | 9 ++- src/response/index.rs | 25 +++--- src/response/mod.rs | 4 +- src/response/slowlog_entry.rs | 16 ++-- src/value/config.rs | 42 ++++++++++ src/value/graph_entities.rs | 37 ++++----- src/value/path.rs | 25 +++--- src/value/point.rs | 16 ++-- 17 files changed, 305 insertions(+), 343 deletions(-) delete mode 100644 src/redis_ext.rs diff --git a/src/client/blocking.rs b/src/client/blocking.rs index 3ee20d1..fc31ec9 100644 --- a/src/client/blocking.rs +++ b/src/client/blocking.rs @@ -6,8 +6,7 @@ use crate::{ client::FalkorClientProvider, connection::blocking::{BorrowedSyncConnection, FalkorSyncConnection}, - parser::string_vec_from_untyped_val, - redis_ext::redis_value_as_string, + parser::{redis_value_as_string, redis_value_as_untyped_string_vec, redis_value_as_vec}, ConfigValue, FalkorConnectionInfo, FalkorDBError, FalkorResult, SyncGraph, }; use parking_lot::{Mutex, RwLock}; @@ -87,11 +86,12 @@ pub(crate) fn get_sentinel_client( // This could have been so simple using the Sentinel API, but it requires a service name // Perhaps in the future we can use it if we only support the master instance to be called 'master'? let sentinel_masters = conn - .execute_command(None, "SENTINEL", Some("MASTERS"), None)? - .into_sequence() - .ok() - .and_then(|vec| (vec.len() == 1).then_some(vec)) - .ok_or(FalkorDBError::SentinelMastersCount)?; + .execute_command(None, "SENTINEL", Some("MASTERS"), None) + .and_then(redis_value_as_vec)?; + + if sentinel_masters.len() != 1 { + return Err(FalkorDBError::SentinelMastersCount); + } let sentinel_master: HashMap<_, _> = sentinel_masters .into_iter() @@ -99,7 +99,7 @@ pub(crate) fn get_sentinel_client( .and_then(|master| master.into_sequence().ok()) .ok_or(FalkorDBError::SentinelMastersCount)? .chunks_exact(2) - .flat_map(TryInto::<&[redis::Value; 2]>::try_into) // TODO: check if this can be done with no copying + .flat_map(TryInto::<&[redis::Value; 2]>::try_into) // TODO: In the future, check if this can be done with no copying, but this should be a rare function call tbh .flat_map(|[key, val]| { redis_value_as_string(key.to_owned()) .and_then(|key| redis_value_as_string(val.to_owned()).map(|val| (key, val))) @@ -199,7 +199,7 @@ impl FalkorSyncClient { pub fn list_graphs(&self) -> FalkorResult> { let mut conn = self.borrow_connection()?; conn.execute_command(None, "GRAPH.LIST", None, None) - .and_then(string_vec_from_untyped_val) + .and_then(redis_value_as_untyped_string_vec) } /// Return the current value of a configuration option in the database. @@ -223,7 +223,7 @@ impl FalkorSyncClient { .and_then(|mut conn| { conn.execute_command(None, "GRAPH.CONFIG", Some("GET"), Some(&[config_key])) }) - .and_then(|res| res.into_sequence().map_err(|_| FalkorDBError::ParsingArray))?; + .and_then(redis_value_as_vec)?; if config.len() == 2 { let [key, val]: [redis::Value; 2] = config.try_into().map_err(|_| { @@ -239,21 +239,18 @@ impl FalkorSyncClient { Ok(config .into_iter() .flat_map(|config| { - config - .into_sequence() - .map_err(|_| FalkorDBError::ParsingArray) - .and_then(|as_vec| { - let [key, val]: [redis::Value; 2] = as_vec.try_into().map_err(|_| { - FalkorDBError::ParsingArrayToStructElementCount( - "Expected exactly 2 elements for configuration option", - ) - })?; - - Result::<_, FalkorDBError>::Ok(( - redis_value_as_string(key)?, - ConfigValue::try_from(val)?, - )) - }) + redis_value_as_vec(config).and_then(|as_vec| { + let [key, val]: [redis::Value; 2] = as_vec.try_into().map_err(|_| { + FalkorDBError::ParsingArrayToStructElementCount( + "Expected exactly 2 elements for configuration option", + ) + })?; + + Result::<_, FalkorDBError>::Ok(( + redis_value_as_string(key)?, + ConfigValue::try_from(val)?, + )) + }) }) .collect::>()) } diff --git a/src/connection/blocking.rs b/src/connection/blocking.rs index fb75a05..f379e7e 100644 --- a/src/connection/blocking.rs +++ b/src/connection/blocking.rs @@ -3,8 +3,10 @@ * Licensed under the Server Side Public License v1 (SSPLv1). */ -use crate::redis_ext::redis_value_as_string; -use crate::{client::blocking::FalkorSyncClientInner, FalkorDBError, FalkorResult}; +use crate::{ + client::blocking::FalkorSyncClientInner, parser::redis_value_as_string, FalkorDBError, + FalkorResult, +}; use std::{ collections::HashMap, sync::{mpsc, Arc}, diff --git a/src/graph/blocking.rs b/src/graph/blocking.rs index 3c538c9..effc2bd 100644 --- a/src/graph/blocking.rs +++ b/src/graph/blocking.rs @@ -4,9 +4,9 @@ */ use crate::{ - client::blocking::FalkorSyncClientInner, Constraint, ConstraintType, EntityType, ExecutionPlan, - FalkorDBError, FalkorIndex, FalkorResponse, FalkorResult, GraphSchema, IndexType, - LazyResultSet, ProcedureQueryBuilder, QueryBuilder, SlowlogEntry, + client::blocking::FalkorSyncClientInner, parser::redis_value_as_vec, Constraint, + ConstraintType, EntityType, ExecutionPlan, FalkorIndex, FalkorResponse, FalkorResult, + GraphSchema, IndexType, LazyResultSet, ProcedureQueryBuilder, QueryBuilder, SlowlogEntry, }; use std::{collections::HashMap, fmt::Display, sync::Arc}; @@ -79,9 +79,8 @@ impl SyncGraph { pub fn slowlog(&self) -> FalkorResult> { self.execute_command("GRAPH.SLOWLOG", None, None) .and_then(|res| { - res.into_sequence() + redis_value_as_vec(res) .map(|as_vec| as_vec.into_iter().flat_map(SlowlogEntry::parse).collect()) - .map_err(|_| FalkorDBError::ParsingArray) }) } diff --git a/src/graph/query_builder.rs b/src/graph/query_builder.rs index 6907efb..859f33c 100644 --- a/src/graph/query_builder.rs +++ b/src/graph/query_builder.rs @@ -4,8 +4,9 @@ */ use crate::{ - connection::blocking::BorrowedSyncConnection, Constraint, ExecutionPlan, FalkorDBError, - FalkorIndex, FalkorResponse, FalkorResult, LazyResultSet, SyncGraph, + connection::blocking::BorrowedSyncConnection, parser::redis_value_as_vec, Constraint, + ExecutionPlan, FalkorDBError, FalkorIndex, FalkorResponse, FalkorResult, LazyResultSet, + SyncGraph, }; use std::{collections::HashMap, fmt::Display, marker::PhantomData, ops::Not}; @@ -119,10 +120,7 @@ impl<'a, T: Display> QueryBuilder<'a, FalkorResponse>, T> { tracing::instrument(name = "Execute Lazy Result Set Query", skip_all, level = "info") )] pub fn execute(mut self) -> FalkorResult>> { - let res = self - .common_execute_steps()? - .into_sequence() - .map_err(|_| FalkorDBError::ParsingArray)?; + let res = self.common_execute_steps().and_then(redis_value_as_vec)?; match res.len() { 1 => { @@ -160,11 +158,7 @@ impl<'a, T: Display> QueryBuilder<'a, FalkorResponse>, T> { FalkorResponse::from_response( Some(header), - LazyResultSet::new( - data.into_sequence() - .map_err(|_| FalkorDBError::ParsingArray)?, - &mut self.graph.graph_schema, - ), + LazyResultSet::new(redis_value_as_vec(data)?, &mut self.graph.graph_schema), stats, ) } @@ -328,22 +322,24 @@ impl<'a> ProcedureQueryBuilder<'a, FalkorResponse>> { .client .borrow_connection(self.graph.client.clone())?, ) - .and_then(|res| res.into_sequence().map_err(|_| FalkorDBError::ParsingArray)) - .and_then(|res_vec| { - let [header, indices, stats]: [redis::Value; 3] = res_vec.try_into().map_err(|_| { - FalkorDBError::ParsingArrayToStructElementCount( - "Expected exactly 3 elements in query response", - ) - })?; + .and_then(|res| { + let [header, indices, stats]: [redis::Value; 3] = + redis_value_as_vec(res).and_then(|res_vec| { + res_vec.try_into().map_err(|_| { + FalkorDBError::ParsingArrayToStructElementCount( + "Expected exactly 3 elements in query response", + ) + }) + })?; FalkorResponse::from_response( Some(header), - indices - .into_sequence() - .map_err(|_| FalkorDBError::ParsingArray)? - .into_iter() - .flat_map(|index| FalkorIndex::parse(index, &mut self.graph.graph_schema)) - .collect(), + redis_value_as_vec(indices).map(|indices| { + indices + .into_iter() + .flat_map(|index| FalkorIndex::parse(index, &mut self.graph.graph_schema)) + .collect() + })?, stats, ) }) @@ -364,27 +360,26 @@ impl<'a> ProcedureQueryBuilder<'a, FalkorResponse>> { .client .borrow_connection(self.graph.client.clone())?, ) - .and_then(|res| res.into_sequence().map_err(|_| FalkorDBError::ParsingArray)) - .and_then(|res_vec| { - let [header, indices, stats]: [redis::Value; 3] = res_vec.try_into().map_err(|_| { - FalkorDBError::ParsingArrayToStructElementCount( - "Expected exactly 3 elements in query response", - ) - })?; + .and_then(|res| { + let [header, constraints, stats]: [redis::Value; 3] = redis_value_as_vec(res) + .and_then(|res_vec| { + res_vec.try_into().map_err(|_| { + FalkorDBError::ParsingArrayToStructElementCount( + "Expected exactly 3 elements in query response", + ) + }) + })?; FalkorResponse::from_response( Some(header), - indices - .into_sequence() - .map(|indices| { - indices - .into_iter() - .flat_map(|constraint| { - Constraint::parse(constraint, &mut self.graph.graph_schema) - }) - .collect() - }) - .map_err(|_| FalkorDBError::ParsingArray)?, + redis_value_as_vec(constraints).map(|constraints| { + constraints + .into_iter() + .flat_map(|constraint| { + Constraint::parse(constraint, &mut self.graph.graph_schema) + }) + .collect() + })?, stats, ) }) diff --git a/src/graph_schema/mod.rs b/src/graph_schema/mod.rs index 8b8577b..7b5a589 100644 --- a/src/graph_schema/mod.rs +++ b/src/graph_schema/mod.rs @@ -5,8 +5,7 @@ use crate::{ client::blocking::FalkorSyncClientInner, - parser::parse_type, - redis_ext::{redis_value_as_int, redis_value_as_string}, + parser::{parse_type, redis_value_as_int, redis_value_as_string, redis_value_as_vec}, FalkorDBError, FalkorResult, FalkorValue, }; use std::{collections::HashMap, sync::Arc}; @@ -35,26 +34,22 @@ impl TryFrom for FKeyTypeVal { tracing::instrument(name = "New KeyTypeValue", skip_all, level = "trace") )] fn try_from(value: redis::Value) -> FalkorResult { - let [key_raw, type_raw, val]: [redis::Value; 3] = value - .into_sequence() - .ok() - .and_then(|seq| seq.try_into().ok()) - .ok_or({ - FalkorDBError::ParsingArrayToStructElementCount( - "Expected exactly 3 elements for key-type-value property", - ) + let [key_raw, type_raw, val]: [redis::Value; 3] = + redis_value_as_vec(value).and_then(|seq| { + seq.try_into().map_err(|_| { + FalkorDBError::ParsingArrayToStructElementCount( + "Expected exactly 3 elements for key-type-value property", + ) + }) })?; - match (redis_value_as_int(key_raw), redis_value_as_int(type_raw)) { - (Ok(key), Ok(type_marker)) => Ok(FKeyTypeVal { + redis_value_as_int(key_raw).and_then(|key| { + redis_value_as_int(type_raw).map(|type_marker| FKeyTypeVal { key, type_marker, val, - }), - (Ok(_), Err(_)) => Err(FalkorDBError::ParsingTypeMarkerTypeMismatch)?, - (Err(_), Ok(_)) => Err(FalkorDBError::ParsingKeyIdTypeMismatch)?, - _ => Err(FalkorDBError::ParsingKTVTypes)?, - } + }) + }) } } @@ -152,38 +147,41 @@ impl GraphSchema { }; // This is essentially the call_procedure(), but can be done here without access to the graph(which would cause ownership issues) - let [_, keys, _]: [redis::Value; 3] = self + let keys = self .client - .borrow_connection(self.client.clone())? - .execute_command( - Some(self.graph_name.as_str()), - "GRAPH.QUERY", - None, - Some(&[format!("CALL {}()", get_refresh_command(schema_type)).as_str()]), - )? - .into_sequence().map_err(|_| FalkorDBError::ParsingArray)? - .try_into() - .map_err(|_| { - FalkorDBError::ParsingArrayToStructElementCount( - "Expected exactly 3 types for header-resultset-stats response from refresh query" + .borrow_connection(self.client.clone()) + .and_then(|mut conn| { + conn.execute_command( + Some(self.graph_name.as_str()), + "GRAPH.QUERY", + None, + Some(&[format!("CALL {}()", get_refresh_command(schema_type)).as_str()]), ) - })?; + }) + .and_then(|res| { + redis_value_as_vec(res).and_then(|as_vec| { + as_vec.into_iter().nth(1).ok_or(FalkorDBError::ParsingArrayToStructElementCount( + "Expected exactly 3 types for header-resultset-stats response from refresh query" + )) + }) + }) + .and_then(redis_value_as_vec)?; let new_keys = keys - .into_sequence() - .map_err(|_| FalkorDBError::ParsingArray)? .into_iter() .enumerate() .flat_map(|(idx, item)| { FalkorResult::<(i64, String)>::Ok(( idx as i64, - item.into_sequence() - .ok() - .and_then(|item_seq| item_seq.into_iter().next()) - .ok_or(FalkorDBError::ParsingError( + redis_value_as_vec(item) + .and_then(|item_seq| { + item_seq.into_iter().next().ok_or_else(|| { + FalkorDBError::ParsingError( "Expected new label/property to be the first element in an array" .to_string(), - )) + ) + }) + }) .and_then(redis_value_as_string)?, )) }) @@ -234,9 +232,8 @@ impl GraphSchema { &mut self, value: redis::Value, ) -> FalkorResult> { - let raw_properties_vec = value - .into_sequence() - .map_err(|_| FalkorDBError::ParsingArray)?; + let raw_properties_vec = redis_value_as_vec(value)?; + let raw_properties_len = raw_properties_vec.len(); raw_properties_vec.into_iter().try_fold( HashMap::with_capacity(raw_properties_len), diff --git a/src/lib.rs b/src/lib.rs index f9860fb..ba1d3d1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,8 +17,6 @@ mod parser; mod response; mod value; -mod redis_ext; - /// A [`Result`] which only returns [`FalkorDBError`] as its E type pub type FalkorResult = Result; diff --git a/src/parser/mod.rs b/src/parser/mod.rs index bd4fb8e..ea530fc 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -3,44 +3,84 @@ * Licensed under the Server Side Public License v1 (SSPLv1). */ -use crate::{ - redis_ext::{ - redis_value_as_bool, redis_value_as_double, redis_value_as_int, redis_value_as_string, - }, - Edge, FalkorDBError, FalkorResult, FalkorValue, GraphSchema, Node, Path, Point, -}; +use crate::{Edge, FalkorDBError, FalkorResult, FalkorValue, GraphSchema, Node, Path, Point}; use std::collections::HashMap; +pub(crate) fn redis_value_as_string(value: redis::Value) -> FalkorResult { + match value { + redis::Value::Data(data) => { + String::from_utf8(data).map_err(|_| FalkorDBError::ParsingFString) + } + redis::Value::Status(status) => Ok(status), + _ => Err(FalkorDBError::ParsingFString), + } +} + +pub(crate) fn redis_value_as_int(value: redis::Value) -> FalkorResult { + match value { + redis::Value::Int(int_val) => Ok(int_val), + _ => Err(FalkorDBError::ParsingI64), + } +} + +pub(crate) fn redis_value_as_bool(value: redis::Value) -> FalkorResult { + redis_value_as_string(value).and_then(|string_val| match string_val.as_str() { + "true" => Ok(true), + "false" => Ok(false), + _ => Err(FalkorDBError::ParsingBool), + }) +} + +pub(crate) fn redis_value_as_double(value: redis::Value) -> FalkorResult { + redis_value_as_string(value) + .and_then(|string_val| string_val.parse().map_err(|_| FalkorDBError::ParsingF64)) +} + +pub(crate) fn redis_value_as_vec(value: redis::Value) -> FalkorResult> { + match value { + redis::Value::Bulk(bulk_val) => Ok(bulk_val), + _ => Err(FalkorDBError::ParsingArray), + } +} + #[cfg_attr( feature = "tracing", tracing::instrument(name = "Parse Falkor Enum", skip_all, level = "trace") )] pub(crate) fn parse_falkor_enum TryFrom<&'a str, Error = impl ToString>>( - val: redis::Value, - graph_schema: &mut GraphSchema, + value: redis::Value ) -> FalkorResult { - T::try_from( - parse_raw_redis_value(val, graph_schema) - .and_then(FalkorValue::into_string)? - .as_str(), - ) - .map_err(|err| FalkorDBError::InvalidEnumType(err.to_string())) + type_val_from_value(value) + .and_then(|(type_marker, val)| { + if type_marker == 2 { + redis_value_as_string(val) + } else { + Err(FalkorDBError::ParsingArray) + } + }) + .and_then(|val_string| { + T::try_from(val_string.as_str()) + .map_err(|err| FalkorDBError::InvalidEnumType(err.to_string())) + }) } #[cfg_attr( feature = "tracing", - tracing::instrument(name = "String Vec From Value", skip_all, level = "debug") + tracing::instrument(name = "String Vec From Redis Value", skip_all, level = "debug") )] -pub(crate) fn string_vec_from_val( - value: redis::Value, - graph_schema: &mut GraphSchema, -) -> FalkorResult> { - parse_raw_redis_value(value, graph_schema) - .and_then(|parsed_value| parsed_value.into_vec()) - .map(|parsed_value_vec| { - parsed_value_vec +pub(crate) fn redis_value_as_typed_string_vec(value: redis::Value) -> FalkorResult> { + type_val_from_value(value) + .and_then(|(type_marker, val)| { + if type_marker == 6 { + redis_value_as_vec(val) + } else { + Err(FalkorDBError::ParsingArray) + } + }) + .map(|val_vec| { + val_vec .into_iter() - .flat_map(FalkorValue::into_string) + .flat_map(redis_value_as_string) .collect() }) } @@ -49,11 +89,9 @@ pub(crate) fn string_vec_from_val( feature = "tracing", tracing::instrument(name = "String Vec From Untyped Value", skip_all, level = "trace") )] -pub(crate) fn string_vec_from_untyped_val(value: redis::Value) -> FalkorResult> { - value - .into_sequence() +pub(crate) fn redis_value_as_untyped_string_vec(value: redis::Value) -> FalkorResult> { + redis_value_as_vec(value) .map(|as_vec| as_vec.into_iter().flat_map(redis_value_as_string).collect()) - .map_err(|_| FalkorDBError::ParsingArray) } #[cfg_attr( @@ -62,9 +100,7 @@ pub(crate) fn string_vec_from_untyped_val(value: redis::Value) -> FalkorResult FalkorResult> { // Convert the header into a sequence - let header_sequence = header - .into_sequence() - .map_err(|_| FalkorDBError::ParsingArray)?; + let header_sequence = redis_value_as_vec(header)?; // Initialize a vector with the capacity of the header sequence length let header_sequence_len = header_sequence.len(); @@ -73,9 +109,7 @@ pub(crate) fn parse_header(header: redis::Value) -> FalkorResult> { Vec::with_capacity(header_sequence_len), |mut result, item| { // Convert the item into a sequence - let item_sequence = item - .into_sequence() - .map_err(|_| FalkorDBError::ParsingArray)?; + let item_sequence = redis_value_as_vec(item)?; // Determine the key based on the length of the item sequence let key = if item_sequence.len() == 2 { @@ -118,17 +152,18 @@ pub(crate) fn parse_raw_redis_value( pub(crate) fn type_val_from_value( value: redis::Value ) -> Result<(i64, redis::Value), FalkorDBError> { - let [type_marker, val]: [redis::Value; 2] = value - .into_sequence() - .map_err(|_| FalkorDBError::ParsingArray)? - .try_into() - .map_err(|_| { - FalkorDBError::ParsingArrayToStructElementCount( - "Expected exactly 2 elements: type marker, and value", - ) - })?; - - Ok((redis_value_as_int(type_marker)?, val)) + redis_value_as_vec(value).and_then(|val_vec| { + val_vec + .try_into() + .map_err(|_| { + FalkorDBError::ParsingArrayToStructElementCount( + "Expected exactly 2 elements: type marker, and value", + ) + }) + .and_then(|[type_marker_raw, val]: [redis::Value; 2]| { + redis_value_as_int(type_marker_raw).map(|type_marker| (type_marker, val)) + }) + }) } #[cfg_attr( @@ -166,13 +201,15 @@ pub(crate) fn parse_type( 3 => FalkorValue::I64(redis_value_as_int(val)?), 4 => FalkorValue::Bool(redis_value_as_bool(val)?), 5 => FalkorValue::F64(redis_value_as_double(val)?), - 6 => FalkorValue::Array( - val.into_sequence() - .map_err(|_| FalkorDBError::ParsingArray)? + 6 => FalkorValue::Array(redis_value_as_vec(val).and_then(|val_vec| { + let len = val_vec.len(); + val_vec .into_iter() - .flat_map(|item| parse_raw_redis_value(item, graph_schema)) - .collect(), - ), + .try_fold(Vec::with_capacity(len), |mut acc, item| { + acc.push(parse_raw_redis_value(item, graph_schema)?); + Ok(acc) + }) + })?), // The following types are sent as an array and require specific parsing functions 7 => FalkorValue::Edge(Edge::parse(val, graph_schema)?), 8 => FalkorValue::Node(Node::parse(val, graph_schema)?), diff --git a/src/redis_ext.rs b/src/redis_ext.rs deleted file mode 100644 index 644979c..0000000 --- a/src/redis_ext.rs +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright FalkorDB Ltd. 2023 - present - * Licensed under the Server Side Public License v1 (SSPLv1). - */ - -use crate::{ConfigValue, FalkorDBError, FalkorResult, FalkorValue}; -use redis::{FromRedisValue, RedisResult, RedisWrite, ToRedisArgs}; - -impl ToRedisArgs for ConfigValue { - fn write_redis_args( - &self, - out: &mut W, - ) where - W: ?Sized + RedisWrite, - { - match self { - ConfigValue::String(str_val) => str_val.write_redis_args(out), - ConfigValue::Int64(int_val) => int_val.write_redis_args(out), - } - } -} - -impl TryFrom<&redis::Value> for ConfigValue { - type Error = FalkorDBError; - fn try_from(value: &redis::Value) -> Result { - Ok(match value { - redis::Value::Int(int_val) => ConfigValue::Int64(*int_val), - redis::Value::Data(str_data) => { - ConfigValue::String(String::from_utf8_lossy(str_data.as_slice()).to_string()) - } - _ => return Err(FalkorDBError::InvalidDataReceived), - }) - } -} - -impl TryFrom for ConfigValue { - type Error = FalkorDBError; - - fn try_from(value: redis::Value) -> Result { - Ok(match value { - redis::Value::Int(int_val) => ConfigValue::Int64(int_val), - redis::Value::Data(str_data) => ConfigValue::String( - String::from_utf8(str_data).map_err(|_| FalkorDBError::ParsingFString)?, - ), - _ => return Err(FalkorDBError::InvalidDataReceived), - }) - } -} - -impl FromRedisValue for FalkorValue { - fn from_redis_value(v: &redis::Value) -> RedisResult { - Ok(match v { - redis::Value::Nil => FalkorValue::None, - redis::Value::Int(int_val) => FalkorValue::I64(*int_val), - redis::Value::Data(str_val) => { - FalkorValue::String(String::from_utf8_lossy(str_val.as_slice()).to_string()) - } - redis::Value::Bulk(bulk) => FalkorValue::Array( - bulk.iter() - .flat_map(FalkorValue::from_redis_value) - .collect(), - ), - redis::Value::Status(status) => FalkorValue::String(status.to_string()), - redis::Value::Okay => FalkorValue::None, - }) - } -} - -pub(crate) fn redis_value_as_string(value: redis::Value) -> FalkorResult { - match value { - redis::Value::Data(data) => { - String::from_utf8(data).map_err(|_| FalkorDBError::ParsingFString) - } - redis::Value::Status(status) => Ok(status), - _ => Err(FalkorDBError::ParsingFString), - } -} - -pub(crate) fn redis_value_as_int(value: redis::Value) -> FalkorResult { - match value { - redis::Value::Int(int_val) => Ok(int_val), - _ => Err(FalkorDBError::ParsingI64), - } -} - -pub(crate) fn redis_value_as_bool(value: redis::Value) -> FalkorResult { - redis_value_as_string(value).and_then(|string_val| match string_val.as_str() { - "true" => Ok(true), - "false" => Ok(false), - _ => Err(FalkorDBError::ParsingBool), - }) -} - -pub(crate) fn redis_value_as_double(value: redis::Value) -> FalkorResult { - redis_value_as_string(value) - .and_then(|string_val| string_val.parse().map_err(|_| FalkorDBError::ParsingF64)) -} diff --git a/src/response/constraint.rs b/src/response/constraint.rs index 9332495..da03a05 100644 --- a/src/response/constraint.rs +++ b/src/response/constraint.rs @@ -4,7 +4,10 @@ */ use crate::{ - parser::{parse_falkor_enum, parse_raw_redis_value, string_vec_from_val}, + parser::{ + parse_falkor_enum, parse_raw_redis_value, redis_value_as_typed_string_vec, + redis_value_as_vec, + }, EntityType, FalkorDBError, FalkorResult, FalkorValue, GraphSchema, }; @@ -56,18 +59,17 @@ impl Constraint { value: redis::Value, graph_schema: &mut GraphSchema, ) -> FalkorResult { - let [constraint_type_raw, label_raw, properties_raw, entity_type_raw, status_raw]: [redis::Value; 5] = value.into_sequence() - .map_err(|_| FalkorDBError::ParsingArray) + let [constraint_type_raw, label_raw, properties_raw, entity_type_raw, status_raw]: [redis::Value; 5] = redis_value_as_vec(value) .and_then(|res| res.try_into() .map_err(|_| FalkorDBError::ParsingArrayToStructElementCount("Expected exactly 5 elements in constraint object")))?; Ok(Self { - constraint_type: parse_falkor_enum(constraint_type_raw, graph_schema)?, + constraint_type: parse_falkor_enum(constraint_type_raw)?, label: parse_raw_redis_value(label_raw, graph_schema) .and_then(FalkorValue::into_string)?, - properties: string_vec_from_val(properties_raw, graph_schema)?, - entity_type: parse_falkor_enum(entity_type_raw, graph_schema)?, - status: parse_falkor_enum(status_raw, graph_schema)?, + properties: redis_value_as_typed_string_vec(properties_raw)?, + entity_type: parse_falkor_enum(entity_type_raw)?, + status: parse_falkor_enum(status_raw)?, }) } } diff --git a/src/response/execution_plan.rs b/src/response/execution_plan.rs index c3b76f8..44b4fa6 100644 --- a/src/response/execution_plan.rs +++ b/src/response/execution_plan.rs @@ -3,7 +3,10 @@ * Licensed under the Server Side Public License v1 (SSPLv1). */ -use crate::{redis_ext::redis_value_as_string, FalkorDBError, FalkorResult}; +use crate::{ + parser::{redis_value_as_string, redis_value_as_vec}, + FalkorDBError, FalkorResult, +}; use regex::Regex; use std::{ cell::RefCell, @@ -190,9 +193,7 @@ impl ExecutionPlan { tracing::instrument(name = "Parse Execution Plan", skip_all, level = "info") )] pub(crate) fn parse(value: redis::Value) -> FalkorResult { - let redis_value_vec = value - .into_sequence() - .map_err(|_| FalkorDBError::ParsingArray)?; + let redis_value_vec = redis_value_as_vec(value)?; let mut string_representation = Vec::with_capacity(redis_value_vec.len() + 1); let mut current_traversal_stack = vec![]; diff --git a/src/response/index.rs b/src/response/index.rs index 7eef248..ffdb7f8 100644 --- a/src/response/index.rs +++ b/src/response/index.rs @@ -3,8 +3,13 @@ * Licensed under the Server Side Public License v1 (SSPLv1). */ -use crate::parser::{parse_falkor_enum, string_vec_from_val}; -use crate::{parser::parse_raw_redis_value, EntityType, FalkorDBError, FalkorValue, GraphSchema}; +use crate::{ + parser::{ + parse_falkor_enum, parse_raw_redis_value, redis_value_as_typed_string_vec, + redis_value_as_vec, + }, + EntityType, FalkorDBError, FalkorValue, GraphSchema, +}; use std::collections::HashMap; /// The status of this index @@ -89,10 +94,8 @@ impl FalkorIndex { value: redis::Value, graph_schema: &mut GraphSchema, ) -> Result { - let [label, fields, field_types, language, stopwords, entity_type, status, info] = value - .into_sequence() - .map_err(|_| FalkorDBError::ParsingArray) - .and_then(|as_vec| { + let [label, fields, field_types, language, stopwords, entity_type, status, info] = + redis_value_as_vec(value).and_then(|as_vec| { as_vec.try_into().map_err(|_| { FalkorDBError::ParsingArrayToStructElementCount( "Expected exactly 8 elements in index object", @@ -101,15 +104,15 @@ impl FalkorIndex { })?; Ok(Self { - entity_type: parse_falkor_enum(entity_type, graph_schema)?, - status: parse_falkor_enum(status, graph_schema)?, + entity_type: parse_falkor_enum(entity_type)?, + status: parse_falkor_enum(status)?, index_label: parse_raw_redis_value(label, graph_schema) - .and_then(|parsed_entity_type| parsed_entity_type.into_string())?, - fields: string_vec_from_val(fields, graph_schema)?, + .and_then(FalkorValue::into_string)?, + fields: redis_value_as_typed_string_vec(fields)?, field_types: parse_types_map(field_types, graph_schema)?, language: parse_raw_redis_value(language, graph_schema) .and_then(FalkorValue::into_string)?, - stopwords: string_vec_from_val(stopwords, graph_schema)?, + stopwords: redis_value_as_typed_string_vec(stopwords)?, info: parse_raw_redis_value(info, graph_schema) .and_then(FalkorValue::into_map) .map(|as_map| { diff --git a/src/response/mod.rs b/src/response/mod.rs index a7ce8c3..5a29f16 100644 --- a/src/response/mod.rs +++ b/src/response/mod.rs @@ -4,7 +4,7 @@ */ use crate::{ - parser::{parse_header, string_vec_from_untyped_val}, + parser::{parse_header, redis_value_as_untyped_string_vec}, FalkorResult, }; @@ -47,7 +47,7 @@ impl FalkorResponse { None => vec![], }, data, - stats: string_vec_from_untyped_val(stats)?, + stats: redis_value_as_untyped_string_vec(stats)?, }) } } diff --git a/src/response/slowlog_entry.rs b/src/response/slowlog_entry.rs index f13ef8b..96a302d 100644 --- a/src/response/slowlog_entry.rs +++ b/src/response/slowlog_entry.rs @@ -3,7 +3,10 @@ * Licensed under the Server Side Public License v1 (SSPLv1). */ -use crate::{redis_ext::redis_value_as_string, FalkorDBError, FalkorResult}; +use crate::{ + parser::{redis_value_as_double, redis_value_as_string, redis_value_as_vec}, + FalkorDBError, FalkorResult, +}; /// A slowlog entry, representing one of the N slowest queries in the current log #[derive(Clone, Debug, PartialEq)] @@ -24,10 +27,8 @@ impl SlowlogEntry { tracing::instrument(name = "Parse Slowlog Entry", skip_all, level = "info") )] pub(crate) fn parse(value: redis::Value) -> FalkorResult { - let [timestamp, command, arguments, time_taken]: [redis::Value; 4] = value - .into_sequence() - .map_err(|_| FalkorDBError::ParsingArray) - .and_then(|as_vec| { + let [timestamp, command, arguments, time_taken]: [redis::Value; 4] = + redis_value_as_vec(value).and_then(|as_vec| { as_vec.try_into().map_err(|_| { FalkorDBError::ParsingArrayToStructElementCount( "Expected exactly 4 elements of slowlog entry", @@ -42,10 +43,7 @@ impl SlowlogEntry { .ok_or(FalkorDBError::ParsingI64)?, command: redis_value_as_string(command)?, arguments: redis_value_as_string(arguments)?, - time_taken: redis_value_as_string(time_taken) - .ok() - .and_then(|time_taken| time_taken.parse().ok()) - .ok_or(FalkorDBError::ParsingF64)?, + time_taken: redis_value_as_double(time_taken)?, }) } } diff --git a/src/value/config.rs b/src/value/config.rs index 80baeac..544a841 100644 --- a/src/value/config.rs +++ b/src/value/config.rs @@ -4,6 +4,7 @@ */ use crate::{FalkorDBError, FalkorValue}; +use redis::{RedisWrite, ToRedisArgs}; use std::fmt::{Display, Formatter}; /// An enum representing the two viable types for a config value @@ -66,3 +67,44 @@ impl TryFrom for ConfigValue { } } } + +impl ToRedisArgs for ConfigValue { + fn write_redis_args( + &self, + out: &mut W, + ) where + W: ?Sized + RedisWrite, + { + match self { + ConfigValue::String(str_val) => str_val.write_redis_args(out), + ConfigValue::Int64(int_val) => int_val.write_redis_args(out), + } + } +} + +impl TryFrom<&redis::Value> for ConfigValue { + type Error = FalkorDBError; + fn try_from(value: &redis::Value) -> Result { + Ok(match value { + redis::Value::Int(int_val) => ConfigValue::Int64(*int_val), + redis::Value::Data(str_data) => { + ConfigValue::String(String::from_utf8_lossy(str_data.as_slice()).to_string()) + } + _ => return Err(FalkorDBError::InvalidDataReceived), + }) + } +} + +impl TryFrom for ConfigValue { + type Error = FalkorDBError; + + fn try_from(value: redis::Value) -> Result { + Ok(match value { + redis::Value::Int(int_val) => ConfigValue::Int64(int_val), + redis::Value::Data(str_data) => ConfigValue::String( + String::from_utf8(str_data).map_err(|_| FalkorDBError::ParsingFString)?, + ), + _ => return Err(FalkorDBError::InvalidDataReceived), + }) + } +} diff --git a/src/value/graph_entities.rs b/src/value/graph_entities.rs index f2ca08a..cdb353c 100644 --- a/src/value/graph_entities.rs +++ b/src/value/graph_entities.rs @@ -4,8 +4,8 @@ */ use crate::{ - redis_ext::redis_value_as_int, FalkorDBError, FalkorResult, FalkorValue, GraphSchema, - SchemaType, + parser::{redis_value_as_int, redis_value_as_vec}, + FalkorDBError, FalkorResult, FalkorValue, GraphSchema, SchemaType, }; use std::collections::HashMap; @@ -40,23 +40,18 @@ impl Node { value: redis::Value, graph_schema: &mut GraphSchema, ) -> FalkorResult { - let [entity_id, labels, properties]: [redis::Value; 3] = value - .into_sequence() - .map_err(|_| FalkorDBError::ParsingArray)? - .try_into() - .map_err(|_| { - FalkorDBError::ParsingArrayToStructElementCount( - "Expected exactly 3 elements in node object", - ) + let [entity_id, labels, properties]: [redis::Value; 3] = redis_value_as_vec(value) + .and_then(|val_vec| { + TryInto::try_into(val_vec).map_err(|_| { + FalkorDBError::ParsingArrayToStructElementCount( + "Expected exactly 3 elements in node object", + ) + }) })?; + Ok(Node { entity_id: redis_value_as_int(entity_id)?, - labels: graph_schema.parse_id_vec( - labels - .into_sequence() - .map_err(|_| FalkorDBError::ParsingArray)?, - SchemaType::Labels, - )?, + labels: graph_schema.parse_id_vec(redis_value_as_vec(labels)?, SchemaType::Labels)?, properties: graph_schema.parse_properties_map(properties)?, }) } @@ -87,15 +82,13 @@ impl Edge { graph_schema: &mut GraphSchema, ) -> FalkorResult { let [entity_id, relationship_id_raw, src_node_id, dst_node_id, properties]: [redis::Value; - 5] = value - .into_sequence() - .map_err(|_| FalkorDBError::ParsingArray)? - .try_into() - .map_err(|_| { + 5] = redis_value_as_vec(value).and_then(|val_vec| { + val_vec.try_into().map_err(|_| { FalkorDBError::ParsingArrayToStructElementCount( "Expected exactly 5 elements in edge object", ) - })?; + }) + })?; let relationship = graph_schema .relationships() diff --git a/src/value/path.rs b/src/value/path.rs index 4da0d5a..627176e 100644 --- a/src/value/path.rs +++ b/src/value/path.rs @@ -3,7 +3,7 @@ * Licensed under the Server Side Public License v1 (SSPLv1). */ -use crate::{Edge, FalkorDBError, FalkorResult, GraphSchema, Node}; +use crate::{parser::redis_value_as_vec, Edge, FalkorDBError, FalkorResult, GraphSchema, Node}; /// Represents a path between two nodes, contains all the nodes, and the relationships between them along the path #[derive(Clone, Debug, Default, PartialEq)] @@ -23,26 +23,21 @@ impl Path { value: redis::Value, graph_schema: &mut GraphSchema, ) -> FalkorResult { - let [nodes, relationships]: [redis::Value; 2] = value - .into_sequence() - .map_err(|_| FalkorDBError::ParsingArray)? - .try_into() - .map_err(|_| { - FalkorDBError::ParsingArrayToStructElementCount( - "Expected exactly 2 elements for path", - ) + let [nodes, relationships]: [redis::Value; 2] = + redis_value_as_vec(value).and_then(|vec_val| { + vec_val.try_into().map_err(|_| { + FalkorDBError::ParsingArrayToStructElementCount( + "Expected exactly 2 elements for path", + ) + }) })?; Ok(Self { - nodes: nodes - .into_sequence() - .map_err(|_| FalkorDBError::ParsingArray)? + nodes: redis_value_as_vec(nodes)? .into_iter() .flat_map(|node| Node::parse(node, graph_schema)) .collect(), - relationships: relationships - .into_sequence() - .map_err(|_| FalkorDBError::ParsingArray)? + relationships: redis_value_as_vec(relationships)? .into_iter() .flat_map(|edge| Edge::parse(edge, graph_schema)) .collect(), diff --git a/src/value/point.rs b/src/value/point.rs index da93e59..f186717 100644 --- a/src/value/point.rs +++ b/src/value/point.rs @@ -3,8 +3,10 @@ * Licensed under the Server Side Public License v1 (SSPLv1). */ -use crate::redis_ext::redis_value_as_double; -use crate::{FalkorDBError, FalkorResult}; +use crate::{ + parser::{redis_value_as_double, redis_value_as_vec}, + FalkorDBError, FalkorResult, +}; /// A point in the world. #[derive(Clone, Debug, Default, PartialEq)] @@ -29,15 +31,13 @@ impl Point { tracing::instrument(name = "Parse Point", skip_all, level = "trace") )] pub fn parse(value: redis::Value) -> FalkorResult { - let [lat, long]: [redis::Value; 2] = value - .into_sequence() - .map_err(|_| FalkorDBError::ParsingArray)? - .try_into() - .map_err(|_| { + let [lat, long]: [redis::Value; 2] = redis_value_as_vec(value).and_then(|val_vec| { + val_vec.try_into().map_err(|_| { FalkorDBError::ParsingArrayToStructElementCount( "Expected exactly 2 element in point - latitude and longitude", ) - })?; + }) + })?; Ok(Point { latitude: redis_value_as_double(lat)?, From 4bf886535651d2f415d1a63442d484d135148fe1 Mon Sep 17 00:00:00 2001 From: Emily Matheys Date: Thu, 13 Jun 2024 11:40:21 +0300 Subject: [PATCH 10/11] Updated README --- README.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 824b44c..55e21dd 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Just add it to your `Cargo.toml`, like so: ```toml -falkordb = { version = "*" } +falkordb = { version = "0.1" } ``` ### Run FalkorDB instance @@ -60,25 +60,28 @@ for n in nodes.data { ### SSL/TLS Support -This client is currently built upon the [`redis`](https://docs.rs/redis/latest/redis/) crate, and therefor supports TLS using +This client is currently built upon the [`redis`](https://docs.rs/redis/latest/redis/) crate, and therefore supports TLS using its implementation, which uses either [`rustls`](https://docs.rs/rustls/latest/rustls/) or [`native_tls`](https://docs.rs/native-tls/latest/native_tls/). This is not enabled by default, and the user ust opt-in by enabling the respective features: `"rustls"`/`"native-tls"`.\ For Rustls: + ```toml -falkordb = { version = "*", features = ["rustls"] } +falkordb = { version = "0.1", features = ["rustls"] } ``` For NativeTLS: + ```toml -falkordb = { version = "*", features = ["native-tls"] } +falkordb = { version = "0.1", features = ["native-tls"] } ``` ### Tracing This crate fully supports instrumentation using the [`tracing`](https://docs.rs/tracing/latest/tracing/) crate, to use it, simply, enable the `tracing` feature: + ```toml -falkordb = { version = "*", features = ["tracing"] } +falkordb = { version = "0.1", features = ["tracing"] } ``` -Note that different functions use different filtration levels, to avoid spamming your tests, be sure to enable the correct level as you desire it. \ No newline at end of file +Note that different functions use different filtration levels, to avoid spamming your tests, be sure to enable the correct level as you desire it. From c64e86f8febced6e49d3f7d053902f87c39cc432 Mon Sep 17 00:00:00 2001 From: Emily Matheys Date: Thu, 13 Jun 2024 12:06:45 +0300 Subject: [PATCH 11/11] Update Names etc --- src/client/builder.rs | 1 - src/connection/blocking.rs | 2 +- src/connection_info/mod.rs | 2 -- src/error/mod.rs | 18 +++++++++--------- src/parser/mod.rs | 6 +++--- src/value/config.rs | 2 +- src/value/mod.rs | 12 ++++++------ 7 files changed, 20 insertions(+), 23 deletions(-) diff --git a/src/client/builder.rs b/src/client/builder.rs index b998fad..340bb7e 100644 --- a/src/client/builder.rs +++ b/src/client/builder.rs @@ -119,7 +119,6 @@ mod tests { } #[test] - fn test_sync_builder_redis_fallback() { let client = FalkorClientBuilder::new().build(); assert!(client.is_ok()); diff --git a/src/connection/blocking.rs b/src/connection/blocking.rs index f379e7e..1f6be49 100644 --- a/src/connection/blocking.rs +++ b/src/connection/blocking.rs @@ -73,7 +73,7 @@ impl FalkorSyncConnection { .map(|[key, val]| (key.to_string(), val.to_string())) .collect() }) - .map_err(|_| FalkorDBError::ParsingFString) + .map_err(|_| FalkorDBError::ParsingString) }) } } diff --git a/src/connection_info/mod.rs b/src/connection_info/mod.rs index 719ff25..516b196 100644 --- a/src/connection_info/mod.rs +++ b/src/connection_info/mod.rs @@ -82,7 +82,6 @@ mod tests { use std::{mem, str::FromStr}; #[test] - fn test_redis_fallback_provider() { let FalkorConnectionInfo::Redis(redis) = FalkorConnectionInfo::fallback_provider("redis://127.0.0.1:6379".to_string()).unwrap(); @@ -91,7 +90,6 @@ mod tests { } #[test] - fn test_try_from_redis() { let res = FalkorConnectionInfo::try_from("redis://0.0.0.0:1234"); assert!(res.is_ok()); diff --git a/src/error/mod.rs b/src/error/mod.rs index 2d57fe4..ac0940f 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -79,21 +79,21 @@ pub enum FalkorDBError { /// Element was not of type Array. #[error("Element was not of type Array")] ParsingArray, - /// Element was not of type FString. - #[error("Element was not of type FString")] - ParsingFString, + /// Element was not of type String. + #[error("Element was not of type String")] + ParsingString, /// Element was not of type FEdge. #[error("Element was not of type FEdge")] ParsingFEdge, /// Element was not of type FNode. #[error("Element was not of type FNode")] ParsingFNode, - /// Element was not of type FPath. - #[error("Element was not of type FPath")] - ParsingFPath, - /// Element was not of type FMap. - #[error("Element was not of type FMap")] - ParsingFMap, + /// Element was not of type Path. + #[error("Element was not of type Path")] + ParsingPath, + /// Element was not of type Map. + #[error("Element was not of type Map")] + ParsingMap, /// Element was not of type FPoint. #[error("Element was not of type FPoint")] ParsingFPoint, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index ea530fc..aa60006 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -9,10 +9,10 @@ use std::collections::HashMap; pub(crate) fn redis_value_as_string(value: redis::Value) -> FalkorResult { match value { redis::Value::Data(data) => { - String::from_utf8(data).map_err(|_| FalkorDBError::ParsingFString) + String::from_utf8(data).map_err(|_| FalkorDBError::ParsingString) } redis::Value::Status(status) => Ok(status), - _ => Err(FalkorDBError::ParsingFString), + _ => Err(FalkorDBError::ParsingString), } } @@ -176,7 +176,7 @@ fn parse_regular_falkor_map( ) -> FalkorResult> { value .into_map_iter() - .map_err(|_| FalkorDBError::ParsingFMap)? + .map_err(|_| FalkorDBError::ParsingMap)? .try_fold(HashMap::new(), |mut out_map, (key, val)| { out_map.insert( redis_value_as_string(key)?, diff --git a/src/value/config.rs b/src/value/config.rs index 544a841..2a3729f 100644 --- a/src/value/config.rs +++ b/src/value/config.rs @@ -102,7 +102,7 @@ impl TryFrom for ConfigValue { Ok(match value { redis::Value::Int(int_val) => ConfigValue::Int64(int_val), redis::Value::Data(str_data) => ConfigValue::String( - String::from_utf8(str_data).map_err(|_| FalkorDBError::ParsingFString)?, + String::from_utf8(str_data).map_err(|_| FalkorDBError::ParsingString)?, ), _ => return Err(FalkorDBError::InvalidDataReceived), }) diff --git a/src/value/mod.rs b/src/value/mod.rs index 749d95a..84c9ea3 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -84,7 +84,7 @@ impl FalkorValue { } } - /// Returns a reference to the internal [`String`] if this is an FString variant. + /// Returns a reference to the internal [`String`] if this is an String variant. /// /// # Returns /// A reference to the internal [`String`] @@ -117,7 +117,7 @@ impl FalkorValue { } } - /// Returns a reference to the internal [`Path`] if this is an FPath variant. + /// Returns a reference to the internal [`Path`] if this is an Path variant. /// /// # Returns /// A reference to the internal [`Path`] @@ -128,7 +128,7 @@ impl FalkorValue { } } - /// Returns a reference to the internal [`HashMap`] if this is an FMap variant. + /// Returns a reference to the internal [`HashMap`] if this is an Map variant. /// /// # Returns /// A reference to the internal [`HashMap`] @@ -199,14 +199,14 @@ impl FalkorValue { } } - /// Consumes itself and returns the inner [`String`] if this is an FString variant + /// Consumes itself and returns the inner [`String`] if this is an String variant /// /// # Returns /// The inner [`String`] pub fn into_string(self) -> FalkorResult { match self { FalkorValue::String(string) => Ok(string), - _ => Err(FalkorDBError::ParsingFString), + _ => Err(FalkorDBError::ParsingString), } } /// Consumes itself and returns the inner [`HashMap`] if this is a Map variant @@ -216,7 +216,7 @@ impl FalkorValue { pub fn into_map(self) -> FalkorResult> { match self { FalkorValue::Map(map) => Ok(map), - _ => Err(FalkorDBError::ParsingFMap), + _ => Err(FalkorDBError::ParsingMap), } } }