From e2c85cf1342987d02e11313cac9c94f1bd98bfff Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Wed, 10 Jul 2024 13:42:36 -0500 Subject: [PATCH 01/54] First pass --- .../src/process/traversal/builder.rs | 12 ++++++ .../src/process/traversal/graph_traversal.rs | 11 ++++++ .../traversal/graph_traversal_source.rs | 37 ++++++++++++++++++- .../process/traversal/step/merge_vertex.rs | 26 +++++++++++++ .../src/process/traversal/step/mod.rs | 1 + gremlin-client/src/structure/map.rs | 9 +++++ gremlin-client/src/structure/t.rs | 2 +- gremlin-client/src/structure/value.rs | 1 + 8 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 gremlin-client/src/process/traversal/step/merge_vertex.rs diff --git a/gremlin-client/src/process/traversal/builder.rs b/gremlin-client/src/process/traversal/builder.rs index f2199ed5..b0bb2286 100644 --- a/gremlin-client/src/process/traversal/builder.rs +++ b/gremlin-client/src/process/traversal/builder.rs @@ -22,6 +22,8 @@ use crate::process::traversal::{Bytecode, Scope}; use crate::structure::{Cardinality, GIDs, IntoPredicate, Labels}; use crate::GValue; +use super::merge_vertex::MergeVertexStep; + #[derive(Clone)] pub struct TraversalBuilder { pub(crate) bytecode: Bytecode, @@ -653,6 +655,16 @@ impl TraversalBuilder { self } + pub fn merge_v(mut self, merge_v: A) -> Self + where + A: Into, + { + self.bytecode + .add_step(String::from("mergeV"), merge_v.into().into()); + + self + } + pub fn identity(mut self) -> Self { self.bytecode.add_step(String::from("identity"), vec![]); self diff --git a/gremlin-client/src/process/traversal/graph_traversal.rs b/gremlin-client/src/process/traversal/graph_traversal.rs index 6542adec..b677baa4 100644 --- a/gremlin-client/src/process/traversal/graph_traversal.rs +++ b/gremlin-client/src/process/traversal/graph_traversal.rs @@ -29,6 +29,8 @@ use crate::{ }; use std::marker::PhantomData; +use super::merge_vertex::MergeVertexStep; + #[derive(Clone)] pub struct GraphTraversal> { start: PhantomData, @@ -669,6 +671,15 @@ impl> GraphTraversal { GraphTraversal::new(self.terminator, self.builder) } + pub fn merge_v(mut self, merge_v: A) -> GraphTraversal + where + A: Into, + T: Terminator, + { + self.builder = self.builder.merge_v(merge_v); + GraphTraversal::new(self.terminator, self.builder) + } + pub fn identity(mut self) -> Self { self.builder = self.builder.identity(); self diff --git a/gremlin-client/src/process/traversal/graph_traversal_source.rs b/gremlin-client/src/process/traversal/graph_traversal_source.rs index 4de6df13..266d770f 100644 --- a/gremlin-client/src/process/traversal/graph_traversal_source.rs +++ b/gremlin-client/src/process/traversal/graph_traversal_source.rs @@ -122,6 +122,21 @@ impl> GraphTraversalSource { ); GraphTraversal::new(self.term.clone(), TraversalBuilder::new(code)) } + + pub fn inject(&self, injection: T) -> GraphTraversal + where + T: Into + FromGValue, + A: Terminator, + { + let mut code = Bytecode::new(); + + code.add_step(String::from("inject"), vec![injection.into()]); + GraphTraversal::new(self.term.clone(), TraversalBuilder::new(code)) + } + + //Need to add inject, mergeV, and mergeE here + //we'll then need to add all 3 to graph_traversal too because they're all mid traversal steps too (probably) + //Permit sending a GValue for the property key, not just &str } // TESTS @@ -982,5 +997,25 @@ mod tests { ); } - // g.V().hasLabel('person').coalesce(values('nickname'), values('name')) + #[test] + fn inject_test() { + let g = empty(); + + let mut code = Bytecode::new(); + + code.add_step( + String::from("inject"), + vec![GValue::List(vec!["foo".into(), "bar".into()].into())], + ); + code.add_step(String::from("unfold"), vec![]); + + assert_eq!( + &code, + g.inject(vec!["foo".into(), "bar".into()]) + .unfold() + .bytecode() + ); + } + + //TODO add tests for mergeV, etc } diff --git a/gremlin-client/src/process/traversal/step/merge_vertex.rs b/gremlin-client/src/process/traversal/step/merge_vertex.rs new file mode 100644 index 00000000..17bcc8ac --- /dev/null +++ b/gremlin-client/src/process/traversal/step/merge_vertex.rs @@ -0,0 +1,26 @@ +use crate::process::traversal::TraversalBuilder; +use crate::structure::GValue; + +pub struct MergeVertexStep { + params: Vec, +} + +impl MergeVertexStep { + fn new(params: Vec) -> Self { + MergeVertexStep { params } + } +} + +//todo need to handle (Map searchCreate)? +//https://tinkerpop.apache.org/docs/current/dev/provider/#merge-v-step +impl From for Vec { + fn from(step: MergeVertexStep) -> Self { + step.params + } +} + +impl From for MergeVertexStep { + fn from(param: TraversalBuilder) -> Self { + MergeVertexStep::new(vec![param.bytecode.into()]) + } +} diff --git a/gremlin-client/src/process/traversal/step/mod.rs b/gremlin-client/src/process/traversal/step/mod.rs index fb2f91c5..7666d7af 100644 --- a/gremlin-client/src/process/traversal/step/mod.rs +++ b/gremlin-client/src/process/traversal/step/mod.rs @@ -8,6 +8,7 @@ pub mod limit; pub mod local; pub mod loops; pub mod match_step; +pub mod merge_vertex; pub mod not; pub mod or; pub mod repeat; diff --git a/gremlin-client/src/structure/map.rs b/gremlin-client/src/structure/map.rs index 3a1c8a08..258e425c 100644 --- a/gremlin-client/src/structure/map.rs +++ b/gremlin-client/src/structure/map.rs @@ -6,6 +6,8 @@ use std::collections::hash_map::IntoIter; use std::collections::{BTreeMap, HashMap}; use std::convert::{TryFrom, TryInto}; +use super::T; + /// Represent a Map<[GKey](struct.GKey),[GValue](struct.GValue)> which has ability to allow for non-String keys. /// TinkerPop type [here](http://tinkerpop.apache.org/docs/current/dev/io/#_map) #[derive(Debug, PartialEq, Clone)] @@ -137,12 +139,19 @@ impl std::iter::FromIterator<(String, GValue)> for Map { #[allow(clippy::large_enum_variant)] #[derive(Debug, PartialEq, Clone, Eq, Hash)] pub enum GKey { + T(T), String(String), Token(Token), Vertex(Vertex), Edge(Edge), } +impl From for GKey { + fn from(val: T) -> Self { + GKey::T(val) + } +} + impl From<&str> for GKey { fn from(val: &str) -> Self { GKey::String(String::from(val)) diff --git a/gremlin-client/src/structure/t.rs b/gremlin-client/src/structure/t.rs index db79a6ce..2a62492c 100644 --- a/gremlin-client/src/structure/t.rs +++ b/gremlin-client/src/structure/t.rs @@ -1,4 +1,4 @@ -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Clone, Eq, Hash)] pub enum T { Id, Key, diff --git a/gremlin-client/src/structure/value.rs b/gremlin-client/src/structure/value.rs index c4d955f9..e29c01ba 100644 --- a/gremlin-client/src/structure/value.rs +++ b/gremlin-client/src/structure/value.rs @@ -229,6 +229,7 @@ impl From for VecDeque { impl From for GValue { fn from(val: GKey) -> Self { match val { + GKey::T(t) => GValue::T(t), GKey::String(s) => GValue::String(s), GKey::Token(s) => GValue::String(s.value().clone()), GKey::Vertex(v) => GValue::Vertex(v), From 8486503e6b848b095da1c33a0ba4a8d286b15cd3 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Thu, 11 Jul 2024 18:39:29 -0500 Subject: [PATCH 02/54] Added JanusGraph in memory mode to docker compose enviornment --- docker-compose/docker-compose.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docker-compose/docker-compose.yaml b/docker-compose/docker-compose.yaml index 8262faac..781ab2b3 100644 --- a/docker-compose/docker-compose.yaml +++ b/docker-compose/docker-compose.yaml @@ -18,3 +18,12 @@ services: command : ["conf/gremlin-server-credentials.yaml"] ports: - "8183:8182" + janusgraph: + image: janusgraph/janusgraph:latest + environment: + - janusgraph.graph.set-vertex-id=true + - janusgraph.graph.allow-custom-vid-types=true + - JANUS_PROPS_TEMPLATE=inmemory + ports: + - "8184:8182" + \ No newline at end of file From e06fe552a6f4dd2401c112e54a7894991bc7f53b Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Thu, 11 Jul 2024 18:39:56 -0500 Subject: [PATCH 03/54] Added mergeV as a start step --- .../process/traversal/graph_traversal_source.rs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/gremlin-client/src/process/traversal/graph_traversal_source.rs b/gremlin-client/src/process/traversal/graph_traversal_source.rs index 266d770f..c598672f 100644 --- a/gremlin-client/src/process/traversal/graph_traversal_source.rs +++ b/gremlin-client/src/process/traversal/graph_traversal_source.rs @@ -15,6 +15,8 @@ use crate::structure::Labels; use crate::structure::{Edge, GValue, Vertex}; use crate::GremlinClient; +use super::merge_vertex::MergeVertexStep; + #[derive(Clone)] pub struct GraphTraversalSource> { term: A, @@ -134,9 +136,17 @@ impl> GraphTraversalSource { GraphTraversal::new(self.term.clone(), TraversalBuilder::new(code)) } - //Need to add inject, mergeV, and mergeE here - //we'll then need to add all 3 to graph_traversal too because they're all mid traversal steps too (probably) - //Permit sending a GValue for the property key, not just &str + pub fn merge_v(&self, merge_v: V) -> GraphTraversal + where + V: Into, + A: Terminator, + { + let mut code = Bytecode::new(); + + code.add_step(String::from("mergeV"), merge_v.into().into()); + + GraphTraversal::new(self.term.clone(), TraversalBuilder::new(code)) + } } // TESTS From b5695308322048c76f51b9661363c1a72f9299a4 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Thu, 11 Jul 2024 18:40:20 -0500 Subject: [PATCH 04/54] Relaxed property step keys to Into from &str support converting a TraversalBuilder into GValue via Bytecode --- gremlin-client/src/process/traversal/graph_traversal.rs | 5 +++-- gremlin-client/src/structure/value.rs | 8 +++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/gremlin-client/src/process/traversal/graph_traversal.rs b/gremlin-client/src/process/traversal/graph_traversal.rs index b677baa4..6dbb0c8b 100644 --- a/gremlin-client/src/process/traversal/graph_traversal.rs +++ b/gremlin-client/src/process/traversal/graph_traversal.rs @@ -91,9 +91,10 @@ impl> GraphTraversal { GraphTraversal::new(self.terminator, self.builder) } - pub fn property(mut self, key: &str, value: A) -> Self + pub fn property(mut self, key: K, value: V) -> Self where - A: Into, + K: Into, + V: Into, { self.builder = self.builder.property(key, value); self diff --git a/gremlin-client/src/structure/value.rs b/gremlin-client/src/structure/value.rs index e29c01ba..21dc37ae 100644 --- a/gremlin-client/src/structure/value.rs +++ b/gremlin-client/src/structure/value.rs @@ -1,5 +1,5 @@ use crate::conversion::{BorrowFromGValue, FromGValue}; -use crate::process::traversal::{Bytecode, Order, Scope}; +use crate::process::traversal::{Bytecode, Order, Scope, TraversalBuilder}; use crate::structure::traverser::Traverser; use crate::structure::{ label::LabelType, Cardinality, Edge, GKey, IntermediateRepr, List, Map, Metric, Path, Property, @@ -290,6 +290,12 @@ impl From for GValue { } } +impl From for GValue { + fn from(value: TraversalBuilder) -> Self { + value.bytecode.into() + } +} + impl std::convert::TryFrom for String { type Error = crate::GremlinError; From 5ec7c71a7a8a634ffbd07892f1544ee98286287f Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Thu, 11 Jul 2024 18:41:55 -0500 Subject: [PATCH 05/54] Added JanusGraph custom vertex id tests --- gremlin-client/tests/common.rs | 8 ++ gremlin-client/tests/custom_vertex_ids.rs | 93 +++++++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 gremlin-client/tests/custom_vertex_ids.rs diff --git a/gremlin-client/tests/common.rs b/gremlin-client/tests/common.rs index 6fec8241..c6c86597 100644 --- a/gremlin-client/tests/common.rs +++ b/gremlin-client/tests/common.rs @@ -6,6 +6,10 @@ pub mod io { GremlinClient::connect(("localhost", 8182)) } + fn connect_janusgraph_client() -> GremlinResult { + GremlinClient::connect(("localhost", 8184)) + } + pub fn connect_serializer(serializer: GraphSON) -> GremlinResult { let port = match serializer { GraphSON::V2 => 8182, @@ -25,6 +29,10 @@ pub mod io { connect().expect("It should connect") } + pub fn expect_janusgraph_client() -> GremlinClient { + connect_janusgraph_client().expect("It should connect") + } + pub fn expect_client_serializer(serializer: GraphSON) -> GremlinClient { connect_serializer(serializer).expect("It should connect") } diff --git a/gremlin-client/tests/custom_vertex_ids.rs b/gremlin-client/tests/custom_vertex_ids.rs new file mode 100644 index 00000000..23d2e8d2 --- /dev/null +++ b/gremlin-client/tests/custom_vertex_ids.rs @@ -0,0 +1,93 @@ +use std::collections::HashMap; + +use common::io::expect_janusgraph_client; +use gremlin_client::{ + process::traversal::{traversal, __}, + structure::T, + GKey, GValue, +}; + +mod common; + +//Custom vertex ids are a feature offered by JanusGraph +//https://docs.janusgraph.org/advanced-topics/custom-vertex-id/ + +#[test] +fn test_merge_v_custom_id() { + let g = traversal().with_remote(expect_janusgraph_client()); + let expected_id = "foo"; + let expected_property = "propValue"; + + let mut map_to_inject: HashMap = HashMap::new(); + let mut lookup_map: HashMap = HashMap::new(); + lookup_map.insert(T::Id.into(), expected_id.into()); + lookup_map.insert(T::Label.into(), "myvertexlabel".into()); + let mut property_map: HashMap = HashMap::new(); + property_map.insert("propertyKey".into(), expected_property.into()); + map_to_inject.insert("lookup".into(), lookup_map.into()); + map_to_inject.insert("properties".into(), property_map.into()); + + let actual_vertex = g + .inject(vec![map_to_inject.into()]) + .unfold() + .as_("payload") + .merge_v(__.select("lookup")) + .property( + "propertyKey", + __.select("payload") + .select("properties") + .select("propertyKey"), + ) + .next() + .expect("Should get response") + .expect("Should have returned a vertex"); + + match actual_vertex.id() { + gremlin_client::GID::String(actual) => assert_eq!(expected_id, actual), + other => panic!("Didn't get expected id type {:?}", other), + } + + let actual_property: &String = actual_vertex + .property("propertyKey") + .expect("Should have property") + .get() + .unwrap(); + assert_eq!(expected_property, actual_property); +} + +#[test] +fn test_merge_v_custom_id_start_step() { + let g = traversal().with_remote(expect_janusgraph_client()); + let expected_id = "test_merge_v_custom_id_start_step_id"; + let expected_label = "myvertexlabel"; + let mut start_step_map: HashMap = HashMap::new(); + start_step_map.insert(T::Id.into(), expected_id.into()); + start_step_map.insert(T::Label.into(), expected_label.into()); + let actual_vertex = g + .merge_v(start_step_map) + .next() + .expect("Should get a response") + .expect("Should return a vertex"); + match actual_vertex.id() { + gremlin_client::GID::String(actual) => assert_eq!(expected_id, actual), + other => panic!("Didn't get expected id type {:?}", other), + } + + assert_eq!(expected_label, actual_vertex.label()) +} + +#[test] +fn test_add_v_custom_id() { + let g = traversal().with_remote(expect_janusgraph_client()); + let expected_id = "test_add_v_custom_id"; + let actual_vertex = g + .add_v("some_label") + .property(T::Id, expected_id) + .next() + .expect("Should get a response") + .expect("Should return a vertex"); + match actual_vertex.id() { + gremlin_client::GID::String(actual) => assert_eq!(expected_id, actual), + other => panic!("Didn't get expected id type {:?}", other), + } +} From 7cf15cd6713cafba3f7df7070e2cdb1b827db73f Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Thu, 11 Jul 2024 18:42:36 -0500 Subject: [PATCH 06/54] Implemented From> for MergeVertexStep to support literal maps being defined for mergeV steps --- .../src/process/traversal/step/merge_vertex.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/gremlin-client/src/process/traversal/step/merge_vertex.rs b/gremlin-client/src/process/traversal/step/merge_vertex.rs index 17bcc8ac..6458a41b 100644 --- a/gremlin-client/src/process/traversal/step/merge_vertex.rs +++ b/gremlin-client/src/process/traversal/step/merge_vertex.rs @@ -1,5 +1,8 @@ +use std::collections::HashMap; + use crate::process::traversal::TraversalBuilder; use crate::structure::GValue; +use crate::GKey; pub struct MergeVertexStep { params: Vec, @@ -11,8 +14,6 @@ impl MergeVertexStep { } } -//todo need to handle (Map searchCreate)? -//https://tinkerpop.apache.org/docs/current/dev/provider/#merge-v-step impl From for Vec { fn from(step: MergeVertexStep) -> Self { step.params @@ -24,3 +25,9 @@ impl From for MergeVertexStep { MergeVertexStep::new(vec![param.bytecode.into()]) } } + +impl From> for MergeVertexStep { + fn from(value: HashMap) -> Self { + MergeVertexStep::new(vec![value.into()]) + } +} From 6a0f4c5ee7e65ed1b2f58b4bc1913c92629742c3 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Fri, 12 Jul 2024 22:31:26 -0500 Subject: [PATCH 07/54] Implemented Option step for mergeV --- gremlin-client/src/io/mod.rs | 11 +- .../src/process/traversal/builder.rs | 11 ++ .../src/process/traversal/graph_traversal.rs | 9 ++ .../src/process/traversal/step/mod.rs | 1 + .../src/process/traversal/step/option.rs | 33 ++++ gremlin-client/src/structure/merge.rs | 5 + gremlin-client/src/structure/mod.rs | 2 + gremlin-client/src/structure/value.rs | 10 ++ gremlin-client/tests/integration_traversal.rs | 153 +++++++++++++++++- 9 files changed, 232 insertions(+), 3 deletions(-) create mode 100644 gremlin-client/src/process/traversal/step/option.rs create mode 100644 gremlin-client/src/structure/merge.rs diff --git a/gremlin-client/src/io/mod.rs b/gremlin-client/src/io/mod.rs index d5898758..c925f899 100644 --- a/gremlin-client/src/io/mod.rs +++ b/gremlin-client/src/io/mod.rs @@ -216,7 +216,16 @@ impl GraphSON { "@value" : v })) } - + (_, GValue::Merge(merge)) => { + let merge_option = match merge { + crate::structure::Merge::OnCreate => "onCreate", + crate::structure::Merge::OnMatch => "onMatch", + }; + Ok(json!({ + "@type" : "g:Merge", + "@value" : merge_option + })) + } (_, _) => panic!("Type {:?} not supported.", value), } } diff --git a/gremlin-client/src/process/traversal/builder.rs b/gremlin-client/src/process/traversal/builder.rs index b0bb2286..78c411e0 100644 --- a/gremlin-client/src/process/traversal/builder.rs +++ b/gremlin-client/src/process/traversal/builder.rs @@ -23,6 +23,7 @@ use crate::structure::{Cardinality, GIDs, IntoPredicate, Labels}; use crate::GValue; use super::merge_vertex::MergeVertexStep; +use super::option::OptionStep; #[derive(Clone)] pub struct TraversalBuilder { @@ -665,6 +666,16 @@ impl TraversalBuilder { self } + pub fn option(mut self, option: A) -> Self + where + A: Into, + { + self.bytecode + .add_step(String::from("option"), option.into().into()); + + self + } + pub fn identity(mut self) -> Self { self.bytecode.add_step(String::from("identity"), vec![]); self diff --git a/gremlin-client/src/process/traversal/graph_traversal.rs b/gremlin-client/src/process/traversal/graph_traversal.rs index 6dbb0c8b..5fa2fb71 100644 --- a/gremlin-client/src/process/traversal/graph_traversal.rs +++ b/gremlin-client/src/process/traversal/graph_traversal.rs @@ -30,6 +30,7 @@ use crate::{ use std::marker::PhantomData; use super::merge_vertex::MergeVertexStep; +use super::option::OptionStep; #[derive(Clone)] pub struct GraphTraversal> { @@ -701,6 +702,14 @@ impl> GraphTraversal { self } + pub fn option(mut self, step: A) -> Self + where + A: Into, + { + self.builder = self.builder.option(step); + self + } + pub fn optional(mut self, step: TraversalBuilder) -> Self { self.builder = self.builder.optional(step); self diff --git a/gremlin-client/src/process/traversal/step/mod.rs b/gremlin-client/src/process/traversal/step/mod.rs index 7666d7af..473c20f3 100644 --- a/gremlin-client/src/process/traversal/step/mod.rs +++ b/gremlin-client/src/process/traversal/step/mod.rs @@ -10,6 +10,7 @@ pub mod loops; pub mod match_step; pub mod merge_vertex; pub mod not; +pub mod option; pub mod or; pub mod repeat; pub mod select; diff --git a/gremlin-client/src/process/traversal/step/option.rs b/gremlin-client/src/process/traversal/step/option.rs new file mode 100644 index 00000000..5b43ca50 --- /dev/null +++ b/gremlin-client/src/process/traversal/step/option.rs @@ -0,0 +1,33 @@ +use std::collections::HashMap; + +use crate::process::traversal::TraversalBuilder; +use crate::structure::{GValue, Merge}; +use crate::GKey; + +pub struct OptionStep { + params: Vec, +} + +impl OptionStep { + fn new(params: Vec) -> Self { + OptionStep { params } + } +} + +impl From for Vec { + fn from(step: OptionStep) -> Self { + step.params + } +} + +impl From<(Merge, TraversalBuilder)> for OptionStep { + fn from(value: (Merge, TraversalBuilder)) -> Self { + OptionStep::new(vec![value.0.into(), value.1.into()]) + } +} + +impl From<(Merge, HashMap)> for OptionStep { + fn from(value: (Merge, HashMap)) -> Self { + OptionStep::new(vec![value.0.into(), value.1.into()]) + } +} diff --git a/gremlin-client/src/structure/merge.rs b/gremlin-client/src/structure/merge.rs new file mode 100644 index 00000000..19f6b99c --- /dev/null +++ b/gremlin-client/src/structure/merge.rs @@ -0,0 +1,5 @@ +#[derive(Debug, PartialEq, Clone)] +pub enum Merge { + OnCreate, + OnMatch, +} diff --git a/gremlin-client/src/structure/mod.rs b/gremlin-client/src/structure/mod.rs index 1e6ab0ec..dbac31f0 100644 --- a/gremlin-client/src/structure/mod.rs +++ b/gremlin-client/src/structure/mod.rs @@ -6,6 +6,7 @@ mod label; mod list; mod macros; mod map; +mod merge; mod metrics; mod p; mod path; @@ -37,6 +38,7 @@ pub use cardinality::Cardinality; pub use either::*; pub use label::Labels; pub use map::{GKey, Map}; +pub use merge::Merge; pub use p::{IntoPredicate, P}; pub use pop::Pop; pub use t::T; diff --git a/gremlin-client/src/structure/value.rs b/gremlin-client/src/structure/value.rs index 21dc37ae..4a3a6e64 100644 --- a/gremlin-client/src/structure/value.rs +++ b/gremlin-client/src/structure/value.rs @@ -11,6 +11,8 @@ use std::collections::{BTreeMap, HashMap, HashSet, VecDeque}; pub type Date = chrono::DateTime; use std::convert::TryInto; use std::hash::Hash; + +use super::Merge; /// Represent possible values coming from the [Gremlin Server](http://tinkerpop.apache.org/docs/3.4.0/dev/io/) #[allow(clippy::large_enum_variant)] #[derive(Debug, PartialEq, Clone)] @@ -46,6 +48,7 @@ pub enum GValue { TextP(TextP), Pop(Pop), Cardinality(Cardinality), + Merge(Merge), } impl GValue { @@ -180,6 +183,13 @@ impl From for GValue { GValue::Order(val) } } + +impl From for GValue { + fn from(value: Merge) -> Self { + GValue::Merge(value) + } +} + impl From for GValue { fn from(val: Token) -> Self { GValue::Token(val) diff --git a/gremlin-client/tests/integration_traversal.rs b/gremlin-client/tests/integration_traversal.rs index fb3faf7b..195e13a9 100644 --- a/gremlin-client/tests/integration_traversal.rs +++ b/gremlin-client/tests/integration_traversal.rs @@ -1,6 +1,11 @@ +use std::collections::HashMap; +use std::convert::TryInto; + use gremlin_client::process::traversal::{traversal, Order, __}; -use gremlin_client::structure::{Cardinality, List, Map, Pop, TextP, Vertex, VertexProperty, P, T}; -use gremlin_client::utils; +use gremlin_client::structure::{ + Cardinality, List, Map, Merge, Pop, TextP, Vertex, VertexProperty, P, T, +}; +use gremlin_client::{utils, GKey, GValue}; mod common; @@ -17,6 +22,150 @@ fn test_simple_vertex_traversal() { assert!(results.len() > 0); } +#[test] +fn test_merge_v_no_options() { + let g = traversal().with_remote(graph()); + let expected_id = 1_000i64; + let mut map1: HashMap = HashMap::new(); + let mut lookup_map: HashMap = HashMap::new(); + lookup_map.insert(T::Id.into(), expected_id.into()); + lookup_map.insert(T::Label.into(), "myvertexlabel".into()); + let mut property_map: HashMap = HashMap::new(); + property_map.insert("propertyKey".into(), "propertyValue".into()); + map1.insert("lookup".into(), lookup_map.into()); + map1.insert("properties".into(), property_map.into()); + + let vertex_properties = g + .inject(vec![map1.into()]) + .unfold() + .as_("payload") + .merge_v(__.select("lookup")) + .property( + "propertyKey", + __.select("payload") + .select("properties") + .select("propertyKey"), + ) + .element_map(()) + .next() + .expect("Should get response") + .expect("Should have returned a vertex"); + + let actual_id: &i64 = vertex_properties + .get("id") + .expect("Should have id") + .get() + .unwrap(); + assert_eq!(expected_id, *actual_id); + + let on_create_prop_value: &String = vertex_properties + .get("propertyKey") + .expect("Should have property") + .get() + .unwrap(); + assert_eq!(on_create_prop_value, "propertyValue"); +} + +#[test] +fn test_merge_v_options() { + let g = traversal().with_remote(graph()); + let expected_label = "test_merge_v_options_label"; + let mut start_step_map: HashMap = HashMap::new(); + start_step_map.insert(T::Label.into(), expected_label.into()); + start_step_map.insert("identifing_prop".into(), "some_Value".into()); + + let prop_key = "some_prop"; + let mut on_create_map: HashMap = HashMap::new(); + let expected_on_create_prop_value = "on_create_value"; + on_create_map.insert(prop_key.into(), expected_on_create_prop_value.into()); + + let mut on_match_map: HashMap = HashMap::new(); + let expected_on_match_prop_value = "on_match_value"; + on_match_map.insert(prop_key.into(), expected_on_match_prop_value.into()); + + let on_create_vertex_map = g + .merge_v(start_step_map.clone()) + .option((Merge::OnCreate, on_create_map.clone())) + .option((Merge::OnMatch, on_match_map.clone())) + .element_map(()) + .next() + .expect("Should get a response") + .expect("Should return a vertex"); + + let actual_label: &String = on_create_vertex_map + .get("label") + .expect("Should have id") + .get() + .unwrap(); + assert_eq!(expected_label, actual_label); + + let on_create_prop_value: &String = on_create_vertex_map + .get(prop_key) + .expect("Should have property") + .get() + .unwrap(); + assert_eq!(on_create_prop_value, expected_on_create_prop_value); + + //Now run the traversal again, and confirm the OnMatch applied this time + let on_match_vertex_map = g + .merge_v(start_step_map) + .option((Merge::OnCreate, on_create_map.clone())) + .option((Merge::OnMatch, on_match_map.clone())) + .element_map(()) + .next() + .expect("Should get a response") + .expect("Should return a vertex"); + + let actual_label: &String = on_match_vertex_map + .get("label") + .expect("Should have id") + .get() + .unwrap(); + assert_eq!(expected_label, actual_label); + + let on_match_prop_value: &String = on_match_vertex_map + .get(prop_key) + .expect("Should have property") + .get() + .unwrap(); + assert_eq!(on_match_prop_value, expected_on_match_prop_value); +} + +#[test] +fn test_merge_v_start_step() { + let g = traversal().with_remote(graph()); + let expected_id = 10000i64; + let expected_label = "myvertexlabel"; + let mut start_step_map: HashMap = HashMap::new(); + start_step_map.insert(T::Id.into(), expected_id.into()); + start_step_map.insert(T::Label.into(), expected_label.into()); + let actual_vertex = g + .merge_v(start_step_map) + .next() + .expect("Should get a response") + .expect("Should return a vertex"); + match actual_vertex.id() { + gremlin_client::GID::Int64(actual) => assert_eq!(expected_id, *actual), + other => panic!("Didn't get expected id type {:?}", other), + } + + assert_eq!(expected_label, actual_vertex.label()) +} + +#[test] +fn test_inject() { + let g = traversal().with_remote(graph()); + let expected_value = "foo"; + let response: String = g + .inject(vec![expected_value.into()]) + .next() + .expect("Should get response") + .expect("Should have gotten a Some") + .try_into() + .expect("Should be parsable into a String"); + assert_eq!(expected_value, response); +} + #[test] fn test_simple_vertex_traversal_with_id() { let client = graph(); From 0f4aee6fbc5941d0bcb3098461232481801a363a Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Fri, 12 Jul 2024 22:41:53 -0500 Subject: [PATCH 08/54] Added healthcheck for JG & wait with timeout for docker compose up in GH Action --- .github/workflows/test.yml | 2 +- docker-compose/docker-compose.yaml | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c6ee807a..4b6e8557 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -23,7 +23,7 @@ jobs: - uses: actions/checkout@v2 - name: Starting Gremlin Servers run: | - docker-compose -f ./docker-compose/docker-compose.yaml up -d + docker-compose -f ./docker-compose/docker-compose.yaml up -d --wait --wait-timeout 30 env: GREMLIN_SERVER: ${{ matrix.gremlin-server }} diff --git a/docker-compose/docker-compose.yaml b/docker-compose/docker-compose.yaml index 781ab2b3..b208dd53 100644 --- a/docker-compose/docker-compose.yaml +++ b/docker-compose/docker-compose.yaml @@ -26,4 +26,9 @@ services: - JANUS_PROPS_TEMPLATE=inmemory ports: - "8184:8182" + healthcheck: + test: ["CMD", "bin/gremlin.sh", "-e", "scripts/remote-connect.groovy"] + interval: 10s + timeout: 30s + retries: 3 \ No newline at end of file From dddf8c010d80548634ac36ba328164bf0776dfcf Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Fri, 12 Jul 2024 22:48:53 -0500 Subject: [PATCH 09/54] Use Docker Compose v2 via "docker compose" vs v1's "docker-compose" in order to leverage v2's wait flag --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4b6e8557..8a3fc099 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -23,7 +23,7 @@ jobs: - uses: actions/checkout@v2 - name: Starting Gremlin Servers run: | - docker-compose -f ./docker-compose/docker-compose.yaml up -d --wait --wait-timeout 30 + docker compose -f ./docker-compose/docker-compose.yaml up -d --wait --wait-timeout 30 env: GREMLIN_SERVER: ${{ matrix.gremlin-server }} From e1d70969409e22fb67082fed81d4022486074d44 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Fri, 12 Jul 2024 22:58:47 -0500 Subject: [PATCH 10/54] Combine merge v custom id test cases --- gremlin-client/tests/custom_vertex_ids.rs | 40 +++++++++++------------ 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/gremlin-client/tests/custom_vertex_ids.rs b/gremlin-client/tests/custom_vertex_ids.rs index 23d2e8d2..d01a94ab 100644 --- a/gremlin-client/tests/custom_vertex_ids.rs +++ b/gremlin-client/tests/custom_vertex_ids.rs @@ -15,6 +15,24 @@ mod common; #[test] fn test_merge_v_custom_id() { let g = traversal().with_remote(expect_janusgraph_client()); + let expected_id = "test_merge_v_custom_id"; + let expected_label = "myvertexlabel"; + let mut start_step_map: HashMap = HashMap::new(); + start_step_map.insert(T::Id.into(), expected_id.into()); + start_step_map.insert(T::Label.into(), expected_label.into()); + let actual_vertex = g + .merge_v(start_step_map) + .next() + .expect("Should get a response") + .expect("Should return a vertex"); + match actual_vertex.id() { + gremlin_client::GID::String(actual) => assert_eq!(expected_id, actual), + other => panic!("Didn't get expected id type {:?}", other), + } + + assert_eq!(expected_label, actual_vertex.label()); + + //Now try it as a mid-traversal step (inject is the start step) let expected_id = "foo"; let expected_property = "propValue"; @@ -55,31 +73,11 @@ fn test_merge_v_custom_id() { assert_eq!(expected_property, actual_property); } -#[test] -fn test_merge_v_custom_id_start_step() { - let g = traversal().with_remote(expect_janusgraph_client()); - let expected_id = "test_merge_v_custom_id_start_step_id"; - let expected_label = "myvertexlabel"; - let mut start_step_map: HashMap = HashMap::new(); - start_step_map.insert(T::Id.into(), expected_id.into()); - start_step_map.insert(T::Label.into(), expected_label.into()); - let actual_vertex = g - .merge_v(start_step_map) - .next() - .expect("Should get a response") - .expect("Should return a vertex"); - match actual_vertex.id() { - gremlin_client::GID::String(actual) => assert_eq!(expected_id, actual), - other => panic!("Didn't get expected id type {:?}", other), - } - - assert_eq!(expected_label, actual_vertex.label()) -} - #[test] fn test_add_v_custom_id() { let g = traversal().with_remote(expect_janusgraph_client()); let expected_id = "test_add_v_custom_id"; + g.v(expected_id).drop().iter().unwrap(); let actual_vertex = g .add_v("some_label") .property(T::Id, expected_id) From 3d3a30084960679f0acfd9a7b9ee600d40b7d8fa Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Sat, 13 Jul 2024 12:02:23 -0500 Subject: [PATCH 11/54] Better handle tests being reran --- gremlin-client/tests/custom_vertex_ids.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gremlin-client/tests/custom_vertex_ids.rs b/gremlin-client/tests/custom_vertex_ids.rs index d01a94ab..ba773b4b 100644 --- a/gremlin-client/tests/custom_vertex_ids.rs +++ b/gremlin-client/tests/custom_vertex_ids.rs @@ -16,6 +16,8 @@ mod common; fn test_merge_v_custom_id() { let g = traversal().with_remote(expect_janusgraph_client()); let expected_id = "test_merge_v_custom_id"; + //Drop the vertex if the test was previously ran since we test on the difference between onCreate vs onMatch + g.v(expected_id).drop(); let expected_label = "myvertexlabel"; let mut start_step_map: HashMap = HashMap::new(); start_step_map.insert(T::Id.into(), expected_id.into()); @@ -77,6 +79,7 @@ fn test_merge_v_custom_id() { fn test_add_v_custom_id() { let g = traversal().with_remote(expect_janusgraph_client()); let expected_id = "test_add_v_custom_id"; + //If the test is reran remove the previously inserted vertex g.v(expected_id).drop().iter().unwrap(); let actual_vertex = g .add_v("some_label") From 6f56392a104a5fafd0bd5ed536a55f23830acb24 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Sat, 13 Jul 2024 12:22:34 -0500 Subject: [PATCH 12/54] Added merge_v_tests feature for tests --- .github/workflows/test.yml | 15 +++++++++++++++ gremlin-client/Cargo.toml | 1 + gremlin-client/tests/integration_traversal.rs | 8 +++++--- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8a3fc099..12d61fe4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -42,12 +42,27 @@ jobs: command: fmt args: --all -- --check - name: Run cargo test with tokio + if: ${{ matrix.gremlin-server }} == 3.5.7 uses: actions-rs/cargo@v1 with: command: test args: --manifest-path gremlin-client/Cargo.toml --features=tokio-runtime - name: Run cargo test with async-std + if: ${{ matrix.gremlin-server }} == 3.5.7 uses: actions-rs/cargo@v1 with: command: test args: --manifest-path gremlin-client/Cargo.toml --features=async-std-runtime + # MergeV as a step doesn't exist in 3.5.x, so selectively run those tests + - name: Run cargo test with tokio + if: ${{ matrix.gremlin-server }} == 3.6.5 + uses: actions-rs/cargo@v1 + with: + command: test + args: --manifest-path gremlin-client/Cargo.toml --features=tokio-runtime,merge_v_tests + - name: Run cargo test with async-std + if: ${{ matrix.gremlin-server }} == 3.6.5 + uses: actions-rs/cargo@v1 + with: + command: test + args: --manifest-path gremlin-client/Cargo.toml --features=async-std-runtime,merge_v_tests diff --git a/gremlin-client/Cargo.toml b/gremlin-client/Cargo.toml index 69cbd0d6..d7f2dbde 100644 --- a/gremlin-client/Cargo.toml +++ b/gremlin-client/Cargo.toml @@ -15,6 +15,7 @@ readme = "README.md" [features] default = [] +merge_v_tests = [] diff --git a/gremlin-client/tests/integration_traversal.rs b/gremlin-client/tests/integration_traversal.rs index 195e13a9..eb6ac210 100644 --- a/gremlin-client/tests/integration_traversal.rs +++ b/gremlin-client/tests/integration_traversal.rs @@ -1,11 +1,10 @@ -use std::collections::HashMap; use std::convert::TryInto; use gremlin_client::process::traversal::{traversal, Order, __}; use gremlin_client::structure::{ - Cardinality, List, Map, Merge, Pop, TextP, Vertex, VertexProperty, P, T, + Cardinality, List, Map, Pop, TextP, Vertex, VertexProperty, P, T, }; -use gremlin_client::{utils, GKey, GValue}; +use gremlin_client::utils; mod common; @@ -23,6 +22,7 @@ fn test_simple_vertex_traversal() { } #[test] +#[cfg(feature = "merge_v_tests")] fn test_merge_v_no_options() { let g = traversal().with_remote(graph()); let expected_id = 1_000i64; @@ -67,6 +67,7 @@ fn test_merge_v_no_options() { } #[test] +#[cfg(feature = "merge_v_tests")] fn test_merge_v_options() { let g = traversal().with_remote(graph()); let expected_label = "test_merge_v_options_label"; @@ -132,6 +133,7 @@ fn test_merge_v_options() { } #[test] +#[cfg(feature = "merge_v_tests")] fn test_merge_v_start_step() { let g = traversal().with_remote(graph()); let expected_id = 10000i64; From dada863319689b277c792c001fd9ec5e127932a6 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Sat, 13 Jul 2024 12:25:11 -0500 Subject: [PATCH 13/54] Formatting --- gremlin-client/tests/integration_traversal.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/gremlin-client/tests/integration_traversal.rs b/gremlin-client/tests/integration_traversal.rs index eb6ac210..1ccf1c45 100644 --- a/gremlin-client/tests/integration_traversal.rs +++ b/gremlin-client/tests/integration_traversal.rs @@ -1,9 +1,7 @@ use std::convert::TryInto; use gremlin_client::process::traversal::{traversal, Order, __}; -use gremlin_client::structure::{ - Cardinality, List, Map, Pop, TextP, Vertex, VertexProperty, P, T, -}; +use gremlin_client::structure::{Cardinality, List, Map, Pop, TextP, Vertex, VertexProperty, P, T}; use gremlin_client::utils; mod common; From bc94862a551ca8bdeff4f7f37f218aed0ba3dfd1 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Sat, 13 Jul 2024 12:33:11 -0500 Subject: [PATCH 14/54] FIxed if condition --- .github/workflows/test.yml | 12 ++++++------ gremlin-client/tests/integration_traversal.rs | 10 +++++++--- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 12d61fe4..6b758585 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -42,27 +42,27 @@ jobs: command: fmt args: --all -- --check - name: Run cargo test with tokio - if: ${{ matrix.gremlin-server }} == 3.5.7 + if: ${{ matrix.gremlin-server }} == "3.5.7" uses: actions-rs/cargo@v1 with: command: test args: --manifest-path gremlin-client/Cargo.toml --features=tokio-runtime - name: Run cargo test with async-std - if: ${{ matrix.gremlin-server }} == 3.5.7 + if: ${{ matrix.gremlin-server }} == "3.5.7" uses: actions-rs/cargo@v1 with: command: test args: --manifest-path gremlin-client/Cargo.toml --features=async-std-runtime # MergeV as a step doesn't exist in 3.5.x, so selectively run those tests - name: Run cargo test with tokio - if: ${{ matrix.gremlin-server }} == 3.6.5 + if: ${{ matrix.gremlin-server }} != "3.5.7" uses: actions-rs/cargo@v1 with: command: test - args: --manifest-path gremlin-client/Cargo.toml --features=tokio-runtime,merge_v_tests + args: --manifest-path gremlin-client/Cargo.toml --features=tokio-runtime,merge_tests - name: Run cargo test with async-std - if: ${{ matrix.gremlin-server }} == 3.6.5 + if: ${{ matrix.gremlin-server }} != "3.5.7" uses: actions-rs/cargo@v1 with: command: test - args: --manifest-path gremlin-client/Cargo.toml --features=async-std-runtime,merge_v_tests + args: --manifest-path gremlin-client/Cargo.toml --features=async-std-runtime,merge_tests diff --git a/gremlin-client/tests/integration_traversal.rs b/gremlin-client/tests/integration_traversal.rs index 1ccf1c45..84abc41e 100644 --- a/gremlin-client/tests/integration_traversal.rs +++ b/gremlin-client/tests/integration_traversal.rs @@ -2,6 +2,10 @@ use std::convert::TryInto; use gremlin_client::process::traversal::{traversal, Order, __}; use gremlin_client::structure::{Cardinality, List, Map, Pop, TextP, Vertex, VertexProperty, P, T}; + +#[cfg(feature = "merge_tests")] +use gremlin_client::structure::Merge; + use gremlin_client::utils; mod common; @@ -20,7 +24,7 @@ fn test_simple_vertex_traversal() { } #[test] -#[cfg(feature = "merge_v_tests")] +#[cfg(feature = "merge_tests")] fn test_merge_v_no_options() { let g = traversal().with_remote(graph()); let expected_id = 1_000i64; @@ -65,7 +69,7 @@ fn test_merge_v_no_options() { } #[test] -#[cfg(feature = "merge_v_tests")] +#[cfg(feature = "merge_tests")] fn test_merge_v_options() { let g = traversal().with_remote(graph()); let expected_label = "test_merge_v_options_label"; @@ -131,7 +135,7 @@ fn test_merge_v_options() { } #[test] -#[cfg(feature = "merge_v_tests")] +#[cfg(feature = "merge_tests")] fn test_merge_v_start_step() { let g = traversal().with_remote(graph()); let expected_id = 10000i64; From 80357fe43656d831235a834c551a9bffe91622eb Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Sat, 13 Jul 2024 12:38:01 -0500 Subject: [PATCH 15/54] Corrected cargo.toml merge_test feature --- gremlin-client/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gremlin-client/Cargo.toml b/gremlin-client/Cargo.toml index d7f2dbde..e67a20b7 100644 --- a/gremlin-client/Cargo.toml +++ b/gremlin-client/Cargo.toml @@ -15,7 +15,7 @@ readme = "README.md" [features] default = [] -merge_v_tests = [] +merge_tests = [] From 18051d3916487cc1577bb5e8c3565c19d40573a8 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Sat, 13 Jul 2024 12:42:14 -0500 Subject: [PATCH 16/54] Changed GH Action if statement formatting --- .github/workflows/test.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6b758585..bb15e894 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -42,26 +42,26 @@ jobs: command: fmt args: --all -- --check - name: Run cargo test with tokio - if: ${{ matrix.gremlin-server }} == "3.5.7" + if: matrix.gremlin-server == '3.5.7' uses: actions-rs/cargo@v1 with: command: test args: --manifest-path gremlin-client/Cargo.toml --features=tokio-runtime - name: Run cargo test with async-std - if: ${{ matrix.gremlin-server }} == "3.5.7" + if: matrix.gremlin-server == '3.5.7' uses: actions-rs/cargo@v1 with: command: test args: --manifest-path gremlin-client/Cargo.toml --features=async-std-runtime # MergeV as a step doesn't exist in 3.5.x, so selectively run those tests - name: Run cargo test with tokio - if: ${{ matrix.gremlin-server }} != "3.5.7" + if: matrix.gremlin-server != '3.5.7' uses: actions-rs/cargo@v1 with: command: test args: --manifest-path gremlin-client/Cargo.toml --features=tokio-runtime,merge_tests - name: Run cargo test with async-std - if: ${{ matrix.gremlin-server }} != "3.5.7" + if: matrix.gremlin-server != '3.5.7' uses: actions-rs/cargo@v1 with: command: test From 6737d919fded210d1583ed7fed6ab81000cace37 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Sat, 13 Jul 2024 12:45:46 -0500 Subject: [PATCH 17/54] Increased docker compose timeout time for healthy service check --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index bb15e894..c209a85c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -23,7 +23,7 @@ jobs: - uses: actions/checkout@v2 - name: Starting Gremlin Servers run: | - docker compose -f ./docker-compose/docker-compose.yaml up -d --wait --wait-timeout 30 + docker compose -f ./docker-compose/docker-compose.yaml up -d --wait --wait-timeout 90 env: GREMLIN_SERVER: ${{ matrix.gremlin-server }} From 7a8a0cd71c156aa2146effb793facfe7a3ffac7f Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Sat, 13 Jul 2024 12:53:20 -0500 Subject: [PATCH 18/54] Fixed imports for merge_test module. Moved merge tests into their own module --- gremlin-client/tests/integration_traversal.rs | 269 +++++++++--------- 1 file changed, 135 insertions(+), 134 deletions(-) diff --git a/gremlin-client/tests/integration_traversal.rs b/gremlin-client/tests/integration_traversal.rs index 84abc41e..d7232a79 100644 --- a/gremlin-client/tests/integration_traversal.rs +++ b/gremlin-client/tests/integration_traversal.rs @@ -3,9 +3,6 @@ use std::convert::TryInto; use gremlin_client::process::traversal::{traversal, Order, __}; use gremlin_client::structure::{Cardinality, List, Map, Pop, TextP, Vertex, VertexProperty, P, T}; -#[cfg(feature = "merge_tests")] -use gremlin_client::structure::Merge; - use gremlin_client::utils; mod common; @@ -14,146 +11,150 @@ use common::io::{ create_edge, create_vertex, create_vertex_with_label, drop_edges, drop_vertices, graph, }; -#[test] -fn test_simple_vertex_traversal() { - let g = traversal().with_remote(graph()); - - let results = g.v(()).to_list().unwrap(); - - assert!(results.len() > 0); -} - -#[test] #[cfg(feature = "merge_tests")] -fn test_merge_v_no_options() { - let g = traversal().with_remote(graph()); - let expected_id = 1_000i64; - let mut map1: HashMap = HashMap::new(); - let mut lookup_map: HashMap = HashMap::new(); - lookup_map.insert(T::Id.into(), expected_id.into()); - lookup_map.insert(T::Label.into(), "myvertexlabel".into()); - let mut property_map: HashMap = HashMap::new(); - property_map.insert("propertyKey".into(), "propertyValue".into()); - map1.insert("lookup".into(), lookup_map.into()); - map1.insert("properties".into(), property_map.into()); - - let vertex_properties = g - .inject(vec![map1.into()]) - .unfold() - .as_("payload") - .merge_v(__.select("lookup")) - .property( - "propertyKey", - __.select("payload") - .select("properties") - .select("propertyKey"), - ) - .element_map(()) - .next() - .expect("Should get response") - .expect("Should have returned a vertex"); +mod merge_tests { + use super::*; + use gremlin_client::{structure::Merge, GKey, GValue}; + use std::collections::HashMap; + + #[test] + fn test_merge_v_no_options() { + let g = traversal().with_remote(graph()); + let expected_id = 1_000i64; + let mut map1: HashMap = HashMap::new(); + let mut lookup_map: HashMap = HashMap::new(); + lookup_map.insert(T::Id.into(), expected_id.into()); + lookup_map.insert(T::Label.into(), "myvertexlabel".into()); + let mut property_map: HashMap = HashMap::new(); + property_map.insert("propertyKey".into(), "propertyValue".into()); + map1.insert("lookup".into(), lookup_map.into()); + map1.insert("properties".into(), property_map.into()); + + let vertex_properties = g + .inject(vec![map1.into()]) + .unfold() + .as_("payload") + .merge_v(__.select("lookup")) + .property( + "propertyKey", + __.select("payload") + .select("properties") + .select("propertyKey"), + ) + .element_map(()) + .next() + .expect("Should get response") + .expect("Should have returned a vertex"); + + let actual_id: &i64 = vertex_properties + .get("id") + .expect("Should have id") + .get() + .unwrap(); + assert_eq!(expected_id, *actual_id); + + let on_create_prop_value: &String = vertex_properties + .get("propertyKey") + .expect("Should have property") + .get() + .unwrap(); + assert_eq!(on_create_prop_value, "propertyValue"); + } - let actual_id: &i64 = vertex_properties - .get("id") - .expect("Should have id") - .get() - .unwrap(); - assert_eq!(expected_id, *actual_id); + #[test] + fn test_merge_v_options() { + let g = traversal().with_remote(graph()); + let expected_label = "test_merge_v_options_label"; + let mut start_step_map: HashMap = HashMap::new(); + start_step_map.insert(T::Label.into(), expected_label.into()); + start_step_map.insert("identifing_prop".into(), "some_Value".into()); + + let prop_key = "some_prop"; + let mut on_create_map: HashMap = HashMap::new(); + let expected_on_create_prop_value = "on_create_value"; + on_create_map.insert(prop_key.into(), expected_on_create_prop_value.into()); + + let mut on_match_map: HashMap = HashMap::new(); + let expected_on_match_prop_value = "on_match_value"; + on_match_map.insert(prop_key.into(), expected_on_match_prop_value.into()); + + let on_create_vertex_map = g + .merge_v(start_step_map.clone()) + .option((Merge::OnCreate, on_create_map.clone())) + .option((Merge::OnMatch, on_match_map.clone())) + .element_map(()) + .next() + .expect("Should get a response") + .expect("Should return a vertex"); + + let actual_label: &String = on_create_vertex_map + .get("label") + .expect("Should have id") + .get() + .unwrap(); + assert_eq!(expected_label, actual_label); + + let on_create_prop_value: &String = on_create_vertex_map + .get(prop_key) + .expect("Should have property") + .get() + .unwrap(); + assert_eq!(on_create_prop_value, expected_on_create_prop_value); + + //Now run the traversal again, and confirm the OnMatch applied this time + let on_match_vertex_map = g + .merge_v(start_step_map) + .option((Merge::OnCreate, on_create_map.clone())) + .option((Merge::OnMatch, on_match_map.clone())) + .element_map(()) + .next() + .expect("Should get a response") + .expect("Should return a vertex"); + + let actual_label: &String = on_match_vertex_map + .get("label") + .expect("Should have id") + .get() + .unwrap(); + assert_eq!(expected_label, actual_label); + + let on_match_prop_value: &String = on_match_vertex_map + .get(prop_key) + .expect("Should have property") + .get() + .unwrap(); + assert_eq!(on_match_prop_value, expected_on_match_prop_value); + } - let on_create_prop_value: &String = vertex_properties - .get("propertyKey") - .expect("Should have property") - .get() - .unwrap(); - assert_eq!(on_create_prop_value, "propertyValue"); + #[test] + fn test_merge_v_start_step() { + let g = traversal().with_remote(graph()); + let expected_id = 10000i64; + let expected_label = "myvertexlabel"; + let mut start_step_map: HashMap = HashMap::new(); + start_step_map.insert(T::Id.into(), expected_id.into()); + start_step_map.insert(T::Label.into(), expected_label.into()); + let actual_vertex = g + .merge_v(start_step_map) + .next() + .expect("Should get a response") + .expect("Should return a vertex"); + match actual_vertex.id() { + gremlin_client::GID::Int64(actual) => assert_eq!(expected_id, *actual), + other => panic!("Didn't get expected id type {:?}", other), + } + + assert_eq!(expected_label, actual_vertex.label()) + } } #[test] -#[cfg(feature = "merge_tests")] -fn test_merge_v_options() { +fn test_simple_vertex_traversal() { let g = traversal().with_remote(graph()); - let expected_label = "test_merge_v_options_label"; - let mut start_step_map: HashMap = HashMap::new(); - start_step_map.insert(T::Label.into(), expected_label.into()); - start_step_map.insert("identifing_prop".into(), "some_Value".into()); - - let prop_key = "some_prop"; - let mut on_create_map: HashMap = HashMap::new(); - let expected_on_create_prop_value = "on_create_value"; - on_create_map.insert(prop_key.into(), expected_on_create_prop_value.into()); - - let mut on_match_map: HashMap = HashMap::new(); - let expected_on_match_prop_value = "on_match_value"; - on_match_map.insert(prop_key.into(), expected_on_match_prop_value.into()); - - let on_create_vertex_map = g - .merge_v(start_step_map.clone()) - .option((Merge::OnCreate, on_create_map.clone())) - .option((Merge::OnMatch, on_match_map.clone())) - .element_map(()) - .next() - .expect("Should get a response") - .expect("Should return a vertex"); - - let actual_label: &String = on_create_vertex_map - .get("label") - .expect("Should have id") - .get() - .unwrap(); - assert_eq!(expected_label, actual_label); - - let on_create_prop_value: &String = on_create_vertex_map - .get(prop_key) - .expect("Should have property") - .get() - .unwrap(); - assert_eq!(on_create_prop_value, expected_on_create_prop_value); - - //Now run the traversal again, and confirm the OnMatch applied this time - let on_match_vertex_map = g - .merge_v(start_step_map) - .option((Merge::OnCreate, on_create_map.clone())) - .option((Merge::OnMatch, on_match_map.clone())) - .element_map(()) - .next() - .expect("Should get a response") - .expect("Should return a vertex"); - - let actual_label: &String = on_match_vertex_map - .get("label") - .expect("Should have id") - .get() - .unwrap(); - assert_eq!(expected_label, actual_label); - let on_match_prop_value: &String = on_match_vertex_map - .get(prop_key) - .expect("Should have property") - .get() - .unwrap(); - assert_eq!(on_match_prop_value, expected_on_match_prop_value); -} - -#[test] -#[cfg(feature = "merge_tests")] -fn test_merge_v_start_step() { - let g = traversal().with_remote(graph()); - let expected_id = 10000i64; - let expected_label = "myvertexlabel"; - let mut start_step_map: HashMap = HashMap::new(); - start_step_map.insert(T::Id.into(), expected_id.into()); - start_step_map.insert(T::Label.into(), expected_label.into()); - let actual_vertex = g - .merge_v(start_step_map) - .next() - .expect("Should get a response") - .expect("Should return a vertex"); - match actual_vertex.id() { - gremlin_client::GID::Int64(actual) => assert_eq!(expected_id, *actual), - other => panic!("Didn't get expected id type {:?}", other), - } + let results = g.v(()).to_list().unwrap(); - assert_eq!(expected_label, actual_vertex.label()) + assert!(results.len() > 0); } #[test] From 097fa04d2bacb292e77c6349c8139a5210303ba6 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Sat, 13 Jul 2024 13:07:47 -0500 Subject: [PATCH 19/54] Use drop vertices test utility function --- gremlin-client/tests/custom_vertex_ids.rs | 19 +++++------ gremlin-client/tests/integration_traversal.rs | 32 +++++++------------ 2 files changed, 22 insertions(+), 29 deletions(-) diff --git a/gremlin-client/tests/custom_vertex_ids.rs b/gremlin-client/tests/custom_vertex_ids.rs index ba773b4b..97b74f96 100644 --- a/gremlin-client/tests/custom_vertex_ids.rs +++ b/gremlin-client/tests/custom_vertex_ids.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use common::io::expect_janusgraph_client; +use common::io::{drop_vertices, expect_janusgraph_client}; use gremlin_client::{ process::traversal::{traversal, __}, structure::T, @@ -14,11 +14,11 @@ mod common; #[test] fn test_merge_v_custom_id() { - let g = traversal().with_remote(expect_janusgraph_client()); + let client = expect_janusgraph_client(); + let expected_label = "test_merge_v_custom_id"; + drop_vertices(&client, expected_label).expect("Failed to drop vertices"); + let g = traversal().with_remote(client); let expected_id = "test_merge_v_custom_id"; - //Drop the vertex if the test was previously ran since we test on the difference between onCreate vs onMatch - g.v(expected_id).drop(); - let expected_label = "myvertexlabel"; let mut start_step_map: HashMap = HashMap::new(); start_step_map.insert(T::Id.into(), expected_id.into()); start_step_map.insert(T::Label.into(), expected_label.into()); @@ -77,12 +77,13 @@ fn test_merge_v_custom_id() { #[test] fn test_add_v_custom_id() { - let g = traversal().with_remote(expect_janusgraph_client()); + let client = expect_janusgraph_client(); let expected_id = "test_add_v_custom_id"; - //If the test is reran remove the previously inserted vertex - g.v(expected_id).drop().iter().unwrap(); + let test_vertex_label = "test_add_v_custom_id"; + drop_vertices(&client, test_vertex_label).expect("Failed to drop vertices"); + let g = traversal().with_remote(client); let actual_vertex = g - .add_v("some_label") + .add_v(test_vertex_label) .property(T::Id, expected_id) .next() .expect("Should get a response") diff --git a/gremlin-client/tests/integration_traversal.rs b/gremlin-client/tests/integration_traversal.rs index d7232a79..4dcee3a6 100644 --- a/gremlin-client/tests/integration_traversal.rs +++ b/gremlin-client/tests/integration_traversal.rs @@ -19,12 +19,15 @@ mod merge_tests { #[test] fn test_merge_v_no_options() { - let g = traversal().with_remote(graph()); - let expected_id = 1_000i64; + let client = graph(); + let test_vertex_label = "test_merge_v_no_options"; + drop_vertices(&client, test_vertex_label) + .expect("Failed to drop vertices in case of rerun"); + let g = traversal().with_remote(client); + let mut map1: HashMap = HashMap::new(); let mut lookup_map: HashMap = HashMap::new(); - lookup_map.insert(T::Id.into(), expected_id.into()); - lookup_map.insert(T::Label.into(), "myvertexlabel".into()); + lookup_map.insert(T::Label.into(), test_vertex_label.into()); let mut property_map: HashMap = HashMap::new(); property_map.insert("propertyKey".into(), "propertyValue".into()); map1.insert("lookup".into(), lookup_map.into()); @@ -46,13 +49,6 @@ mod merge_tests { .expect("Should get response") .expect("Should have returned a vertex"); - let actual_id: &i64 = vertex_properties - .get("id") - .expect("Should have id") - .get() - .unwrap(); - assert_eq!(expected_id, *actual_id); - let on_create_prop_value: &String = vertex_properties .get("propertyKey") .expect("Should have property") @@ -64,7 +60,7 @@ mod merge_tests { #[test] fn test_merge_v_options() { let g = traversal().with_remote(graph()); - let expected_label = "test_merge_v_options_label"; + let expected_label = "test_merge_v_options"; let mut start_step_map: HashMap = HashMap::new(); start_step_map.insert(T::Label.into(), expected_label.into()); start_step_map.insert("identifing_prop".into(), "some_Value".into()); @@ -128,21 +124,17 @@ mod merge_tests { #[test] fn test_merge_v_start_step() { - let g = traversal().with_remote(graph()); - let expected_id = 10000i64; - let expected_label = "myvertexlabel"; + let client = graph(); + let expected_label = "test_merge_v_start_step"; + drop_vertices(&client, &expected_label).expect("Failed to drop vertiecs"); + let g = traversal().with_remote(client); let mut start_step_map: HashMap = HashMap::new(); - start_step_map.insert(T::Id.into(), expected_id.into()); start_step_map.insert(T::Label.into(), expected_label.into()); let actual_vertex = g .merge_v(start_step_map) .next() .expect("Should get a response") .expect("Should return a vertex"); - match actual_vertex.id() { - gremlin_client::GID::Int64(actual) => assert_eq!(expected_id, *actual), - other => panic!("Didn't get expected id type {:?}", other), - } assert_eq!(expected_label, actual_vertex.label()) } From aff1d273507971a507dd30364bb63aca14041a98 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Sat, 13 Jul 2024 13:09:36 -0500 Subject: [PATCH 20/54] Drop vertices for test_merge_v_options --- gremlin-client/tests/integration_traversal.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gremlin-client/tests/integration_traversal.rs b/gremlin-client/tests/integration_traversal.rs index 4dcee3a6..0adea6aa 100644 --- a/gremlin-client/tests/integration_traversal.rs +++ b/gremlin-client/tests/integration_traversal.rs @@ -59,8 +59,10 @@ mod merge_tests { #[test] fn test_merge_v_options() { - let g = traversal().with_remote(graph()); + let client = graph(); let expected_label = "test_merge_v_options"; + drop_vertices(&client, expected_label).expect("Failed to drop vertices"); + let g = traversal().with_remote(client); let mut start_step_map: HashMap = HashMap::new(); start_step_map.insert(T::Label.into(), expected_label.into()); start_step_map.insert("identifing_prop".into(), "some_Value".into()); From 44cdfd373744328b9fb98cfaf7ca0a37492a85af Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Sat, 13 Jul 2024 13:19:32 -0500 Subject: [PATCH 21/54] Added mergeV step to anonymous traversals --- .../traversal/anonymous_traversal_source.rs | 9 +++++++++ gremlin-client/tests/integration_traversal.rs | 17 +++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/gremlin-client/src/process/traversal/anonymous_traversal_source.rs b/gremlin-client/src/process/traversal/anonymous_traversal_source.rs index 63293f33..afbe9aa6 100644 --- a/gremlin-client/src/process/traversal/anonymous_traversal_source.rs +++ b/gremlin-client/src/process/traversal/anonymous_traversal_source.rs @@ -10,6 +10,8 @@ use crate::process::traversal::TraversalBuilder; use crate::structure::{Either2, GIDs, IntoPredicate, Labels, T}; use crate::GValue; +use super::merge_vertex::MergeVertexStep; + pub struct AnonymousTraversalSource { traversal: TraversalBuilder, } @@ -39,6 +41,13 @@ impl AnonymousTraversalSource { self.traversal.clone().add_v(label) } + pub fn merge_v(&self, merge_v: V) -> TraversalBuilder + where + V: Into, + { + self.traversal.clone().merge_v(merge_v) + } + pub fn property(&self, key: Either2<&str, T>, value: A) -> TraversalBuilder where A: Into, diff --git a/gremlin-client/tests/integration_traversal.rs b/gremlin-client/tests/integration_traversal.rs index 0adea6aa..e1f2b887 100644 --- a/gremlin-client/tests/integration_traversal.rs +++ b/gremlin-client/tests/integration_traversal.rs @@ -140,6 +140,23 @@ mod merge_tests { assert_eq!(expected_label, actual_vertex.label()) } + + #[test] + fn test_merge_v_anonymous_traversal() { + let client = graph(); + let expected_label = "test_merge_v_anonymous_traversal"; + drop_vertices(&client, &expected_label).expect("Failed to drop vertiecs"); + let g = traversal().with_remote(client); + let mut start_step_map: HashMap = HashMap::new(); + start_step_map.insert(T::Label.into(), expected_label.into()); + let actual_vertex = g.inject(1) + .unfold() + .coalesce::([__.merge_v(start_step_map)]) + .next() + .expect("Should get a response") + .expect("Should return a vertex"); + assert_eq!(expected_label, actual_vertex.label()) + } } #[test] From 1c97441155d094fadfb9525bfaed07cf83c28973 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Sat, 13 Jul 2024 15:17:30 -0500 Subject: [PATCH 22/54] Implemented mergeE step --- gremlin-client/src/conversion.rs | 1 + gremlin-client/src/io/mod.rs | 10 + gremlin-client/src/io/serializer_v2.rs | 17 +- gremlin-client/src/io/serializer_v3.rs | 17 +- .../traversal/anonymous_traversal_source.rs | 8 + .../src/process/traversal/builder.rs | 11 + .../src/process/traversal/graph_traversal.rs | 10 + .../traversal/graph_traversal_source.rs | 13 + .../src/process/traversal/step/merge_edge.rs | 33 ++ .../src/process/traversal/step/mod.rs | 1 + gremlin-client/src/structure/direction.rs | 5 + gremlin-client/src/structure/map.rs | 9 +- gremlin-client/src/structure/mod.rs | 2 + gremlin-client/src/structure/value.rs | 19 +- gremlin-client/tests/integration_traversal.rs | 293 +++++++++++++++++- 15 files changed, 438 insertions(+), 11 deletions(-) create mode 100644 gremlin-client/src/process/traversal/step/merge_edge.rs create mode 100644 gremlin-client/src/structure/direction.rs diff --git a/gremlin-client/src/conversion.rs b/gremlin-client/src/conversion.rs index fbe60d25..5103e714 100644 --- a/gremlin-client/src/conversion.rs +++ b/gremlin-client/src/conversion.rs @@ -137,6 +137,7 @@ impl_from_gvalue!(Traverser, GValue::Traverser); impl FromGValue for GKey { fn from_gvalue(v: GValue) -> GremlinResult { match v { + GValue::Direction(d) => Ok(GKey::Direction(d)), GValue::String(s) => Ok(GKey::String(s)), GValue::Token(s) => Ok(GKey::String(s.value().clone())), GValue::Vertex(s) => Ok(GKey::Vertex(s)), diff --git a/gremlin-client/src/io/mod.rs b/gremlin-client/src/io/mod.rs index c925f899..819cbc9d 100644 --- a/gremlin-client/src/io/mod.rs +++ b/gremlin-client/src/io/mod.rs @@ -226,6 +226,16 @@ impl GraphSON { "@value" : merge_option })) } + (_, GValue::Direction(direction)) => { + let direction = match direction { + crate::structure::Direction::Out => "OUT", + crate::structure::Direction::In => "IN", + }; + Ok(json!({ + "@type" : "g:Direction", + "@value" : direction, + })) + } (_, _) => panic!("Type {:?} not supported.", value), } } diff --git a/gremlin-client/src/io/serializer_v2.rs b/gremlin-client/src/io/serializer_v2.rs index 9e494c32..a67b2fae 100644 --- a/gremlin-client/src/io/serializer_v2.rs +++ b/gremlin-client/src/io/serializer_v2.rs @@ -125,6 +125,20 @@ where Ok(GValue::Token(token)) } +pub fn deserialize_direction(_: &T, val: &Value) -> GremlinResult +where + T: Fn(&Value) -> GremlinResult, +{ + let val = get_value!(val, Value::String)?; + match val.as_str() { + "OUT" => Ok(GValue::Direction(crate::structure::Direction::Out)), + "IN" => Ok(GValue::Direction(crate::structure::Direction::In)), + other => Err(GremlinError::Cast(format!( + "Unknown direction literal {other}" + ))), + } +} + // Vertex deserializer [docs](http://tinkerpop.apache.org/docs/current/dev/io/#_vertex_3) pub fn deserialize_vertex(reader: &T, val: &Value) -> GremlinResult where @@ -360,7 +374,8 @@ g_serializer_2!(deserializer_v2, { "g:TraversalMetrics" => deserialize_metrics, "g:Metrics" => deserialize_metric, "g:TraversalExplanation" => deserialize_explain, - "g:Traverser" => deserialize_traverser + "g:Traverser" => deserialize_traverser, + "g:Direction" => deserialize_direction }); fn deserialize_vertex_properties( diff --git a/gremlin-client/src/io/serializer_v3.rs b/gremlin-client/src/io/serializer_v3.rs index 8b5ea3bb..ec96fc7b 100644 --- a/gremlin-client/src/io/serializer_v3.rs +++ b/gremlin-client/src/io/serializer_v3.rs @@ -337,6 +337,20 @@ where Ok(Property::new(label, v).into()) } +pub fn deserialize_direction(_: &T, val: &Value) -> GremlinResult +where + T: Fn(&Value) -> GremlinResult, +{ + let val = get_value!(val, Value::String)?; + match val.as_str() { + "OUT" => Ok(GValue::Direction(crate::structure::Direction::Out)), + "IN" => Ok(GValue::Direction(crate::structure::Direction::In)), + other => Err(GremlinError::Cast(format!( + "Unknown direction literal {other}" + ))), + } +} + // Traverser deserializer [docs](http://tinkerpop.apache.org/docs/3.4.1/dev/io/#_traverser_2) pub fn deserialize_traverser(reader: &T, val: &Value) -> GremlinResult where @@ -368,7 +382,8 @@ g_serializer!(deserializer_v3, { "g:TraversalMetrics" => deserialize_metrics, "g:Metrics" => deserialize_metric, "g:TraversalExplanation" => deserialize_explain, - "g:Traverser" => deserialize_traverser + "g:Traverser" => deserialize_traverser, + "g:Direction" => deserialize_direction }); fn deserialize_vertex_properties( diff --git a/gremlin-client/src/process/traversal/anonymous_traversal_source.rs b/gremlin-client/src/process/traversal/anonymous_traversal_source.rs index afbe9aa6..01623d71 100644 --- a/gremlin-client/src/process/traversal/anonymous_traversal_source.rs +++ b/gremlin-client/src/process/traversal/anonymous_traversal_source.rs @@ -10,6 +10,7 @@ use crate::process::traversal::TraversalBuilder; use crate::structure::{Either2, GIDs, IntoPredicate, Labels, T}; use crate::GValue; +use super::merge_edge::MergeEdgeStep; use super::merge_vertex::MergeVertexStep; pub struct AnonymousTraversalSource { @@ -48,6 +49,13 @@ impl AnonymousTraversalSource { self.traversal.clone().merge_v(merge_v) } + pub fn merge_e(&self, merge_e: V) -> TraversalBuilder + where + V: Into, + { + self.traversal.clone().merge_e(merge_e) + } + pub fn property(&self, key: Either2<&str, T>, value: A) -> TraversalBuilder where A: Into, diff --git a/gremlin-client/src/process/traversal/builder.rs b/gremlin-client/src/process/traversal/builder.rs index 78c411e0..ca2ee292 100644 --- a/gremlin-client/src/process/traversal/builder.rs +++ b/gremlin-client/src/process/traversal/builder.rs @@ -22,6 +22,7 @@ use crate::process::traversal::{Bytecode, Scope}; use crate::structure::{Cardinality, GIDs, IntoPredicate, Labels}; use crate::GValue; +use super::merge_edge::MergeEdgeStep; use super::merge_vertex::MergeVertexStep; use super::option::OptionStep; @@ -666,6 +667,16 @@ impl TraversalBuilder { self } + pub fn merge_e(mut self, merge_e: A) -> Self + where + A: Into, + { + self.bytecode + .add_step(String::from("mergeE"), merge_e.into().into()); + + self + } + pub fn option(mut self, option: A) -> Self where A: Into, diff --git a/gremlin-client/src/process/traversal/graph_traversal.rs b/gremlin-client/src/process/traversal/graph_traversal.rs index 5fa2fb71..e5ce9146 100644 --- a/gremlin-client/src/process/traversal/graph_traversal.rs +++ b/gremlin-client/src/process/traversal/graph_traversal.rs @@ -29,6 +29,7 @@ use crate::{ }; use std::marker::PhantomData; +use super::merge_edge::MergeEdgeStep; use super::merge_vertex::MergeVertexStep; use super::option::OptionStep; @@ -682,6 +683,15 @@ impl> GraphTraversal { GraphTraversal::new(self.terminator, self.builder) } + pub fn merge_e(mut self, merge_e: A) -> GraphTraversal + where + A: Into, + T: Terminator, + { + self.builder = self.builder.merge_e(merge_e); + GraphTraversal::new(self.terminator, self.builder) + } + pub fn identity(mut self) -> Self { self.builder = self.builder.identity(); self diff --git a/gremlin-client/src/process/traversal/graph_traversal_source.rs b/gremlin-client/src/process/traversal/graph_traversal_source.rs index c598672f..ac3a3199 100644 --- a/gremlin-client/src/process/traversal/graph_traversal_source.rs +++ b/gremlin-client/src/process/traversal/graph_traversal_source.rs @@ -15,6 +15,7 @@ use crate::structure::Labels; use crate::structure::{Edge, GValue, Vertex}; use crate::GremlinClient; +use super::merge_edge::MergeEdgeStep; use super::merge_vertex::MergeVertexStep; #[derive(Clone)] @@ -147,6 +148,18 @@ impl> GraphTraversalSource { GraphTraversal::new(self.term.clone(), TraversalBuilder::new(code)) } + + pub fn merge_e(&self, merge_e: V) -> GraphTraversal + where + V: Into, + A: Terminator, + { + let mut code = Bytecode::new(); + + code.add_step(String::from("mergeE"), merge_e.into().into()); + + GraphTraversal::new(self.term.clone(), TraversalBuilder::new(code)) + } } // TESTS diff --git a/gremlin-client/src/process/traversal/step/merge_edge.rs b/gremlin-client/src/process/traversal/step/merge_edge.rs new file mode 100644 index 00000000..7cc73c09 --- /dev/null +++ b/gremlin-client/src/process/traversal/step/merge_edge.rs @@ -0,0 +1,33 @@ +use std::collections::HashMap; + +use crate::process::traversal::TraversalBuilder; +use crate::structure::GValue; +use crate::GKey; + +pub struct MergeEdgeStep { + params: Vec, +} + +impl MergeEdgeStep { + fn new(params: Vec) -> Self { + MergeEdgeStep { params } + } +} + +impl From for Vec { + fn from(step: MergeEdgeStep) -> Self { + step.params + } +} + +impl From for MergeEdgeStep { + fn from(param: TraversalBuilder) -> Self { + MergeEdgeStep::new(vec![param.bytecode.into()]) + } +} + +impl From> for MergeEdgeStep { + fn from(value: HashMap) -> Self { + MergeEdgeStep::new(vec![value.into()]) + } +} diff --git a/gremlin-client/src/process/traversal/step/mod.rs b/gremlin-client/src/process/traversal/step/mod.rs index 473c20f3..eecf9934 100644 --- a/gremlin-client/src/process/traversal/step/mod.rs +++ b/gremlin-client/src/process/traversal/step/mod.rs @@ -8,6 +8,7 @@ pub mod limit; pub mod local; pub mod loops; pub mod match_step; +pub mod merge_edge; pub mod merge_vertex; pub mod not; pub mod option; diff --git a/gremlin-client/src/structure/direction.rs b/gremlin-client/src/structure/direction.rs new file mode 100644 index 00000000..e58f9f3c --- /dev/null +++ b/gremlin-client/src/structure/direction.rs @@ -0,0 +1,5 @@ +#[derive(Debug, PartialEq, Clone, Eq, Hash)] +pub enum Direction { + Out, + In, +} diff --git a/gremlin-client/src/structure/map.rs b/gremlin-client/src/structure/map.rs index 258e425c..10e491e0 100644 --- a/gremlin-client/src/structure/map.rs +++ b/gremlin-client/src/structure/map.rs @@ -6,7 +6,7 @@ use std::collections::hash_map::IntoIter; use std::collections::{BTreeMap, HashMap}; use std::convert::{TryFrom, TryInto}; -use super::T; +use super::{Direction, T}; /// Represent a Map<[GKey](struct.GKey),[GValue](struct.GValue)> which has ability to allow for non-String keys. /// TinkerPop type [here](http://tinkerpop.apache.org/docs/current/dev/io/#_map) @@ -144,6 +144,7 @@ pub enum GKey { Token(Token), Vertex(Vertex), Edge(Edge), + Direction(Direction), } impl From for GKey { @@ -152,6 +153,12 @@ impl From for GKey { } } +impl From for GKey { + fn from(value: Direction) -> Self { + GKey::Direction(value) + } +} + impl From<&str> for GKey { fn from(val: &str) -> Self { GKey::String(String::from(val)) diff --git a/gremlin-client/src/structure/mod.rs b/gremlin-client/src/structure/mod.rs index dbac31f0..960cf769 100644 --- a/gremlin-client/src/structure/mod.rs +++ b/gremlin-client/src/structure/mod.rs @@ -1,4 +1,5 @@ mod cardinality; +mod direction; mod edge; mod either; mod gid; @@ -35,6 +36,7 @@ pub use self::value::GValue; pub use self::vertex::Vertex; pub use self::vertex_property::{GProperty, VertexProperty}; pub use cardinality::Cardinality; +pub use direction::Direction; pub use either::*; pub use label::Labels; pub use map::{GKey, Map}; diff --git a/gremlin-client/src/structure/value.rs b/gremlin-client/src/structure/value.rs index 4a3a6e64..abf78501 100644 --- a/gremlin-client/src/structure/value.rs +++ b/gremlin-client/src/structure/value.rs @@ -6,13 +6,13 @@ use crate::structure::{ Set, Token, TraversalExplanation, TraversalMetrics, Vertex, VertexProperty, }; use crate::structure::{Pop, TextP, P, T}; -use crate::{GremlinError, GremlinResult}; +use crate::{GremlinError, GremlinResult, ToGValue, GID}; use std::collections::{BTreeMap, HashMap, HashSet, VecDeque}; pub type Date = chrono::DateTime; use std::convert::TryInto; use std::hash::Hash; -use super::Merge; +use super::{Direction, Merge}; /// Represent possible values coming from the [Gremlin Server](http://tinkerpop.apache.org/docs/3.4.0/dev/io/) #[allow(clippy::large_enum_variant)] #[derive(Debug, PartialEq, Clone)] @@ -49,6 +49,7 @@ pub enum GValue { Pop(Pop), Cardinality(Cardinality), Merge(Merge), + Direction(Direction), } impl GValue { @@ -102,6 +103,13 @@ impl From for GValue { GValue::Float(val) } } + +impl From<&GID> for GValue { + fn from(value: &GID) -> Self { + value.to_gvalue() + } +} + impl From for GValue { fn from(val: f64) -> Self { GValue::Double(val) @@ -190,6 +198,12 @@ impl From for GValue { } } +impl From for GValue { + fn from(value: Direction) -> Self { + GValue::Direction(value) + } +} + impl From for GValue { fn from(val: Token) -> Self { GValue::Token(val) @@ -239,6 +253,7 @@ impl From for VecDeque { impl From for GValue { fn from(val: GKey) -> Self { match val { + GKey::Direction(d) => GValue::Direction(d), GKey::T(t) => GValue::T(t), GKey::String(s) => GValue::String(s), GKey::Token(s) => GValue::String(s.value().clone()), diff --git a/gremlin-client/tests/integration_traversal.rs b/gremlin-client/tests/integration_traversal.rs index e1f2b887..20fc9d43 100644 --- a/gremlin-client/tests/integration_traversal.rs +++ b/gremlin-client/tests/integration_traversal.rs @@ -14,7 +14,11 @@ use common::io::{ #[cfg(feature = "merge_tests")] mod merge_tests { use super::*; - use gremlin_client::{structure::Merge, GKey, GValue}; + use gremlin_client::{ + process::traversal::{GraphTraversalSource, SyncTerminator}, + structure::{Direction, Merge}, + Edge, GKey, GValue, ToGValue, + }; use std::collections::HashMap; #[test] @@ -25,16 +29,16 @@ mod merge_tests { .expect("Failed to drop vertices in case of rerun"); let g = traversal().with_remote(client); - let mut map1: HashMap = HashMap::new(); + let mut injection_map: HashMap = HashMap::new(); let mut lookup_map: HashMap = HashMap::new(); lookup_map.insert(T::Label.into(), test_vertex_label.into()); let mut property_map: HashMap = HashMap::new(); property_map.insert("propertyKey".into(), "propertyValue".into()); - map1.insert("lookup".into(), lookup_map.into()); - map1.insert("properties".into(), property_map.into()); + injection_map.insert("lookup".into(), lookup_map.into()); + injection_map.insert("properties".into(), property_map.into()); let vertex_properties = g - .inject(vec![map1.into()]) + .inject(vec![injection_map.into()]) .unfold() .as_("payload") .merge_v(__.select("lookup")) @@ -149,7 +153,8 @@ mod merge_tests { let g = traversal().with_remote(client); let mut start_step_map: HashMap = HashMap::new(); start_step_map.insert(T::Label.into(), expected_label.into()); - let actual_vertex = g.inject(1) + let actual_vertex = g + .inject(1) .unfold() .coalesce::([__.merge_v(start_step_map)]) .next() @@ -157,6 +162,282 @@ mod merge_tests { .expect("Should return a vertex"); assert_eq!(expected_label, actual_vertex.label()) } + + #[test] + fn test_merge_e_start_step() { + let client = graph(); + let expected_vertex_label = "test_merge_e_start_step_vertex"; + let expected_edge_label = "test_merge_e_start_step_edge"; + let expected_edge_property_key = "test_merge_e_start_step_edge_prop"; + let expected_edge_property_value = "test_merge_e_start_step_edge_value"; + drop_vertices(&client, &expected_vertex_label).expect("Failed to drop vertiecs"); + let g = traversal().with_remote(client); + + let vertex_a = g + .add_v(expected_vertex_label) + .next() + .expect("Should get a response") + .expect("Should return a vertex"); + + let vertex_b = g + .add_v(expected_vertex_label) + .next() + .expect("Should get a response") + .expect("Should return a vertex"); + + let mut start_step_map: HashMap = HashMap::new(); + start_step_map.insert(Direction::In.into(), vertex_a.id().into()); + start_step_map.insert(Direction::Out.into(), vertex_b.id().into()); + start_step_map.insert(T::Label.into(), expected_edge_label.into()); + start_step_map.insert( + expected_edge_property_key.into(), + expected_edge_property_value.into(), + ); + let merged_edge_properties = g + .merge_e(start_step_map) + .element_map(()) + .next() + .expect("Should get a response") + .expect("Should return a edge properties"); + + let actual_edge_label: &String = merged_edge_properties + .get("label") + .expect("Should have returned edge label") + .get() + .unwrap(); + assert_eq!(actual_edge_label, expected_edge_label); + + let actual_edge_property: &String = merged_edge_properties + .get(expected_edge_property_key) + .expect("Should have returned edge property") + .get() + .unwrap(); + assert_eq!(actual_edge_property, expected_edge_property_value); + + let incoming_vertex: &Map = merged_edge_properties + .get(Direction::In) + .expect("Should have returned incoming vertex info") + .get() + .unwrap(); + let incoming_vertex_id = incoming_vertex + .get("id") + .expect("Should have returned vertex id"); + assert_eq!(incoming_vertex_id, &vertex_a.id().to_gvalue()); + + let outgoing_vertex: &Map = merged_edge_properties + .get(Direction::Out) + .expect("Should have returned outgoing vertex info") + .get() + .unwrap(); + let outgoing_vertex_id = outgoing_vertex + .get("id") + .expect("Should have returned vertex id"); + assert_eq!(outgoing_vertex_id, &vertex_b.id().to_gvalue()); + } + + #[test] + fn test_merge_e_no_options() { + let client = graph(); + let expected_vertex_label = "test_merge_e_no_options_vertex"; + let expected_edge_label = "test_merge_e_no_options_edge"; + let expected_edge_property_key = "test_merge_e_no_options_edge_prop"; + let expected_edge_property_value = "test_merge_e_no_options_edge_value"; + drop_vertices(&client, &expected_vertex_label).expect("Failed to drop vertiecs"); + let g = traversal().with_remote(client); + + let vertex_a = g + .add_v(expected_vertex_label) + .next() + .expect("Should get a response") + .expect("Should return a vertex"); + + let vertex_b = g + .add_v(expected_vertex_label) + .next() + .expect("Should get a response") + .expect("Should return a vertex"); + + let mut assignment_map: HashMap = HashMap::new(); + assignment_map.insert(Direction::In.into(), vertex_a.id().into()); + assignment_map.insert(Direction::Out.into(), vertex_b.id().into()); + assignment_map.insert(T::Label.into(), expected_edge_label.into()); + assignment_map.insert( + expected_edge_property_key.into(), + expected_edge_property_value.into(), + ); + + let merged_edge_properties = g + .inject(vec![assignment_map.into()]) + .unfold() + .as_("payload") + .merge_e(__.select("payload")) + .element_map(()) + .next() + .expect("Should get a response") + .expect("Should return edge properties"); + + let actual_edge_label: &String = merged_edge_properties + .get("label") + .expect("Should have returned edge label") + .get() + .unwrap(); + assert_eq!(actual_edge_label, expected_edge_label); + + let actual_edge_property: &String = merged_edge_properties + .get(expected_edge_property_key) + .expect("Should have returned edge property") + .get() + .unwrap(); + assert_eq!(actual_edge_property, expected_edge_property_value); + + let incoming_vertex: &Map = merged_edge_properties + .get(Direction::In) + .expect("Should have returned incoming vertex info") + .get() + .unwrap(); + let incoming_vertex_id = incoming_vertex + .get("id") + .expect("Should have returned vertex id"); + assert_eq!(incoming_vertex_id, &vertex_a.id().to_gvalue()); + + let outgoing_vertex: &Map = merged_edge_properties + .get(Direction::Out) + .expect("Should have returned outgoing vertex info") + .get() + .unwrap(); + let outgoing_vertex_id = outgoing_vertex + .get("id") + .expect("Should have returned vertex id"); + assert_eq!(outgoing_vertex_id, &vertex_b.id().to_gvalue()); + } + + #[test] + fn test_merge_e_options() { + let client = graph(); + let expected_vertex_label = "test_merge_e_options_vertex"; + let expected_edge_label = "test_merge_e_options_edge"; + let expected_edge_property_key = "test_merge_e_options_edge_prop"; + drop_vertices(&client, &expected_vertex_label).expect("Failed to drop vertiecs"); + let g = traversal().with_remote(client); + + let vertex_a = g + .add_v(expected_vertex_label) + .next() + .expect("Should get a response") + .expect("Should return a vertex"); + + let vertex_b = g + .add_v(expected_vertex_label) + .next() + .expect("Should get a response") + .expect("Should return a vertex"); + + let mut assignment_map: HashMap = HashMap::new(); + assignment_map.insert(Direction::In.into(), vertex_a.id().into()); + assignment_map.insert(Direction::Out.into(), vertex_b.id().into()); + assignment_map.insert(T::Label.into(), expected_edge_label.into()); + + let mut on_create_map: HashMap = HashMap::new(); + on_create_map.insert(expected_edge_property_key.into(), "on_create".into()); + + let mut on_match_map: HashMap = HashMap::new(); + on_match_map.insert(expected_edge_property_key.into(), "on_match".into()); + + let mut injection_map: HashMap = HashMap::new(); + injection_map.insert("merge_params".into(), assignment_map.into()); + injection_map.insert("create_params".into(), on_create_map.into()); + injection_map.insert("match_params".into(), on_match_map.into()); + + let do_merge_edge = |g: GraphTraversalSource| -> Map { + g.inject(vec![injection_map.clone().into()]) + .unfold() + .as_("payload") + .merge_e(__.select("payload").select("merge_params")) + .option(( + Merge::OnCreate, + __.select("payload").select("create_params"), + )) + .option((Merge::OnMatch, __.select("payload").select("match_params"))) + .element_map(()) + .next() + .expect("Should get a response") + .expect("Should return a edge properties") + }; + + let on_create_edge_properties = do_merge_edge(g.clone()); + + //Initially the edge should be the on create value + let actual_edge_property: &String = on_create_edge_properties + .get(expected_edge_property_key) + .expect("Should have returned edge property") + .get() + .unwrap(); + assert_eq!(actual_edge_property, "on_create"); + + let on_match_edge_properties = do_merge_edge(g); + + let actual_edge_property: &String = on_match_edge_properties + .get(expected_edge_property_key) + .expect("Should have returned edge property") + .get() + .unwrap(); + assert_eq!(actual_edge_property, "on_match"); + } + + #[test] + fn test_merge_e_anonymous_traversal() { + let client = graph(); + let expected_vertex_label = "test_merge_e_options_vertex"; + let expected_edge_label = "test_merge_e_options_edge"; + drop_vertices(&client, &expected_vertex_label).expect("Failed to drop vertiecs"); + let g = traversal().with_remote(client); + + let vertex_a = g + .add_v(expected_vertex_label) + .next() + .expect("Should get a response") + .expect("Should return a vertex"); + + let vertex_b = g + .add_v(expected_vertex_label) + .next() + .expect("Should get a response") + .expect("Should return a vertex"); + + let mut assignment_map: HashMap = HashMap::new(); + assignment_map.insert(Direction::In.into(), vertex_a.id().into()); + assignment_map.insert(Direction::Out.into(), vertex_b.id().into()); + assignment_map.insert(T::Label.into(), expected_edge_label.into()); + + let anonymous_merge_e_properties = g + .inject(1) + .unfold() + .coalesce::([__.merge_e(assignment_map)]) + .element_map(()) + .next() + .expect("Should get a response") + .expect("Should return a edge properties"); + + let incoming_vertex: &Map = anonymous_merge_e_properties + .get(Direction::In) + .expect("Should have returned incoming vertex info") + .get() + .unwrap(); + let incoming_vertex_id = incoming_vertex + .get("id") + .expect("Should have returned vertex id"); + assert_eq!(incoming_vertex_id, &vertex_a.id().to_gvalue()); + + let outgoing_vertex: &Map = anonymous_merge_e_properties + .get(Direction::Out) + .expect("Should have returned outgoing vertex info") + .get() + .unwrap(); + let outgoing_vertex_id = outgoing_vertex + .get("id") + .expect("Should have returned vertex id"); + assert_eq!(outgoing_vertex_id, &vertex_b.id().to_gvalue()); + } } #[test] From 8b28edccce5186ede8d10a195320faec3937830c Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Sat, 13 Jul 2024 15:18:01 -0500 Subject: [PATCH 23/54] Added mergeV and mergeE to Bytecode WRITE_OPERATORS --- gremlin-client/src/process/traversal/bytecode.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gremlin-client/src/process/traversal/bytecode.rs b/gremlin-client/src/process/traversal/bytecode.rs index c9c23cec..31c99ed6 100644 --- a/gremlin-client/src/process/traversal/bytecode.rs +++ b/gremlin-client/src/process/traversal/bytecode.rs @@ -39,7 +39,7 @@ impl Bytecode { lazy_static! { pub static ref WRITE_OPERATORS: Vec<&'static str> = - vec!["addV", "property", "addE", "from", "to", "drop"]; + vec!["addV", "property", "addE", "from", "to", "drop", "mergeV", "mergeE"]; } #[derive(Debug, PartialEq, Clone)] From 72615b9a21de3348c6213d9f7c28d599c54f0c43 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Sat, 13 Jul 2024 16:06:04 -0500 Subject: [PATCH 24/54] Implemented travsal test based on reference doc combo mergeV and mergeE --- gremlin-client/src/io/mod.rs | 12 ++-- gremlin-client/src/structure/direction.rs | 2 + gremlin-client/src/structure/merge.rs | 2 + gremlin-client/tests/integration_traversal.rs | 69 +++++++++++++++++++ 4 files changed, 80 insertions(+), 5 deletions(-) diff --git a/gremlin-client/src/io/mod.rs b/gremlin-client/src/io/mod.rs index 819cbc9d..aca6df8c 100644 --- a/gremlin-client/src/io/mod.rs +++ b/gremlin-client/src/io/mod.rs @@ -5,7 +5,7 @@ mod serializer_v3; use crate::conversion::ToGValue; use crate::process::traversal::{Order, Scope}; -use crate::structure::{Cardinality, GValue, T}; +use crate::structure::{Cardinality, Direction, GValue, Merge, T}; use serde_json::{json, Map, Value}; use std::string::ToString; @@ -218,8 +218,10 @@ impl GraphSON { } (_, GValue::Merge(merge)) => { let merge_option = match merge { - crate::structure::Merge::OnCreate => "onCreate", - crate::structure::Merge::OnMatch => "onMatch", + Merge::OnCreate => "onCreate", + Merge::OnMatch => "onMatch", + Merge::OutV => "outV", + Merge::InV => "inV", }; Ok(json!({ "@type" : "g:Merge", @@ -228,8 +230,8 @@ impl GraphSON { } (_, GValue::Direction(direction)) => { let direction = match direction { - crate::structure::Direction::Out => "OUT", - crate::structure::Direction::In => "IN", + Direction::Out | Direction::From => "OUT", + Direction::In | Direction::To => "IN", }; Ok(json!({ "@type" : "g:Direction", diff --git a/gremlin-client/src/structure/direction.rs b/gremlin-client/src/structure/direction.rs index e58f9f3c..bce2beef 100644 --- a/gremlin-client/src/structure/direction.rs +++ b/gremlin-client/src/structure/direction.rs @@ -2,4 +2,6 @@ pub enum Direction { Out, In, + From, + To, } diff --git a/gremlin-client/src/structure/merge.rs b/gremlin-client/src/structure/merge.rs index 19f6b99c..df406e91 100644 --- a/gremlin-client/src/structure/merge.rs +++ b/gremlin-client/src/structure/merge.rs @@ -2,4 +2,6 @@ pub enum Merge { OnCreate, OnMatch, + OutV, + InV, } diff --git a/gremlin-client/tests/integration_traversal.rs b/gremlin-client/tests/integration_traversal.rs index 20fc9d43..5d32b858 100644 --- a/gremlin-client/tests/integration_traversal.rs +++ b/gremlin-client/tests/integration_traversal.rs @@ -438,6 +438,75 @@ mod merge_tests { .expect("Should have returned vertex id"); assert_eq!(outgoing_vertex_id, &vertex_b.id().to_gvalue()); } + + #[test] + fn test_merge_v_into_merge_e() { + //Based on the reference doc's combo example + let client = graph(); + let expected_vertex_label = "test_merge_v_into_merge_e_vertex"; + let expected_edge_label = "test_merge_v_into_merge_e_edge"; + drop_vertices(&client, &expected_vertex_label).expect("Failed to drop vertiecs"); + let g = traversal().with_remote(client); + + let expected_toby_id = 100_001i64; + let expected_brandy_id = 200_001i64; + + let mut vertex_a_map: HashMap = HashMap::new(); + vertex_a_map.insert(T::Label.into(), expected_vertex_label.into()); + vertex_a_map.insert(T::Id.into(), expected_toby_id.into()); + vertex_a_map.insert("name".into(), "Toby".into()); + + let mut vertex_b_map: HashMap = HashMap::new(); + vertex_b_map.insert(T::Label.into(), expected_vertex_label.into()); + vertex_b_map.insert(T::Id.into(), expected_brandy_id.into()); + vertex_b_map.insert("name".into(), "Brandy".into()); + + let mut edge_map: HashMap = HashMap::new(); + edge_map.insert(T::Label.into(), expected_edge_label.into()); + edge_map.insert("some_key".into(), "some_value".into()); + edge_map.insert(Direction::From.into(), Merge::OutV.into()); + edge_map.insert(Direction::To.into(), Merge::InV.into()); + + let combo_merge_edge_properties = g + .merge_v(vertex_a_map) + .as_("Toby") + .merge_v(vertex_b_map) + .as_("Brandy") + .merge_e(edge_map) + .option((Merge::OutV, __.select("Toby"))) + .option((Merge::InV, __.select("Brandy"))) + .element_map(()) + .next() + .expect("Should get a response") + .expect("Should return a edge properties"); + + let brandy_vertex: &Map = combo_merge_edge_properties + .get(Direction::In) + .expect("Should have returned incoming vertex info") + .get() + .unwrap(); + let brandy_vertex_id = brandy_vertex + .get("id") + .expect("Should have returned vertex id"); + assert_eq!(*brandy_vertex_id, GValue::Int64(expected_brandy_id)); + + let toby_vertex: &Map = combo_merge_edge_properties + .get(Direction::Out) + .expect("Should have returned outgoing vertex info") + .get() + .unwrap(); + let toby_vertex_id = toby_vertex + .get("id") + .expect("Should have returned vertex id"); + assert_eq!(*toby_vertex_id, GValue::Int64(expected_toby_id)); + + let actual_edge_label: &String = combo_merge_edge_properties + .get("label") + .expect("Should have returned edge label") + .get() + .unwrap(); + assert_eq!(actual_edge_label, expected_edge_label); + } } #[test] From edffffbf41ee6b72d1823e8bc66af90a443e3bd3 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Sat, 13 Jul 2024 16:25:31 -0500 Subject: [PATCH 25/54] Support literal options for choose step and added test --- .../traversal/anonymous_traversal_source.rs | 4 +++ .../src/process/traversal/step/option.rs | 6 ++++ gremlin-client/tests/integration_traversal.rs | 30 ++++++++++++++++++- 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/gremlin-client/src/process/traversal/anonymous_traversal_source.rs b/gremlin-client/src/process/traversal/anonymous_traversal_source.rs index 01623d71..aa826beb 100644 --- a/gremlin-client/src/process/traversal/anonymous_traversal_source.rs +++ b/gremlin-client/src/process/traversal/anonymous_traversal_source.rs @@ -42,6 +42,10 @@ impl AnonymousTraversalSource { self.traversal.clone().add_v(label) } + pub fn identity(&self) -> TraversalBuilder { + self.traversal.clone().identity() + } + pub fn merge_v(&self, merge_v: V) -> TraversalBuilder where V: Into, diff --git a/gremlin-client/src/process/traversal/step/option.rs b/gremlin-client/src/process/traversal/step/option.rs index 5b43ca50..f50c413a 100644 --- a/gremlin-client/src/process/traversal/step/option.rs +++ b/gremlin-client/src/process/traversal/step/option.rs @@ -20,6 +20,12 @@ impl From for Vec { } } +impl From<(GValue, TraversalBuilder)> for OptionStep { + fn from(value: (GValue, TraversalBuilder)) -> Self { + OptionStep::new(vec![value.0.into(), value.1.into()]) + } +} + impl From<(Merge, TraversalBuilder)> for OptionStep { fn from(value: (Merge, TraversalBuilder)) -> Self { OptionStep::new(vec![value.0.into(), value.1.into()]) diff --git a/gremlin-client/tests/integration_traversal.rs b/gremlin-client/tests/integration_traversal.rs index 5d32b858..0ea0b173 100644 --- a/gremlin-client/tests/integration_traversal.rs +++ b/gremlin-client/tests/integration_traversal.rs @@ -3,7 +3,7 @@ use std::convert::TryInto; use gremlin_client::process::traversal::{traversal, Order, __}; use gremlin_client::structure::{Cardinality, List, Map, Pop, TextP, Vertex, VertexProperty, P, T}; -use gremlin_client::utils; +use gremlin_client::{utils, GValue}; mod common; @@ -2379,6 +2379,34 @@ fn test_choose() { assert_eq!(success_vertices.is_some(), true); } +#[test] +fn test_choose_by_literal_options() { + let client = graph(); + let g = traversal().with_remote(client); + + let choosen_literal_a = g + .inject(1) + .unfold() + .choose(__.identity()) + .option((GValue::Int64(1), __.constant("option-a"))) + .option((GValue::Int64(2), __.constant("option-b"))) + .next() + .unwrap(); + + assert_eq!(choosen_literal_a, Some("option-a".into())); + + let choosen_literal_b = g + .inject(2) + .unfold() + .choose(__.identity()) + .option((GValue::Int64(1), __.constant("option-a"))) + .option((GValue::Int64(2), __.constant("option-b"))) + .next() + .unwrap(); + + assert_eq!(choosen_literal_b, Some("option-b".into())); +} + #[test] fn test_coalesce() { let client = graph(); From 7a255dfb4d8fbb8769119cfade22dd594ba30a69 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Tue, 16 Jul 2024 11:23:23 -0500 Subject: [PATCH 26/54] Rewrote side effect and expose via GraphTraversal --- .../src/process/traversal/builder.rs | 6 ++--- .../src/process/traversal/graph_traversal.rs | 10 +++++++ .../src/process/traversal/step/side_effect.rs | 22 +++++++++++---- gremlin-client/tests/integration_traversal.rs | 27 +++++++++++++++++++ 4 files changed, 57 insertions(+), 8 deletions(-) diff --git a/gremlin-client/src/process/traversal/builder.rs b/gremlin-client/src/process/traversal/builder.rs index ca2ee292..d4eb971a 100644 --- a/gremlin-client/src/process/traversal/builder.rs +++ b/gremlin-client/src/process/traversal/builder.rs @@ -13,7 +13,6 @@ use crate::process::traversal::step::not::NotStep; use crate::process::traversal::step::or::OrStep; use crate::process::traversal::step::repeat::RepeatStep; use crate::process::traversal::step::select::SelectStep; -use crate::process::traversal::step::side_effect::IntoSideEffectStep; use crate::process::traversal::step::to::ToStep; use crate::process::traversal::step::until::UntilStep; use crate::process::traversal::step::where_step::WhereStep; @@ -25,6 +24,7 @@ use crate::GValue; use super::merge_edge::MergeEdgeStep; use super::merge_vertex::MergeVertexStep; use super::option::OptionStep; +use super::side_effect::SideEffectStep; #[derive(Clone)] pub struct TraversalBuilder { @@ -143,10 +143,10 @@ impl TraversalBuilder { pub fn side_effect(mut self, step: A) -> Self where - A: IntoSideEffectStep, + A: Into, { self.bytecode - .add_step(String::from("sideEffect"), step.into_step()); + .add_step(String::from("sideEffect"), step.into().into()); self } diff --git a/gremlin-client/src/process/traversal/graph_traversal.rs b/gremlin-client/src/process/traversal/graph_traversal.rs index e5ce9146..05606c73 100644 --- a/gremlin-client/src/process/traversal/graph_traversal.rs +++ b/gremlin-client/src/process/traversal/graph_traversal.rs @@ -32,6 +32,7 @@ use std::marker::PhantomData; use super::merge_edge::MergeEdgeStep; use super::merge_vertex::MergeVertexStep; use super::option::OptionStep; +use super::side_effect::SideEffectStep; #[derive(Clone)] pub struct GraphTraversal> { @@ -189,6 +190,15 @@ impl> GraphTraversal { self } + pub fn side_effect(mut self, step: A) -> Self + where + A: Into, + { + self.builder = self.builder.side_effect(step); + + self + } + pub fn add_e(mut self, label: A) -> GraphTraversal where A: Into, diff --git a/gremlin-client/src/process/traversal/step/side_effect.rs b/gremlin-client/src/process/traversal/step/side_effect.rs index 8fb00636..6fa6b487 100644 --- a/gremlin-client/src/process/traversal/step/side_effect.rs +++ b/gremlin-client/src/process/traversal/step/side_effect.rs @@ -1,11 +1,23 @@ use crate::{process::traversal::TraversalBuilder, GValue}; -pub trait IntoSideEffectStep { - fn into_step(self) -> Vec; +pub struct SideEffectStep { + params: Vec, } -impl IntoSideEffectStep for TraversalBuilder { - fn into_step(self) -> Vec { - vec![self.bytecode.into()] +impl SideEffectStep { + fn new(params: Vec) -> Self { + SideEffectStep { params } + } +} + +impl From for Vec { + fn from(step: SideEffectStep) -> Self { + step.params + } +} + +impl From for SideEffectStep { + fn from(param: TraversalBuilder) -> Self { + SideEffectStep::new(vec![param.bytecode.into()]) } } diff --git a/gremlin-client/tests/integration_traversal.rs b/gremlin-client/tests/integration_traversal.rs index 0ea0b173..aeb308e2 100644 --- a/gremlin-client/tests/integration_traversal.rs +++ b/gremlin-client/tests/integration_traversal.rs @@ -2306,6 +2306,33 @@ fn test_local() { assert_eq!(results.len(), 2); } +#[test] +fn test_side_effect() { + let client = graph(); + let test_vertex_label = "test_side_effect"; + let expected_side_effect_key = "prop_key"; + let expected_side_effect_value = "prop_val"; + + drop_vertices(&client, &test_vertex_label).unwrap(); + + let g = traversal().with_remote(client); + + let element_map = g + .add_v(test_vertex_label) + .side_effect(__.property( + gremlin_client::structure::Either2::A(expected_side_effect_key), + expected_side_effect_value, + )) + .element_map(()) + .next() + .expect("Should get response") + .expect("Should have returned an element map"); + + let actual_prop_value: &String = element_map.get(expected_side_effect_key).expect("Should have property").get().expect("Should be str"); + + assert_eq!(expected_side_effect_value, actual_prop_value); +} + #[test] fn test_property_cardinality() { let client = graph(); From 09d562c0af2a99ed35a4326a0149e6fd12e7eb75 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Tue, 16 Jul 2024 13:02:18 -0500 Subject: [PATCH 27/54] Implemented support for Columns in By Step --- gremlin-client/src/io/mod.rs | 10 + .../src/process/traversal/step/by.rs | 8 +- gremlin-client/src/structure/column.rs | 5 + gremlin-client/src/structure/mod.rs | 2 + gremlin-client/src/structure/value.rs | 9 +- gremlin-client/tests/common.rs | 11 ++ gremlin-client/tests/integration_traversal.rs | 179 ++++++++++-------- 7 files changed, 143 insertions(+), 81 deletions(-) create mode 100644 gremlin-client/src/structure/column.rs diff --git a/gremlin-client/src/io/mod.rs b/gremlin-client/src/io/mod.rs index aca6df8c..214d59ef 100644 --- a/gremlin-client/src/io/mod.rs +++ b/gremlin-client/src/io/mod.rs @@ -238,6 +238,16 @@ impl GraphSON { "@value" : direction, })) } + (_, GValue::Column(column)) => { + let column = match column { + crate::structure::Column::Keys => "keys", + crate::structure::Column::Values => "values", + }; + Ok(json!({ + "@type" : "g:Column", + "@value" : column, + })) + } (_, _) => panic!("Type {:?} not supported.", value), } } diff --git a/gremlin-client/src/process/traversal/step/by.rs b/gremlin-client/src/process/traversal/step/by.rs index bc12d1e9..3c377581 100644 --- a/gremlin-client/src/process/traversal/step/by.rs +++ b/gremlin-client/src/process/traversal/step/by.rs @@ -1,5 +1,5 @@ use crate::process::traversal::{Order, TraversalBuilder}; -use crate::structure::{GValue, T}; +use crate::structure::{Column, GValue, T}; pub struct ByStep { params: Vec, @@ -35,6 +35,12 @@ impl From for ByStep { } } +impl From for ByStep { + fn from(value: Column) -> Self { + ByStep::new(vec![value.into()]) + } +} + impl From for ByStep { fn from(param: T) -> Self { ByStep::new(vec![param.into()]) diff --git a/gremlin-client/src/structure/column.rs b/gremlin-client/src/structure/column.rs new file mode 100644 index 00000000..99ba0755 --- /dev/null +++ b/gremlin-client/src/structure/column.rs @@ -0,0 +1,5 @@ +#[derive(Debug, PartialEq, Clone, Eq, Hash)] +pub enum Column { + Keys, + Values, +} diff --git a/gremlin-client/src/structure/mod.rs b/gremlin-client/src/structure/mod.rs index 960cf769..a2695315 100644 --- a/gremlin-client/src/structure/mod.rs +++ b/gremlin-client/src/structure/mod.rs @@ -1,4 +1,5 @@ mod cardinality; +mod column; mod direction; mod edge; mod either; @@ -36,6 +37,7 @@ pub use self::value::GValue; pub use self::vertex::Vertex; pub use self::vertex_property::{GProperty, VertexProperty}; pub use cardinality::Cardinality; +pub use column::Column; pub use direction::Direction; pub use either::*; pub use label::Labels; diff --git a/gremlin-client/src/structure/value.rs b/gremlin-client/src/structure/value.rs index abf78501..5ad9b4d5 100644 --- a/gremlin-client/src/structure/value.rs +++ b/gremlin-client/src/structure/value.rs @@ -12,7 +12,7 @@ pub type Date = chrono::DateTime; use std::convert::TryInto; use std::hash::Hash; -use super::{Direction, Merge}; +use super::{Column, Direction, Merge}; /// Represent possible values coming from the [Gremlin Server](http://tinkerpop.apache.org/docs/3.4.0/dev/io/) #[allow(clippy::large_enum_variant)] #[derive(Debug, PartialEq, Clone)] @@ -50,6 +50,7 @@ pub enum GValue { Cardinality(Cardinality), Merge(Merge), Direction(Direction), + Column(Column), } impl GValue { @@ -204,6 +205,12 @@ impl From for GValue { } } +impl From for GValue { + fn from(value: Column) -> Self { + GValue::Column(value) + } +} + impl From for GValue { fn from(val: Token) -> Self { GValue::Token(val) diff --git a/gremlin-client/tests/common.rs b/gremlin-client/tests/common.rs index c6c86597..af3a5b44 100644 --- a/gremlin-client/tests/common.rs +++ b/gremlin-client/tests/common.rs @@ -1,3 +1,14 @@ +use gremlin_client::Map; + +pub fn assert_map_property(element_map: &Map, expected_key: &str, expected_value: &str) { + let actual_prop_value: &String = element_map + .get(expected_key) + .unwrap_or_else(|| panic!("Didn't have expected key {}", expected_key)) + .get() + .expect("Should be String"); + assert_eq!(expected_value, actual_prop_value); +} + #[allow(dead_code)] pub mod io { use gremlin_client::{ConnectionOptions, Edge, GraphSON, GremlinClient, GremlinResult, Vertex}; diff --git a/gremlin-client/tests/integration_traversal.rs b/gremlin-client/tests/integration_traversal.rs index aeb308e2..513790f7 100644 --- a/gremlin-client/tests/integration_traversal.rs +++ b/gremlin-client/tests/integration_traversal.rs @@ -1,9 +1,13 @@ +use std::collections::HashMap; use std::convert::TryInto; +use common::assert_map_property; use gremlin_client::process::traversal::{traversal, Order, __}; -use gremlin_client::structure::{Cardinality, List, Map, Pop, TextP, Vertex, VertexProperty, P, T}; +use gremlin_client::structure::{ + Cardinality, Column, List, Map, Pop, TextP, Vertex, VertexProperty, P, T, +}; -use gremlin_client::{utils, GValue}; +use gremlin_client::{utils, GKey, GValue}; mod common; @@ -17,7 +21,7 @@ mod merge_tests { use gremlin_client::{ process::traversal::{GraphTraversalSource, SyncTerminator}, structure::{Direction, Merge}, - Edge, GKey, GValue, ToGValue, + Edge, GValue, ToGValue, }; use std::collections::HashMap; @@ -53,12 +57,7 @@ mod merge_tests { .expect("Should get response") .expect("Should have returned a vertex"); - let on_create_prop_value: &String = vertex_properties - .get("propertyKey") - .expect("Should have property") - .get() - .unwrap(); - assert_eq!(on_create_prop_value, "propertyValue"); + assert_map_property(&vertex_properties, "propertyKey", "propertyValue"); } #[test] @@ -89,19 +88,13 @@ mod merge_tests { .expect("Should get a response") .expect("Should return a vertex"); - let actual_label: &String = on_create_vertex_map - .get("label") - .expect("Should have id") - .get() - .unwrap(); - assert_eq!(expected_label, actual_label); + assert_map_property(&on_create_vertex_map, "label", expected_label); - let on_create_prop_value: &String = on_create_vertex_map - .get(prop_key) - .expect("Should have property") - .get() - .unwrap(); - assert_eq!(on_create_prop_value, expected_on_create_prop_value); + assert_map_property( + &on_create_vertex_map, + prop_key, + expected_on_create_prop_value, + ); //Now run the traversal again, and confirm the OnMatch applied this time let on_match_vertex_map = g @@ -113,19 +106,9 @@ mod merge_tests { .expect("Should get a response") .expect("Should return a vertex"); - let actual_label: &String = on_match_vertex_map - .get("label") - .expect("Should have id") - .get() - .unwrap(); - assert_eq!(expected_label, actual_label); + assert_map_property(&on_match_vertex_map, "label", expected_label); - let on_match_prop_value: &String = on_match_vertex_map - .get(prop_key) - .expect("Should have property") - .get() - .unwrap(); - assert_eq!(on_match_prop_value, expected_on_match_prop_value); + assert_map_property(&on_match_vertex_map, prop_key, expected_on_match_prop_value); } #[test] @@ -200,25 +183,20 @@ mod merge_tests { .expect("Should get a response") .expect("Should return a edge properties"); - let actual_edge_label: &String = merged_edge_properties - .get("label") - .expect("Should have returned edge label") - .get() - .unwrap(); - assert_eq!(actual_edge_label, expected_edge_label); + assert_map_property(&merged_edge_properties, "label", expected_edge_label); - let actual_edge_property: &String = merged_edge_properties - .get(expected_edge_property_key) - .expect("Should have returned edge property") - .get() - .unwrap(); - assert_eq!(actual_edge_property, expected_edge_property_value); + assert_map_property( + &merged_edge_properties, + expected_edge_property_key, + expected_edge_property_value, + ); let incoming_vertex: &Map = merged_edge_properties .get(Direction::In) .expect("Should have returned incoming vertex info") .get() .unwrap(); + let incoming_vertex_id = incoming_vertex .get("id") .expect("Should have returned vertex id"); @@ -276,19 +254,12 @@ mod merge_tests { .expect("Should get a response") .expect("Should return edge properties"); - let actual_edge_label: &String = merged_edge_properties - .get("label") - .expect("Should have returned edge label") - .get() - .unwrap(); - assert_eq!(actual_edge_label, expected_edge_label); - - let actual_edge_property: &String = merged_edge_properties - .get(expected_edge_property_key) - .expect("Should have returned edge property") - .get() - .unwrap(); - assert_eq!(actual_edge_property, expected_edge_property_value); + assert_map_property(&merged_edge_properties, "label", expected_edge_label); + assert_map_property( + &merged_edge_properties, + expected_edge_property_key, + expected_edge_property_value, + ); let incoming_vertex: &Map = merged_edge_properties .get(Direction::In) @@ -367,21 +338,18 @@ mod merge_tests { let on_create_edge_properties = do_merge_edge(g.clone()); //Initially the edge should be the on create value - let actual_edge_property: &String = on_create_edge_properties - .get(expected_edge_property_key) - .expect("Should have returned edge property") - .get() - .unwrap(); - assert_eq!(actual_edge_property, "on_create"); + assert_map_property( + &on_create_edge_properties, + expected_edge_property_key, + "on_create", + ); let on_match_edge_properties = do_merge_edge(g); - - let actual_edge_property: &String = on_match_edge_properties - .get(expected_edge_property_key) - .expect("Should have returned edge property") - .get() - .unwrap(); - assert_eq!(actual_edge_property, "on_match"); + assert_map_property( + &on_match_edge_properties, + expected_edge_property_key, + "on_match", + ); } #[test] @@ -500,12 +468,7 @@ mod merge_tests { .expect("Should have returned vertex id"); assert_eq!(*toby_vertex_id, GValue::Int64(expected_toby_id)); - let actual_edge_label: &String = combo_merge_edge_properties - .get("label") - .expect("Should have returned edge label") - .get() - .unwrap(); - assert_eq!(actual_edge_label, expected_edge_label); + assert_map_property(&combo_merge_edge_properties, "label", expected_edge_label); } } @@ -2328,9 +2291,67 @@ fn test_side_effect() { .expect("Should get response") .expect("Should have returned an element map"); - let actual_prop_value: &String = element_map.get(expected_side_effect_key).expect("Should have property").get().expect("Should be str"); + assert_map_property( + &element_map, + expected_side_effect_key, + expected_side_effect_value, + ); +} + +#[test] +fn test_by_columns() { + let client = graph(); + let test_vertex_label = "test_by_columns"; + let expected_side_effect_key_a = "prop_key_a"; + let expected_side_effect_value_a = "prop_val_a"; + let expected_side_effect_key_b = "prop_key_b"; + let expected_side_effect_value_b = "prop_val_b"; + + drop_vertices(&client, &test_vertex_label).unwrap(); + + let g = traversal().with_remote(client); + let mut property_map: HashMap = HashMap::new(); + property_map.insert( + expected_side_effect_key_a.into(), + expected_side_effect_value_a.into(), + ); + property_map.insert( + expected_side_effect_key_b.into(), + expected_side_effect_value_b.into(), + ); + + let element_map = g + .inject(vec![property_map.into()]) + .unfold() + .as_("properties") + .add_v(test_vertex_label) + .as_("v") + .side_effect( + __.select("properties") + .unfold() + .as_("kv_pair") + .select("v") + .property( + __.select("kv_pair").by(Column::Keys), + __.select("kv_pair").by(Column::Values), + ), + ) + .element_map(()) + .next() + .expect("Should get response") + .expect("Should have returned an element map"); + + assert_map_property( + &element_map, + expected_side_effect_key_a, + expected_side_effect_value_a, + ); - assert_eq!(expected_side_effect_value, actual_prop_value); + assert_map_property( + &element_map, + expected_side_effect_key_b, + expected_side_effect_value_b, + ); } #[test] From 97b677cb586e2314cd3076be6aaca591bd5d4a09 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Tue, 16 Jul 2024 14:52:13 -0500 Subject: [PATCH 28/54] Expose properties() step in an anonymouse traversal --- .../traversal/anonymous_traversal_source.rs | 7 +++ gremlin-client/tests/integration_traversal.rs | 55 +++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/gremlin-client/src/process/traversal/anonymous_traversal_source.rs b/gremlin-client/src/process/traversal/anonymous_traversal_source.rs index aa826beb..8765697a 100644 --- a/gremlin-client/src/process/traversal/anonymous_traversal_source.rs +++ b/gremlin-client/src/process/traversal/anonymous_traversal_source.rs @@ -67,6 +67,13 @@ impl AnonymousTraversalSource { self.traversal.clone().property(key, value) } + pub fn properties(&self, labels: L) -> TraversalBuilder + where + L: Into, + { + self.traversal.clone().properties(labels) + } + pub fn v(&self, ids: T) -> TraversalBuilder where T: Into, diff --git a/gremlin-client/tests/integration_traversal.rs b/gremlin-client/tests/integration_traversal.rs index 513790f7..6f973dda 100644 --- a/gremlin-client/tests/integration_traversal.rs +++ b/gremlin-client/tests/integration_traversal.rs @@ -2298,6 +2298,61 @@ fn test_side_effect() { ); } +#[test] +fn test_anonymous_traversal_properties_drop() { + let client = graph(); + let test_vertex_label = "test_anonymous_traversal_properties_drop"; + let pre_drop_prop_key = "pre_drop_prop_key"; + let expected_prop_value = "prop_val"; + + drop_vertices(&client, &test_vertex_label).unwrap(); + + let g = traversal().with_remote(client); + + let element_map = g + .add_v(test_vertex_label) + .side_effect(__.property( + gremlin_client::structure::Either2::A(pre_drop_prop_key), + expected_prop_value, + )) + .element_map(()) + .next() + .expect("Should get response") + .expect("Should have returned an element map"); + + //Make sure the property was assigned + assert_map_property(&element_map, pre_drop_prop_key, expected_prop_value); + + let created_vertex_id = element_map.get("id").expect("Should have id property"); + let GValue::Int64(id) = created_vertex_id else { + panic!("Not expected id type"); + }; + + let post_drop_prop_key = "post_drop_prop_key"; + //Operate on the same vertex via id + let post_drop_map = g + .v(*id) + //Drop all properties first + .side_effect(__.properties(()).drop()) + //Then add a different property + .side_effect(__.property( + gremlin_client::structure::Either2::A(pre_drop_prop_key), + expected_prop_value, + )) + .element_map(()) + .next() + .expect("Should get response") + .expect("Should have returned an element map"); + + assert_map_property(&post_drop_map, pre_drop_prop_key, expected_prop_value); + + //Now make sure the pre drop property key is no longer present + assert!( + post_drop_map.get(post_drop_prop_key).is_none(), + "Pre drop key should have been dropped" + ); +} + #[test] fn test_by_columns() { let client = graph(); From f0115d2b268673fb6e6e1758a2b02851b93a08cd Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Tue, 16 Jul 2024 23:19:17 -0500 Subject: [PATCH 29/54] Also update property_many, property_with_cardinality, and property_many_with_cardinality to take a Key that impls Into instead of just &str --- .../src/process/traversal/builder.rs | 16 ++++++---- .../src/process/traversal/graph_traversal.rs | 32 +++++++++---------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/gremlin-client/src/process/traversal/builder.rs b/gremlin-client/src/process/traversal/builder.rs index d4eb971a..de159676 100644 --- a/gremlin-client/src/process/traversal/builder.rs +++ b/gremlin-client/src/process/traversal/builder.rs @@ -102,9 +102,10 @@ impl TraversalBuilder { self } - pub fn property_many(mut self, values: Vec<(String, A)>) -> Self + pub fn property_many(mut self, values: Vec<(K, V)>) -> Self where - A: Into, + K: Into, + V: Into, { for property in values { self.bytecode.add_step( @@ -116,18 +117,19 @@ impl TraversalBuilder { self } - pub fn property_with_cardinality( + pub fn property_with_cardinality( mut self, cardinality: Cardinality, - key: &str, - value: A, + key: K, + value: V, ) -> Self where - A: Into, + K: Into, + V: Into, { self.bytecode.add_step( String::from("property"), - vec![cardinality.into(), String::from(key).into(), value.into()], + vec![cardinality.into(), key.into(), value.into()], ); self } diff --git a/gremlin-client/src/process/traversal/graph_traversal.rs b/gremlin-client/src/process/traversal/graph_traversal.rs index 05606c73..b0984a53 100644 --- a/gremlin-client/src/process/traversal/graph_traversal.rs +++ b/gremlin-client/src/process/traversal/graph_traversal.rs @@ -103,14 +103,15 @@ impl> GraphTraversal { self } - pub fn property_with_cardinality( + pub fn property_with_cardinality( mut self, cardinality: Cardinality, - key: &str, - value: A, + key: K, + value: V, ) -> Self where - A: Into, + K: Into, + V: Into, { self.builder = self .builder @@ -118,30 +119,27 @@ impl> GraphTraversal { self } - pub fn property_many(mut self, values: Vec<(String, A)>) -> Self + pub fn property_many(mut self, values: Vec<(K, V)>) -> Self where - A: Into, + K: Into, + V: Into, { for property in values { - self.builder = self - .builder - .property::<&str, A>(property.0.as_ref(), property.1) + self.builder = self.builder.property(property.0, property.1) } self } - pub fn property_many_with_cardinality( - mut self, - values: Vec<(Cardinality, String, A)>, - ) -> Self + pub fn property_many_with_cardinality(mut self, values: Vec<(Cardinality, K, V)>) -> Self where - A: Into, + K: Into, + V: Into, { for property in values { - self.builder = - self.builder - .property_with_cardinality(property.0, property.1.as_ref(), property.2); + self.builder = self + .builder + .property_with_cardinality(property.0, property.1, property.2); } self From f8f5fec156d40a0513d64c9680f52f8ab0420e80 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Thu, 1 Aug 2024 17:47:08 -0700 Subject: [PATCH 30/54] If a request responds with a websocket error mark the conneciton as invalid --- gremlin-client/src/aio/connection.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/gremlin-client/src/aio/connection.rs b/gremlin-client/src/aio/connection.rs index 0e74e264..a8f199c2 100644 --- a/gremlin-client/src/aio/connection.rs +++ b/gremlin-client/src/aio/connection.rs @@ -190,6 +190,16 @@ impl Conn { .await .expect("It should contain the response") .map(|r| (r, receiver)) + .map_err(|e| { + //If there's been an websocket layer error, mark the connection as invalid + match e { + GremlinError::WebSocket(_) | GremlinError::WebSocketAsync(_) => { + self.valid = false; + } + _ => {} + } + e + }) } pub fn is_valid(&self) -> bool { From 668c564927c7c5e2487415b773fe3c66f17fcc9a Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Mon, 5 Aug 2024 21:00:28 -0500 Subject: [PATCH 31/54] Map additional tungstenite errors to GremlinError::WebSocketAsync --- gremlin-client/src/aio/error.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/gremlin-client/src/aio/error.rs b/gremlin-client/src/aio/error.rs index 5861c77b..152aaeb3 100644 --- a/gremlin-client/src/aio/error.rs +++ b/gremlin-client/src/aio/error.rs @@ -3,11 +3,18 @@ use async_tungstenite::tungstenite; impl From<&tungstenite::error::Error> for GremlinError { fn from(e: &tungstenite::error::Error) -> GremlinError { + //Some of the tungstenite errors are cloneable or can be lightly recreated + //for those that cannot be, their message is wrapped in a GremlinError::Generic + //this does change the observed error type. In the future maybe sending tungstenite errors + //wrapped in an Arc can avoid this let error = match e { tungstenite::error::Error::AlreadyClosed => tungstenite::error::Error::AlreadyClosed, tungstenite::error::Error::ConnectionClosed => { tungstenite::error::Error::ConnectionClosed } + tungstenite::Error::Protocol(e) => tungstenite::Error::Protocol(e.clone()), + tungstenite::Error::Utf8 => tungstenite::Error::Utf8, + tungstenite::Error::AttackAttempt => tungstenite::Error::AttackAttempt, _ => return GremlinError::Generic(format!("Error from ws {}", e)), }; GremlinError::WebSocketAsync(error) From cdcc212b294731190eef5d4be46bcc6eeaece4fb Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Thu, 22 Aug 2024 18:50:22 -0500 Subject: [PATCH 32/54] Arc tungstenite::Error into WebSocketAsync error to maintain Async error enum back to caller --- gremlin-client/src/aio/connection.rs | 8 ++++++-- gremlin-client/src/aio/error.rs | 22 ---------------------- gremlin-client/src/aio/mod.rs | 1 - gremlin-client/src/connection.rs | 5 +++-- gremlin-client/src/error.rs | 4 +++- 5 files changed, 12 insertions(+), 28 deletions(-) delete mode 100644 gremlin-client/src/aio/error.rs diff --git a/gremlin-client/src/aio/connection.rs b/gremlin-client/src/aio/connection.rs index a8f199c2..b106f08c 100644 --- a/gremlin-client/src/aio/connection.rs +++ b/gremlin-client/src/aio/connection.rs @@ -21,6 +21,7 @@ mod tokio_use { pub use tokio_native_tls::TlsStream; } +use futures::TryFutureExt; #[cfg(feature = "tokio-runtime")] use tokio_use::*; @@ -144,6 +145,7 @@ impl Conn { tls::connector(&opts), websocket_config, ) + .map_err(|e| Arc::new(e)) .await? }; #[cfg(feature = "tokio-runtime")] @@ -153,6 +155,7 @@ impl Conn { tls::connector(&opts), websocket_config, ) + .map_err(|e| Arc::new(e)) .await? }; @@ -232,7 +235,7 @@ fn sender_loop( if let Err(e) = sink.send(Message::Binary(msg.2)).await { let mut sender = guard.remove(&msg.1).unwrap(); sender - .send(Err(GremlinError::from(e))) + .send(Err(GremlinError::from(Arc::new(e)))) .await .expect("Failed to send error"); } @@ -267,8 +270,9 @@ fn receiver_loop( match stream.next().await { Some(Err(error)) => { let mut guard = requests.lock().await; + let error = Arc::new(error); for s in guard.values_mut() { - match s.send(Err(GremlinError::from(&error))).await { + match s.send(Err(error.clone().into())).await { Ok(_r) => {} Err(_e) => {} } diff --git a/gremlin-client/src/aio/error.rs b/gremlin-client/src/aio/error.rs deleted file mode 100644 index 152aaeb3..00000000 --- a/gremlin-client/src/aio/error.rs +++ /dev/null @@ -1,22 +0,0 @@ -use crate::GremlinError; -use async_tungstenite::tungstenite; - -impl From<&tungstenite::error::Error> for GremlinError { - fn from(e: &tungstenite::error::Error) -> GremlinError { - //Some of the tungstenite errors are cloneable or can be lightly recreated - //for those that cannot be, their message is wrapped in a GremlinError::Generic - //this does change the observed error type. In the future maybe sending tungstenite errors - //wrapped in an Arc can avoid this - let error = match e { - tungstenite::error::Error::AlreadyClosed => tungstenite::error::Error::AlreadyClosed, - tungstenite::error::Error::ConnectionClosed => { - tungstenite::error::Error::ConnectionClosed - } - tungstenite::Error::Protocol(e) => tungstenite::Error::Protocol(e.clone()), - tungstenite::Error::Utf8 => tungstenite::Error::Utf8, - tungstenite::Error::AttackAttempt => tungstenite::Error::AttackAttempt, - _ => return GremlinError::Generic(format!("Error from ws {}", e)), - }; - GremlinError::WebSocketAsync(error) - } -} diff --git a/gremlin-client/src/aio/mod.rs b/gremlin-client/src/aio/mod.rs index 03047c06..6cb6a773 100644 --- a/gremlin-client/src/aio/mod.rs +++ b/gremlin-client/src/aio/mod.rs @@ -3,7 +3,6 @@ pub(crate) mod connection; pub(crate) mod pool; mod result; -mod error; pub(crate) mod process; pub use client::GremlinClient; pub use process::traversal::AsyncTerminator; diff --git a/gremlin-client/src/connection.rs b/gremlin-client/src/connection.rs index 16fa44a4..c8072ab5 100644 --- a/gremlin-client/src/connection.rs +++ b/gremlin-client/src/connection.rs @@ -1,4 +1,4 @@ -use std::{net::TcpStream, time::Duration}; +use std::{net::TcpStream, sync::Arc, time::Duration}; use crate::{GraphSON, GremlinError, GremlinResult}; use native_tls::TlsConnector; @@ -63,11 +63,12 @@ impl ConnectionStream { fn send(&mut self, payload: Vec) -> GremlinResult<()> { self.0 .write_message(Message::Binary(payload)) + .map_err(|e| Arc::new(e)) .map_err(GremlinError::from) } fn recv(&mut self) -> GremlinResult> { - match self.0.read_message()? { + match self.0.read_message().map_err(|e| Arc::new(e))? { Message::Binary(binary) => Ok(binary), _ => unimplemented!(), } diff --git a/gremlin-client/src/error.rs b/gremlin-client/src/error.rs index a803075b..ac1db02a 100644 --- a/gremlin-client/src/error.rs +++ b/gremlin-client/src/error.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use crate::structure::GValue; use thiserror::Error; @@ -34,7 +36,7 @@ pub enum GremlinError { #[cfg(feature = "async_gremlin")] #[error(transparent)] - WebSocketAsync(#[from] async_tungstenite::tungstenite::Error), + WebSocketAsync(#[from] Arc), #[cfg(feature = "async_gremlin")] #[error(transparent)] ChannelSend(#[from] futures::channel::mpsc::SendError), From 2b72a1ada590822d65445adb65d84dd6582ca803 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Mon, 26 Aug 2024 19:13:12 -0500 Subject: [PATCH 33/54] Expose healthcheck interval setting on async connection pool --- gremlin-client/src/aio/client.rs | 1 + gremlin-client/src/connection.rs | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/gremlin-client/src/aio/client.rs b/gremlin-client/src/aio/client.rs index c2f7a727..fcaa4c47 100644 --- a/gremlin-client/src/aio/client.rs +++ b/gremlin-client/src/aio/client.rs @@ -59,6 +59,7 @@ impl GremlinClient { let pool = Pool::builder() .get_timeout(opts.pool_get_connection_timeout) .max_open(pool_size as u64) + .health_check_interval(opts.pool_healthcheck_interval) .build(manager); Ok(GremlinClient { diff --git a/gremlin-client/src/connection.rs b/gremlin-client/src/connection.rs index c8072ab5..dff7857f 100644 --- a/gremlin-client/src/connection.rs +++ b/gremlin-client/src/connection.rs @@ -121,6 +121,13 @@ impl ConnectionOptionsBuilder { self } + /// Only applicable to async client. By default a connection is checked on each return to the pool (None) + /// This allows setting an interval of how often it is checked on return. + pub fn pool_healthcheck_interval(mut self, pool_healthcheck_interval: Option) -> Self { + self.0.pool_healthcheck_interval = pool_healthcheck_interval; + self + } + /// Both the sync and async pool providers use a default of 30 seconds, /// Async pool interprets `None` as no timeout. Sync pool maps `None` to the default value pub fn pool_connection_timeout(mut self, pool_connection_timeout: Option) -> Self { @@ -171,6 +178,7 @@ pub struct ConnectionOptions { pub(crate) host: String, pub(crate) port: u16, pub(crate) pool_size: u32, + pub(crate) pool_healthcheck_interval: Option, pub(crate) pool_get_connection_timeout: Option, pub(crate) credentials: Option, pub(crate) ssl: bool, @@ -255,6 +263,7 @@ impl Default for ConnectionOptions { port: 8182, pool_size: 10, pool_get_connection_timeout: Some(Duration::from_secs(30)), + pool_healthcheck_interval: None, credentials: None, ssl: false, tls_options: None, From 093c04cd3ebd4f97760ceaa6467d9c61de170372 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Mon, 26 Aug 2024 19:23:00 -0500 Subject: [PATCH 34/54] Formatting --- gremlin-client/src/connection.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/gremlin-client/src/connection.rs b/gremlin-client/src/connection.rs index dff7857f..7b31462a 100644 --- a/gremlin-client/src/connection.rs +++ b/gremlin-client/src/connection.rs @@ -123,7 +123,10 @@ impl ConnectionOptionsBuilder { /// Only applicable to async client. By default a connection is checked on each return to the pool (None) /// This allows setting an interval of how often it is checked on return. - pub fn pool_healthcheck_interval(mut self, pool_healthcheck_interval: Option) -> Self { + pub fn pool_healthcheck_interval( + mut self, + pool_healthcheck_interval: Option, + ) -> Self { self.0.pool_healthcheck_interval = pool_healthcheck_interval; self } From 802a20c28ec85fd4911ee91b091c7e826bc27f02 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Tue, 27 Aug 2024 13:49:28 -0500 Subject: [PATCH 35/54] Map mobc pool errors to type that would invalidate connection --- gremlin-client/src/aio/connection.rs | 4 +++- gremlin-client/src/error.rs | 8 ++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/gremlin-client/src/aio/connection.rs b/gremlin-client/src/aio/connection.rs index b106f08c..a0f311e0 100644 --- a/gremlin-client/src/aio/connection.rs +++ b/gremlin-client/src/aio/connection.rs @@ -196,7 +196,9 @@ impl Conn { .map_err(|e| { //If there's been an websocket layer error, mark the connection as invalid match e { - GremlinError::WebSocket(_) | GremlinError::WebSocketAsync(_) => { + GremlinError::WebSocket(_) + | GremlinError::WebSocketAsync(_) + | GremlinError::WebSocketPoolAsync(_) => { self.valid = false; } _ => {} diff --git a/gremlin-client/src/error.rs b/gremlin-client/src/error.rs index ac1db02a..58e01214 100644 --- a/gremlin-client/src/error.rs +++ b/gremlin-client/src/error.rs @@ -39,6 +39,9 @@ pub enum GremlinError { WebSocketAsync(#[from] Arc), #[cfg(feature = "async_gremlin")] #[error(transparent)] + WebSocketPoolAsync(#[from] Arc>), + #[cfg(feature = "async_gremlin")] + #[error(transparent)] ChannelSend(#[from] futures::channel::mpsc::SendError), #[error(transparent)] Uuid(#[from] uuid::Error), @@ -49,10 +52,7 @@ impl From> for GremlinError { fn from(e: mobc::Error) -> GremlinError { match e { mobc::Error::Inner(e) => e, - mobc::Error::BadConn => { - GremlinError::Generic(String::from("Async pool bad connection")) - } - mobc::Error::Timeout => GremlinError::Generic(String::from("Async pool timeout")), + other => GremlinError::WebSocketPoolAsync(Arc::new(other)), } } } From 03c24febba8780a95e1c8477463355f0c9a78f83 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Sat, 31 Aug 2024 08:26:09 -0500 Subject: [PATCH 36/54] Exploratory logging --- gremlin-client/Cargo.toml | 1 + gremlin-client/src/aio/connection.rs | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/gremlin-client/Cargo.toml b/gremlin-client/Cargo.toml index e67a20b7..5a65eaf2 100644 --- a/gremlin-client/Cargo.toml +++ b/gremlin-client/Cargo.toml @@ -64,6 +64,7 @@ futures = { version = "0.3.1", optional = true} pin-project-lite = { version = "0.2", optional = true} tokio = { version = "1", optional=true, features = ["full"] } +log = "0.4.22" [dependencies.uuid] features = ["serde", "v4"] diff --git a/gremlin-client/src/aio/connection.rs b/gremlin-client/src/aio/connection.rs index a0f311e0..2e103520 100644 --- a/gremlin-client/src/aio/connection.rs +++ b/gremlin-client/src/aio/connection.rs @@ -22,6 +22,7 @@ mod tokio_use { } use futures::TryFutureExt; +use log::{error, info, warn}; #[cfg(feature = "tokio-runtime")] use tokio_use::*; @@ -137,6 +138,7 @@ impl Conn { let url = url::Url::parse(&opts.websocket_url()).expect("failed to parse url"); let websocket_config = opts.websocket_options.as_ref().map(WebSocketConfig::from); + info!("Openning websocket connection"); #[cfg(feature = "async-std-runtime")] let (client, _) = { @@ -159,6 +161,7 @@ impl Conn { .await? }; + info!("Opened websocket connection"); let (sink, stream) = client.split(); let (sender, receiver) = channel(20); let requests = Arc::new(Mutex::new(HashMap::new())); @@ -184,6 +187,7 @@ impl Conn { .send(Cmd::Msg((sender, id, payload))) .await .map_err(|e| { + error!("Marking websocket connection invalid on send error"); self.valid = false; e })?; @@ -199,6 +203,7 @@ impl Conn { GremlinError::WebSocket(_) | GremlinError::WebSocketAsync(_) | GremlinError::WebSocketPoolAsync(_) => { + error!("Marking websocket connection invalid on received error"); self.valid = false; } _ => {} @@ -214,11 +219,13 @@ impl Conn { impl Drop for Conn { fn drop(&mut self) { + warn!("Websocket connection instance dropped"); send_shutdown(self); } } fn send_shutdown(conn: &mut Conn) { + warn!("Websocket connection instance shutting down channel"); conn.sender.close_channel(); } @@ -235,6 +242,7 @@ fn sender_loop( let mut guard = requests.lock().await; guard.insert(msg.1, msg.0); if let Err(e) = sink.send(Message::Binary(msg.2)).await { + error!("Sink sending error occured"); let mut sender = guard.remove(&msg.1).unwrap(); sender .send(Err(GremlinError::from(Arc::new(e)))) @@ -244,20 +252,24 @@ fn sender_loop( drop(guard); } Cmd::Pong(data) => { + info!("Sending Pong"); sink.send(Message::Pong(data)) .await .expect("Failed to send pong message."); } Cmd::Shutdown => { + warn!("Shuting down connection"); let mut guard = requests.lock().await; guard.clear(); } }, None => { + warn!("Sending loop breaking"); break; } } } + warn!("Sending loop closing sink"); let _ = sink.close().await; }); } @@ -273,6 +285,7 @@ fn receiver_loop( Some(Err(error)) => { let mut guard = requests.lock().await; let error = Arc::new(error); + error!("Receiver loop error"); for s in guard.values_mut() { match s.send(Err(error.clone().into())).await { Ok(_r) => {} @@ -306,11 +319,13 @@ fn receiver_loop( } } Message::Ping(data) => { + info!("Received Ping"); let _ = sender.send(Cmd::Pong(data)).await; } _ => {} }, None => { + warn!("Receiver loop breaking"); break; } } From a342d8caaf0b93ee81dfac2545aeea969333e7be Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Sat, 31 Aug 2024 08:43:26 -0500 Subject: [PATCH 37/54] Added uuid to connection instance logging --- gremlin-client/src/aio/connection.rs | 54 +++++++++++++++++++--------- 1 file changed, 38 insertions(+), 16 deletions(-) diff --git a/gremlin-client/src/aio/connection.rs b/gremlin-client/src/aio/connection.rs index 2e103520..b560d28f 100644 --- a/gremlin-client/src/aio/connection.rs +++ b/gremlin-client/src/aio/connection.rs @@ -65,6 +65,7 @@ pub enum Cmd { pub(crate) struct Conn { sender: Sender, valid: bool, + connection_uuid: Uuid, } impl std::fmt::Debug for Conn { @@ -138,7 +139,8 @@ impl Conn { let url = url::Url::parse(&opts.websocket_url()).expect("failed to parse url"); let websocket_config = opts.websocket_options.as_ref().map(WebSocketConfig::from); - info!("Openning websocket connection"); + let connection_uuid = Uuid::new_v4(); + info!("{connection_uuid} Openning websocket connection"); #[cfg(feature = "async-std-runtime")] let (client, _) = { @@ -161,18 +163,24 @@ impl Conn { .await? }; - info!("Opened websocket connection"); + info!("{connection_uuid} Opened websocket connection"); let (sink, stream) = client.split(); let (sender, receiver) = channel(20); let requests = Arc::new(Mutex::new(HashMap::new())); - sender_loop(sink, requests.clone(), receiver); + sender_loop(connection_uuid.clone(), sink, requests.clone(), receiver); - receiver_loop(stream, requests.clone(), sender.clone()); + receiver_loop( + connection_uuid.clone(), + stream, + requests.clone(), + sender.clone(), + ); Ok(Conn { sender, valid: true, + connection_uuid, }) } @@ -187,7 +195,10 @@ impl Conn { .send(Cmd::Msg((sender, id, payload))) .await .map_err(|e| { - error!("Marking websocket connection invalid on send error"); + error!( + "{} Marking websocket connection invalid on send error", + self.connection_uuid + ); self.valid = false; e })?; @@ -203,7 +214,10 @@ impl Conn { GremlinError::WebSocket(_) | GremlinError::WebSocketAsync(_) | GremlinError::WebSocketPoolAsync(_) => { - error!("Marking websocket connection invalid on received error"); + error!( + "{} Marking websocket connection invalid on received error", + self.connection_uuid + ); self.valid = false; } _ => {} @@ -219,17 +233,24 @@ impl Conn { impl Drop for Conn { fn drop(&mut self) { - warn!("Websocket connection instance dropped"); + warn!( + "{} Websocket connection instance dropped", + self.connection_uuid + ); send_shutdown(self); } } fn send_shutdown(conn: &mut Conn) { - warn!("Websocket connection instance shutting down channel"); + warn!( + "{} Websocket connection instance shutting down channel", + conn.connection_uuid + ); conn.sender.close_channel(); } fn sender_loop( + connection_uuid: Uuid, mut sink: SplitSink, requests: Arc>>>>, mut receiver: Receiver, @@ -242,7 +263,7 @@ fn sender_loop( let mut guard = requests.lock().await; guard.insert(msg.1, msg.0); if let Err(e) = sink.send(Message::Binary(msg.2)).await { - error!("Sink sending error occured"); + error!("{connection_uuid} Sink sending error occured"); let mut sender = guard.remove(&msg.1).unwrap(); sender .send(Err(GremlinError::from(Arc::new(e)))) @@ -252,29 +273,30 @@ fn sender_loop( drop(guard); } Cmd::Pong(data) => { - info!("Sending Pong"); + info!("{connection_uuid} Sending Pong",); sink.send(Message::Pong(data)) .await .expect("Failed to send pong message."); } Cmd::Shutdown => { - warn!("Shuting down connection"); + warn!("{connection_uuid} Shuting down connection"); let mut guard = requests.lock().await; guard.clear(); } }, None => { - warn!("Sending loop breaking"); + warn!("{connection_uuid} Sending loop breaking"); break; } } } - warn!("Sending loop closing sink"); + warn!("{connection_uuid} Sending loop closing sink"); let _ = sink.close().await; }); } fn receiver_loop( + connection_uuid: Uuid, mut stream: SplitStream, requests: Arc>>>>, mut sender: Sender, @@ -285,7 +307,7 @@ fn receiver_loop( Some(Err(error)) => { let mut guard = requests.lock().await; let error = Arc::new(error); - error!("Receiver loop error"); + error!("{connection_uuid} Receiver loop error"); for s in guard.values_mut() { match s.send(Err(error.clone().into())).await { Ok(_r) => {} @@ -319,13 +341,13 @@ fn receiver_loop( } } Message::Ping(data) => { - info!("Received Ping"); + info!("{connection_uuid} Received Ping"); let _ = sender.send(Cmd::Pong(data)).await; } _ => {} }, None => { - warn!("Receiver loop breaking"); + warn!("{connection_uuid} Receiver loop breaking"); break; } } From ec1437db68871f5c0a7b1cec8791b70a4484b0be Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Sun, 1 Sep 2024 11:28:38 -0500 Subject: [PATCH 38/54] Update mobc and make its idle connection behavior the same as the rd2d sync pool --- gremlin-client/Cargo.toml | 2 +- gremlin-client/src/aio/client.rs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/gremlin-client/Cargo.toml b/gremlin-client/Cargo.toml index 5a65eaf2..d5e6b063 100644 --- a/gremlin-client/Cargo.toml +++ b/gremlin-client/Cargo.toml @@ -58,7 +58,7 @@ thiserror = "1.0.20" -mobc = {version = "0.7", optional = true, default-features=false, features = ["unstable"] } +mobc = {version = "0.8", optional = true, default-features=false, features = ["unstable"] } url = {version = "2.1.0", optional = true} futures = { version = "0.3.1", optional = true} pin-project-lite = { version = "0.2", optional = true} diff --git a/gremlin-client/src/aio/client.rs b/gremlin-client/src/aio/client.rs index fcaa4c47..ad3c52f2 100644 --- a/gremlin-client/src/aio/client.rs +++ b/gremlin-client/src/aio/client.rs @@ -60,6 +60,8 @@ impl GremlinClient { .get_timeout(opts.pool_get_connection_timeout) .max_open(pool_size as u64) .health_check_interval(opts.pool_healthcheck_interval) + //Makes max idle connections equal to max open, matching the behavior of the sync pool r2d2 + .max_idle(0) .build(manager); Ok(GremlinClient { From 784ca876bf8d7ff518c61b9d2cd3c96d45b04a98 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Sun, 1 Sep 2024 11:53:09 -0500 Subject: [PATCH 39/54] 0.8 mobc does not treat async-std feature as mutually exclusive from tokio --- gremlin-client/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gremlin-client/Cargo.toml b/gremlin-client/Cargo.toml index d5e6b063..3b902778 100644 --- a/gremlin-client/Cargo.toml +++ b/gremlin-client/Cargo.toml @@ -22,7 +22,7 @@ merge_tests = [] async_gremlin = ["futures","mobc","async-tungstenite","async-trait","url","pin-project-lite"] async_std = ["async-std-runtime"] -tokio-runtime = ["async_gremlin","tokio","mobc/tokio","async-tungstenite/tokio-runtime","async-tungstenite/tokio-native-tls","tokio-native-tls","tokio-stream"] +tokio-runtime = ["async_gremlin","tokio","async-tungstenite/tokio-runtime","async-tungstenite/tokio-native-tls","tokio-native-tls","tokio-stream"] async-std-runtime = ["async_gremlin","async-std","async-tungstenite/async-std-runtime","async-tungstenite/async-tls","mobc/async-std","async-tls","rustls","webpki"] derive = ["gremlin-derive"] @@ -58,7 +58,7 @@ thiserror = "1.0.20" -mobc = {version = "0.8", optional = true, default-features=false, features = ["unstable"] } +mobc = {version = "0.8", optional = true } url = {version = "2.1.0", optional = true} futures = { version = "0.3.1", optional = true} pin-project-lite = { version = "0.2", optional = true} From c10ceaa90e54388c9982986dfb89749e72b205fe Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Sun, 1 Sep 2024 12:30:43 -0500 Subject: [PATCH 40/54] Include tokio/sync for mobc compilation in async-std-runtime feature --- gremlin-client/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gremlin-client/Cargo.toml b/gremlin-client/Cargo.toml index 3b902778..c8911b1d 100644 --- a/gremlin-client/Cargo.toml +++ b/gremlin-client/Cargo.toml @@ -23,7 +23,7 @@ async_gremlin = ["futures","mobc","async-tungstenite","async-trait","url","pin-p async_std = ["async-std-runtime"] tokio-runtime = ["async_gremlin","tokio","async-tungstenite/tokio-runtime","async-tungstenite/tokio-native-tls","tokio-native-tls","tokio-stream"] -async-std-runtime = ["async_gremlin","async-std","async-tungstenite/async-std-runtime","async-tungstenite/async-tls","mobc/async-std","async-tls","rustls","webpki"] +async-std-runtime = ["async_gremlin","async-std","async-tungstenite/async-std-runtime","async-tungstenite/async-tls","tokio/sync", "mobc/async-std","async-tls","rustls","webpki"] derive = ["gremlin-derive"] From 6a94a88c0bb688e68f9443deb50ae0918eaa7fdd Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Mon, 16 Sep 2024 20:21:56 -0500 Subject: [PATCH 41/54] Implemented None step --- gremlin-client/src/conversion.rs | 15 ++++++++- .../src/process/traversal/builder.rs | 6 ++++ .../src/process/traversal/graph_traversal.rs | 12 ++++++- gremlin-client/src/structure/mod.rs | 2 ++ gremlin-client/src/structure/null.rs | 2 ++ gremlin-client/tests/integration_traversal.rs | 31 +++++++++++++++++++ 6 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 gremlin-client/src/structure/null.rs diff --git a/gremlin-client/src/conversion.rs b/gremlin-client/src/conversion.rs index 5103e714..fd2102dd 100644 --- a/gremlin-client/src/conversion.rs +++ b/gremlin-client/src/conversion.rs @@ -1,6 +1,6 @@ use crate::{ process::traversal::Bytecode, - structure::{TextP, P as Predicate}, + structure::{Null, TextP, P as Predicate}, Edge, GKey, GValue, GremlinError, GremlinResult, IntermediateRepr, List, Map, Metric, Path, Property, Token, TraversalExplanation, TraversalMetrics, Vertex, VertexProperty, GID, }; @@ -134,6 +134,19 @@ impl_from_gvalue!(IntermediateRepr, GValue::IntermediateRepr); impl_from_gvalue!(chrono::DateTime, GValue::Date); impl_from_gvalue!(Traverser, GValue::Traverser); +impl FromGValue for Null { + fn from_gvalue(v: GValue) -> GremlinResult { + match v { + GValue::Null => Ok(crate::structure::Null {}), + _ => Err(GremlinError::Cast(format!( + "Cannot convert {:?} to {}", + v, + stringify!($t) + ))), + } + } +} + impl FromGValue for GKey { fn from_gvalue(v: GValue) -> GremlinResult { match v { diff --git a/gremlin-client/src/process/traversal/builder.rs b/gremlin-client/src/process/traversal/builder.rs index de159676..49491aea 100644 --- a/gremlin-client/src/process/traversal/builder.rs +++ b/gremlin-client/src/process/traversal/builder.rs @@ -299,6 +299,12 @@ impl TraversalBuilder { self } + pub fn none(mut self) -> Self { + self.bytecode.add_step(String::from("none"), vec![]); + + self + } + pub fn label(mut self) -> Self { self.bytecode.add_step(String::from("label"), vec![]); diff --git a/gremlin-client/src/process/traversal/graph_traversal.rs b/gremlin-client/src/process/traversal/graph_traversal.rs index b0984a53..be734946 100644 --- a/gremlin-client/src/process/traversal/graph_traversal.rs +++ b/gremlin-client/src/process/traversal/graph_traversal.rs @@ -22,7 +22,7 @@ use crate::process::traversal::strategies::{ RemoteStrategy, TraversalStrategies, TraversalStrategy, }; use crate::process::traversal::{Bytecode, Scope, TraversalBuilder, WRITE_OPERATORS}; -use crate::structure::{Cardinality, Labels}; +use crate::structure::{Cardinality, Labels, Null}; use crate::{ structure::GIDs, structure::GProperty, structure::IntoPredicate, Edge, GValue, GremlinClient, List, Map, Path, Vertex, @@ -302,6 +302,16 @@ impl> GraphTraversal { GraphTraversal::new(self.terminator, self.builder) } + ///Filters all objects from the traversal stream. Generally only useful for applying traversal sideffects and avoiding unwanted response I/O + pub fn none(mut self) -> GraphTraversal + where + T: Terminator, + { + self.builder = self.builder.none(); + + GraphTraversal::new(self.terminator, self.builder) + } + pub fn label(mut self) -> GraphTraversal where T: Terminator, diff --git a/gremlin-client/src/structure/mod.rs b/gremlin-client/src/structure/mod.rs index a2695315..523adbc1 100644 --- a/gremlin-client/src/structure/mod.rs +++ b/gremlin-client/src/structure/mod.rs @@ -10,6 +10,7 @@ mod macros; mod map; mod merge; mod metrics; +mod null; mod p; mod path; mod pop; @@ -28,6 +29,7 @@ pub use self::edge::Edge; pub use self::gid::{GIDs, GID}; pub use self::list::List; pub use self::metrics::{IntermediateRepr, Metric, TraversalExplanation, TraversalMetrics}; +pub use self::null::Null; pub use self::path::Path; pub use self::property::Property; pub use self::result::GResultSet; diff --git a/gremlin-client/src/structure/null.rs b/gremlin-client/src/structure/null.rs new file mode 100644 index 00000000..76dbac88 --- /dev/null +++ b/gremlin-client/src/structure/null.rs @@ -0,0 +1,2 @@ +#[derive(Debug, Clone)] +pub struct Null {} diff --git a/gremlin-client/tests/integration_traversal.rs b/gremlin-client/tests/integration_traversal.rs index 6f973dda..20564cb0 100644 --- a/gremlin-client/tests/integration_traversal.rs +++ b/gremlin-client/tests/integration_traversal.rs @@ -2603,6 +2603,37 @@ fn test_coalesce_unfold() { ); } +#[test] +fn test_none_step() { + let client = graph(); + + drop_vertices(&client, "test_none_step").unwrap(); + + let g = traversal().with_remote(client); + + //The addition of a None step however should not IO a vertex back + let mut iter = g + .add_v("test_none_step") + .none() + .iter() + .expect("Should get a iter back"); + assert!( + iter.next().is_none(), + "But the iter should have no elements because of the None step" + ); + + //Make sure the vertex is present in the graph + let vertex_count = g + .v(()) + .has_label("test_none_step") + .count() + .next() + .ok() + .flatten() + .expect("Should have gotten a response"); + assert_eq!(1, vertex_count); +} + #[test] #[cfg(feature = "derive")] fn test_traversal_vertex_mapping() { From bce3737633951dba363a278ce6d9b9d5174a1fae Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Mon, 16 Sep 2024 21:14:19 -0500 Subject: [PATCH 42/54] Implemented iterator() method on returned remote stream to consume stream for only Null terminated traversals --- gremlin-client/src/aio/process/traversal/mod.rs | 12 ++++++++++++ gremlin-client/src/process/traversal/mod.rs | 10 ++++++++++ gremlin-client/tests/integration_traversal.rs | 11 ++++------- 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/gremlin-client/src/aio/process/traversal/mod.rs b/gremlin-client/src/aio/process/traversal/mod.rs index b6ddd1a9..cdd0a4b2 100644 --- a/gremlin-client/src/aio/process/traversal/mod.rs +++ b/gremlin-client/src/aio/process/traversal/mod.rs @@ -6,6 +6,7 @@ use crate::GremlinResult; use core::task::Context; use core::task::Poll; use futures::Stream; +use futures::StreamExt; use std::marker::PhantomData; use std::pin::Pin; @@ -29,6 +30,17 @@ impl RemoteTraversalStream { } } } + +impl RemoteTraversalStream { + pub async fn iterate(&mut self) -> GremlinResult<()> { + while let Some(response) = self.next().await { + //consume the entire stream, returning any errors + response?; + } + Ok(()) + } +} + impl Stream for RemoteTraversalStream { type Item = GremlinResult; diff --git a/gremlin-client/src/process/traversal/mod.rs b/gremlin-client/src/process/traversal/mod.rs index 2826bd2e..4001c2d2 100644 --- a/gremlin-client/src/process/traversal/mod.rs +++ b/gremlin-client/src/process/traversal/mod.rs @@ -47,6 +47,16 @@ impl RemoteTraversalIterator { } } +impl RemoteTraversalIterator { + pub fn iterate(&mut self) -> GremlinResult<()> { + while let Some(response) = self.next() { + //consume the entire iterator, returning any errors + response?; + } + Ok(()) + } +} + impl Iterator for RemoteTraversalIterator { type Item = GremlinResult; diff --git a/gremlin-client/tests/integration_traversal.rs b/gremlin-client/tests/integration_traversal.rs index 20564cb0..78c4725a 100644 --- a/gremlin-client/tests/integration_traversal.rs +++ b/gremlin-client/tests/integration_traversal.rs @@ -2612,15 +2612,12 @@ fn test_none_step() { let g = traversal().with_remote(client); //The addition of a None step however should not IO a vertex back - let mut iter = g - .add_v("test_none_step") + g.add_v("test_none_step") .none() .iter() - .expect("Should get a iter back"); - assert!( - iter.next().is_none(), - "But the iter should have no elements because of the None step" - ); + .expect("Should get a iter back") + .iterate() + .expect("Shouldn't error consuming iterator"); //Make sure the vertex is present in the graph let vertex_count = g From 5db2b58905cce80df5200b011ee4578c8d296d92 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Sat, 21 Sep 2024 14:45:06 -0500 Subject: [PATCH 43/54] Trial connection multiplexing for non-credential configured clients --- gremlin-client/src/aio/client.rs | 23 ++++-- gremlin-client/src/aio/connection.rs | 100 ++++++++++++--------------- gremlin-client/src/aio/pool.rs | 15 +++- 3 files changed, 76 insertions(+), 62 deletions(-) diff --git a/gremlin-client/src/aio/client.rs b/gremlin-client/src/aio/client.rs index ad3c52f2..c7dfa1c8 100644 --- a/gremlin-client/src/aio/client.rs +++ b/gremlin-client/src/aio/client.rs @@ -10,6 +10,7 @@ use crate::ToGValue; use crate::{ConnectionOptions, GremlinError, GremlinResult}; use base64::encode; use futures::future::{BoxFuture, FutureExt}; +use futures::StreamExt; use mobc::{Connection, Pool}; use serde::Serialize; use std::collections::{HashMap, VecDeque}; @@ -162,8 +163,22 @@ impl GremlinClient { let payload = String::from("") + content_type + &message; let mut binary = payload.into_bytes(); binary.insert(0, content_type.len() as u8); - - let (response, receiver) = conn.send(id, binary).await?; + let mut receiver = conn.send(id, binary).await?; + let response = receiver + .next() + .await + .expect("It should contain the response")?; + //Prepare holding onto the connection for an auth challenge if we have credentials + //Tinkerpop performs authentication at the channel level, and if we let it go, + //a healthcheck may disrupt the challenge + //Otherwise drop the connection so it can be multiplexed + let retained_auth_context = match self.options.credentials.as_ref() { + None => { + drop(conn); + None + } + Some(credentials) => Some((credentials, conn)), + }; let (response, results) = match response.status.code { 200 | 206 => { @@ -176,8 +191,8 @@ impl GremlinClient { Ok((response, results)) } 204 => Ok((response, VecDeque::new())), - 407 => match &self.options.credentials { - Some(c) => { + 407 => match retained_auth_context { + Some((c, conn)) => { let mut args = HashMap::new(); args.insert( diff --git a/gremlin-client/src/aio/connection.rs b/gremlin-client/src/aio/connection.rs index b560d28f..23af3b10 100644 --- a/gremlin-client/src/aio/connection.rs +++ b/gremlin-client/src/aio/connection.rs @@ -43,6 +43,7 @@ use futures::{ use futures::channel::mpsc::{channel, Receiver, Sender}; use std::collections::HashMap; +use std::sync::atomic::{self, AtomicBool}; use std::sync::Arc; use url; use uuid::Uuid; @@ -64,7 +65,7 @@ pub enum Cmd { pub(crate) struct Conn { sender: Sender, - valid: bool, + valid: Arc, connection_uuid: Uuid, } @@ -170,16 +171,19 @@ impl Conn { sender_loop(connection_uuid.clone(), sink, requests.clone(), receiver); + let valid_flag = Arc::new(AtomicBool::new(true)); + receiver_loop( connection_uuid.clone(), stream, requests.clone(), sender.clone(), + valid_flag.clone(), ); Ok(Conn { sender, - valid: true, + valid: valid_flag, connection_uuid, }) } @@ -188,8 +192,8 @@ impl Conn { &mut self, id: Uuid, payload: Vec, - ) -> GremlinResult<(Response, Receiver>)> { - let (sender, mut receiver) = channel(1); + ) -> GremlinResult>> { + let (sender, receiver) = channel(1); self.sender .send(Cmd::Msg((sender, id, payload))) @@ -199,35 +203,14 @@ impl Conn { "{} Marking websocket connection invalid on send error", self.connection_uuid ); - self.valid = false; - e - })?; - - receiver - .next() - .await - .expect("It should contain the response") - .map(|r| (r, receiver)) - .map_err(|e| { - //If there's been an websocket layer error, mark the connection as invalid - match e { - GremlinError::WebSocket(_) - | GremlinError::WebSocketAsync(_) - | GremlinError::WebSocketPoolAsync(_) => { - error!( - "{} Marking websocket connection invalid on received error", - self.connection_uuid - ); - self.valid = false; - } - _ => {} - } - e + self.valid.store(false, atomic::Ordering::Release); + GremlinError::from(e) }) + .map(|_| receiver) } pub fn is_valid(&self) -> bool { - self.valid + self.valid.load(atomic::Ordering::Acquire) } } @@ -300,11 +283,14 @@ fn receiver_loop( mut stream: SplitStream, requests: Arc>>>>, mut sender: Sender, + connection_valid_flag: Arc, ) { task::spawn(async move { loop { match stream.next().await { Some(Err(error)) => { + //If there's been an websocket layer error, mark the connection as invalid + connection_valid_flag.store(false, atomic::Ordering::Release); let mut guard = requests.lock().await; let error = Arc::new(error); error!("{connection_uuid} Receiver loop error"); @@ -316,36 +302,38 @@ fn receiver_loop( } guard.clear(); } - Some(Ok(item)) => match item { - Message::Binary(data) => { - let response: Response = serde_json::from_slice(&data).unwrap(); - let mut guard = requests.lock().await; - if response.status.code != 206 { - let item = guard.remove(&response.request_id); - drop(guard); - if let Some(mut s) = item { - match s.send(Ok(response)).await { - Ok(_r) => {} - Err(_e) => {} - }; - } - } else { - let item = guard.get_mut(&response.request_id); - if let Some(s) = item { - match s.send(Ok(response)).await { - Ok(_r) => {} - Err(_e) => {} - }; + Some(Ok(item)) => { + match item { + Message::Binary(data) => { + let response: Response = serde_json::from_slice(&data).unwrap(); + let mut guard = requests.lock().await; + if response.status.code != 206 { + let item = guard.remove(&response.request_id); + drop(guard); + if let Some(mut s) = item { + match s.send(Ok(response)).await { + Ok(_r) => {} + Err(_e) => {} + }; + } + } else { + let item = guard.get_mut(&response.request_id); + if let Some(s) = item { + match s.send(Ok(response)).await { + Ok(_r) => {} + Err(_e) => {} + }; + } + drop(guard); } - drop(guard); } + Message::Ping(data) => { + info!("{connection_uuid} Received Ping"); + let _ = sender.send(Cmd::Pong(data)).await; + } + _ => {} } - Message::Ping(data) => { - info!("{connection_uuid} Received Ping"); - let _ = sender.send(Cmd::Pong(data)).await; - } - _ => {} - }, + } None => { warn!("{connection_uuid} Receiver loop breaking"); break; diff --git a/gremlin-client/src/aio/pool.rs b/gremlin-client/src/aio/pool.rs index 3d2ecfca..a9050ae0 100644 --- a/gremlin-client/src/aio/pool.rs +++ b/gremlin-client/src/aio/pool.rs @@ -1,3 +1,4 @@ +use futures::StreamExt; use mobc::Manager; use crate::aio::connection::Conn; @@ -56,7 +57,12 @@ impl Manager for GremlinConnectionManager { let mut binary = payload.into_bytes(); binary.insert(0, content_type.len() as u8); - let (response, _receiver) = conn.send(id, binary).await?; + let response = conn + .send(id, binary) + .await? + .next() + .await + .expect("Should have received response")?; match response.status.code { 200 | 206 => Ok(conn), @@ -87,7 +93,12 @@ impl Manager for GremlinConnectionManager { let mut binary = payload.into_bytes(); binary.insert(0, content_type.len() as u8); - let (response, _receiver) = conn.send(id, binary).await?; + let response = conn + .send(id, binary) + .await? + .next() + .await + .expect("Should have received response")?; match response.status.code { 200 | 206 => Ok(conn), From 8082d3b4fb34cf4c0381f7a2419b87ce9eda6ddf Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Sat, 21 Sep 2024 14:56:57 -0500 Subject: [PATCH 44/54] Formatting --- gremlin-client/src/aio/connection.rs | 56 ++++++++++++++-------------- 1 file changed, 27 insertions(+), 29 deletions(-) diff --git a/gremlin-client/src/aio/connection.rs b/gremlin-client/src/aio/connection.rs index 23af3b10..6fae0eb7 100644 --- a/gremlin-client/src/aio/connection.rs +++ b/gremlin-client/src/aio/connection.rs @@ -302,38 +302,36 @@ fn receiver_loop( } guard.clear(); } - Some(Ok(item)) => { - match item { - Message::Binary(data) => { - let response: Response = serde_json::from_slice(&data).unwrap(); - let mut guard = requests.lock().await; - if response.status.code != 206 { - let item = guard.remove(&response.request_id); - drop(guard); - if let Some(mut s) = item { - match s.send(Ok(response)).await { - Ok(_r) => {} - Err(_e) => {} - }; - } - } else { - let item = guard.get_mut(&response.request_id); - if let Some(s) = item { - match s.send(Ok(response)).await { - Ok(_r) => {} - Err(_e) => {} - }; - } - drop(guard); + Some(Ok(item)) => match item { + Message::Binary(data) => { + let response: Response = serde_json::from_slice(&data).unwrap(); + let mut guard = requests.lock().await; + if response.status.code != 206 { + let item = guard.remove(&response.request_id); + drop(guard); + if let Some(mut s) = item { + match s.send(Ok(response)).await { + Ok(_r) => {} + Err(_e) => {} + }; } + } else { + let item = guard.get_mut(&response.request_id); + if let Some(s) = item { + match s.send(Ok(response)).await { + Ok(_r) => {} + Err(_e) => {} + }; + } + drop(guard); } - Message::Ping(data) => { - info!("{connection_uuid} Received Ping"); - let _ = sender.send(Cmd::Pong(data)).await; - } - _ => {} } - } + Message::Ping(data) => { + info!("{connection_uuid} Received Ping"); + let _ = sender.send(Cmd::Pong(data)).await; + } + _ => {} + }, None => { warn!("{connection_uuid} Receiver loop breaking"); break; From 75008201c2ba4d1c122972df3b0d3eb3675421e6 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Tue, 24 Sep 2024 10:56:44 -0500 Subject: [PATCH 45/54] Removed internal channel bounding --- gremlin-client/src/aio/connection.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/gremlin-client/src/aio/connection.rs b/gremlin-client/src/aio/connection.rs index 6fae0eb7..f439348c 100644 --- a/gremlin-client/src/aio/connection.rs +++ b/gremlin-client/src/aio/connection.rs @@ -41,7 +41,9 @@ use futures::{ SinkExt, StreamExt, }; -use futures::channel::mpsc::{channel, Receiver, Sender}; +use futures::channel::mpsc::{ + channel, unbounded, Receiver, Sender, UnboundedReceiver, UnboundedSender, +}; use std::collections::HashMap; use std::sync::atomic::{self, AtomicBool}; use std::sync::Arc; @@ -64,7 +66,7 @@ pub enum Cmd { } pub(crate) struct Conn { - sender: Sender, + sender: UnboundedSender, valid: Arc, connection_uuid: Uuid, } @@ -166,7 +168,7 @@ impl Conn { info!("{connection_uuid} Opened websocket connection"); let (sink, stream) = client.split(); - let (sender, receiver) = channel(20); + let (sender, receiver) = unbounded(); let requests = Arc::new(Mutex::new(HashMap::new())); sender_loop(connection_uuid.clone(), sink, requests.clone(), receiver); @@ -236,7 +238,7 @@ fn sender_loop( connection_uuid: Uuid, mut sink: SplitSink, requests: Arc>>>>, - mut receiver: Receiver, + mut receiver: UnboundedReceiver, ) { task::spawn(async move { loop { @@ -282,7 +284,7 @@ fn receiver_loop( connection_uuid: Uuid, mut stream: SplitStream, requests: Arc>>>>, - mut sender: Sender, + mut sender: UnboundedSender, connection_valid_flag: Arc, ) { task::spawn(async move { From 839ef5e7c0ea390b94c7e444661b51086a740b48 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Thu, 26 Sep 2024 22:27:49 -0500 Subject: [PATCH 46/54] Revert "Removed internal channel bounding" This reverts commit 75008201c2ba4d1c122972df3b0d3eb3675421e6. --- gremlin-client/src/aio/connection.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/gremlin-client/src/aio/connection.rs b/gremlin-client/src/aio/connection.rs index f439348c..6fae0eb7 100644 --- a/gremlin-client/src/aio/connection.rs +++ b/gremlin-client/src/aio/connection.rs @@ -41,9 +41,7 @@ use futures::{ SinkExt, StreamExt, }; -use futures::channel::mpsc::{ - channel, unbounded, Receiver, Sender, UnboundedReceiver, UnboundedSender, -}; +use futures::channel::mpsc::{channel, Receiver, Sender}; use std::collections::HashMap; use std::sync::atomic::{self, AtomicBool}; use std::sync::Arc; @@ -66,7 +64,7 @@ pub enum Cmd { } pub(crate) struct Conn { - sender: UnboundedSender, + sender: Sender, valid: Arc, connection_uuid: Uuid, } @@ -168,7 +166,7 @@ impl Conn { info!("{connection_uuid} Opened websocket connection"); let (sink, stream) = client.split(); - let (sender, receiver) = unbounded(); + let (sender, receiver) = channel(20); let requests = Arc::new(Mutex::new(HashMap::new())); sender_loop(connection_uuid.clone(), sink, requests.clone(), receiver); @@ -238,7 +236,7 @@ fn sender_loop( connection_uuid: Uuid, mut sink: SplitSink, requests: Arc>>>>, - mut receiver: UnboundedReceiver, + mut receiver: Receiver, ) { task::spawn(async move { loop { @@ -284,7 +282,7 @@ fn receiver_loop( connection_uuid: Uuid, mut stream: SplitStream, requests: Arc>>>>, - mut sender: UnboundedSender, + mut sender: Sender, connection_valid_flag: Arc, ) { task::spawn(async move { From 41c753d60f8f59dd516d75b647dccbb59d812d01 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Thu, 26 Sep 2024 22:28:43 -0500 Subject: [PATCH 47/54] Revert "Formatting" This reverts commit 8082d3b4fb34cf4c0381f7a2419b87ce9eda6ddf. --- gremlin-client/src/aio/connection.rs | 56 ++++++++++++++-------------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/gremlin-client/src/aio/connection.rs b/gremlin-client/src/aio/connection.rs index 6fae0eb7..23af3b10 100644 --- a/gremlin-client/src/aio/connection.rs +++ b/gremlin-client/src/aio/connection.rs @@ -302,36 +302,38 @@ fn receiver_loop( } guard.clear(); } - Some(Ok(item)) => match item { - Message::Binary(data) => { - let response: Response = serde_json::from_slice(&data).unwrap(); - let mut guard = requests.lock().await; - if response.status.code != 206 { - let item = guard.remove(&response.request_id); - drop(guard); - if let Some(mut s) = item { - match s.send(Ok(response)).await { - Ok(_r) => {} - Err(_e) => {} - }; - } - } else { - let item = guard.get_mut(&response.request_id); - if let Some(s) = item { - match s.send(Ok(response)).await { - Ok(_r) => {} - Err(_e) => {} - }; + Some(Ok(item)) => { + match item { + Message::Binary(data) => { + let response: Response = serde_json::from_slice(&data).unwrap(); + let mut guard = requests.lock().await; + if response.status.code != 206 { + let item = guard.remove(&response.request_id); + drop(guard); + if let Some(mut s) = item { + match s.send(Ok(response)).await { + Ok(_r) => {} + Err(_e) => {} + }; + } + } else { + let item = guard.get_mut(&response.request_id); + if let Some(s) = item { + match s.send(Ok(response)).await { + Ok(_r) => {} + Err(_e) => {} + }; + } + drop(guard); } - drop(guard); } + Message::Ping(data) => { + info!("{connection_uuid} Received Ping"); + let _ = sender.send(Cmd::Pong(data)).await; + } + _ => {} } - Message::Ping(data) => { - info!("{connection_uuid} Received Ping"); - let _ = sender.send(Cmd::Pong(data)).await; - } - _ => {} - }, + } None => { warn!("{connection_uuid} Receiver loop breaking"); break; From 19c9a407d8b02b9cd66570baeaf5204c4a5ecbca Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Thu, 26 Sep 2024 22:28:50 -0500 Subject: [PATCH 48/54] Revert "Trial connection multiplexing for non-credential configured clients" This reverts commit 5db2b58905cce80df5200b011ee4578c8d296d92. --- gremlin-client/src/aio/client.rs | 23 ++---- gremlin-client/src/aio/connection.rs | 100 +++++++++++++++------------ gremlin-client/src/aio/pool.rs | 15 +--- 3 files changed, 62 insertions(+), 76 deletions(-) diff --git a/gremlin-client/src/aio/client.rs b/gremlin-client/src/aio/client.rs index c7dfa1c8..ad3c52f2 100644 --- a/gremlin-client/src/aio/client.rs +++ b/gremlin-client/src/aio/client.rs @@ -10,7 +10,6 @@ use crate::ToGValue; use crate::{ConnectionOptions, GremlinError, GremlinResult}; use base64::encode; use futures::future::{BoxFuture, FutureExt}; -use futures::StreamExt; use mobc::{Connection, Pool}; use serde::Serialize; use std::collections::{HashMap, VecDeque}; @@ -163,22 +162,8 @@ impl GremlinClient { let payload = String::from("") + content_type + &message; let mut binary = payload.into_bytes(); binary.insert(0, content_type.len() as u8); - let mut receiver = conn.send(id, binary).await?; - let response = receiver - .next() - .await - .expect("It should contain the response")?; - //Prepare holding onto the connection for an auth challenge if we have credentials - //Tinkerpop performs authentication at the channel level, and if we let it go, - //a healthcheck may disrupt the challenge - //Otherwise drop the connection so it can be multiplexed - let retained_auth_context = match self.options.credentials.as_ref() { - None => { - drop(conn); - None - } - Some(credentials) => Some((credentials, conn)), - }; + + let (response, receiver) = conn.send(id, binary).await?; let (response, results) = match response.status.code { 200 | 206 => { @@ -191,8 +176,8 @@ impl GremlinClient { Ok((response, results)) } 204 => Ok((response, VecDeque::new())), - 407 => match retained_auth_context { - Some((c, conn)) => { + 407 => match &self.options.credentials { + Some(c) => { let mut args = HashMap::new(); args.insert( diff --git a/gremlin-client/src/aio/connection.rs b/gremlin-client/src/aio/connection.rs index 23af3b10..b560d28f 100644 --- a/gremlin-client/src/aio/connection.rs +++ b/gremlin-client/src/aio/connection.rs @@ -43,7 +43,6 @@ use futures::{ use futures::channel::mpsc::{channel, Receiver, Sender}; use std::collections::HashMap; -use std::sync::atomic::{self, AtomicBool}; use std::sync::Arc; use url; use uuid::Uuid; @@ -65,7 +64,7 @@ pub enum Cmd { pub(crate) struct Conn { sender: Sender, - valid: Arc, + valid: bool, connection_uuid: Uuid, } @@ -171,19 +170,16 @@ impl Conn { sender_loop(connection_uuid.clone(), sink, requests.clone(), receiver); - let valid_flag = Arc::new(AtomicBool::new(true)); - receiver_loop( connection_uuid.clone(), stream, requests.clone(), sender.clone(), - valid_flag.clone(), ); Ok(Conn { sender, - valid: valid_flag, + valid: true, connection_uuid, }) } @@ -192,8 +188,8 @@ impl Conn { &mut self, id: Uuid, payload: Vec, - ) -> GremlinResult>> { - let (sender, receiver) = channel(1); + ) -> GremlinResult<(Response, Receiver>)> { + let (sender, mut receiver) = channel(1); self.sender .send(Cmd::Msg((sender, id, payload))) @@ -203,14 +199,35 @@ impl Conn { "{} Marking websocket connection invalid on send error", self.connection_uuid ); - self.valid.store(false, atomic::Ordering::Release); - GremlinError::from(e) + self.valid = false; + e + })?; + + receiver + .next() + .await + .expect("It should contain the response") + .map(|r| (r, receiver)) + .map_err(|e| { + //If there's been an websocket layer error, mark the connection as invalid + match e { + GremlinError::WebSocket(_) + | GremlinError::WebSocketAsync(_) + | GremlinError::WebSocketPoolAsync(_) => { + error!( + "{} Marking websocket connection invalid on received error", + self.connection_uuid + ); + self.valid = false; + } + _ => {} + } + e }) - .map(|_| receiver) } pub fn is_valid(&self) -> bool { - self.valid.load(atomic::Ordering::Acquire) + self.valid } } @@ -283,14 +300,11 @@ fn receiver_loop( mut stream: SplitStream, requests: Arc>>>>, mut sender: Sender, - connection_valid_flag: Arc, ) { task::spawn(async move { loop { match stream.next().await { Some(Err(error)) => { - //If there's been an websocket layer error, mark the connection as invalid - connection_valid_flag.store(false, atomic::Ordering::Release); let mut guard = requests.lock().await; let error = Arc::new(error); error!("{connection_uuid} Receiver loop error"); @@ -302,38 +316,36 @@ fn receiver_loop( } guard.clear(); } - Some(Ok(item)) => { - match item { - Message::Binary(data) => { - let response: Response = serde_json::from_slice(&data).unwrap(); - let mut guard = requests.lock().await; - if response.status.code != 206 { - let item = guard.remove(&response.request_id); - drop(guard); - if let Some(mut s) = item { - match s.send(Ok(response)).await { - Ok(_r) => {} - Err(_e) => {} - }; - } - } else { - let item = guard.get_mut(&response.request_id); - if let Some(s) = item { - match s.send(Ok(response)).await { - Ok(_r) => {} - Err(_e) => {} - }; - } - drop(guard); + Some(Ok(item)) => match item { + Message::Binary(data) => { + let response: Response = serde_json::from_slice(&data).unwrap(); + let mut guard = requests.lock().await; + if response.status.code != 206 { + let item = guard.remove(&response.request_id); + drop(guard); + if let Some(mut s) = item { + match s.send(Ok(response)).await { + Ok(_r) => {} + Err(_e) => {} + }; } + } else { + let item = guard.get_mut(&response.request_id); + if let Some(s) = item { + match s.send(Ok(response)).await { + Ok(_r) => {} + Err(_e) => {} + }; + } + drop(guard); } - Message::Ping(data) => { - info!("{connection_uuid} Received Ping"); - let _ = sender.send(Cmd::Pong(data)).await; - } - _ => {} } - } + Message::Ping(data) => { + info!("{connection_uuid} Received Ping"); + let _ = sender.send(Cmd::Pong(data)).await; + } + _ => {} + }, None => { warn!("{connection_uuid} Receiver loop breaking"); break; diff --git a/gremlin-client/src/aio/pool.rs b/gremlin-client/src/aio/pool.rs index a9050ae0..3d2ecfca 100644 --- a/gremlin-client/src/aio/pool.rs +++ b/gremlin-client/src/aio/pool.rs @@ -1,4 +1,3 @@ -use futures::StreamExt; use mobc::Manager; use crate::aio::connection::Conn; @@ -57,12 +56,7 @@ impl Manager for GremlinConnectionManager { let mut binary = payload.into_bytes(); binary.insert(0, content_type.len() as u8); - let response = conn - .send(id, binary) - .await? - .next() - .await - .expect("Should have received response")?; + let (response, _receiver) = conn.send(id, binary).await?; match response.status.code { 200 | 206 => Ok(conn), @@ -93,12 +87,7 @@ impl Manager for GremlinConnectionManager { let mut binary = payload.into_bytes(); binary.insert(0, content_type.len() as u8); - let response = conn - .send(id, binary) - .await? - .next() - .await - .expect("Should have received response")?; + let (response, _receiver) = conn.send(id, binary).await?; match response.status.code { 200 | 206 => Ok(conn), From cebe99adf1bf7139ce4dcf29ba7011dcfcb8029e Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Thu, 26 Sep 2024 22:33:21 -0500 Subject: [PATCH 49/54] Revert "Added uuid to connection instance logging" This reverts commit a342d8caaf0b93ee81dfac2545aeea969333e7be. --- gremlin-client/src/aio/connection.rs | 54 +++++++++------------------- 1 file changed, 16 insertions(+), 38 deletions(-) diff --git a/gremlin-client/src/aio/connection.rs b/gremlin-client/src/aio/connection.rs index b560d28f..2e103520 100644 --- a/gremlin-client/src/aio/connection.rs +++ b/gremlin-client/src/aio/connection.rs @@ -65,7 +65,6 @@ pub enum Cmd { pub(crate) struct Conn { sender: Sender, valid: bool, - connection_uuid: Uuid, } impl std::fmt::Debug for Conn { @@ -139,8 +138,7 @@ impl Conn { let url = url::Url::parse(&opts.websocket_url()).expect("failed to parse url"); let websocket_config = opts.websocket_options.as_ref().map(WebSocketConfig::from); - let connection_uuid = Uuid::new_v4(); - info!("{connection_uuid} Openning websocket connection"); + info!("Openning websocket connection"); #[cfg(feature = "async-std-runtime")] let (client, _) = { @@ -163,24 +161,18 @@ impl Conn { .await? }; - info!("{connection_uuid} Opened websocket connection"); + info!("Opened websocket connection"); let (sink, stream) = client.split(); let (sender, receiver) = channel(20); let requests = Arc::new(Mutex::new(HashMap::new())); - sender_loop(connection_uuid.clone(), sink, requests.clone(), receiver); + sender_loop(sink, requests.clone(), receiver); - receiver_loop( - connection_uuid.clone(), - stream, - requests.clone(), - sender.clone(), - ); + receiver_loop(stream, requests.clone(), sender.clone()); Ok(Conn { sender, valid: true, - connection_uuid, }) } @@ -195,10 +187,7 @@ impl Conn { .send(Cmd::Msg((sender, id, payload))) .await .map_err(|e| { - error!( - "{} Marking websocket connection invalid on send error", - self.connection_uuid - ); + error!("Marking websocket connection invalid on send error"); self.valid = false; e })?; @@ -214,10 +203,7 @@ impl Conn { GremlinError::WebSocket(_) | GremlinError::WebSocketAsync(_) | GremlinError::WebSocketPoolAsync(_) => { - error!( - "{} Marking websocket connection invalid on received error", - self.connection_uuid - ); + error!("Marking websocket connection invalid on received error"); self.valid = false; } _ => {} @@ -233,24 +219,17 @@ impl Conn { impl Drop for Conn { fn drop(&mut self) { - warn!( - "{} Websocket connection instance dropped", - self.connection_uuid - ); + warn!("Websocket connection instance dropped"); send_shutdown(self); } } fn send_shutdown(conn: &mut Conn) { - warn!( - "{} Websocket connection instance shutting down channel", - conn.connection_uuid - ); + warn!("Websocket connection instance shutting down channel"); conn.sender.close_channel(); } fn sender_loop( - connection_uuid: Uuid, mut sink: SplitSink, requests: Arc>>>>, mut receiver: Receiver, @@ -263,7 +242,7 @@ fn sender_loop( let mut guard = requests.lock().await; guard.insert(msg.1, msg.0); if let Err(e) = sink.send(Message::Binary(msg.2)).await { - error!("{connection_uuid} Sink sending error occured"); + error!("Sink sending error occured"); let mut sender = guard.remove(&msg.1).unwrap(); sender .send(Err(GremlinError::from(Arc::new(e)))) @@ -273,30 +252,29 @@ fn sender_loop( drop(guard); } Cmd::Pong(data) => { - info!("{connection_uuid} Sending Pong",); + info!("Sending Pong"); sink.send(Message::Pong(data)) .await .expect("Failed to send pong message."); } Cmd::Shutdown => { - warn!("{connection_uuid} Shuting down connection"); + warn!("Shuting down connection"); let mut guard = requests.lock().await; guard.clear(); } }, None => { - warn!("{connection_uuid} Sending loop breaking"); + warn!("Sending loop breaking"); break; } } } - warn!("{connection_uuid} Sending loop closing sink"); + warn!("Sending loop closing sink"); let _ = sink.close().await; }); } fn receiver_loop( - connection_uuid: Uuid, mut stream: SplitStream, requests: Arc>>>>, mut sender: Sender, @@ -307,7 +285,7 @@ fn receiver_loop( Some(Err(error)) => { let mut guard = requests.lock().await; let error = Arc::new(error); - error!("{connection_uuid} Receiver loop error"); + error!("Receiver loop error"); for s in guard.values_mut() { match s.send(Err(error.clone().into())).await { Ok(_r) => {} @@ -341,13 +319,13 @@ fn receiver_loop( } } Message::Ping(data) => { - info!("{connection_uuid} Received Ping"); + info!("Received Ping"); let _ = sender.send(Cmd::Pong(data)).await; } _ => {} }, None => { - warn!("{connection_uuid} Receiver loop breaking"); + warn!("Receiver loop breaking"); break; } } From 3b894b444d978b9604f8dbda2612a5d742d7c84d Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Thu, 26 Sep 2024 22:33:28 -0500 Subject: [PATCH 50/54] Revert "Exploratory logging" This reverts commit 03c24febba8780a95e1c8477463355f0c9a78f83. --- gremlin-client/Cargo.toml | 1 - gremlin-client/src/aio/connection.rs | 15 --------------- 2 files changed, 16 deletions(-) diff --git a/gremlin-client/Cargo.toml b/gremlin-client/Cargo.toml index c8911b1d..e3b6dfa4 100644 --- a/gremlin-client/Cargo.toml +++ b/gremlin-client/Cargo.toml @@ -64,7 +64,6 @@ futures = { version = "0.3.1", optional = true} pin-project-lite = { version = "0.2", optional = true} tokio = { version = "1", optional=true, features = ["full"] } -log = "0.4.22" [dependencies.uuid] features = ["serde", "v4"] diff --git a/gremlin-client/src/aio/connection.rs b/gremlin-client/src/aio/connection.rs index 2e103520..a0f311e0 100644 --- a/gremlin-client/src/aio/connection.rs +++ b/gremlin-client/src/aio/connection.rs @@ -22,7 +22,6 @@ mod tokio_use { } use futures::TryFutureExt; -use log::{error, info, warn}; #[cfg(feature = "tokio-runtime")] use tokio_use::*; @@ -138,7 +137,6 @@ impl Conn { let url = url::Url::parse(&opts.websocket_url()).expect("failed to parse url"); let websocket_config = opts.websocket_options.as_ref().map(WebSocketConfig::from); - info!("Openning websocket connection"); #[cfg(feature = "async-std-runtime")] let (client, _) = { @@ -161,7 +159,6 @@ impl Conn { .await? }; - info!("Opened websocket connection"); let (sink, stream) = client.split(); let (sender, receiver) = channel(20); let requests = Arc::new(Mutex::new(HashMap::new())); @@ -187,7 +184,6 @@ impl Conn { .send(Cmd::Msg((sender, id, payload))) .await .map_err(|e| { - error!("Marking websocket connection invalid on send error"); self.valid = false; e })?; @@ -203,7 +199,6 @@ impl Conn { GremlinError::WebSocket(_) | GremlinError::WebSocketAsync(_) | GremlinError::WebSocketPoolAsync(_) => { - error!("Marking websocket connection invalid on received error"); self.valid = false; } _ => {} @@ -219,13 +214,11 @@ impl Conn { impl Drop for Conn { fn drop(&mut self) { - warn!("Websocket connection instance dropped"); send_shutdown(self); } } fn send_shutdown(conn: &mut Conn) { - warn!("Websocket connection instance shutting down channel"); conn.sender.close_channel(); } @@ -242,7 +235,6 @@ fn sender_loop( let mut guard = requests.lock().await; guard.insert(msg.1, msg.0); if let Err(e) = sink.send(Message::Binary(msg.2)).await { - error!("Sink sending error occured"); let mut sender = guard.remove(&msg.1).unwrap(); sender .send(Err(GremlinError::from(Arc::new(e)))) @@ -252,24 +244,20 @@ fn sender_loop( drop(guard); } Cmd::Pong(data) => { - info!("Sending Pong"); sink.send(Message::Pong(data)) .await .expect("Failed to send pong message."); } Cmd::Shutdown => { - warn!("Shuting down connection"); let mut guard = requests.lock().await; guard.clear(); } }, None => { - warn!("Sending loop breaking"); break; } } } - warn!("Sending loop closing sink"); let _ = sink.close().await; }); } @@ -285,7 +273,6 @@ fn receiver_loop( Some(Err(error)) => { let mut guard = requests.lock().await; let error = Arc::new(error); - error!("Receiver loop error"); for s in guard.values_mut() { match s.send(Err(error.clone().into())).await { Ok(_r) => {} @@ -319,13 +306,11 @@ fn receiver_loop( } } Message::Ping(data) => { - info!("Received Ping"); let _ = sender.send(Cmd::Pong(data)).await; } _ => {} }, None => { - warn!("Receiver loop breaking"); break; } } From f2d4346bdb6d3bbf7e987bb8676338c221340803 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Thu, 26 Sep 2024 22:37:37 -0500 Subject: [PATCH 51/54] Formatting --- docker-compose/docker-compose.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/docker-compose/docker-compose.yaml b/docker-compose/docker-compose.yaml index b208dd53..38817ac5 100644 --- a/docker-compose/docker-compose.yaml +++ b/docker-compose/docker-compose.yaml @@ -31,4 +31,3 @@ services: interval: 10s timeout: 30s retries: 3 - \ No newline at end of file From 2c5bf51d0675cb5e4711e037529c36cc315cc87d Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Thu, 26 Sep 2024 22:39:36 -0500 Subject: [PATCH 52/54] Switched command to docker compose in coverage GH Action Workflow --- .github/workflows/coverage.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 8a5ece43..5ccca0ab 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -23,7 +23,7 @@ jobs: - uses: actions/checkout@v2 - name: Starting Gremlin Servers run: | - docker-compose -f ./docker-compose/docker-compose.yaml up -d + docker compose -f ./docker-compose/docker-compose.yaml up -d env: GREMLIN_SERVER: ${{ matrix.gremlin-server }} From d56179dc235874a49ab8ce0be79b59db4e9e84b3 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Mon, 7 Oct 2024 20:40:09 -0500 Subject: [PATCH 53/54] Corrected non-async tungstenite Error to using #[from] --- gremlin-client/src/connection.rs | 3 +-- gremlin-client/src/error.rs | 16 +--------------- 2 files changed, 2 insertions(+), 17 deletions(-) diff --git a/gremlin-client/src/connection.rs b/gremlin-client/src/connection.rs index 7b31462a..186d06ca 100644 --- a/gremlin-client/src/connection.rs +++ b/gremlin-client/src/connection.rs @@ -63,12 +63,11 @@ impl ConnectionStream { fn send(&mut self, payload: Vec) -> GremlinResult<()> { self.0 .write_message(Message::Binary(payload)) - .map_err(|e| Arc::new(e)) .map_err(GremlinError::from) } fn recv(&mut self) -> GremlinResult> { - match self.0.read_message().map_err(|e| Arc::new(e))? { + match self.0.read_message()? { Message::Binary(binary) => Ok(binary), _ => unimplemented!(), } diff --git a/gremlin-client/src/error.rs b/gremlin-client/src/error.rs index 58e01214..c88be829 100644 --- a/gremlin-client/src/error.rs +++ b/gremlin-client/src/error.rs @@ -14,7 +14,7 @@ pub enum GremlinError { Generic(String), #[error(transparent)] - WebSocket(tungstenite::error::Error), + WebSocket(#[from] tungstenite::Error), #[error(transparent)] Pool(#[from] r2d2::Error), @@ -56,17 +56,3 @@ impl From> for GremlinError { } } } - -#[cfg(not(feature = "async_gremlin"))] -impl From for GremlinError { - fn from(e: tungstenite::error::Error) -> GremlinError { - let error = match e { - tungstenite::error::Error::AlreadyClosed => tungstenite::error::Error::AlreadyClosed, - tungstenite::error::Error::ConnectionClosed => { - tungstenite::error::Error::ConnectionClosed - } - _ => return GremlinError::Generic(format!("Error from ws {}", e)), - }; - GremlinError::WebSocket(error) - } -} From fb18462d6a3a46708b2d6a87ae86694ef1727b6a Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Mon, 7 Oct 2024 21:21:12 -0500 Subject: [PATCH 54/54] Added running cargo test with no async feature enabled --- .github/workflows/test.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c209a85c..daece672 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -41,6 +41,12 @@ jobs: with: command: fmt args: --all -- --check + - name: Run cargo test with blocking client + if: matrix.gremlin-server == '3.5.7' + uses: actions-rs/cargo@v1 + with: + command: test + args: --manifest-path gremlin-client/Cargo.toml - name: Run cargo test with tokio if: matrix.gremlin-server == '3.5.7' uses: actions-rs/cargo@v1 @@ -54,6 +60,12 @@ jobs: command: test args: --manifest-path gremlin-client/Cargo.toml --features=async-std-runtime # MergeV as a step doesn't exist in 3.5.x, so selectively run those tests + - name: Run cargo test with blocking client + if: matrix.gremlin-server != '3.5.7' + uses: actions-rs/cargo@v1 + with: + command: test + args: --manifest-path gremlin-client/Cargo.toml --features=merge_tests - name: Run cargo test with tokio if: matrix.gremlin-server != '3.5.7' uses: actions-rs/cargo@v1