From 153b6cd3be6d97a27188b3c7f9713b8619849c09 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Mon, 7 Oct 2024 13:52:05 -0500 Subject: [PATCH 01/56] Generalized protocol enum to graphson alternatives --- gremlin-client/src/aio/client.rs | 10 +- gremlin-client/src/aio/pool.rs | 6 +- gremlin-client/src/client.rs | 10 +- gremlin-client/src/connection.rs | 14 +-- gremlin-client/src/io/graph_binary_v1.rs | 0 gremlin-client/src/io/mod.rs | 25 ++--- gremlin-client/src/lib.rs | 2 +- gremlin-client/src/pool.rs | 10 +- gremlin-client/tests/common.rs | 20 ++-- .../tests/integration_client_async_v2.rs | 18 ++-- gremlin-client/tests/integration_client_v2.rs | 34 +++---- .../tests/integration_traversal_async_v2.rs | 4 +- .../tests/integration_traversal_v2.rs | 94 +++++++++---------- 13 files changed, 124 insertions(+), 123 deletions(-) create mode 100644 gremlin-client/src/io/graph_binary_v1.rs diff --git a/gremlin-client/src/aio/client.rs b/gremlin-client/src/aio/client.rs index ad3c52f2..351e1661 100644 --- a/gremlin-client/src/aio/client.rs +++ b/gremlin-client/src/aio/client.rs @@ -1,6 +1,6 @@ use crate::aio::pool::GremlinConnectionManager; use crate::aio::GResultSet; -use crate::io::GraphSON; +use crate::io::Protocol; use crate::message::{ message_with_args, message_with_args_and_uuid, message_with_args_v2, Message, }; @@ -26,8 +26,8 @@ impl SessionedClient { let processor = "session".to_string(); let message = match self.options.serializer { - GraphSON::V2 => message_with_args_v2(String::from("close"), processor, args), - GraphSON::V3 => message_with_args(String::from("close"), processor, args), + Protocol::GraphSONV2 => message_with_args_v2(String::from("close"), processor, args), + Protocol::GraphSONV3 => message_with_args(String::from("close"), processor, args), }; let conn = self.pool.get().await?; @@ -140,8 +140,8 @@ impl GremlinClient { }; let message = match self.options.serializer { - GraphSON::V2 => message_with_args_v2(String::from("eval"), processor, args), - GraphSON::V3 => message_with_args(String::from("eval"), processor, args), + Protocol::GraphSONV2 => message_with_args_v2(String::from("eval"), processor, args), + Protocol::GraphSONV3 => message_with_args(String::from("eval"), processor, args), }; let conn = self.pool.get().await?; diff --git a/gremlin-client/src/aio/pool.rs b/gremlin-client/src/aio/pool.rs index 3d2ecfca..c2f05ae8 100644 --- a/gremlin-client/src/aio/pool.rs +++ b/gremlin-client/src/aio/pool.rs @@ -4,7 +4,7 @@ use crate::aio::connection::Conn; use crate::connection::ConnectionOptions; use crate::error::GremlinError; use crate::message::{message_with_args, message_with_args_and_uuid, message_with_args_v2}; -use crate::{GValue, GraphSON}; +use crate::{GValue, Protocol}; use async_trait::async_trait; use base64::encode; use std::collections::HashMap; @@ -43,8 +43,8 @@ impl Manager for GremlinConnectionManager { let args = self.options.serializer.write(&GValue::from(args))?; let message = match self.options.serializer { - GraphSON::V2 => message_with_args_v2(String::from("eval"), String::default(), args), - GraphSON::V3 => message_with_args(String::from("eval"), String::default(), args), + Protocol::GraphSONV2 => message_with_args_v2(String::from("eval"), String::default(), args), + Protocol::GraphSONV3 => message_with_args(String::from("eval"), String::default(), args), }; let id = message.id().clone(); diff --git a/gremlin-client/src/client.rs b/gremlin-client/src/client.rs index 8a24a8df..c7b6e355 100644 --- a/gremlin-client/src/client.rs +++ b/gremlin-client/src/client.rs @@ -1,4 +1,4 @@ -use crate::io::GraphSON; +use crate::io::Protocol; use crate::message::{ message_with_args, message_with_args_and_uuid, message_with_args_v2, Message, Response, }; @@ -24,8 +24,8 @@ impl SessionedClient { let processor = "session".to_string(); let message = match self.options.serializer { - GraphSON::V2 => message_with_args_v2(String::from("close"), processor, args), - GraphSON::V3 => message_with_args(String::from("close"), processor, args), + Protocol::GraphSONV2 => message_with_args_v2(String::from("close"), processor, args), + Protocol::GraphSONV3 => message_with_args(String::from("close"), processor, args), }; let conn = self.pool.get()?; @@ -136,8 +136,8 @@ impl GremlinClient { }; let message = match self.options.serializer { - GraphSON::V2 => message_with_args_v2(String::from("eval"), processor, args), - GraphSON::V3 => message_with_args(String::from("eval"), processor, args), + Protocol::GraphSONV2 => message_with_args_v2(String::from("eval"), processor, args), + Protocol::GraphSONV3 => message_with_args(String::from("eval"), processor, args), }; let conn = self.pool.get()?; diff --git a/gremlin-client/src/connection.rs b/gremlin-client/src/connection.rs index 186d06ca..26cd4a64 100644 --- a/gremlin-client/src/connection.rs +++ b/gremlin-client/src/connection.rs @@ -1,6 +1,6 @@ use std::{net::TcpStream, sync::Arc, time::Duration}; -use crate::{GraphSON, GremlinError, GremlinResult}; +use crate::{Protocol, GremlinError, GremlinResult}; use native_tls::TlsConnector; use tungstenite::{ client::{uri_mode, IntoClientRequest}, @@ -164,12 +164,12 @@ impl ConnectionOptionsBuilder { self } - pub fn serializer(mut self, serializer: GraphSON) -> Self { + pub fn serializer(mut self, serializer: Protocol) -> Self { self.0.serializer = serializer; self } - pub fn deserializer(mut self, deserializer: GraphSON) -> Self { + pub fn deserializer(mut self, deserializer: Protocol) -> Self { self.0.deserializer = deserializer; self } @@ -185,8 +185,8 @@ pub struct ConnectionOptions { pub(crate) credentials: Option, pub(crate) ssl: bool, pub(crate) tls_options: Option, - pub(crate) serializer: GraphSON, - pub(crate) deserializer: GraphSON, + pub(crate) serializer: Protocol, + pub(crate) deserializer: Protocol, pub(crate) websocket_options: Option, } @@ -269,8 +269,8 @@ impl Default for ConnectionOptions { credentials: None, ssl: false, tls_options: None, - serializer: GraphSON::V3, - deserializer: GraphSON::V3, + serializer: Protocol::GraphSONV3, + deserializer: Protocol::GraphSONV3, websocket_options: None, } } diff --git a/gremlin-client/src/io/graph_binary_v1.rs b/gremlin-client/src/io/graph_binary_v1.rs new file mode 100644 index 00000000..e69de29b diff --git a/gremlin-client/src/io/mod.rs b/gremlin-client/src/io/mod.rs index 214d59ef..85008390 100644 --- a/gremlin-client/src/io/mod.rs +++ b/gremlin-client/src/io/mod.rs @@ -2,6 +2,7 @@ mod macros; mod serializer_v2; mod serializer_v3; +mod graph_binary_v1; use crate::conversion::ToGValue; use crate::process::traversal::{Order, Scope}; @@ -12,19 +13,19 @@ use std::string::ToString; use crate::{GremlinError, GremlinResult}; #[derive(Debug, Clone)] -pub enum GraphSON { - V2, - V3, +pub enum Protocol { + GraphSONV2, + GraphSONV3, } -impl GraphSON { +impl Protocol { pub fn read(&self, value: &Value) -> GremlinResult> { if let Value::Null = value { return Ok(None); } match self { - GraphSON::V2 => serializer_v2::deserializer_v2(value).map(Some), - GraphSON::V3 => serializer_v3::deserializer_v3(value).map(Some), + Protocol::GraphSONV2 => serializer_v2::deserializer_v2(value).map(Some), + Protocol::GraphSONV3 => serializer_v3::deserializer_v3(value).map(Some), } } @@ -55,11 +56,11 @@ impl GraphSON { "@type" : "g:Date", "@value" : d.timestamp_millis() })), - (GraphSON::V2, GValue::List(d)) => { + (Protocol::GraphSONV2, GValue::List(d)) => { let elements: GremlinResult> = d.iter().map(|e| self.write(e)).collect(); Ok(json!(elements?)) } - (GraphSON::V3, GValue::List(d)) => { + (Protocol::GraphSONV3, GValue::List(d)) => { let elements: GremlinResult> = d.iter().map(|e| self.write(e)).collect(); Ok(json!({ "@type" : "g:List", @@ -120,7 +121,7 @@ impl GraphSON { } })) } - (GraphSON::V2, GValue::Map(map)) => { + (Protocol::GraphSONV2, GValue::Map(map)) => { let mut params = Map::new(); for (k, v) in map.iter() { @@ -137,7 +138,7 @@ impl GraphSON { Ok(json!(params)) } - (GraphSON::V3, GValue::Map(map)) => { + (Protocol::GraphSONV3, GValue::Map(map)) => { let mut params = vec![]; for (k, v) in map.iter() { @@ -254,8 +255,8 @@ impl GraphSON { pub fn content_type(&self) -> &str { match self { - GraphSON::V2 => "application/vnd.gremlin-v2.0+json", - GraphSON::V3 => "application/vnd.gremlin-v3.0+json", + Protocol::GraphSONV2 => "application/vnd.gremlin-v2.0+json", + Protocol::GraphSONV3 => "application/vnd.gremlin-v3.0+json", } } } diff --git a/gremlin-client/src/lib.rs b/gremlin-client/src/lib.rs index 8b28ce22..06452a3f 100644 --- a/gremlin-client/src/lib.rs +++ b/gremlin-client/src/lib.rs @@ -128,7 +128,7 @@ pub use connection::{ }; pub use conversion::{BorrowFromGValue, FromGValue, ToGValue}; pub use error::GremlinError; -pub use io::GraphSON; +pub use io::Protocol; pub use message::Message; pub type GremlinResult = Result; diff --git a/gremlin-client/src/pool.rs b/gremlin-client/src/pool.rs index 2085f356..473b1e74 100644 --- a/gremlin-client/src/pool.rs +++ b/gremlin-client/src/pool.rs @@ -6,7 +6,7 @@ use crate::error::GremlinError; use crate::message::{ message_with_args, message_with_args_and_uuid, message_with_args_v2, Response, }; -use crate::{GValue, GraphSON, GremlinResult}; +use crate::{GValue, Protocol, GremlinResult}; use base64::encode; use std::collections::HashMap; @@ -43,15 +43,15 @@ impl ManageConnection for GremlinConnectionManager { let args = self.options.serializer.write(&GValue::from(args))?; let message = match self.options.serializer { - GraphSON::V2 => message_with_args_v2(String::from("eval"), String::default(), args), - GraphSON::V3 => message_with_args(String::from("eval"), String::default(), args), + Protocol::GraphSONV2 => message_with_args_v2(String::from("eval"), String::default(), args), + Protocol::GraphSONV3 => message_with_args(String::from("eval"), String::default(), args), }; let msg = serde_json::to_string(&message).map_err(GremlinError::from)?; let content_type = match self.options.serializer { - GraphSON::V2 => "application/vnd.gremlin-v2.0+json", - GraphSON::V3 => "application/vnd.gremlin-v3.0+json", + Protocol::GraphSONV2 => "application/vnd.gremlin-v2.0+json", + Protocol::GraphSONV3 => "application/vnd.gremlin-v3.0+json", }; let payload = String::from("") + content_type + &msg; diff --git a/gremlin-client/tests/common.rs b/gremlin-client/tests/common.rs index af3a5b44..88fc0598 100644 --- a/gremlin-client/tests/common.rs +++ b/gremlin-client/tests/common.rs @@ -11,7 +11,7 @@ pub fn assert_map_property(element_map: &Map, expected_key: &str, expected_value #[allow(dead_code)] pub mod io { - use gremlin_client::{ConnectionOptions, Edge, GraphSON, GremlinClient, GremlinResult, Vertex}; + use gremlin_client::{ConnectionOptions, Edge, Protocol, GremlinClient, GremlinResult, Vertex}; pub fn connect() -> GremlinResult { GremlinClient::connect(("localhost", 8182)) @@ -21,10 +21,10 @@ pub mod io { GremlinClient::connect(("localhost", 8184)) } - pub fn connect_serializer(serializer: GraphSON) -> GremlinResult { + pub fn connect_serializer(serializer: Protocol) -> GremlinResult { let port = match serializer { - GraphSON::V2 => 8182, - GraphSON::V3 => 8182, + Protocol::GraphSONV2 => 8182, + Protocol::GraphSONV3 => 8182, }; GremlinClient::connect( ConnectionOptions::builder() @@ -44,7 +44,7 @@ pub mod io { connect_janusgraph_client().expect("It should connect") } - pub fn expect_client_serializer(serializer: GraphSON) -> GremlinClient { + pub fn expect_client_serializer(serializer: Protocol) -> GremlinClient { connect_serializer(serializer).expect("It should connect") } @@ -54,7 +54,7 @@ pub mod io { client } - pub fn graph_serializer(serializer: GraphSON) -> GremlinClient { + pub fn graph_serializer(serializer: Protocol) -> GremlinClient { let client = expect_client_serializer(serializer); client @@ -112,7 +112,7 @@ pub mod io { pub mod aio { use gremlin_client::aio::GremlinClient; - use gremlin_client::{ConnectionOptions, Edge, GraphSON, GremlinResult, Vertex}; + use gremlin_client::{ConnectionOptions, Edge, Protocol, GremlinResult, Vertex}; #[cfg(feature = "async-std-runtime")] use async_std::prelude::*; @@ -126,10 +126,10 @@ pub mod aio { .expect("It should connect") } - pub async fn connect_serializer(serializer: GraphSON) -> GremlinClient { + pub async fn connect_serializer(serializer: Protocol) -> GremlinClient { let port = match serializer { - GraphSON::V2 => 8182, - GraphSON::V3 => 8182, + Protocol::GraphSONV2 => 8182, + Protocol::GraphSONV3 => 8182, }; GremlinClient::connect( ConnectionOptions::builder() diff --git a/gremlin-client/tests/integration_client_async_v2.rs b/gremlin-client/tests/integration_client_async_v2.rs index 270eb7bd..41ff7ce5 100644 --- a/gremlin-client/tests/integration_client_async_v2.rs +++ b/gremlin-client/tests/integration_client_async_v2.rs @@ -5,7 +5,7 @@ mod common; mod aio { use gremlin_client::GremlinError; - use gremlin_client::{Edge, GValue, GraphSON, Map, Vertex}; + use gremlin_client::{Edge, GValue, Protocol, Map, Vertex}; use super::common::aio::{connect_serializer, create_edge, create_vertex}; #[cfg(feature = "async-std-runtime")] @@ -17,13 +17,13 @@ mod aio { #[cfg_attr(feature = "async-std-runtime", async_std::test)] #[cfg_attr(feature = "tokio-runtime", tokio::test)] async fn test_client_connection_ok_v2() { - connect_serializer(GraphSON::V2).await; + connect_serializer(Protocol::GraphSONV2).await; } #[cfg(feature = "async-std-runtime")] #[cfg_attr(feature = "async-std-runtime", async_std::test)] async fn test_empty_query_v2() { - let graph = connect_serializer(GraphSON::V2).await; + let graph = connect_serializer(Protocol::GraphSONV2).await; assert_eq!( 0, @@ -39,7 +39,7 @@ mod aio { #[cfg_attr(feature = "async-std-runtime", async_std::test)] #[cfg_attr(feature = "tokio-runtime", tokio::test)] async fn test_wrong_query_v2() { - let error = connect_serializer(GraphSON::V2) + let error = connect_serializer(Protocol::GraphSONV2) .await .execute("g.V", &[]) .await @@ -57,7 +57,7 @@ mod aio { #[cfg_attr(feature = "async-std-runtime", async_std::test)] #[cfg_attr(feature = "tokio-runtime", tokio::test)] async fn test_wrong_alias_v2() { - let error = connect_serializer(GraphSON::V2) + let error = connect_serializer(Protocol::GraphSONV2) .await .alias("foo") .execute("g.V()", &[]) @@ -77,7 +77,7 @@ mod aio { #[cfg_attr(feature = "tokio-runtime", tokio::test)] async fn test_vertex_query_v2() { - let graph = connect_serializer(GraphSON::V2).await; + let graph = connect_serializer(Protocol::GraphSONV2).await; println!("About to execute query."); let vertices = graph @@ -98,7 +98,7 @@ mod aio { #[cfg_attr(feature = "async-std-runtime", async_std::test)] #[cfg_attr(feature = "tokio-runtime", tokio::test)] async fn test_edge_query_v2() { - let graph = connect_serializer(GraphSON::V2).await; + let graph = connect_serializer(Protocol::GraphSONV2).await; let edges = graph .execute("g.E().hasLabel('knows').limit(1)", &[]) .await @@ -115,7 +115,7 @@ mod aio { #[cfg_attr(feature = "async-std-runtime", async_std::test)] #[cfg_attr(feature = "tokio-runtime", tokio::test)] async fn test_vertex_creation_v2() { - let graph = connect_serializer(GraphSON::V2).await; + let graph = connect_serializer(Protocol::GraphSONV2).await; let mark = create_vertex(&graph, "mark").await; assert_eq!("person", mark.label()); @@ -141,7 +141,7 @@ mod aio { #[cfg_attr(feature = "async-std-runtime", async_std::test)] #[cfg_attr(feature = "tokio-runtime", tokio::test)] async fn test_edge_creation_v2() { - let graph = connect_serializer(GraphSON::V2).await; + let graph = connect_serializer(Protocol::GraphSONV2).await; let mark = create_vertex(&graph, "mark").await; let frank = create_vertex(&graph, "frank").await; diff --git a/gremlin-client/tests/integration_client_v2.rs b/gremlin-client/tests/integration_client_v2.rs index 47bfb18a..6e2cb8dd 100644 --- a/gremlin-client/tests/integration_client_v2.rs +++ b/gremlin-client/tests/integration_client_v2.rs @@ -1,7 +1,7 @@ mod common; use gremlin_client::{ - ConnectionOptions, GraphSON, GremlinClient, GremlinError, List, TlsOptions, ToGValue, + ConnectionOptions, Protocol, GremlinClient, GremlinError, List, TlsOptions, ToGValue, TraversalExplanation, TraversalMetrics, VertexProperty, }; use gremlin_client::{Edge, GKey, GValue, Map, Vertex, GID}; @@ -10,14 +10,14 @@ use common::io::{create_edge, create_vertex, expect_client_serializer, graph_ser #[test] fn test_client_connection_ok_v2() { - expect_client_serializer(GraphSON::V2); + expect_client_serializer(Protocol::GraphSONV2); } #[test] fn test_empty_query_v2() { assert_eq!( 0, - graph_serializer(GraphSON::V2) + graph_serializer(Protocol::GraphSONV2) .execute("g.V().hasLabel('NotFound')", &[]) .expect("It should execute a traversal") .count() @@ -35,7 +35,7 @@ fn test_ok_credentials_v2() { .tls_options(TlsOptions { accept_invalid_certs: true, }) - .serializer(GraphSON::V2) + .serializer(Protocol::GraphSONV2) .build(), ) .expect("Cannot connect"); @@ -55,7 +55,7 @@ fn test_ko_credentials_v2() { .tls_options(TlsOptions { accept_invalid_certs: true, }) - .serializer(GraphSON::V2) + .serializer(Protocol::GraphSONV2) .build(), ) .expect("Cannot connect"); @@ -66,7 +66,7 @@ fn test_ko_credentials_v2() { #[test] fn test_wrong_query_v2() { - let error = graph_serializer(GraphSON::V2) + let error = graph_serializer(Protocol::GraphSONV2) .execute("g.V", &[]) .expect_err("it should return an error"); @@ -81,7 +81,7 @@ fn test_wrong_query_v2() { #[test] fn test_wrong_alias_v2() { - let error = graph_serializer(GraphSON::V2) + let error = graph_serializer(Protocol::GraphSONV2) .alias("foo") .execute("g.V()", &[]) .expect_err("it should return an error"); @@ -98,7 +98,7 @@ fn test_wrong_alias_v2() { #[test] fn test_vertex_query_v2() { - let graph = graph_serializer(GraphSON::V2); + let graph = graph_serializer(Protocol::GraphSONV2); let vertices = graph .execute( "g.V().hasLabel('person').has('name',name)", @@ -114,7 +114,7 @@ fn test_vertex_query_v2() { } #[test] fn test_edge_query_v2() { - let graph = graph_serializer(GraphSON::V2); + let graph = graph_serializer(Protocol::GraphSONV2); let edges = graph .execute("g.E().hasLabel('knows').limit(1)", &[]) .expect("it should execute a query") @@ -128,7 +128,7 @@ fn test_edge_query_v2() { #[test] fn test_vertex_creation_v2() { - let graph = graph_serializer(GraphSON::V2); + let graph = graph_serializer(Protocol::GraphSONV2); let mark = create_vertex(&graph, "mark"); assert_eq!("person", mark.label()); @@ -155,7 +155,7 @@ fn test_inserting_date_with_milisecond_precision() { use chrono::DateTime; use chrono::Utc; - let graph = graph_serializer(GraphSON::V2); + let graph = graph_serializer(Protocol::GraphSONV2); let q = r#"g.addV('person').property('dateTime',dateTime).propertyMap()"#; @@ -187,7 +187,7 @@ fn test_inserting_date_with_milisecond_precision() { fn test_complex_vertex_creation_with_properties_v2() { use chrono::offset::TimeZone; - let graph = graph_serializer(GraphSON::V2); + let graph = graph_serializer(Protocol::GraphSONV2); let q = r#" g.addV('person') @@ -295,7 +295,7 @@ fn test_complex_vertex_creation_with_properties_v2() { #[test] fn test_edge_creation_v2() { - let graph = graph_serializer(GraphSON::V2); + let graph = graph_serializer(Protocol::GraphSONV2); let mark = create_vertex(&graph, "mark"); let frank = create_vertex(&graph, "frank"); @@ -326,7 +326,7 @@ fn test_edge_creation_v2() { #[test] fn test_profile_v2() { - let graph = graph_serializer(GraphSON::V2); + let graph = graph_serializer(Protocol::GraphSONV2); let metrics = graph .execute("g.V().limit(1).profile()", &[]) @@ -358,7 +358,7 @@ fn test_profile_v2() { #[test] fn test_explain_v2() { - let graph = graph_serializer(GraphSON::V2); + let graph = graph_serializer(Protocol::GraphSONV2); let metrics = graph .execute("g.V().limit(1).explain()", &[]) @@ -393,7 +393,7 @@ fn test_explain_v2() { #[test] fn test_group_count_vertex_v2() { - let graph = graph_serializer(GraphSON::V2); + let graph = graph_serializer(Protocol::GraphSONV2); let mark = create_vertex(&graph, "mark"); let frank = create_vertex(&graph, "frank"); @@ -432,7 +432,7 @@ fn test_group_count_vertex_v2() { #[test] fn test_group_count_edge_v2() { - let graph = graph_serializer(GraphSON::V2); + let graph = graph_serializer(Protocol::GraphSONV2); let mark = create_vertex(&graph, "mark"); let frank = create_vertex(&graph, "frank"); diff --git a/gremlin-client/tests/integration_traversal_async_v2.rs b/gremlin-client/tests/integration_traversal_async_v2.rs index b7ef1c70..82435f94 100644 --- a/gremlin-client/tests/integration_traversal_async_v2.rs +++ b/gremlin-client/tests/integration_traversal_async_v2.rs @@ -13,12 +13,12 @@ mod aio { #[cfg(feature = "tokio-runtime")] use tokio_stream::StreamExt; - use gremlin_client::{GraphSON, Vertex}; + use gremlin_client::{Protocol, Vertex}; #[cfg_attr(feature = "async-std-runtime", async_std::test)] #[cfg_attr(feature = "tokio-runtime", tokio::test)] async fn test_simple_vertex_traversal_with_multiple_id_v2() { - let client = connect_serializer(GraphSON::V2).await; + let client = connect_serializer(Protocol::GraphSONV2).await; drop_vertices(&client, "test_simple_vertex_traversal_async") .await .unwrap(); diff --git a/gremlin-client/tests/integration_traversal_v2.rs b/gremlin-client/tests/integration_traversal_v2.rs index 50e3ec00..3534ef76 100644 --- a/gremlin-client/tests/integration_traversal_v2.rs +++ b/gremlin-client/tests/integration_traversal_v2.rs @@ -2,7 +2,7 @@ use gremlin_client::process::traversal::{traversal, Order, __}; use gremlin_client::structure::{ Cardinality, GKey, GValue, List, Map, Pop, TextP, Vertex, VertexProperty, GID, P, T, }; -use gremlin_client::{utils, GraphSON}; +use gremlin_client::{utils, Protocol}; mod common; @@ -13,7 +13,7 @@ use common::io::{ #[test] fn test_simple_vertex_traversal_v2() { - let g = traversal().with_remote(graph_serializer(GraphSON::V2)); + let g = traversal().with_remote(graph_serializer(Protocol::GraphSONV2)); let results = g.v(()).to_list().unwrap(); @@ -22,7 +22,7 @@ fn test_simple_vertex_traversal_v2() { #[test] fn test_simple_vertex_traversal_with_id_v2() { - let client = graph_serializer(GraphSON::V2); + let client = graph_serializer(Protocol::GraphSONV2); let vertex = create_vertex(&client, "Traversal"); @@ -37,7 +37,7 @@ fn test_simple_vertex_traversal_with_id_v2() { #[test] fn test_simple_vertex_traversal_with_multiple_id_v2() { - let client = graph_serializer(GraphSON::V2); + let client = graph_serializer(Protocol::GraphSONV2); drop_vertices(&client, "test_simple_vertex_traversal").unwrap(); let vertex = create_vertex_with_label(&client, "test_simple_vertex_traversal", "Traversal"); @@ -55,7 +55,7 @@ fn test_simple_vertex_traversal_with_multiple_id_v2() { #[test] fn test_simple_vertex_traversal_with_label_v2() { - let client = graph_serializer(GraphSON::V2); + let client = graph_serializer(Protocol::GraphSONV2); drop_vertices(&client, "test_simple_vertex_traversal_with_label").unwrap(); @@ -80,7 +80,7 @@ fn test_simple_vertex_traversal_with_label_v2() { #[test] fn test_simple_vertex_traversal_with_label_and_has_v2() { - let client = graph_serializer(GraphSON::V2); + let client = graph_serializer(Protocol::GraphSONV2); drop_vertices(&client, "test_simple_vertex_traversal_with_label_and_has").unwrap(); @@ -148,7 +148,7 @@ fn test_simple_vertex_traversal_with_label_and_has_v2() { #[test] fn test_simple_edge_traversal_v2() { - let g = traversal().with_remote(graph_serializer(GraphSON::V2)); + let g = traversal().with_remote(graph_serializer(Protocol::GraphSONV2)); let results = g.e(()).to_list().unwrap(); @@ -157,7 +157,7 @@ fn test_simple_edge_traversal_v2() { #[test] fn test_simple_edge_traversal_id_v2() { - let client = graph_serializer(GraphSON::V2); + let client = graph_serializer(Protocol::GraphSONV2); let v = create_vertex(&client, "Traversal"); let v1 = create_vertex(&client, "Traversal"); @@ -175,7 +175,7 @@ fn test_simple_edge_traversal_id_v2() { #[test] fn test_simple_edge_traversal_with_label_v2() { - let client = graph_serializer(GraphSON::V2); + let client = graph_serializer(Protocol::GraphSONV2); drop_edges(&client, "test_simple_edge_traversal_with_label").unwrap(); @@ -199,7 +199,7 @@ fn test_simple_edge_traversal_with_label_v2() { #[test] fn test_traversal_v2() { - let client = graph_serializer(GraphSON::V2); + let client = graph_serializer(Protocol::GraphSONV2); drop_edges(&client, "test_vertex_out_traversal").unwrap(); @@ -342,7 +342,7 @@ fn test_traversal_v2() { #[test] fn test_add_v_v2() { - let g = traversal().with_remote(graph_serializer(GraphSON::V2)); + let g = traversal().with_remote(graph_serializer(Protocol::GraphSONV2)); let results = g.add_v("person").to_list().unwrap(); @@ -360,7 +360,7 @@ fn test_add_v_v2() { #[test] fn test_add_v_with_properties_v2() { - let client = graph_serializer(GraphSON::V2); + let client = graph_serializer(Protocol::GraphSONV2); let g = traversal().with_remote(client.clone()); let results = g @@ -405,7 +405,7 @@ fn test_add_v_with_properties_v2() { #[test] fn test_add_v_with_property_many_v2() { - let client = graph_serializer(GraphSON::V2); + let client = graph_serializer(Protocol::GraphSONV2); drop_vertices(&client, "test_add_v_with_property_many").unwrap(); @@ -455,7 +455,7 @@ fn test_add_v_with_property_many_v2() { #[test] fn test_has_many_v2() { - let client = graph_serializer(GraphSON::V2); + let client = graph_serializer(Protocol::GraphSONV2); drop_vertices(&client, "test_has_many").unwrap(); @@ -488,7 +488,7 @@ fn test_has_many_v2() { #[test] fn test_add_e_v2() { - let client = graph_serializer(GraphSON::V2); + let client = graph_serializer(Protocol::GraphSONV2); let g = traversal().with_remote(client.clone()); let v = g @@ -551,7 +551,7 @@ fn test_add_e_v2() { #[test] fn test_label_step_v2() { - let client = graph_serializer(GraphSON::V2); + let client = graph_serializer(Protocol::GraphSONV2); let vertex = create_vertex(&client, "Traversal"); @@ -566,7 +566,7 @@ fn test_label_step_v2() { #[test] fn test_properties_step_v2() { - let client = graph_serializer(GraphSON::V2); + let client = graph_serializer(Protocol::GraphSONV2); let vertex = create_vertex(&client, "Traversal"); @@ -591,7 +591,7 @@ fn test_properties_step_v2() { #[test] fn test_property_map_v2() { - let client = graph_serializer(GraphSON::V2); + let client = graph_serializer(Protocol::GraphSONV2); let vertex = create_vertex(&client, "Traversal"); @@ -638,7 +638,7 @@ fn test_property_map_v2() { #[test] fn test_values_v2() { - let client = graph_serializer(GraphSON::V2); + let client = graph_serializer(Protocol::GraphSONV2); let vertex = create_vertex(&client, "Traversal"); @@ -667,7 +667,7 @@ fn test_values_v2() { #[test] fn test_value_map_v2() { - let client = graph_serializer(GraphSON::V2); + let client = graph_serializer(Protocol::GraphSONV2); let g = traversal().with_remote(client); @@ -718,7 +718,7 @@ fn test_value_map_v2() { #[test] fn test_unwrap_map_v2() { - let client = graph_serializer(GraphSON::V2); + let client = graph_serializer(Protocol::GraphSONV2); let g = traversal().with_remote(client); @@ -748,7 +748,7 @@ fn test_unwrap_map_v2() { #[test] fn test_element_map_v2() { - let client = graph_serializer(GraphSON::V2); + let client = graph_serializer(Protocol::GraphSONV2); let g = traversal().with_remote(client); @@ -791,7 +791,7 @@ fn test_element_map_v2() { #[test] fn test_count_v2() { - let client = graph_serializer(GraphSON::V2); + let client = graph_serializer(Protocol::GraphSONV2); let vertex = create_vertex_with_label(&client, "test_count", "Count"); @@ -808,7 +808,7 @@ fn test_count_v2() { #[test] fn test_group_count_step_v2() { - let client = graph_serializer(GraphSON::V2); + let client = graph_serializer(Protocol::GraphSONV2); drop_vertices(&client, "test_group_count").unwrap(); @@ -871,7 +871,7 @@ fn test_group_count_step_v2() { #[test] fn test_group_by_step_v2() { - let client = graph_serializer(GraphSON::V2); + let client = graph_serializer(Protocol::GraphSONV2); drop_vertices(&client, "test_group_by_step").unwrap(); @@ -927,7 +927,7 @@ fn test_group_by_step_v2() { #[test] fn test_select_step_v2() { - let client = graph_serializer(GraphSON::V2); + let client = graph_serializer(Protocol::GraphSONV2); drop_vertices(&client, "test_select_step").unwrap(); @@ -953,7 +953,7 @@ fn test_select_step_v2() { #[test] fn test_fold_step_v2() { - let client = graph_serializer(GraphSON::V2); + let client = graph_serializer(Protocol::GraphSONV2); drop_vertices(&client, "test_fold_step").unwrap(); @@ -978,7 +978,7 @@ fn test_fold_step_v2() { #[test] fn test_unfold_step_v2() { - let client = graph_serializer(GraphSON::V2); + let client = graph_serializer(Protocol::GraphSONV2); drop_vertices(&client, "test_unfold_step").unwrap(); @@ -1009,7 +1009,7 @@ fn test_unfold_step_v2() { #[test] fn test_path_step_v2() { - let client = graph_serializer(GraphSON::V2); + let client = graph_serializer(Protocol::GraphSONV2); drop_vertices(&client, "test_path_step").unwrap(); @@ -1033,7 +1033,7 @@ fn test_path_step_v2() { #[test] fn test_limit_step_v2() { - let client = graph_serializer(GraphSON::V2); + let client = graph_serializer(Protocol::GraphSONV2); drop_vertices(&client, "test_limit_step").unwrap(); @@ -1054,7 +1054,7 @@ fn test_limit_step_v2() { #[test] fn test_dedup_step_v2() { - let client = graph_serializer(GraphSON::V2); + let client = graph_serializer(Protocol::GraphSONV2); drop_vertices(&client, "test_limit_step").unwrap(); @@ -1076,7 +1076,7 @@ fn test_dedup_step_v2() { #[test] fn test_numerical_steps_v2() { - let client = graph_serializer(GraphSON::V2); + let client = graph_serializer(Protocol::GraphSONV2); drop_vertices(&client, "test_numerical_steps").unwrap(); @@ -1148,7 +1148,7 @@ fn test_numerical_steps_v2() { #[test] fn test_has_with_p_steps_v2() { - let client = graph_serializer(GraphSON::V2); + let client = graph_serializer(Protocol::GraphSONV2); drop_vertices(&client, "test_has_with_p_steps").unwrap(); @@ -1201,7 +1201,7 @@ fn test_has_with_p_steps_v2() { #[test] fn test_has_with_text_p_step_v2() { - let client = graph_serializer(GraphSON::V2); + let client = graph_serializer(Protocol::GraphSONV2); drop_vertices(&client, "test_has_with_text_p_step").unwrap(); @@ -1268,7 +1268,7 @@ fn test_has_with_text_p_step_v2() { #[test] fn where_step_test_v2() { - let client = graph_serializer(GraphSON::V2); + let client = graph_serializer(Protocol::GraphSONV2); drop_vertices(&client, "where_step_test").unwrap(); @@ -1294,7 +1294,7 @@ fn where_step_test_v2() { #[test] fn not_step_test_v2() { - let client = graph_serializer(GraphSON::V2); + let client = graph_serializer(Protocol::GraphSONV2); drop_vertices(&client, "not_step_test").unwrap(); @@ -1317,7 +1317,7 @@ fn not_step_test_v2() { #[test] fn order_step_test_v2() { - let client = graph_serializer(GraphSON::V2); + let client = graph_serializer(Protocol::GraphSONV2); drop_vertices(&client, "order_step_test").unwrap(); @@ -1361,7 +1361,7 @@ fn order_step_test_v2() { #[test] fn match_step_test_v2() { - let client = graph_serializer(GraphSON::V2); + let client = graph_serializer(Protocol::GraphSONV2); drop_vertices(&client, "match_step_test").unwrap(); @@ -1423,7 +1423,7 @@ fn match_step_test_v2() { #[test] fn drop_step_test_v2() { - let client = graph_serializer(GraphSON::V2); + let client = graph_serializer(Protocol::GraphSONV2); drop_vertices(&client, "drop_step_test").unwrap(); @@ -1456,7 +1456,7 @@ fn drop_step_test_v2() { #[test] fn or_step_test_v2() { - let client = graph_serializer(GraphSON::V2); + let client = graph_serializer(Protocol::GraphSONV2); drop_vertices(&client, "or_step_test").unwrap(); @@ -1497,7 +1497,7 @@ fn or_step_test_v2() { #[test] fn iter_terminator_test_v2() { - let client = graph_serializer(GraphSON::V2); + let client = graph_serializer(Protocol::GraphSONV2); drop_vertices(&client, "iter_terminator_test").unwrap(); @@ -1526,7 +1526,7 @@ fn iter_terminator_test_v2() { #[test] fn test_select_pop_v2() { - let client = graph_serializer(GraphSON::V2); + let client = graph_serializer(Protocol::GraphSONV2); drop_vertices(&client, "test_select_pop").unwrap(); drop_vertices(&client, "test_select_pop_child").unwrap(); @@ -1615,7 +1615,7 @@ fn test_select_pop_v2() { #[test] fn test_repeat_until_loops_loops_v2() { - let client = graph_serializer(GraphSON::V2); + let client = graph_serializer(Protocol::GraphSONV2); drop_vertices(&client, "test_repeat_until_loops").unwrap(); drop_vertices(&client, "test_repeat_until_loops_child").unwrap(); @@ -1656,7 +1656,7 @@ fn test_repeat_until_loops_loops_v2() { #[test] fn test_simple_path_v2() { - let client = graph_serializer(GraphSON::V2); + let client = graph_serializer(Protocol::GraphSONV2); drop_vertices(&client, "test_simple_path").unwrap(); drop_vertices(&client, "test_simple_path_child").unwrap(); @@ -1698,7 +1698,7 @@ fn test_simple_path_v2() { #[test] fn test_sample_v2() { - let client = graph_serializer(GraphSON::V2); + let client = graph_serializer(Protocol::GraphSONV2); drop_vertices(&client, "test_sample").unwrap(); drop_vertices(&client, "test_sample_child").unwrap(); @@ -1731,7 +1731,7 @@ fn test_sample_v2() { #[test] fn test_local_v2() { - let client = graph_serializer(GraphSON::V2); + let client = graph_serializer(Protocol::GraphSONV2); drop_vertices(&client, "test_local").unwrap(); drop_vertices(&client, "test_local_child").unwrap(); @@ -1807,7 +1807,7 @@ fn test_local_v2() { #[test] fn test_property_cardinality_v2() { - let client = graph_serializer(GraphSON::V2); + let client = graph_serializer(Protocol::GraphSONV2); drop_vertices(&client, "test_property_cardinality").unwrap(); From 076949f5dd8171edc249c33ac37483d7cf2eca42 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Mon, 7 Oct 2024 13:58:17 -0500 Subject: [PATCH 02/56] Removed redundant serializer content type mapping --- gremlin-client/src/pool.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/gremlin-client/src/pool.rs b/gremlin-client/src/pool.rs index 473b1e74..16dd284a 100644 --- a/gremlin-client/src/pool.rs +++ b/gremlin-client/src/pool.rs @@ -49,10 +49,7 @@ impl ManageConnection for GremlinConnectionManager { let msg = serde_json::to_string(&message).map_err(GremlinError::from)?; - let content_type = match self.options.serializer { - Protocol::GraphSONV2 => "application/vnd.gremlin-v2.0+json", - Protocol::GraphSONV3 => "application/vnd.gremlin-v3.0+json", - }; + let content_type = self.options.serializer.content_type(); let payload = String::from("") + content_type + &msg; let mut binary = payload.into_bytes(); From cbaa2bfdad6f6a523025a30392a4f43af005ff11 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Mon, 7 Oct 2024 20:09:30 -0500 Subject: [PATCH 03/56] Missed import in generalization --- gremlin-cli/src/actions/connect.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gremlin-cli/src/actions/connect.rs b/gremlin-cli/src/actions/connect.rs index d37cb05b..e9c13284 100644 --- a/gremlin-cli/src/actions/connect.rs +++ b/gremlin-cli/src/actions/connect.rs @@ -1,6 +1,6 @@ use crate::{actions::Action, command::Command, context::GremlinContext}; use futures::FutureExt; -use gremlin_client::{aio::GremlinClient, ConnectionOptions, GraphSON, TlsOptions}; +use gremlin_client::{aio::GremlinClient, ConnectionOptions, Protocol, TlsOptions}; use std::str::FromStr; use structopt::StructOpt; @@ -30,11 +30,11 @@ impl FromStr for Serializer { } } -impl From for GraphSON { +impl From for Protocol { fn from(serializer: Serializer) -> Self { match serializer { - Serializer::GraphSONV2 => GraphSON::V2, - Serializer::GraphSONV3 => GraphSON::V3, + Serializer::GraphSONV2 => Protocol::GraphSONV2, + Serializer::GraphSONV3 => Protocol::GraphSONV3, } } } From 946d132264ab66e335fcca0afa5f750916373180 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Mon, 7 Oct 2024 21:34:40 -0500 Subject: [PATCH 04/56] Renamed Protocol to IoProtcol following Tinkerpop naming --- gremlin-cli/src/actions/connect.rs | 8 +- gremlin-client/src/aio/client.rs | 10 +- gremlin-client/src/aio/pool.rs | 6 +- gremlin-client/src/client.rs | 10 +- gremlin-client/src/connection.rs | 14 +-- gremlin-client/src/io/mod.rs | 20 ++-- gremlin-client/src/lib.rs | 2 +- gremlin-client/src/pool.rs | 6 +- gremlin-client/tests/common.rs | 20 ++-- .../tests/integration_client_async_v2.rs | 18 ++-- gremlin-client/tests/integration_client_v2.rs | 34 +++---- .../tests/integration_traversal_async_v2.rs | 4 +- .../tests/integration_traversal_v2.rs | 94 +++++++++---------- 13 files changed, 123 insertions(+), 123 deletions(-) diff --git a/gremlin-cli/src/actions/connect.rs b/gremlin-cli/src/actions/connect.rs index e9c13284..bd4ab859 100644 --- a/gremlin-cli/src/actions/connect.rs +++ b/gremlin-cli/src/actions/connect.rs @@ -1,6 +1,6 @@ use crate::{actions::Action, command::Command, context::GremlinContext}; use futures::FutureExt; -use gremlin_client::{aio::GremlinClient, ConnectionOptions, Protocol, TlsOptions}; +use gremlin_client::{aio::GremlinClient, ConnectionOptions, IoProtocol, TlsOptions}; use std::str::FromStr; use structopt::StructOpt; @@ -30,11 +30,11 @@ impl FromStr for Serializer { } } -impl From for Protocol { +impl From for IoProtocol { fn from(serializer: Serializer) -> Self { match serializer { - Serializer::GraphSONV2 => Protocol::GraphSONV2, - Serializer::GraphSONV3 => Protocol::GraphSONV3, + Serializer::GraphSONV2 => IoProtocol::GraphSONV2, + Serializer::GraphSONV3 => IoProtocol::GraphSONV3, } } } diff --git a/gremlin-client/src/aio/client.rs b/gremlin-client/src/aio/client.rs index 351e1661..582c9ee0 100644 --- a/gremlin-client/src/aio/client.rs +++ b/gremlin-client/src/aio/client.rs @@ -1,6 +1,6 @@ use crate::aio::pool::GremlinConnectionManager; use crate::aio::GResultSet; -use crate::io::Protocol; +use crate::io::IoProtocol; use crate::message::{ message_with_args, message_with_args_and_uuid, message_with_args_v2, Message, }; @@ -26,8 +26,8 @@ impl SessionedClient { let processor = "session".to_string(); let message = match self.options.serializer { - Protocol::GraphSONV2 => message_with_args_v2(String::from("close"), processor, args), - Protocol::GraphSONV3 => message_with_args(String::from("close"), processor, args), + IoProtocol::GraphSONV2 => message_with_args_v2(String::from("close"), processor, args), + IoProtocol::GraphSONV3 => message_with_args(String::from("close"), processor, args), }; let conn = self.pool.get().await?; @@ -140,8 +140,8 @@ impl GremlinClient { }; let message = match self.options.serializer { - Protocol::GraphSONV2 => message_with_args_v2(String::from("eval"), processor, args), - Protocol::GraphSONV3 => message_with_args(String::from("eval"), processor, args), + IoProtocol::GraphSONV2 => message_with_args_v2(String::from("eval"), processor, args), + IoProtocol::GraphSONV3 => message_with_args(String::from("eval"), processor, args), }; let conn = self.pool.get().await?; diff --git a/gremlin-client/src/aio/pool.rs b/gremlin-client/src/aio/pool.rs index c2f05ae8..6569e3a5 100644 --- a/gremlin-client/src/aio/pool.rs +++ b/gremlin-client/src/aio/pool.rs @@ -4,7 +4,7 @@ use crate::aio::connection::Conn; use crate::connection::ConnectionOptions; use crate::error::GremlinError; use crate::message::{message_with_args, message_with_args_and_uuid, message_with_args_v2}; -use crate::{GValue, Protocol}; +use crate::{GValue, IoProtocol}; use async_trait::async_trait; use base64::encode; use std::collections::HashMap; @@ -43,8 +43,8 @@ impl Manager for GremlinConnectionManager { let args = self.options.serializer.write(&GValue::from(args))?; let message = match self.options.serializer { - Protocol::GraphSONV2 => message_with_args_v2(String::from("eval"), String::default(), args), - Protocol::GraphSONV3 => message_with_args(String::from("eval"), String::default(), args), + IoProtocol::GraphSONV2 => message_with_args_v2(String::from("eval"), String::default(), args), + IoProtocol::GraphSONV3 => message_with_args(String::from("eval"), String::default(), args), }; let id = message.id().clone(); diff --git a/gremlin-client/src/client.rs b/gremlin-client/src/client.rs index c7b6e355..6cf8bbce 100644 --- a/gremlin-client/src/client.rs +++ b/gremlin-client/src/client.rs @@ -1,4 +1,4 @@ -use crate::io::Protocol; +use crate::io::IoProtocol; use crate::message::{ message_with_args, message_with_args_and_uuid, message_with_args_v2, Message, Response, }; @@ -24,8 +24,8 @@ impl SessionedClient { let processor = "session".to_string(); let message = match self.options.serializer { - Protocol::GraphSONV2 => message_with_args_v2(String::from("close"), processor, args), - Protocol::GraphSONV3 => message_with_args(String::from("close"), processor, args), + IoProtocol::GraphSONV2 => message_with_args_v2(String::from("close"), processor, args), + IoProtocol::GraphSONV3 => message_with_args(String::from("close"), processor, args), }; let conn = self.pool.get()?; @@ -136,8 +136,8 @@ impl GremlinClient { }; let message = match self.options.serializer { - Protocol::GraphSONV2 => message_with_args_v2(String::from("eval"), processor, args), - Protocol::GraphSONV3 => message_with_args(String::from("eval"), processor, args), + IoProtocol::GraphSONV2 => message_with_args_v2(String::from("eval"), processor, args), + IoProtocol::GraphSONV3 => message_with_args(String::from("eval"), processor, args), }; let conn = self.pool.get()?; diff --git a/gremlin-client/src/connection.rs b/gremlin-client/src/connection.rs index 26cd4a64..e99050d4 100644 --- a/gremlin-client/src/connection.rs +++ b/gremlin-client/src/connection.rs @@ -1,6 +1,6 @@ use std::{net::TcpStream, sync::Arc, time::Duration}; -use crate::{Protocol, GremlinError, GremlinResult}; +use crate::{IoProtocol, GremlinError, GremlinResult}; use native_tls::TlsConnector; use tungstenite::{ client::{uri_mode, IntoClientRequest}, @@ -164,12 +164,12 @@ impl ConnectionOptionsBuilder { self } - pub fn serializer(mut self, serializer: Protocol) -> Self { + pub fn serializer(mut self, serializer: IoProtocol) -> Self { self.0.serializer = serializer; self } - pub fn deserializer(mut self, deserializer: Protocol) -> Self { + pub fn deserializer(mut self, deserializer: IoProtocol) -> Self { self.0.deserializer = deserializer; self } @@ -185,8 +185,8 @@ pub struct ConnectionOptions { pub(crate) credentials: Option, pub(crate) ssl: bool, pub(crate) tls_options: Option, - pub(crate) serializer: Protocol, - pub(crate) deserializer: Protocol, + pub(crate) serializer: IoProtocol, + pub(crate) deserializer: IoProtocol, pub(crate) websocket_options: Option, } @@ -269,8 +269,8 @@ impl Default for ConnectionOptions { credentials: None, ssl: false, tls_options: None, - serializer: Protocol::GraphSONV3, - deserializer: Protocol::GraphSONV3, + serializer: IoProtocol::GraphSONV3, + deserializer: IoProtocol::GraphSONV3, websocket_options: None, } } diff --git a/gremlin-client/src/io/mod.rs b/gremlin-client/src/io/mod.rs index 85008390..79b3cc27 100644 --- a/gremlin-client/src/io/mod.rs +++ b/gremlin-client/src/io/mod.rs @@ -13,19 +13,19 @@ use std::string::ToString; use crate::{GremlinError, GremlinResult}; #[derive(Debug, Clone)] -pub enum Protocol { +pub enum IoProtocol { GraphSONV2, GraphSONV3, } -impl Protocol { +impl IoProtocol { pub fn read(&self, value: &Value) -> GremlinResult> { if let Value::Null = value { return Ok(None); } match self { - Protocol::GraphSONV2 => serializer_v2::deserializer_v2(value).map(Some), - Protocol::GraphSONV3 => serializer_v3::deserializer_v3(value).map(Some), + IoProtocol::GraphSONV2 => serializer_v2::deserializer_v2(value).map(Some), + IoProtocol::GraphSONV3 => serializer_v3::deserializer_v3(value).map(Some), } } @@ -56,11 +56,11 @@ impl Protocol { "@type" : "g:Date", "@value" : d.timestamp_millis() })), - (Protocol::GraphSONV2, GValue::List(d)) => { + (IoProtocol::GraphSONV2, GValue::List(d)) => { let elements: GremlinResult> = d.iter().map(|e| self.write(e)).collect(); Ok(json!(elements?)) } - (Protocol::GraphSONV3, GValue::List(d)) => { + (IoProtocol::GraphSONV3, GValue::List(d)) => { let elements: GremlinResult> = d.iter().map(|e| self.write(e)).collect(); Ok(json!({ "@type" : "g:List", @@ -121,7 +121,7 @@ impl Protocol { } })) } - (Protocol::GraphSONV2, GValue::Map(map)) => { + (IoProtocol::GraphSONV2, GValue::Map(map)) => { let mut params = Map::new(); for (k, v) in map.iter() { @@ -138,7 +138,7 @@ impl Protocol { Ok(json!(params)) } - (Protocol::GraphSONV3, GValue::Map(map)) => { + (IoProtocol::GraphSONV3, GValue::Map(map)) => { let mut params = vec![]; for (k, v) in map.iter() { @@ -255,8 +255,8 @@ impl Protocol { pub fn content_type(&self) -> &str { match self { - Protocol::GraphSONV2 => "application/vnd.gremlin-v2.0+json", - Protocol::GraphSONV3 => "application/vnd.gremlin-v3.0+json", + IoProtocol::GraphSONV2 => "application/vnd.gremlin-v2.0+json", + IoProtocol::GraphSONV3 => "application/vnd.gremlin-v3.0+json", } } } diff --git a/gremlin-client/src/lib.rs b/gremlin-client/src/lib.rs index 06452a3f..13e7279e 100644 --- a/gremlin-client/src/lib.rs +++ b/gremlin-client/src/lib.rs @@ -128,7 +128,7 @@ pub use connection::{ }; pub use conversion::{BorrowFromGValue, FromGValue, ToGValue}; pub use error::GremlinError; -pub use io::Protocol; +pub use io::IoProtocol; pub use message::Message; pub type GremlinResult = Result; diff --git a/gremlin-client/src/pool.rs b/gremlin-client/src/pool.rs index 16dd284a..9af619a6 100644 --- a/gremlin-client/src/pool.rs +++ b/gremlin-client/src/pool.rs @@ -6,7 +6,7 @@ use crate::error::GremlinError; use crate::message::{ message_with_args, message_with_args_and_uuid, message_with_args_v2, Response, }; -use crate::{GValue, Protocol, GremlinResult}; +use crate::{GValue, IoProtocol, GremlinResult}; use base64::encode; use std::collections::HashMap; @@ -43,8 +43,8 @@ impl ManageConnection for GremlinConnectionManager { let args = self.options.serializer.write(&GValue::from(args))?; let message = match self.options.serializer { - Protocol::GraphSONV2 => message_with_args_v2(String::from("eval"), String::default(), args), - Protocol::GraphSONV3 => message_with_args(String::from("eval"), String::default(), args), + IoProtocol::GraphSONV2 => message_with_args_v2(String::from("eval"), String::default(), args), + IoProtocol::GraphSONV3 => message_with_args(String::from("eval"), String::default(), args), }; let msg = serde_json::to_string(&message).map_err(GremlinError::from)?; diff --git a/gremlin-client/tests/common.rs b/gremlin-client/tests/common.rs index 88fc0598..5f2c9f95 100644 --- a/gremlin-client/tests/common.rs +++ b/gremlin-client/tests/common.rs @@ -11,7 +11,7 @@ pub fn assert_map_property(element_map: &Map, expected_key: &str, expected_value #[allow(dead_code)] pub mod io { - use gremlin_client::{ConnectionOptions, Edge, Protocol, GremlinClient, GremlinResult, Vertex}; + use gremlin_client::{ConnectionOptions, Edge, IoProtocol, GremlinClient, GremlinResult, Vertex}; pub fn connect() -> GremlinResult { GremlinClient::connect(("localhost", 8182)) @@ -21,10 +21,10 @@ pub mod io { GremlinClient::connect(("localhost", 8184)) } - pub fn connect_serializer(serializer: Protocol) -> GremlinResult { + pub fn connect_serializer(serializer: IoProtocol) -> GremlinResult { let port = match serializer { - Protocol::GraphSONV2 => 8182, - Protocol::GraphSONV3 => 8182, + IoProtocol::GraphSONV2 => 8182, + IoProtocol::GraphSONV3 => 8182, }; GremlinClient::connect( ConnectionOptions::builder() @@ -44,7 +44,7 @@ pub mod io { connect_janusgraph_client().expect("It should connect") } - pub fn expect_client_serializer(serializer: Protocol) -> GremlinClient { + pub fn expect_client_serializer(serializer: IoProtocol) -> GremlinClient { connect_serializer(serializer).expect("It should connect") } @@ -54,7 +54,7 @@ pub mod io { client } - pub fn graph_serializer(serializer: Protocol) -> GremlinClient { + pub fn graph_serializer(serializer: IoProtocol) -> GremlinClient { let client = expect_client_serializer(serializer); client @@ -112,7 +112,7 @@ pub mod io { pub mod aio { use gremlin_client::aio::GremlinClient; - use gremlin_client::{ConnectionOptions, Edge, Protocol, GremlinResult, Vertex}; + use gremlin_client::{ConnectionOptions, Edge, IoProtocol, GremlinResult, Vertex}; #[cfg(feature = "async-std-runtime")] use async_std::prelude::*; @@ -126,10 +126,10 @@ pub mod aio { .expect("It should connect") } - pub async fn connect_serializer(serializer: Protocol) -> GremlinClient { + pub async fn connect_serializer(serializer: IoProtocol) -> GremlinClient { let port = match serializer { - Protocol::GraphSONV2 => 8182, - Protocol::GraphSONV3 => 8182, + IoProtocol::GraphSONV2 => 8182, + IoProtocol::GraphSONV3 => 8182, }; GremlinClient::connect( ConnectionOptions::builder() diff --git a/gremlin-client/tests/integration_client_async_v2.rs b/gremlin-client/tests/integration_client_async_v2.rs index 41ff7ce5..f98d9249 100644 --- a/gremlin-client/tests/integration_client_async_v2.rs +++ b/gremlin-client/tests/integration_client_async_v2.rs @@ -5,7 +5,7 @@ mod common; mod aio { use gremlin_client::GremlinError; - use gremlin_client::{Edge, GValue, Protocol, Map, Vertex}; + use gremlin_client::{Edge, GValue, IoProtocol, Map, Vertex}; use super::common::aio::{connect_serializer, create_edge, create_vertex}; #[cfg(feature = "async-std-runtime")] @@ -17,13 +17,13 @@ mod aio { #[cfg_attr(feature = "async-std-runtime", async_std::test)] #[cfg_attr(feature = "tokio-runtime", tokio::test)] async fn test_client_connection_ok_v2() { - connect_serializer(Protocol::GraphSONV2).await; + connect_serializer(IoProtocol::GraphSONV2).await; } #[cfg(feature = "async-std-runtime")] #[cfg_attr(feature = "async-std-runtime", async_std::test)] async fn test_empty_query_v2() { - let graph = connect_serializer(Protocol::GraphSONV2).await; + let graph = connect_serializer(IoProtocol::GraphSONV2).await; assert_eq!( 0, @@ -39,7 +39,7 @@ mod aio { #[cfg_attr(feature = "async-std-runtime", async_std::test)] #[cfg_attr(feature = "tokio-runtime", tokio::test)] async fn test_wrong_query_v2() { - let error = connect_serializer(Protocol::GraphSONV2) + let error = connect_serializer(IoProtocol::GraphSONV2) .await .execute("g.V", &[]) .await @@ -57,7 +57,7 @@ mod aio { #[cfg_attr(feature = "async-std-runtime", async_std::test)] #[cfg_attr(feature = "tokio-runtime", tokio::test)] async fn test_wrong_alias_v2() { - let error = connect_serializer(Protocol::GraphSONV2) + let error = connect_serializer(IoProtocol::GraphSONV2) .await .alias("foo") .execute("g.V()", &[]) @@ -77,7 +77,7 @@ mod aio { #[cfg_attr(feature = "tokio-runtime", tokio::test)] async fn test_vertex_query_v2() { - let graph = connect_serializer(Protocol::GraphSONV2).await; + let graph = connect_serializer(IoProtocol::GraphSONV2).await; println!("About to execute query."); let vertices = graph @@ -98,7 +98,7 @@ mod aio { #[cfg_attr(feature = "async-std-runtime", async_std::test)] #[cfg_attr(feature = "tokio-runtime", tokio::test)] async fn test_edge_query_v2() { - let graph = connect_serializer(Protocol::GraphSONV2).await; + let graph = connect_serializer(IoProtocol::GraphSONV2).await; let edges = graph .execute("g.E().hasLabel('knows').limit(1)", &[]) .await @@ -115,7 +115,7 @@ mod aio { #[cfg_attr(feature = "async-std-runtime", async_std::test)] #[cfg_attr(feature = "tokio-runtime", tokio::test)] async fn test_vertex_creation_v2() { - let graph = connect_serializer(Protocol::GraphSONV2).await; + let graph = connect_serializer(IoProtocol::GraphSONV2).await; let mark = create_vertex(&graph, "mark").await; assert_eq!("person", mark.label()); @@ -141,7 +141,7 @@ mod aio { #[cfg_attr(feature = "async-std-runtime", async_std::test)] #[cfg_attr(feature = "tokio-runtime", tokio::test)] async fn test_edge_creation_v2() { - let graph = connect_serializer(Protocol::GraphSONV2).await; + let graph = connect_serializer(IoProtocol::GraphSONV2).await; let mark = create_vertex(&graph, "mark").await; let frank = create_vertex(&graph, "frank").await; diff --git a/gremlin-client/tests/integration_client_v2.rs b/gremlin-client/tests/integration_client_v2.rs index 6e2cb8dd..87a96282 100644 --- a/gremlin-client/tests/integration_client_v2.rs +++ b/gremlin-client/tests/integration_client_v2.rs @@ -1,7 +1,7 @@ mod common; use gremlin_client::{ - ConnectionOptions, Protocol, GremlinClient, GremlinError, List, TlsOptions, ToGValue, + ConnectionOptions, IoProtocol, GremlinClient, GremlinError, List, TlsOptions, ToGValue, TraversalExplanation, TraversalMetrics, VertexProperty, }; use gremlin_client::{Edge, GKey, GValue, Map, Vertex, GID}; @@ -10,14 +10,14 @@ use common::io::{create_edge, create_vertex, expect_client_serializer, graph_ser #[test] fn test_client_connection_ok_v2() { - expect_client_serializer(Protocol::GraphSONV2); + expect_client_serializer(IoProtocol::GraphSONV2); } #[test] fn test_empty_query_v2() { assert_eq!( 0, - graph_serializer(Protocol::GraphSONV2) + graph_serializer(IoProtocol::GraphSONV2) .execute("g.V().hasLabel('NotFound')", &[]) .expect("It should execute a traversal") .count() @@ -35,7 +35,7 @@ fn test_ok_credentials_v2() { .tls_options(TlsOptions { accept_invalid_certs: true, }) - .serializer(Protocol::GraphSONV2) + .serializer(IoProtocol::GraphSONV2) .build(), ) .expect("Cannot connect"); @@ -55,7 +55,7 @@ fn test_ko_credentials_v2() { .tls_options(TlsOptions { accept_invalid_certs: true, }) - .serializer(Protocol::GraphSONV2) + .serializer(IoProtocol::GraphSONV2) .build(), ) .expect("Cannot connect"); @@ -66,7 +66,7 @@ fn test_ko_credentials_v2() { #[test] fn test_wrong_query_v2() { - let error = graph_serializer(Protocol::GraphSONV2) + let error = graph_serializer(IoProtocol::GraphSONV2) .execute("g.V", &[]) .expect_err("it should return an error"); @@ -81,7 +81,7 @@ fn test_wrong_query_v2() { #[test] fn test_wrong_alias_v2() { - let error = graph_serializer(Protocol::GraphSONV2) + let error = graph_serializer(IoProtocol::GraphSONV2) .alias("foo") .execute("g.V()", &[]) .expect_err("it should return an error"); @@ -98,7 +98,7 @@ fn test_wrong_alias_v2() { #[test] fn test_vertex_query_v2() { - let graph = graph_serializer(Protocol::GraphSONV2); + let graph = graph_serializer(IoProtocol::GraphSONV2); let vertices = graph .execute( "g.V().hasLabel('person').has('name',name)", @@ -114,7 +114,7 @@ fn test_vertex_query_v2() { } #[test] fn test_edge_query_v2() { - let graph = graph_serializer(Protocol::GraphSONV2); + let graph = graph_serializer(IoProtocol::GraphSONV2); let edges = graph .execute("g.E().hasLabel('knows').limit(1)", &[]) .expect("it should execute a query") @@ -128,7 +128,7 @@ fn test_edge_query_v2() { #[test] fn test_vertex_creation_v2() { - let graph = graph_serializer(Protocol::GraphSONV2); + let graph = graph_serializer(IoProtocol::GraphSONV2); let mark = create_vertex(&graph, "mark"); assert_eq!("person", mark.label()); @@ -155,7 +155,7 @@ fn test_inserting_date_with_milisecond_precision() { use chrono::DateTime; use chrono::Utc; - let graph = graph_serializer(Protocol::GraphSONV2); + let graph = graph_serializer(IoProtocol::GraphSONV2); let q = r#"g.addV('person').property('dateTime',dateTime).propertyMap()"#; @@ -187,7 +187,7 @@ fn test_inserting_date_with_milisecond_precision() { fn test_complex_vertex_creation_with_properties_v2() { use chrono::offset::TimeZone; - let graph = graph_serializer(Protocol::GraphSONV2); + let graph = graph_serializer(IoProtocol::GraphSONV2); let q = r#" g.addV('person') @@ -295,7 +295,7 @@ fn test_complex_vertex_creation_with_properties_v2() { #[test] fn test_edge_creation_v2() { - let graph = graph_serializer(Protocol::GraphSONV2); + let graph = graph_serializer(IoProtocol::GraphSONV2); let mark = create_vertex(&graph, "mark"); let frank = create_vertex(&graph, "frank"); @@ -326,7 +326,7 @@ fn test_edge_creation_v2() { #[test] fn test_profile_v2() { - let graph = graph_serializer(Protocol::GraphSONV2); + let graph = graph_serializer(IoProtocol::GraphSONV2); let metrics = graph .execute("g.V().limit(1).profile()", &[]) @@ -358,7 +358,7 @@ fn test_profile_v2() { #[test] fn test_explain_v2() { - let graph = graph_serializer(Protocol::GraphSONV2); + let graph = graph_serializer(IoProtocol::GraphSONV2); let metrics = graph .execute("g.V().limit(1).explain()", &[]) @@ -393,7 +393,7 @@ fn test_explain_v2() { #[test] fn test_group_count_vertex_v2() { - let graph = graph_serializer(Protocol::GraphSONV2); + let graph = graph_serializer(IoProtocol::GraphSONV2); let mark = create_vertex(&graph, "mark"); let frank = create_vertex(&graph, "frank"); @@ -432,7 +432,7 @@ fn test_group_count_vertex_v2() { #[test] fn test_group_count_edge_v2() { - let graph = graph_serializer(Protocol::GraphSONV2); + let graph = graph_serializer(IoProtocol::GraphSONV2); let mark = create_vertex(&graph, "mark"); let frank = create_vertex(&graph, "frank"); diff --git a/gremlin-client/tests/integration_traversal_async_v2.rs b/gremlin-client/tests/integration_traversal_async_v2.rs index 82435f94..c813d5ee 100644 --- a/gremlin-client/tests/integration_traversal_async_v2.rs +++ b/gremlin-client/tests/integration_traversal_async_v2.rs @@ -13,12 +13,12 @@ mod aio { #[cfg(feature = "tokio-runtime")] use tokio_stream::StreamExt; - use gremlin_client::{Protocol, Vertex}; + use gremlin_client::{IoProtocol, Vertex}; #[cfg_attr(feature = "async-std-runtime", async_std::test)] #[cfg_attr(feature = "tokio-runtime", tokio::test)] async fn test_simple_vertex_traversal_with_multiple_id_v2() { - let client = connect_serializer(Protocol::GraphSONV2).await; + let client = connect_serializer(IoProtocol::GraphSONV2).await; drop_vertices(&client, "test_simple_vertex_traversal_async") .await .unwrap(); diff --git a/gremlin-client/tests/integration_traversal_v2.rs b/gremlin-client/tests/integration_traversal_v2.rs index 3534ef76..2cae261a 100644 --- a/gremlin-client/tests/integration_traversal_v2.rs +++ b/gremlin-client/tests/integration_traversal_v2.rs @@ -2,7 +2,7 @@ use gremlin_client::process::traversal::{traversal, Order, __}; use gremlin_client::structure::{ Cardinality, GKey, GValue, List, Map, Pop, TextP, Vertex, VertexProperty, GID, P, T, }; -use gremlin_client::{utils, Protocol}; +use gremlin_client::{utils, IoProtocol}; mod common; @@ -13,7 +13,7 @@ use common::io::{ #[test] fn test_simple_vertex_traversal_v2() { - let g = traversal().with_remote(graph_serializer(Protocol::GraphSONV2)); + let g = traversal().with_remote(graph_serializer(IoProtocol::GraphSONV2)); let results = g.v(()).to_list().unwrap(); @@ -22,7 +22,7 @@ fn test_simple_vertex_traversal_v2() { #[test] fn test_simple_vertex_traversal_with_id_v2() { - let client = graph_serializer(Protocol::GraphSONV2); + let client = graph_serializer(IoProtocol::GraphSONV2); let vertex = create_vertex(&client, "Traversal"); @@ -37,7 +37,7 @@ fn test_simple_vertex_traversal_with_id_v2() { #[test] fn test_simple_vertex_traversal_with_multiple_id_v2() { - let client = graph_serializer(Protocol::GraphSONV2); + let client = graph_serializer(IoProtocol::GraphSONV2); drop_vertices(&client, "test_simple_vertex_traversal").unwrap(); let vertex = create_vertex_with_label(&client, "test_simple_vertex_traversal", "Traversal"); @@ -55,7 +55,7 @@ fn test_simple_vertex_traversal_with_multiple_id_v2() { #[test] fn test_simple_vertex_traversal_with_label_v2() { - let client = graph_serializer(Protocol::GraphSONV2); + let client = graph_serializer(IoProtocol::GraphSONV2); drop_vertices(&client, "test_simple_vertex_traversal_with_label").unwrap(); @@ -80,7 +80,7 @@ fn test_simple_vertex_traversal_with_label_v2() { #[test] fn test_simple_vertex_traversal_with_label_and_has_v2() { - let client = graph_serializer(Protocol::GraphSONV2); + let client = graph_serializer(IoProtocol::GraphSONV2); drop_vertices(&client, "test_simple_vertex_traversal_with_label_and_has").unwrap(); @@ -148,7 +148,7 @@ fn test_simple_vertex_traversal_with_label_and_has_v2() { #[test] fn test_simple_edge_traversal_v2() { - let g = traversal().with_remote(graph_serializer(Protocol::GraphSONV2)); + let g = traversal().with_remote(graph_serializer(IoProtocol::GraphSONV2)); let results = g.e(()).to_list().unwrap(); @@ -157,7 +157,7 @@ fn test_simple_edge_traversal_v2() { #[test] fn test_simple_edge_traversal_id_v2() { - let client = graph_serializer(Protocol::GraphSONV2); + let client = graph_serializer(IoProtocol::GraphSONV2); let v = create_vertex(&client, "Traversal"); let v1 = create_vertex(&client, "Traversal"); @@ -175,7 +175,7 @@ fn test_simple_edge_traversal_id_v2() { #[test] fn test_simple_edge_traversal_with_label_v2() { - let client = graph_serializer(Protocol::GraphSONV2); + let client = graph_serializer(IoProtocol::GraphSONV2); drop_edges(&client, "test_simple_edge_traversal_with_label").unwrap(); @@ -199,7 +199,7 @@ fn test_simple_edge_traversal_with_label_v2() { #[test] fn test_traversal_v2() { - let client = graph_serializer(Protocol::GraphSONV2); + let client = graph_serializer(IoProtocol::GraphSONV2); drop_edges(&client, "test_vertex_out_traversal").unwrap(); @@ -342,7 +342,7 @@ fn test_traversal_v2() { #[test] fn test_add_v_v2() { - let g = traversal().with_remote(graph_serializer(Protocol::GraphSONV2)); + let g = traversal().with_remote(graph_serializer(IoProtocol::GraphSONV2)); let results = g.add_v("person").to_list().unwrap(); @@ -360,7 +360,7 @@ fn test_add_v_v2() { #[test] fn test_add_v_with_properties_v2() { - let client = graph_serializer(Protocol::GraphSONV2); + let client = graph_serializer(IoProtocol::GraphSONV2); let g = traversal().with_remote(client.clone()); let results = g @@ -405,7 +405,7 @@ fn test_add_v_with_properties_v2() { #[test] fn test_add_v_with_property_many_v2() { - let client = graph_serializer(Protocol::GraphSONV2); + let client = graph_serializer(IoProtocol::GraphSONV2); drop_vertices(&client, "test_add_v_with_property_many").unwrap(); @@ -455,7 +455,7 @@ fn test_add_v_with_property_many_v2() { #[test] fn test_has_many_v2() { - let client = graph_serializer(Protocol::GraphSONV2); + let client = graph_serializer(IoProtocol::GraphSONV2); drop_vertices(&client, "test_has_many").unwrap(); @@ -488,7 +488,7 @@ fn test_has_many_v2() { #[test] fn test_add_e_v2() { - let client = graph_serializer(Protocol::GraphSONV2); + let client = graph_serializer(IoProtocol::GraphSONV2); let g = traversal().with_remote(client.clone()); let v = g @@ -551,7 +551,7 @@ fn test_add_e_v2() { #[test] fn test_label_step_v2() { - let client = graph_serializer(Protocol::GraphSONV2); + let client = graph_serializer(IoProtocol::GraphSONV2); let vertex = create_vertex(&client, "Traversal"); @@ -566,7 +566,7 @@ fn test_label_step_v2() { #[test] fn test_properties_step_v2() { - let client = graph_serializer(Protocol::GraphSONV2); + let client = graph_serializer(IoProtocol::GraphSONV2); let vertex = create_vertex(&client, "Traversal"); @@ -591,7 +591,7 @@ fn test_properties_step_v2() { #[test] fn test_property_map_v2() { - let client = graph_serializer(Protocol::GraphSONV2); + let client = graph_serializer(IoProtocol::GraphSONV2); let vertex = create_vertex(&client, "Traversal"); @@ -638,7 +638,7 @@ fn test_property_map_v2() { #[test] fn test_values_v2() { - let client = graph_serializer(Protocol::GraphSONV2); + let client = graph_serializer(IoProtocol::GraphSONV2); let vertex = create_vertex(&client, "Traversal"); @@ -667,7 +667,7 @@ fn test_values_v2() { #[test] fn test_value_map_v2() { - let client = graph_serializer(Protocol::GraphSONV2); + let client = graph_serializer(IoProtocol::GraphSONV2); let g = traversal().with_remote(client); @@ -718,7 +718,7 @@ fn test_value_map_v2() { #[test] fn test_unwrap_map_v2() { - let client = graph_serializer(Protocol::GraphSONV2); + let client = graph_serializer(IoProtocol::GraphSONV2); let g = traversal().with_remote(client); @@ -748,7 +748,7 @@ fn test_unwrap_map_v2() { #[test] fn test_element_map_v2() { - let client = graph_serializer(Protocol::GraphSONV2); + let client = graph_serializer(IoProtocol::GraphSONV2); let g = traversal().with_remote(client); @@ -791,7 +791,7 @@ fn test_element_map_v2() { #[test] fn test_count_v2() { - let client = graph_serializer(Protocol::GraphSONV2); + let client = graph_serializer(IoProtocol::GraphSONV2); let vertex = create_vertex_with_label(&client, "test_count", "Count"); @@ -808,7 +808,7 @@ fn test_count_v2() { #[test] fn test_group_count_step_v2() { - let client = graph_serializer(Protocol::GraphSONV2); + let client = graph_serializer(IoProtocol::GraphSONV2); drop_vertices(&client, "test_group_count").unwrap(); @@ -871,7 +871,7 @@ fn test_group_count_step_v2() { #[test] fn test_group_by_step_v2() { - let client = graph_serializer(Protocol::GraphSONV2); + let client = graph_serializer(IoProtocol::GraphSONV2); drop_vertices(&client, "test_group_by_step").unwrap(); @@ -927,7 +927,7 @@ fn test_group_by_step_v2() { #[test] fn test_select_step_v2() { - let client = graph_serializer(Protocol::GraphSONV2); + let client = graph_serializer(IoProtocol::GraphSONV2); drop_vertices(&client, "test_select_step").unwrap(); @@ -953,7 +953,7 @@ fn test_select_step_v2() { #[test] fn test_fold_step_v2() { - let client = graph_serializer(Protocol::GraphSONV2); + let client = graph_serializer(IoProtocol::GraphSONV2); drop_vertices(&client, "test_fold_step").unwrap(); @@ -978,7 +978,7 @@ fn test_fold_step_v2() { #[test] fn test_unfold_step_v2() { - let client = graph_serializer(Protocol::GraphSONV2); + let client = graph_serializer(IoProtocol::GraphSONV2); drop_vertices(&client, "test_unfold_step").unwrap(); @@ -1009,7 +1009,7 @@ fn test_unfold_step_v2() { #[test] fn test_path_step_v2() { - let client = graph_serializer(Protocol::GraphSONV2); + let client = graph_serializer(IoProtocol::GraphSONV2); drop_vertices(&client, "test_path_step").unwrap(); @@ -1033,7 +1033,7 @@ fn test_path_step_v2() { #[test] fn test_limit_step_v2() { - let client = graph_serializer(Protocol::GraphSONV2); + let client = graph_serializer(IoProtocol::GraphSONV2); drop_vertices(&client, "test_limit_step").unwrap(); @@ -1054,7 +1054,7 @@ fn test_limit_step_v2() { #[test] fn test_dedup_step_v2() { - let client = graph_serializer(Protocol::GraphSONV2); + let client = graph_serializer(IoProtocol::GraphSONV2); drop_vertices(&client, "test_limit_step").unwrap(); @@ -1076,7 +1076,7 @@ fn test_dedup_step_v2() { #[test] fn test_numerical_steps_v2() { - let client = graph_serializer(Protocol::GraphSONV2); + let client = graph_serializer(IoProtocol::GraphSONV2); drop_vertices(&client, "test_numerical_steps").unwrap(); @@ -1148,7 +1148,7 @@ fn test_numerical_steps_v2() { #[test] fn test_has_with_p_steps_v2() { - let client = graph_serializer(Protocol::GraphSONV2); + let client = graph_serializer(IoProtocol::GraphSONV2); drop_vertices(&client, "test_has_with_p_steps").unwrap(); @@ -1201,7 +1201,7 @@ fn test_has_with_p_steps_v2() { #[test] fn test_has_with_text_p_step_v2() { - let client = graph_serializer(Protocol::GraphSONV2); + let client = graph_serializer(IoProtocol::GraphSONV2); drop_vertices(&client, "test_has_with_text_p_step").unwrap(); @@ -1268,7 +1268,7 @@ fn test_has_with_text_p_step_v2() { #[test] fn where_step_test_v2() { - let client = graph_serializer(Protocol::GraphSONV2); + let client = graph_serializer(IoProtocol::GraphSONV2); drop_vertices(&client, "where_step_test").unwrap(); @@ -1294,7 +1294,7 @@ fn where_step_test_v2() { #[test] fn not_step_test_v2() { - let client = graph_serializer(Protocol::GraphSONV2); + let client = graph_serializer(IoProtocol::GraphSONV2); drop_vertices(&client, "not_step_test").unwrap(); @@ -1317,7 +1317,7 @@ fn not_step_test_v2() { #[test] fn order_step_test_v2() { - let client = graph_serializer(Protocol::GraphSONV2); + let client = graph_serializer(IoProtocol::GraphSONV2); drop_vertices(&client, "order_step_test").unwrap(); @@ -1361,7 +1361,7 @@ fn order_step_test_v2() { #[test] fn match_step_test_v2() { - let client = graph_serializer(Protocol::GraphSONV2); + let client = graph_serializer(IoProtocol::GraphSONV2); drop_vertices(&client, "match_step_test").unwrap(); @@ -1423,7 +1423,7 @@ fn match_step_test_v2() { #[test] fn drop_step_test_v2() { - let client = graph_serializer(Protocol::GraphSONV2); + let client = graph_serializer(IoProtocol::GraphSONV2); drop_vertices(&client, "drop_step_test").unwrap(); @@ -1456,7 +1456,7 @@ fn drop_step_test_v2() { #[test] fn or_step_test_v2() { - let client = graph_serializer(Protocol::GraphSONV2); + let client = graph_serializer(IoProtocol::GraphSONV2); drop_vertices(&client, "or_step_test").unwrap(); @@ -1497,7 +1497,7 @@ fn or_step_test_v2() { #[test] fn iter_terminator_test_v2() { - let client = graph_serializer(Protocol::GraphSONV2); + let client = graph_serializer(IoProtocol::GraphSONV2); drop_vertices(&client, "iter_terminator_test").unwrap(); @@ -1526,7 +1526,7 @@ fn iter_terminator_test_v2() { #[test] fn test_select_pop_v2() { - let client = graph_serializer(Protocol::GraphSONV2); + let client = graph_serializer(IoProtocol::GraphSONV2); drop_vertices(&client, "test_select_pop").unwrap(); drop_vertices(&client, "test_select_pop_child").unwrap(); @@ -1615,7 +1615,7 @@ fn test_select_pop_v2() { #[test] fn test_repeat_until_loops_loops_v2() { - let client = graph_serializer(Protocol::GraphSONV2); + let client = graph_serializer(IoProtocol::GraphSONV2); drop_vertices(&client, "test_repeat_until_loops").unwrap(); drop_vertices(&client, "test_repeat_until_loops_child").unwrap(); @@ -1656,7 +1656,7 @@ fn test_repeat_until_loops_loops_v2() { #[test] fn test_simple_path_v2() { - let client = graph_serializer(Protocol::GraphSONV2); + let client = graph_serializer(IoProtocol::GraphSONV2); drop_vertices(&client, "test_simple_path").unwrap(); drop_vertices(&client, "test_simple_path_child").unwrap(); @@ -1698,7 +1698,7 @@ fn test_simple_path_v2() { #[test] fn test_sample_v2() { - let client = graph_serializer(Protocol::GraphSONV2); + let client = graph_serializer(IoProtocol::GraphSONV2); drop_vertices(&client, "test_sample").unwrap(); drop_vertices(&client, "test_sample_child").unwrap(); @@ -1731,7 +1731,7 @@ fn test_sample_v2() { #[test] fn test_local_v2() { - let client = graph_serializer(Protocol::GraphSONV2); + let client = graph_serializer(IoProtocol::GraphSONV2); drop_vertices(&client, "test_local").unwrap(); drop_vertices(&client, "test_local_child").unwrap(); @@ -1807,7 +1807,7 @@ fn test_local_v2() { #[test] fn test_property_cardinality_v2() { - let client = graph_serializer(Protocol::GraphSONV2); + let client = graph_serializer(IoProtocol::GraphSONV2); drop_vertices(&client, "test_property_cardinality").unwrap(); From dce540056f1ab811f138cfe403fffccae9bd01f5 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Fri, 18 Oct 2024 17:13:58 -0500 Subject: [PATCH 05/56] Added rstest --- 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 e3b6dfa4..94149bbd 100644 --- a/gremlin-client/Cargo.toml +++ b/gremlin-client/Cargo.toml @@ -69,8 +69,8 @@ tokio = { version = "1", optional=true, features = ["full"] } features = ["serde", "v4"] version = "1.1.2" - - +[dev-dependencies] +rstest = "0.23.0" [[example]] name = "traversal_async" From e426d0d63fdc97d322bd32ae4309fd4c56bcece3 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Fri, 18 Oct 2024 17:16:28 -0500 Subject: [PATCH 06/56] Serialized message binary parity to java driver for test traversal --- gremlin-client/src/aio/client.rs | 2 + gremlin-client/src/aio/pool.rs | 1 + gremlin-client/src/client.rs | 70 +-- gremlin-client/src/io/graph_binary_v1.rs | 467 ++++++++++++++++++ gremlin-client/src/io/mod.rs | 168 ++++++- gremlin-client/src/message.rs | 4 +- gremlin-client/src/pool.rs | 50 +- gremlin-client/src/process/traversal/mod.rs | 2 +- gremlin-client/tests/common.rs | 2 + .../integration_traversal_graph_binary.rs | 11 + 10 files changed, 712 insertions(+), 65 deletions(-) create mode 100644 gremlin-client/tests/integration_traversal_graph_binary.rs diff --git a/gremlin-client/src/aio/client.rs b/gremlin-client/src/aio/client.rs index 582c9ee0..50e5040c 100644 --- a/gremlin-client/src/aio/client.rs +++ b/gremlin-client/src/aio/client.rs @@ -28,6 +28,7 @@ impl SessionedClient { let message = match self.options.serializer { IoProtocol::GraphSONV2 => message_with_args_v2(String::from("close"), processor, args), IoProtocol::GraphSONV3 => message_with_args(String::from("close"), processor, args), + IoProtocol::GraphBinaryV1 => todo!("Need to add the handling logic for writing to a processor op") }; let conn = self.pool.get().await?; @@ -142,6 +143,7 @@ impl GremlinClient { let message = match self.options.serializer { IoProtocol::GraphSONV2 => message_with_args_v2(String::from("eval"), processor, args), IoProtocol::GraphSONV3 => message_with_args(String::from("eval"), processor, args), + IoProtocol::GraphBinaryV1 => todo!("Need to add the handling logic for writing to a processor op") }; let conn = self.pool.get().await?; diff --git a/gremlin-client/src/aio/pool.rs b/gremlin-client/src/aio/pool.rs index 6569e3a5..f1b7b0ec 100644 --- a/gremlin-client/src/aio/pool.rs +++ b/gremlin-client/src/aio/pool.rs @@ -45,6 +45,7 @@ impl Manager for GremlinConnectionManager { let message = match self.options.serializer { IoProtocol::GraphSONV2 => message_with_args_v2(String::from("eval"), String::default(), args), IoProtocol::GraphSONV3 => message_with_args(String::from("eval"), String::default(), args), + IoProtocol::GraphBinaryV1 => todo!("Need to add the handling logic for writing to a processor op") }; let id = message.id().clone(); diff --git a/gremlin-client/src/client.rs b/gremlin-client/src/client.rs index 6cf8bbce..c5fa220d 100644 --- a/gremlin-client/src/client.rs +++ b/gremlin-client/src/client.rs @@ -24,13 +24,19 @@ impl SessionedClient { let processor = "session".to_string(); let message = match self.options.serializer { - IoProtocol::GraphSONV2 => message_with_args_v2(String::from("close"), processor, args), + IoProtocol::GraphSONV2 => { + message_with_args_v2(String::from("close"), processor, args) + } IoProtocol::GraphSONV3 => message_with_args(String::from("close"), processor, args), + IoProtocol::GraphBinaryV1 => { + todo!("Need to add the handling logic for writing to a processor op") + } }; let conn = self.pool.get()?; - self.send_message(conn, message) + todo!() + // self.send_message(conn, message) } else { Err(GremlinError::Generic("No session to close".to_string())) } @@ -138,37 +144,23 @@ impl GremlinClient { let message = match self.options.serializer { IoProtocol::GraphSONV2 => message_with_args_v2(String::from("eval"), processor, args), IoProtocol::GraphSONV3 => message_with_args(String::from("eval"), processor, args), + IoProtocol::GraphBinaryV1 => { + todo!("Need to add the handling logic for writing to a processor op") + } }; let conn = self.pool.get()?; - self.send_message(conn, message) + // self.send_message(conn, message) + todo!() } - pub(crate) fn write_message( - &self, - conn: &mut r2d2::PooledConnection, - msg: Message, - ) -> GremlinResult<()> { - let message = self.build_message(msg)?; - - let content_type = self.options.serializer.content_type(); - let payload = String::from("") + content_type + &message; - - let mut binary = payload.into_bytes(); - binary.insert(0, content_type.len() as u8); - - conn.send(binary)?; - - Ok(()) - } - - pub(crate) fn send_message( + pub(crate) fn send_message( &self, mut conn: r2d2::PooledConnection, - msg: Message, + msg: Vec, ) -> GremlinResult { - self.write_message(&mut conn, msg)?; + conn.send(msg)?; let (response, results) = self.read_response(&mut conn)?; @@ -206,7 +198,26 @@ impl GremlinClient { } pub(crate) fn submit_traversal(&self, bytecode: &Bytecode) -> GremlinResult { - let message = self.generate_message(bytecode)?; + let aliases = self + .alias + .clone() + .or_else(|| Some(String::from("g"))) + .map(|s| { + let mut map = HashMap::new(); + map.insert(String::from("g"), GValue::String(s)); + map + }) + .unwrap_or_else(HashMap::new); + + let message = self + .options + .serializer + .build_traversal_message(aliases, bytecode)?; + + if true { + let message: Vec = message.into_iter().map(|byte| byte as i8).collect(); + panic!("{:?}", message); + } let conn = self.pool.get()?; @@ -218,7 +229,7 @@ impl GremlinClient { conn: &mut r2d2::PooledConnection, ) -> GremlinResult<(Response, VecDeque)> { let result = conn.recv()?; - let response: Response = serde_json::from_slice(&result)?; + let response = self.options.deserializer.read_response(&result)?; match response.status.code { 200 | 206 => { @@ -249,9 +260,10 @@ impl GremlinClient { args, ); - self.write_message(conn, message)?; + todo!() + // self.write_message(conn, message)?; - self.read_response(conn) + // self.read_response(conn) } None => Err(GremlinError::Request(( response.status.code, @@ -264,7 +276,9 @@ impl GremlinClient { ))), } } + fn build_message(&self, msg: Message) -> GremlinResult { + //TODO this should be gone by the end serde_json::to_string(&msg).map_err(GremlinError::from) } } diff --git a/gremlin-client/src/io/graph_binary_v1.rs b/gremlin-client/src/io/graph_binary_v1.rs index e69de29b..b46c574d 100644 --- a/gremlin-client/src/io/graph_binary_v1.rs +++ b/gremlin-client/src/io/graph_binary_v1.rs @@ -0,0 +1,467 @@ +use std::{ + collections::HashMap, + convert::TryInto, + fmt::{self, Display}, + io::Read, +}; + +use serde::{de, ser, Serialize}; +use uuid::Uuid; + +use crate::{process::traversal::Instruction, GKey, GValue, GremlinError, GremlinResult}; + +struct RequestV1 { + version: u8, + request_id: Uuid, + op: String, + args: HashMap, +} + +//https://tinkerpop.apache.org/docs/3.7.2/dev/io/#_data_type_codes +//Each type has a "fully qualified" serialized form usually: {type_code}{type_info}{value_flag}{value} +//{type_code} is a single unsigned byte representing the type number. +//{type_info} is an optional sequence of bytes providing additional information of the type represented. This is specially useful for representing complex and custom types. +//{value_flag} is a single byte providing information about the value. Flags have the following meaning: +// 0x01 The value is null. When this flag is set, no bytes for {value} will be provided. +//{value} is a sequence of bytes which content is determined by the type. +//All encodings are big-endian. + +//However there are occassion when just "the value" is written without the fully qualified form, for example the 4 bytes of a integer without the type_code +//this is usually done in scenarios when the type in unambiguous by schema. + +//Generally this is written such that serializing a value wrapped by GValue is taken to mean to write the fully qualified representation +//and serializing just "the value" is done directly upon the underlying value type + +fn write_usize_as_i32_be_bytes(val: usize, buf: &mut Vec) -> GremlinResult<()> { + let val_i32 = TryInto::::try_into(val) + .map_err(|_| GremlinError::Cast(format!("Invalid usize bytes exceed i32")))?; + GraphBinaryV1Serde::to_be_bytes(val_i32, buf) +} + +impl GraphBinaryV1Serde for &GValue { + fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { + match self { + GValue::Null => { + //Type code of 0xfe: Unspecified null object + buf.push(0xfe); + //Then the null {value_flag} set and no sequence of bytes. + buf.push(0x01); + } + // GValue::Vertex(vertex) => todo!(), + // GValue::Edge(edge) => todo!(), + // GValue::VertexProperty(vertex_property) => todo!(), + // GValue::Property(property) => todo!(), + // GValue::Uuid(uuid) => todo!(), + GValue::Int32(value) => { + //Type code of 0x01 + buf.push(0x01); + //Empty value flag + buf.push(0x00); + //then value bytes + GraphBinaryV1Serde::to_be_bytes(*value, buf)?; + } + // GValue::Int64(_) => todo!(), + // GValue::Float(_) => todo!(), + // GValue::Double(_) => todo!(), + // GValue::Date(date_time) => todo!(), + // GValue::List(list) => todo!(), + // GValue::Set(set) => todo!(), + GValue::Map(map) => { + //Type code of 0x0a: Map + buf.push(0x0a); + // //Empty value flag + buf.push(0x00); + + //{length} is an Int describing the length of the map. + write_usize_as_i32_be_bytes(map.len(), buf)?; + + //{item_0}…​{item_n} are the items of the map. {item_i} is sequence of 2 fully qualified typed values one representing the key + // and the following representing the value, each composed of {type_code}{type_info}{value_flag}{value}. + for (k, v) in map.iter() { + k.to_be_bytes(buf)?; + v.to_be_bytes(buf)?; + } + } + // GValue::Token(token) => todo!(), + GValue::String(value) => { + //Type code of 0x03: String + buf.push(0x03); + //Empty value flag + buf.push(0x00); + //Format: {length}{text_value} + // {length} is an Int describing the byte length of the text. Length is a positive number or zero to represent the empty string. + // {text_value} is a sequence of bytes representing the string value in UTF8 encoding. + GraphBinaryV1Serde::to_be_bytes(value.as_str(), buf)?; + } + // GValue::Path(path) => todo!(), + // GValue::TraversalMetrics(traversal_metrics) => todo!(), + // GValue::Metric(metric) => todo!(), + // GValue::TraversalExplanation(traversal_explanation) => todo!(), + // GValue::IntermediateRepr(intermediate_repr) => todo!(), + // GValue::P(p) => todo!(), + // GValue::T(t) => todo!(), + GValue::Bytecode(code) => { + //Type code of 0x15: Bytecode + buf.push(0x15); + //Empty value flag + buf.push(0x00); + //then value bytes + // {steps_length}{step_0}…​{step_n}{sources_length}{source_0}…​{source_n} + //{steps_length} is an Int value describing the amount of steps. + //{step_i} is composed of {name}{values_length}{value_0}…​{value_n}, where: + // {name} is a String. + // {values_length} is an Int describing the amount values. + // {value_i} is a fully qualified typed value composed of {type_code}{type_info}{value_flag}{value} describing the step argument. + + fn write_instructions(instructions: &Vec, buf: &mut Vec) -> GremlinResult<()>{ + write_usize_as_i32_be_bytes(instructions.len(), buf)?; + for instruction in instructions { + GraphBinaryV1Serde::to_be_bytes(instruction.operator().as_str(), buf)?; + write_usize_as_i32_be_bytes(instruction.args().len(), buf)?; + instruction.args().iter().try_for_each(|arg| arg.to_be_bytes(buf))?; + } + Ok(()) + } + write_instructions(code.steps(), buf)?; + write_instructions(code.sources(), buf)?; + } + // GValue::Traverser(traverser) => todo!(), + GValue::Scope(scope) => { + //Type code of 0x1f: Scope + buf.push(0x1f); + //Empty value flag + buf.push(0x00); + + //Format: a fully qualified single String representing the enum value. + match scope { + crate::process::traversal::Scope::Global => (&GValue::from(String::from("global"))).to_be_bytes(buf)?, + crate::process::traversal::Scope::Local => (&GValue::from(String::from("local"))).to_be_bytes(buf)?, + } + } + // GValue::Order(order) => todo!(), + // GValue::Bool(_) => todo!(), + // GValue::TextP(text_p) => todo!(), + // GValue::Pop(pop) => todo!(), + + // GValue::Cardinality(cardinality) => todo!(), + // GValue::Merge(merge) => todo!(), + // GValue::Direction(direction) => todo!(), + // GValue::Column(column) => todo!(), + other => unimplemented!("TODO {other:?}"), + } + Ok(()) + } + + // fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { + // todo!() + // } +} + +impl GraphBinaryV1Serde for &GKey { + fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { + match self { + GKey::T(t) => todo!(), + GKey::String(str) => (&GValue::from(str.clone())).to_be_bytes(buf), + GKey::Token(token) => todo!(), + GKey::Vertex(vertex) => todo!(), + GKey::Edge(edge) => todo!(), + GKey::Direction(direction) => todo!(), + } + } + + // fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { + // todo!() + // } +} + +impl, V: Into> GraphBinaryV1Serde for HashMap { + //This represents a complicated meeting point. The request message has a non-qualified emission of a map + //for the arguments, but the contained elements needs to be fully qualified + //Ideally this would just be K: GraphBinaryV1Serde & V: GraphBinaryV1Serde + //but that exposes as a type declaration emission things like HashMap which will then + //invoke the value bytes only impl for String and not doing the qualified and then be rejected by the server + fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { + write_usize_as_i32_be_bytes(self.len(), buf)?; + for (k, v) in self { + //TODO we could just move this logic into mod.rs since it's a detail about the nature of + //how the request message is implemented, and not the generialized notion of how to write a HashMap + //That'd also duck the issue of passing through GKey here + let k: GKey = k.into(); + let v: GValue = v.into(); + k.to_be_bytes(buf)?; + v.to_be_bytes(buf)?; + } + Ok(()) + } + + // fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { + // todo!() + // } +} + +fn deserialize<'a, T: Iterator>(mut value: T) -> GremlinResult { + let data_code = value + .next() + .ok_or_else(|| GremlinError::Cast(format!("Invalid bytes no data code byte")))?; + match data_code { + // GValue::Null => { + // buf.reserve_exact(2); + // //Type code of 0xfe: Unspecified null object + // buf.push(0xfe); + // //Then the null {value_flag} set and no sequence of bytes. + // buf.push(0x01); + // } + // GValue::Vertex(vertex) => todo!(), + // GValue::Edge(edge) => todo!(), + // GValue::VertexProperty(vertex_property) => todo!(), + // GValue::Property(property) => todo!(), + // GValue::Uuid(uuid) => todo!(), + 0x01 => { + //Type code of 0x01: Integer + //Check null flag + match value.next() { + Some(0x00) => { + //We've got a value to parse + // GraphBinaryV1Serde::from_be_bytes(&mut value).map(GValue::Int32) + todo!() + } + Some(0x01) => Ok(GValue::Null), + _ => Err(GremlinError::Cast(format!("Invalid bytes into i32"))), + } + } + // GValue::Int64(_) => todo!(), + // GValue::Float(_) => todo!(), + // GValue::Double(_) => todo!(), + // GValue::Date(date_time) => todo!(), + // GValue::List(list) => todo!(), + // GValue::Set(set) => todo!(), + // GValue::Map(map) => todo!(), + // GValue::Token(token) => todo!(), + 0x03 => { + //Type code of 0x03: String + match value.next() { + Some(0x00) => { + //We've got a value to parse + // GraphBinaryV1Serde::from_be_bytes(&mut value).map(GValue::String) + todo!() + } + Some(0x01) => Ok(GValue::Null), + _ => Err(GremlinError::Cast(format!("Invalid bytes into String"))), + } + + // GValue::String(value) => { + // + // //Empty value flag + // //Format: {length}{text_value} + // // {length} is an Int describing the byte length of the text. Length is a positive number or zero to represent the empty string. + // // {text_value} is a sequence of bytes representing the string value in UTF8 encoding. + } + // GValue::Path(path) => todo!(), + // GValue::TraversalMetrics(traversal_metrics) => todo!(), + // GValue::Metric(metric) => todo!(), + // GValue::TraversalExplanation(traversal_explanation) => todo!(), + // GValue::IntermediateRepr(intermediate_repr) => todo!(), + // GValue::P(p) => todo!(), + // GValue::T(t) => todo!(), + // GValue::Bytecode(code) => { + // //Type code of 0x15: Bytecode + // buf.push(0x15); + // //Empty value flag + // buf.push(0x00); + // //then value bytes + // // {steps_length}{step_0}…​{step_n}{sources_length}{source_0}…​{source_n} + // //{steps_length} is an Int value describing the amount of steps. + // let steps_length: i32 = code.steps().len().try_into().expect("Number of steps should fit in i32"); + // buf.extend_from_slice(serialize(&GValue::Int32(steps_length))); + + // //{step_i} is composed of {name}{values_length}{value_0}…​{value_n}, where: + // // {name} is a String. + // // {values_length} is an Int describing the amount values. + // // {value_i} is a fully qualified typed value composed of {type_code}{type_info}{value_flag}{value} describing the step argument. + + // let steps: GremlinResult> = code + // .steps() + // .iter() + // .map(|m| { + // let mut instruction = vec![]; + // instruction.push(Value::String(m.operator().clone())); + + // let arguments: GremlinResult> = + // m.args().iter().map(|a| self.write(a)).collect(); + + // instruction.extend(arguments?); + // Ok(Value::Array(instruction)) + // }) + // .collect(); + + // let sources: GremlinResult> = code + // .sources() + // .iter() + // .map(|m| { + // let mut instruction = vec![]; + // instruction.push(Value::String(m.operator().clone())); + + // let arguments: GremlinResult> = + // m.args().iter().map(|a| self.write(a)).collect(); + + // instruction.extend(arguments?); + // Ok(Value::Array(instruction)) + // }) + // .collect(); + // } + // GValue::Traverser(traverser) => todo!(), + // GValue::Scope(scope) => todo!(), + // GValue::Order(order) => todo!(), + // GValue::Bool(_) => todo!(), + // GValue::TextP(text_p) => todo!(), + // GValue::Pop(pop) => todo!(), + // GValue::Cardinality(cardinality) => todo!(), + // GValue::Merge(merge) => todo!(), + // GValue::Direction(direction) => todo!(), + // GValue::Column(column) => todo!(), + _ => unimplemented!("TODO"), + } +} + +pub trait GraphBinaryV1Serde: Sized { + fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()>; + // fn to_fully_qualified_be_bytes(&self, buf: &mut Vec) -> GremlinResult<()>; + // fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult; + // fn from_fully_qualified_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult; + + //TODO implement a to_fully_qualified_be_bytes method & from_fully_qualified_be_bytes instead of serialize/deserialize methods + // maybe this doesn't make sense, since it would require us to peek the first byte anyways and then match to the that impl to seek over the byte again +} + +impl GraphBinaryV1Serde for &str { + fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { + let length: i32 = self + .len() + .try_into() + .map_err(|_| GremlinError::Cast(format!("String length exceeds i32")))?; + GraphBinaryV1Serde::to_be_bytes(length, buf)?; + buf.extend_from_slice(self.as_bytes()); + Ok(()) + } + + // fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { + // let string_bytes_length: i32 = GraphBinaryV1Serde::from_be_bytes(bytes) + // .map_err((|_| GremlinError::Cast(format!("Invalid bytes for string length"))))?; + // let string_bytes_length = string_bytes_length + // .try_into() + // .map_err((|_| GremlinError::Cast(format!("String length did not fit into usize"))))?; + // let string_value_bytes: Vec = bytes.take(string_bytes_length).cloned().collect(); + // if string_value_bytes.len() < string_bytes_length { + // return Err(GremlinError::Cast(format!( + // "Missing bytes for string value" + // ))); + // } + // String::from_utf8(string_value_bytes) + // .map_err((|_| GremlinError::Cast(format!("Invalid bytes for string value")))) + // } +} + +impl GraphBinaryV1Serde for i32 { + fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { + buf.extend_from_slice(&self.to_be_bytes()); + Ok(()) + } + + // fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { + // bytes + // .take(4) + // .cloned() + // .collect::>() + // .try_into() + // .map_err(|_| GremlinError::Cast(format!("Invalid bytes into i32"))) + // .map(i32::from_be_bytes) + // } +} + +impl GraphBinaryV1Serde for Uuid { + fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { + buf.extend_from_slice(self.as_bytes().as_slice()); + Ok(()) + } + + // fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { + // bytes + // .take(16) + // .cloned() + // .collect::>() + // .try_into() + // .map_err(|_| GremlinError::Cast(format!("Invalid bytes into Uuid"))) + // .map(Uuid::from_bytes) + // } +} + +// fn deserialize_i32(bytes: &[u8]) -> GremlinResult { +// bytes +// .try_into() +// .map(i32::from_be_bytes) +// .map_err(|_| GremlinError::Cast(format!("Invalid bytes into i32"))) +// } + +// fn serialize_i32(value: i32) -> [u8; 4] { +// value.to_be_bytes() +// } + +#[cfg(test)] +mod tests { + use rstest::rstest; + + use super::*; + + // All encodings are big-endian. + + // Quick examples, using hexadecimal notation to represent each byte: + + // 01 00 00 00 00 01: a 32-bit integer number, that represents the decimal number 1. It’s composed by the type_code 0x01, and empty flag value 0x00 and four bytes to describe the value. + + // 01 00 00 00 00 ff: a 32-bit integer, representing the number 256. + + // 01 01: a null value for a 32-bit integer. It’s composed by the type_code 0x01, and a null flag value 0x01. + + // 02 00 00 00 00 00 00 00 00 01: a 64-bit integer number 1. It’s composed by the type_code 0x02, empty flags and eight bytes to describe the value. + + //Seems like generalized flow should be, be given a slice: + //Read the first byte + //then match on it + + // {type_code}{type_info}{value_flag}{value} + + // {type_code} is a single unsigned byte representing the type number. + + // {type_info} is an optional sequence of bytes providing additional information of the type represented. This is specially useful for representing complex and custom types. + + // {value_flag} is a single byte providing information about the value. Flags have the following meaning: + + // 0x01 The value is null. When this flag is set, no bytes for {value} will be provided. + + // {value} is a sequence of bytes which content is determined by the type. + + #[rstest] + //Non-Null i32 Integer (01 00) + #[case::int_1(&[0x01, 0x00, 0x00, 0x00, 0x00, 0x01], GValue::Int32(1))] + #[case::int_256(&[0x01, 0x00, 0x00, 0x00, 0x01, 0x00], GValue::Int32(256))] + #[case::int_257(&[0x01, 0x00, 0x00, 0x00, 0x01, 0x01], GValue::Int32(257))] + #[case::int_neg_1(&[0x01, 0x00, 0xFF, 0xFF, 0xFF, 0xFF], GValue::Int32(-1))] + #[case::int_neg_2(&[0x01, 0x00, 0xFF, 0xFF, 0xFF, 0xFE], GValue::Int32(-2))] + //Non-Null Strings (03 00) + #[case::str_abc(&[0x03, 0x00, 0x00, 0x00, 0x00, 0x03, 0x61, 0x62, 0x63], GValue::String("abc".into()))] + #[case::str_abcd(&[0x03, 0x00, 0x00, 0x00, 0x00, 0x04, 0x61, 0x62, 0x63, 0x64], GValue::String("abcd".into()))] + #[case::empty_str(&[0x03, 0x00, 0x00, 0x00, 0x00, 0x00], GValue::String("".into()))] + fn serde(#[case] expected_serialized: &[u8], #[case] expected: GValue) { + let mut serialized = Vec::new(); + (&expected).to_be_bytes(&mut serialized).expect("Shouldn't fail parsing"); + assert_eq!(serialized, expected_serialized); + let deserialized = deserialize(serialized.iter()).expect("Shouldn't fail parsing"); + assert_eq!(deserialized, expected); + } + + #[rstest] + #[case::too_few_bytes( &[0x01, 0x00, 0x00, 0x00, 0x00])] + fn serde_int32_invalid_bytes(#[case] bytes: &[u8]) { + deserialize(bytes.iter()).expect_err("Should have failed due invalid bytes"); + } +} diff --git a/gremlin-client/src/io/mod.rs b/gremlin-client/src/io/mod.rs index 79b3cc27..0a355d75 100644 --- a/gremlin-client/src/io/mod.rs +++ b/gremlin-client/src/io/mod.rs @@ -1,24 +1,31 @@ #[macro_use] mod macros; +mod graph_binary_v1; mod serializer_v2; mod serializer_v3; -mod graph_binary_v1; use crate::conversion::ToGValue; -use crate::process::traversal::{Order, Scope}; +use crate::message::{Response, RequestIdV2}; +use crate::process::traversal::{Bytecode, Order, Scope}; use crate::structure::{Cardinality, Direction, GValue, Merge, T}; use serde_json::{json, Map, Value}; +use uuid::Uuid; +use std::collections::HashMap; +use std::convert::TryInto; +use std::f64::consts::E; use std::string::ToString; -use crate::{GremlinError, GremlinResult}; +use crate::{GKey, GremlinError, GremlinResult, Message, io::graph_binary_v1::GraphBinaryV1Serde}; #[derive(Debug, Clone)] pub enum IoProtocol { GraphSONV2, GraphSONV3, + GraphBinaryV1, } impl IoProtocol { + //TODO maybe we could remove pub from read/write? pub fn read(&self, value: &Value) -> GremlinResult> { if let Value::Null = value { return Ok(None); @@ -26,10 +33,164 @@ impl IoProtocol { match self { IoProtocol::GraphSONV2 => serializer_v2::deserializer_v2(value).map(Some), IoProtocol::GraphSONV3 => serializer_v3::deserializer_v3(value).map(Some), + IoProtocol::GraphBinaryV1 => todo!(), } } pub fn write(&self, value: &GValue) -> GremlinResult { + match self { + IoProtocol::GraphSONV2 | IoProtocol::GraphSONV3 => self.write_graphson(value), + IoProtocol::GraphBinaryV1 => todo!(), + } + } + + pub fn read_response(&self, response: &[u8]) -> GremlinResult{ + match self { + IoProtocol::GraphSONV2 | IoProtocol::GraphSONV3 => serde_json::from_slice(&response).map_err(GremlinError::from), + IoProtocol::GraphBinaryV1 => todo!() + } + } + + pub fn build_eval_message(&self, args: HashMap) -> GremlinResult>{ + let op = String::from("eval"); + let processor = String::default(); + let content_type = self.content_type(); + + match self { + IoProtocol::GraphSONV2 | IoProtocol::GraphSONV3 => { + let args = self.write(&GValue::from(args))?; + let message = match self { + IoProtocol::GraphSONV2 => Message::V2 { + request_id: RequestIdV2 { + id_type: "g:UUID".to_string(), + value: Uuid::new_v4(), + }, + op, + processor, + args, + }, + IoProtocol::GraphSONV3 => { + Message::V3 { request_id: Uuid::new_v4(), op, processor, args} + } + _ => panic!("Invalid branch") + }; + + let msg = serde_json::to_string(&message).map_err(GremlinError::from)?; + let payload = String::from("") + content_type + &msg; + let mut binary = payload.into_bytes(); + binary.insert(0, content_type.len() as u8); + Ok(binary) + } + IoProtocol::GraphBinaryV1 => { + let mut message_bytes: Vec = Vec::new(); + //Need to write header first, its length is a Byte not a Int + let header = String::from(content_type); + let header_length: u8 = header.len().try_into().expect("Header length should fit in u8"); + message_bytes.push(header_length); + message_bytes.extend_from_slice(header.as_bytes()); + + //Version byte + message_bytes.push(0x81); + + //Request Id + Uuid::new_v4().to_be_bytes(&mut message_bytes)?; + + //Op + op.to_be_bytes(&mut message_bytes)?; + + //Processor + processor.to_be_bytes(&mut message_bytes)?; + + //Args + (&GValue::from(args)).to_be_bytes(&mut message_bytes)?; + Ok(message_bytes) + } + } + } + + pub fn build_traversal_message(&self, aliases: HashMap, bytecode: &Bytecode) -> GremlinResult> { + let mut args = HashMap::new(); + args.insert(String::from("gremlin"), GValue::Bytecode(bytecode.clone())); + args.insert(String::from("aliases"), GValue::from(aliases)); + let content_type = self.content_type(); + + match self { + IoProtocol::GraphSONV2 | IoProtocol::GraphSONV3 => { + let args = GValue::from(args); + //TODO this should be calling something more congruent with the graphbinary side + let args = self.write(&args)?; + let message =serde_json::to_string(&Message::V3 { + request_id: Uuid::new_v4(), + op: String::from("bytecode"), + processor: String::from("traversal"), + args, + }).map_err(GremlinError::from)?; + + let payload = String::from("") + content_type + &message; + let mut binary = payload.into_bytes(); + binary.insert(0, content_type.len() as u8); + Ok(binary) + } + IoProtocol::GraphBinaryV1 => { + let mut message_bytes: Vec = Vec::new(); + //Need to write header first, its length is a Byte not a Int + let header = String::from(content_type); + let header_length: u8 = header.len().try_into().expect("Header length should fit in u8"); + message_bytes.push(header_length); + message_bytes.extend_from_slice(header.as_bytes()); + + //Version byte + message_bytes.push(0x81); + + //Request Id + Uuid::new_v4().to_be_bytes(&mut message_bytes)?; + + //Op + String::from("bytecode").to_be_bytes(&mut message_bytes)?; + + //Processor + String::from("traversal").to_be_bytes(&mut message_bytes)?; + + //Args + args.to_be_bytes(&mut message_bytes)?; + Ok(message_bytes) + } + } + } + + //TODO we can probably generalize this + // pub fn generate_traversal_message( + // &self, + // aliases: HashMap, + // bytecode: &Bytecode, + // ) -> GremlinResult> { + // let mut args = HashMap::new(); + + // args.insert(String::from("gremlin"), GValue::Bytecode(bytecode.clone())); + + // // let aliases = self + // // .alias + // // .clone() + // // .or_else(|| Some(String::from("g"))) + // // .map(|s| { + // // let mut map = HashMap::new(); + // // map.insert(String::from("g"), GValue::String(s)); + // // map + // // }) + // // .unwrap_or_else(HashMap::new); + + // args.insert(String::from("aliases"), GValue::from(aliases)); + + // let args = self.write(&GValue::from(args))?; + + // Ok(message_with_args( + // String::from("bytecode"), + // String::from("traversal"), + // args, + // )) + // } + + fn write_graphson(&self, value: &GValue) -> GremlinResult { match (self, value) { (_, GValue::Double(d)) => Ok(json!({ "@type" : "g:Double", @@ -257,6 +418,7 @@ impl IoProtocol { match self { IoProtocol::GraphSONV2 => "application/vnd.gremlin-v2.0+json", IoProtocol::GraphSONV3 => "application/vnd.gremlin-v3.0+json", + IoProtocol::GraphBinaryV1 => "application/vnd.graphbinary-v1.0", } } } diff --git a/gremlin-client/src/message.rs b/gremlin-client/src/message.rs index bd2530b3..8fc8a728 100644 --- a/gremlin-client/src/message.rs +++ b/gremlin-client/src/message.rs @@ -7,10 +7,10 @@ use uuid::Uuid; #[serde(rename_all = "camelCase")] pub struct RequestIdV2 { #[serde(rename = "@type")] - id_type: String, + pub id_type: String, #[serde(rename = "@value")] - value: Uuid, + pub value: Uuid, } #[derive(Serialize)] diff --git a/gremlin-client/src/pool.rs b/gremlin-client/src/pool.rs index 9af619a6..7f22ffe1 100644 --- a/gremlin-client/src/pool.rs +++ b/gremlin-client/src/pool.rs @@ -40,25 +40,12 @@ impl ManageConnection for GremlinConnectionManager { String::from("language"), GValue::String(String::from("gremlin-groovy")), ); - let args = self.options.serializer.write(&GValue::from(args))?; - - let message = match self.options.serializer { - IoProtocol::GraphSONV2 => message_with_args_v2(String::from("eval"), String::default(), args), - IoProtocol::GraphSONV3 => message_with_args(String::from("eval"), String::default(), args), - }; - - let msg = serde_json::to_string(&message).map_err(GremlinError::from)?; - - let content_type = self.options.serializer.content_type(); - let payload = String::from("") + content_type + &msg; - - let mut binary = payload.into_bytes(); - binary.insert(0, content_type.len() as u8); - - conn.send(binary)?; + + let message = self.options.serializer.build_eval_message(args)?; + conn.send(message)?; let result = conn.recv()?; - let response: Response = serde_json::from_slice(&result)?; + let response = self.options.deserializer.read_response(&result)?; match response.status.code { 200 | 206 => Ok(()), @@ -91,20 +78,21 @@ impl ManageConnection for GremlinConnectionManager { conn.send(binary)?; let result = conn.recv()?; - let response: Response = serde_json::from_slice(&result)?; - - match response.status.code { - 200 | 206 => Ok(()), - 204 => Ok(()), - 401 => Ok(()), - // 401 is actually a username/password incorrect error, but if not - // not returned as okay, the pool loops infinitely trying - // to authenticate. - _ => Err(GremlinError::Request(( - response.status.code, - response.status.message, - ))), - } + todo!() + // let response: Response = serde_json::from_slice(&result)?; + + // match response.status.code { + // 200 | 206 => Ok(()), + // 204 => Ok(()), + // 401 => Ok(()), + // // 401 is actually a username/password incorrect error, but if not + // // not returned as okay, the pool loops infinitely trying + // // to authenticate. + // _ => Err(GremlinError::Request(( + // response.status.code, + // response.status.message, + // ))), + // } } None => Err(GremlinError::Request(( response.status.code, diff --git a/gremlin-client/src/process/traversal/mod.rs b/gremlin-client/src/process/traversal/mod.rs index 4001c2d2..efed376b 100644 --- a/gremlin-client/src/process/traversal/mod.rs +++ b/gremlin-client/src/process/traversal/mod.rs @@ -18,7 +18,7 @@ pub use order::Order; pub use remote::{traversal, SyncTerminator, Terminator}; pub use builder::TraversalBuilder; -pub use bytecode::{Bytecode, WRITE_OPERATORS}; +pub use bytecode::{Bytecode, WRITE_OPERATORS, Instruction}; pub use graph_traversal::GraphTraversal; pub use graph_traversal_source::GraphTraversalSource; pub use scope::Scope; diff --git a/gremlin-client/tests/common.rs b/gremlin-client/tests/common.rs index 5f2c9f95..c0dae324 100644 --- a/gremlin-client/tests/common.rs +++ b/gremlin-client/tests/common.rs @@ -25,6 +25,7 @@ pub mod io { let port = match serializer { IoProtocol::GraphSONV2 => 8182, IoProtocol::GraphSONV3 => 8182, + IoProtocol::GraphBinaryV1 => 8182, }; GremlinClient::connect( ConnectionOptions::builder() @@ -130,6 +131,7 @@ pub mod aio { let port = match serializer { IoProtocol::GraphSONV2 => 8182, IoProtocol::GraphSONV3 => 8182, + IoProtocol::GraphBinaryV1 => 8182, }; GremlinClient::connect( ConnectionOptions::builder() diff --git a/gremlin-client/tests/integration_traversal_graph_binary.rs b/gremlin-client/tests/integration_traversal_graph_binary.rs new file mode 100644 index 00000000..08425537 --- /dev/null +++ b/gremlin-client/tests/integration_traversal_graph_binary.rs @@ -0,0 +1,11 @@ +use common::io::graph_serializer; +use gremlin_client::{process::traversal::{traversal, Scope}, GValue, IoProtocol}; + +mod common; + +#[test] +fn demo() { + let g = traversal().with_remote(graph_serializer(IoProtocol::GraphBinaryV1)); + let y = g.inject(1).sum(Scope::Global).next().unwrap(); + panic!("Got {:?}", y); +} From 6cfc168b6dd5b44c51f6f279d9079a9f0077962d Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Fri, 18 Oct 2024 20:18:17 -0500 Subject: [PATCH 07/56] Centralized on new serializer based message building & reading --- gremlin-client/src/aio/client.rs | 70 +++++------- gremlin-client/src/aio/pool.rs | 37 +------ gremlin-client/src/client.rs | 103 +++++------------- gremlin-client/src/io/mod.rs | 177 +++++++++---------------------- gremlin-client/src/pool.rs | 50 +++------ 5 files changed, 118 insertions(+), 319 deletions(-) diff --git a/gremlin-client/src/aio/client.rs b/gremlin-client/src/aio/client.rs index 50e5040c..28a72480 100644 --- a/gremlin-client/src/aio/client.rs +++ b/gremlin-client/src/aio/client.rs @@ -13,6 +13,7 @@ use futures::future::{BoxFuture, FutureExt}; use mobc::{Connection, Pool}; use serde::Serialize; use std::collections::{HashMap, VecDeque}; +use uuid::Uuid; pub type SessionedClient = GremlinClient; @@ -21,19 +22,14 @@ impl SessionedClient { if let Some(session_name) = self.session.take() { let mut args = HashMap::new(); args.insert(String::from("session"), GValue::from(session_name.clone())); - let args = self.options.serializer.write(&GValue::from(args))?; - - let processor = "session".to_string(); - - let message = match self.options.serializer { - IoProtocol::GraphSONV2 => message_with_args_v2(String::from("close"), processor, args), - IoProtocol::GraphSONV3 => message_with_args(String::from("close"), processor, args), - IoProtocol::GraphBinaryV1 => todo!("Need to add the handling logic for writing to a processor op") - }; + let (id, message) = self + .options + .serializer + .build_message("close", "session", args, None)?; let conn = self.pool.get().await?; - self.send_message_new(conn, message).await + self.send_message_new(conn, id, message).await } else { Err(GremlinError::Generic("No session to close".to_string())) } @@ -132,39 +128,29 @@ impl GremlinClient { args.insert(String::from("session"), GValue::from(session_name.clone())); } - let args = self.options.serializer.write(&GValue::from(args))?; - let processor = if self.session.is_some() { - "session".to_string() + "session" } else { - String::default() + "" }; - let message = match self.options.serializer { - IoProtocol::GraphSONV2 => message_with_args_v2(String::from("eval"), processor, args), - IoProtocol::GraphSONV3 => message_with_args(String::from("eval"), processor, args), - IoProtocol::GraphBinaryV1 => todo!("Need to add the handling logic for writing to a processor op") - }; + let (id, message) = self + .options + .serializer + .build_message("eval", processor, args, None)?; let conn = self.pool.get().await?; - self.send_message_new(conn, message).await + self.send_message_new(conn, id, message).await } pub(crate) fn send_message_new<'a, T: Serialize>( &'a self, mut conn: Connection, - msg: Message, + id: Uuid, + binary: Vec, ) -> BoxFuture<'a, GremlinResult> { - let id = msg.id().clone(); - let message = self.build_message(msg).unwrap(); - async move { - let content_type = self.options.serializer.content_type(); - 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 (response, results) = match response.status.code { @@ -187,15 +173,14 @@ impl GremlinClient { GValue::String(encode(&format!("\0{}\0{}", c.username, c.password))), ); - let args = self.options.serializer.write(&GValue::from(args))?; - let message = message_with_args_and_uuid( - String::from("authentication"), - String::from("traversal"), - response.request_id, + let (id, message) = self.options.serializer.build_message( + "authentication", + "traversal", args, - ); + Some(response.request_id), + )?; - return self.send_message_new(conn, message).await; + return self.send_message_new(conn, id, message).await; } None => Err(GremlinError::Request(( response.status.code, @@ -231,16 +216,13 @@ impl GremlinClient { args.insert(String::from("aliases"), GValue::from(aliases)); - let args = self.options.serializer.write(&GValue::from(args))?; - - let message = message_with_args(String::from("bytecode"), String::from("traversal"), args); + let (id, message) = self + .options + .serializer + .build_message("bytecode", "traversal", args, None)?; let conn = self.pool.get().await?; - self.send_message_new(conn, message).await - } - - fn build_message(&self, msg: Message) -> GremlinResult { - serde_json::to_string(&msg).map_err(GremlinError::from) + self.send_message_new(conn, id, message).await } } diff --git a/gremlin-client/src/aio/pool.rs b/gremlin-client/src/aio/pool.rs index f1b7b0ec..f74f6bca 100644 --- a/gremlin-client/src/aio/pool.rs +++ b/gremlin-client/src/aio/pool.rs @@ -40,22 +40,8 @@ impl Manager for GremlinConnectionManager { String::from("language"), GValue::String(String::from("gremlin-groovy")), ); - let args = self.options.serializer.write(&GValue::from(args))?; - let message = match self.options.serializer { - IoProtocol::GraphSONV2 => message_with_args_v2(String::from("eval"), String::default(), args), - IoProtocol::GraphSONV3 => message_with_args(String::from("eval"), String::default(), args), - IoProtocol::GraphBinaryV1 => todo!("Need to add the handling logic for writing to a processor op") - }; - - let id = message.id().clone(); - let msg = serde_json::to_string(&message).map_err(GremlinError::from)?; - - let content_type = self.options.serializer.content_type(); - - let payload = String::from("") + content_type + &msg; - let mut binary = payload.into_bytes(); - binary.insert(0, content_type.len() as u8); + let (id, message) = self.options.serializer.build_message("eval", "", args, None)?; let (response, _receiver) = conn.send(id, binary).await?; @@ -71,25 +57,8 @@ impl Manager for GremlinConnectionManager { GValue::String(encode(&format!("\0{}\0{}", c.username, c.password))), ); - let args = self.options.serializer.write(&GValue::from(args))?; - let message = message_with_args_and_uuid( - String::from("authentication"), - String::from("traversal"), - response.request_id, - args, - ); - - let id = message.id().clone(); - let msg = serde_json::to_string(&message).map_err(GremlinError::from)?; - - let content_type = self.options.serializer.content_type(); - let payload = String::from("") + content_type + &msg; - - let mut binary = payload.into_bytes(); - binary.insert(0, content_type.len() as u8); - - let (response, _receiver) = conn.send(id, binary).await?; - + let (id, message) = self.options.serializer.build_message("authentication", "traversal", args, Some(response.request_id))?; + let (response, _receiver) = conn.send(id, message).await?; match response.status.code { 200 | 206 => Ok(conn), 204 => Ok(conn), diff --git a/gremlin-client/src/client.rs b/gremlin-client/src/client.rs index c5fa220d..7753ea23 100644 --- a/gremlin-client/src/client.rs +++ b/gremlin-client/src/client.rs @@ -19,24 +19,15 @@ impl SessionedClient { if let Some(session_name) = self.session.take() { let mut args = HashMap::new(); args.insert(String::from("session"), GValue::from(session_name.clone())); - let args = self.options.serializer.write(&GValue::from(args))?; - let processor = "session".to_string(); - - let message = match self.options.serializer { - IoProtocol::GraphSONV2 => { - message_with_args_v2(String::from("close"), processor, args) - } - IoProtocol::GraphSONV3 => message_with_args(String::from("close"), processor, args), - IoProtocol::GraphBinaryV1 => { - todo!("Need to add the handling logic for writing to a processor op") - } - }; + let (_, message) = self + .options + .serializer + .build_message("close", "session", args, None)?; let conn = self.pool.get()?; - todo!() - // self.send_message(conn, message) + self.send_message(conn, message) } else { Err(GremlinError::Generic("No session to close".to_string())) } @@ -133,26 +124,20 @@ impl GremlinClient { args.insert(String::from("session"), GValue::from(session_name.clone())); } - let args = self.options.serializer.write(&GValue::from(args))?; - let processor = if self.session.is_some() { - "session".to_string() + "session" } else { - String::default() + "" }; - let message = match self.options.serializer { - IoProtocol::GraphSONV2 => message_with_args_v2(String::from("eval"), processor, args), - IoProtocol::GraphSONV3 => message_with_args(String::from("eval"), processor, args), - IoProtocol::GraphBinaryV1 => { - todo!("Need to add the handling logic for writing to a processor op") - } - }; + let (_, message) = self + .options + .serializer + .build_message("eval", processor, args, None)?; let conn = self.pool.get()?; - // self.send_message(conn, message) - todo!() + self.send_message(conn, message) } pub(crate) fn send_message( @@ -167,14 +152,7 @@ impl GremlinClient { Ok(GResultSet::new(self.clone(), results, response, conn)) } - pub fn generate_message( - &self, - bytecode: &Bytecode, - ) -> GremlinResult> { - let mut args = HashMap::new(); - - args.insert(String::from("gremlin"), GValue::Bytecode(bytecode.clone())); - + pub(crate) fn submit_traversal(&self, bytecode: &Bytecode) -> GremlinResult { let aliases = self .alias .clone() @@ -186,39 +164,14 @@ impl GremlinClient { }) .unwrap_or_else(HashMap::new); + let mut args = HashMap::new(); + args.insert(String::from("gremlin"), GValue::Bytecode(bytecode.clone())); args.insert(String::from("aliases"), GValue::from(aliases)); - let args = self.options.serializer.write(&GValue::from(args))?; - - Ok(message_with_args( - String::from("bytecode"), - String::from("traversal"), - args, - )) - } - - pub(crate) fn submit_traversal(&self, bytecode: &Bytecode) -> GremlinResult { - let aliases = self - .alias - .clone() - .or_else(|| Some(String::from("g"))) - .map(|s| { - let mut map = HashMap::new(); - map.insert(String::from("g"), GValue::String(s)); - map - }) - .unwrap_or_else(HashMap::new); - - let message = self + let (_,message) = self .options .serializer - .build_traversal_message(aliases, bytecode)?; - - if true { - let message: Vec = message.into_iter().map(|byte| byte as i8).collect(); - panic!("{:?}", message); - } - + .build_message("bytecode", "traversal", args, None)?; let conn = self.pool.get()?; self.send_message(conn, message) @@ -252,18 +205,15 @@ impl GremlinClient { GValue::String(encode(&format!("\0{}\0{}", c.username, c.password))), ); - let args = self.options.serializer.write(&GValue::from(args))?; - let message = message_with_args_and_uuid( - String::from("authentication"), - String::from("traversal"), - response.request_id, + let (_, message) = self.options.serializer.build_message( + "authentication", + "traversal", args, - ); - - todo!() - // self.write_message(conn, message)?; + Some(response.request_id), + )?; + conn.send(message)?; - // self.read_response(conn) + self.read_response(conn) } None => Err(GremlinError::Request(( response.status.code, @@ -276,9 +226,4 @@ impl GremlinClient { ))), } } - - fn build_message(&self, msg: Message) -> GremlinResult { - //TODO this should be gone by the end - serde_json::to_string(&msg).map_err(GremlinError::from) - } } diff --git a/gremlin-client/src/io/mod.rs b/gremlin-client/src/io/mod.rs index 0a355d75..240801ce 100644 --- a/gremlin-client/src/io/mod.rs +++ b/gremlin-client/src/io/mod.rs @@ -5,17 +5,17 @@ mod serializer_v2; mod serializer_v3; use crate::conversion::ToGValue; -use crate::message::{Response, RequestIdV2}; +use crate::message::{RequestIdV2, Response}; use crate::process::traversal::{Bytecode, Order, Scope}; use crate::structure::{Cardinality, Direction, GValue, Merge, T}; use serde_json::{json, Map, Value}; -use uuid::Uuid; use std::collections::HashMap; use std::convert::TryInto; use std::f64::consts::E; use std::string::ToString; +use uuid::Uuid; -use crate::{GKey, GremlinError, GremlinResult, Message, io::graph_binary_v1::GraphBinaryV1Serde}; +use crate::{io::graph_binary_v1::GraphBinaryV1Serde, GKey, GremlinError, GremlinResult, Message}; #[derive(Debug, Clone)] pub enum IoProtocol { @@ -25,7 +25,6 @@ pub enum IoProtocol { } impl IoProtocol { - //TODO maybe we could remove pub from read/write? pub fn read(&self, value: &Value) -> GremlinResult> { if let Value::Null = value { return Ok(None); @@ -37,55 +36,56 @@ impl IoProtocol { } } - pub fn write(&self, value: &GValue) -> GremlinResult { + pub fn read_response(&self, response: &[u8]) -> GremlinResult { match self { - IoProtocol::GraphSONV2 | IoProtocol::GraphSONV3 => self.write_graphson(value), + IoProtocol::GraphSONV2 | IoProtocol::GraphSONV3 => { + serde_json::from_slice(&response).map_err(GremlinError::from) + } IoProtocol::GraphBinaryV1 => todo!(), } } - pub fn read_response(&self, response: &[u8]) -> GremlinResult{ - match self { - IoProtocol::GraphSONV2 | IoProtocol::GraphSONV3 => serde_json::from_slice(&response).map_err(GremlinError::from), - IoProtocol::GraphBinaryV1 => todo!() - } - } - - pub fn build_eval_message(&self, args: HashMap) -> GremlinResult>{ - let op = String::from("eval"); - let processor = String::default(); + pub fn build_message(&self, op: &str, processor: &str, args: HashMap, request_id: Option) -> GremlinResult<(Uuid, Vec)> { let content_type = self.content_type(); - - match self { + let request_id = request_id.unwrap_or_else(Uuid::new_v4); + let message_bytes = match self { IoProtocol::GraphSONV2 | IoProtocol::GraphSONV3 => { - let args = self.write(&GValue::from(args))?; + let op = op.into(); + let processor = processor.into(); + let args = self.write_graphson(&GValue::from(args))?; let message = match self { IoProtocol::GraphSONV2 => Message::V2 { - request_id: RequestIdV2 { - id_type: "g:UUID".to_string(), - value: Uuid::new_v4(), + request_id: RequestIdV2 { + id_type: "g:UUID".to_string(), + value: request_id, + }, + op, + processor, + args, }, - op, - processor, - args, - }, - IoProtocol::GraphSONV3 => { - Message::V3 { request_id: Uuid::new_v4(), op, processor, args} - } - _ => panic!("Invalid branch") - }; + IoProtocol::GraphSONV3 => Message::V3 { + request_id, + op, + processor, + args, + }, + _ => unreachable!("Invalid branch"), + }; let msg = serde_json::to_string(&message).map_err(GremlinError::from)?; let payload = String::from("") + content_type + &msg; let mut binary = payload.into_bytes(); binary.insert(0, content_type.len() as u8); - Ok(binary) + binary } IoProtocol::GraphBinaryV1 => { let mut message_bytes: Vec = Vec::new(); //Need to write header first, its length is a Byte not a Int let header = String::from(content_type); - let header_length: u8 = header.len().try_into().expect("Header length should fit in u8"); + let header_length: u8 = header + .len() + .try_into() + .expect("Header length should fit in u8"); message_bytes.push(header_length); message_bytes.extend_from_slice(header.as_bytes()); @@ -93,7 +93,7 @@ impl IoProtocol { message_bytes.push(0x81); //Request Id - Uuid::new_v4().to_be_bytes(&mut message_bytes)?; + request_id.to_be_bytes(&mut message_bytes)?; //Op op.to_be_bytes(&mut message_bytes)?; @@ -101,95 +101,14 @@ impl IoProtocol { //Processor processor.to_be_bytes(&mut message_bytes)?; - //Args - (&GValue::from(args)).to_be_bytes(&mut message_bytes)?; - Ok(message_bytes) - } - } - } - - pub fn build_traversal_message(&self, aliases: HashMap, bytecode: &Bytecode) -> GremlinResult> { - let mut args = HashMap::new(); - args.insert(String::from("gremlin"), GValue::Bytecode(bytecode.clone())); - args.insert(String::from("aliases"), GValue::from(aliases)); - let content_type = self.content_type(); - - match self { - IoProtocol::GraphSONV2 | IoProtocol::GraphSONV3 => { - let args = GValue::from(args); - //TODO this should be calling something more congruent with the graphbinary side - let args = self.write(&args)?; - let message =serde_json::to_string(&Message::V3 { - request_id: Uuid::new_v4(), - op: String::from("bytecode"), - processor: String::from("traversal"), - args, - }).map_err(GremlinError::from)?; - - let payload = String::from("") + content_type + &message; - let mut binary = payload.into_bytes(); - binary.insert(0, content_type.len() as u8); - Ok(binary) - } - IoProtocol::GraphBinaryV1 => { - let mut message_bytes: Vec = Vec::new(); - //Need to write header first, its length is a Byte not a Int - let header = String::from(content_type); - let header_length: u8 = header.len().try_into().expect("Header length should fit in u8"); - message_bytes.push(header_length); - message_bytes.extend_from_slice(header.as_bytes()); - - //Version byte - message_bytes.push(0x81); - - //Request Id - Uuid::new_v4().to_be_bytes(&mut message_bytes)?; - - //Op - String::from("bytecode").to_be_bytes(&mut message_bytes)?; - - //Processor - String::from("traversal").to_be_bytes(&mut message_bytes)?; - //Args args.to_be_bytes(&mut message_bytes)?; - Ok(message_bytes) + message_bytes } - } + }; + Ok((request_id, message_bytes)) } - //TODO we can probably generalize this - // pub fn generate_traversal_message( - // &self, - // aliases: HashMap, - // bytecode: &Bytecode, - // ) -> GremlinResult> { - // let mut args = HashMap::new(); - - // args.insert(String::from("gremlin"), GValue::Bytecode(bytecode.clone())); - - // // let aliases = self - // // .alias - // // .clone() - // // .or_else(|| Some(String::from("g"))) - // // .map(|s| { - // // let mut map = HashMap::new(); - // // map.insert(String::from("g"), GValue::String(s)); - // // map - // // }) - // // .unwrap_or_else(HashMap::new); - - // args.insert(String::from("aliases"), GValue::from(aliases)); - - // let args = self.write(&GValue::from(args))?; - - // Ok(message_with_args( - // String::from("bytecode"), - // String::from("traversal"), - // args, - // )) - // } - fn write_graphson(&self, value: &GValue) -> GremlinResult { match (self, value) { (_, GValue::Double(d)) => Ok(json!({ @@ -218,11 +137,11 @@ impl IoProtocol { "@value" : d.timestamp_millis() })), (IoProtocol::GraphSONV2, GValue::List(d)) => { - let elements: GremlinResult> = d.iter().map(|e| self.write(e)).collect(); + let elements: GremlinResult> = d.iter().map(|e| self.write_graphson(e)).collect(); Ok(json!(elements?)) } (IoProtocol::GraphSONV3, GValue::List(d)) => { - let elements: GremlinResult> = d.iter().map(|e| self.write(e)).collect(); + let elements: GremlinResult> = d.iter().map(|e| self.write_graphson(e)).collect(); Ok(json!({ "@type" : "g:List", "@value" : elements? @@ -232,7 +151,7 @@ impl IoProtocol { "@type" : "g:P", "@value" : { "predicate" : p.operator(), - "value" : self.write(p.value())? + "value" : self.write_graphson(p.value())? } })), (_, GValue::Bytecode(code)) => { @@ -244,7 +163,7 @@ impl IoProtocol { instruction.push(Value::String(m.operator().clone())); let arguments: GremlinResult> = - m.args().iter().map(|a| self.write(a)).collect(); + m.args().iter().map(|a| self.write_graphson(a)).collect(); instruction.extend(arguments?); Ok(Value::Array(instruction)) @@ -259,7 +178,7 @@ impl IoProtocol { instruction.push(Value::String(m.operator().clone())); let arguments: GremlinResult> = - m.args().iter().map(|a| self.write(a)).collect(); + m.args().iter().map(|a| self.write_graphson(a)).collect(); instruction.extend(arguments?); Ok(Value::Array(instruction)) @@ -274,7 +193,7 @@ impl IoProtocol { })) } (_, GValue::Vertex(v)) => { - let id = self.write(&v.id().to_gvalue())?; + let id = self.write_graphson(&v.id().to_gvalue())?; Ok(json!({ "@type" : "g:Vertex", "@value" : { @@ -287,13 +206,13 @@ impl IoProtocol { for (k, v) in map.iter() { params.insert( - self.write(&k.clone().into())? + self.write_graphson(&k.clone().into())? .as_str() .ok_or_else(|| { GremlinError::Generic("Non-string key value.".to_string()) })? .to_string(), - self.write(&v)?, + self.write_graphson(&v)?, ); } @@ -303,8 +222,8 @@ impl IoProtocol { let mut params = vec![]; for (k, v) in map.iter() { - params.push(self.write(&k.clone().into())?); - params.push(self.write(&v)?); + params.push(self.write_graphson(&k.clone().into())?); + params.push(self.write_graphson(&v)?); } Ok(json!({ @@ -360,7 +279,7 @@ impl IoProtocol { "@type" : "g:TextP", "@value" : { "predicate" : text_p.operator(), - "value" : self.write(text_p.value())? + "value" : self.write_graphson(text_p.value())? } })), (_, GValue::Pop(pop)) => Ok(json!({ diff --git a/gremlin-client/src/pool.rs b/gremlin-client/src/pool.rs index 7f22ffe1..2c1e519e 100644 --- a/gremlin-client/src/pool.rs +++ b/gremlin-client/src/pool.rs @@ -41,7 +41,7 @@ impl ManageConnection for GremlinConnectionManager { GValue::String(String::from("gremlin-groovy")), ); - let message = self.options.serializer.build_eval_message(args)?; + let (_, message) = self.options.serializer.build_message("eval", "", args, None)?; conn.send(message)?; let result = conn.recv()?; @@ -59,40 +59,24 @@ impl ManageConnection for GremlinConnectionManager { GValue::String(encode(&format!("\0{}\0{}", c.username, c.password))), ); - let args = self.options.serializer.write(&GValue::from(args))?; - let message = message_with_args_and_uuid( - String::from("authentication"), - String::from("traversal"), - response.request_id, - args, - ); - - let msg = serde_json::to_string(&message).map_err(GremlinError::from)?; - - let content_type = self.options.serializer.content_type(); - let payload = String::from("") + content_type + &msg; - - let mut binary = payload.into_bytes(); - binary.insert(0, content_type.len() as u8); - - conn.send(binary)?; + let (_, message)= self.options.serializer.build_message("authentication", "traversal", args, Some(response.request_id))?; + conn.send(message)?; let result = conn.recv()?; - todo!() - // let response: Response = serde_json::from_slice(&result)?; - - // match response.status.code { - // 200 | 206 => Ok(()), - // 204 => Ok(()), - // 401 => Ok(()), - // // 401 is actually a username/password incorrect error, but if not - // // not returned as okay, the pool loops infinitely trying - // // to authenticate. - // _ => Err(GremlinError::Request(( - // response.status.code, - // response.status.message, - // ))), - // } + let response = self.options.deserializer.read_response(&result)?; + + match response.status.code { + 200 | 206 => Ok(()), + 204 => Ok(()), + 401 => Ok(()), + // 401 is actually a username/password incorrect error, but if not + // not returned as okay, the pool loops infinitely trying + // to authenticate. + _ => Err(GremlinError::Request(( + response.status.code, + response.status.message, + ))), + } } None => Err(GremlinError::Request(( response.status.code, From 97a3de62e9fae0ef4a2fb58a72c247fc9e300b71 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Fri, 18 Oct 2024 20:22:14 -0500 Subject: [PATCH 08/56] Formatting --- gremlin-client/src/aio/client.rs | 8 ++--- gremlin-client/src/aio/pool.rs | 12 ++++++-- gremlin-client/src/client.rs | 8 ++--- gremlin-client/src/connection.rs | 2 +- gremlin-client/src/io/graph_binary_v1.rs | 30 +++++++++++++------ gremlin-client/src/io/mod.rs | 14 +++++++-- gremlin-client/src/pool.rs | 16 +++++++--- gremlin-client/src/process/traversal/mod.rs | 2 +- gremlin-client/tests/common.rs | 6 ++-- gremlin-client/tests/integration_client_v2.rs | 2 +- .../integration_traversal_graph_binary.rs | 5 +++- 11 files changed, 73 insertions(+), 32 deletions(-) diff --git a/gremlin-client/src/aio/client.rs b/gremlin-client/src/aio/client.rs index 28a72480..ef75e9eb 100644 --- a/gremlin-client/src/aio/client.rs +++ b/gremlin-client/src/aio/client.rs @@ -216,10 +216,10 @@ impl GremlinClient { args.insert(String::from("aliases"), GValue::from(aliases)); - let (id, message) = self - .options - .serializer - .build_message("bytecode", "traversal", args, None)?; + let (id, message) = + self.options + .serializer + .build_message("bytecode", "traversal", args, None)?; let conn = self.pool.get().await?; diff --git a/gremlin-client/src/aio/pool.rs b/gremlin-client/src/aio/pool.rs index f74f6bca..43d53193 100644 --- a/gremlin-client/src/aio/pool.rs +++ b/gremlin-client/src/aio/pool.rs @@ -41,7 +41,10 @@ impl Manager for GremlinConnectionManager { GValue::String(String::from("gremlin-groovy")), ); - let (id, message) = self.options.serializer.build_message("eval", "", args, None)?; + let (id, message) = self + .options + .serializer + .build_message("eval", "", args, None)?; let (response, _receiver) = conn.send(id, binary).await?; @@ -57,7 +60,12 @@ impl Manager for GremlinConnectionManager { GValue::String(encode(&format!("\0{}\0{}", c.username, c.password))), ); - let (id, message) = self.options.serializer.build_message("authentication", "traversal", args, Some(response.request_id))?; + let (id, message) = self.options.serializer.build_message( + "authentication", + "traversal", + args, + Some(response.request_id), + )?; let (response, _receiver) = conn.send(id, message).await?; match response.status.code { 200 | 206 => Ok(conn), diff --git a/gremlin-client/src/client.rs b/gremlin-client/src/client.rs index 7753ea23..52821142 100644 --- a/gremlin-client/src/client.rs +++ b/gremlin-client/src/client.rs @@ -168,10 +168,10 @@ impl GremlinClient { args.insert(String::from("gremlin"), GValue::Bytecode(bytecode.clone())); args.insert(String::from("aliases"), GValue::from(aliases)); - let (_,message) = self - .options - .serializer - .build_message("bytecode", "traversal", args, None)?; + let (_, message) = + self.options + .serializer + .build_message("bytecode", "traversal", args, None)?; let conn = self.pool.get()?; self.send_message(conn, message) diff --git a/gremlin-client/src/connection.rs b/gremlin-client/src/connection.rs index e99050d4..7660448c 100644 --- a/gremlin-client/src/connection.rs +++ b/gremlin-client/src/connection.rs @@ -1,6 +1,6 @@ use std::{net::TcpStream, sync::Arc, time::Duration}; -use crate::{IoProtocol, GremlinError, GremlinResult}; +use crate::{GremlinError, GremlinResult, IoProtocol}; use native_tls::TlsConnector; use tungstenite::{ client::{uri_mode, IntoClientRequest}, diff --git a/gremlin-client/src/io/graph_binary_v1.rs b/gremlin-client/src/io/graph_binary_v1.rs index b46c574d..004027a2 100644 --- a/gremlin-client/src/io/graph_binary_v1.rs +++ b/gremlin-client/src/io/graph_binary_v1.rs @@ -34,7 +34,7 @@ struct RequestV1 { fn write_usize_as_i32_be_bytes(val: usize, buf: &mut Vec) -> GremlinResult<()> { let val_i32 = TryInto::::try_into(val) - .map_err(|_| GremlinError::Cast(format!("Invalid usize bytes exceed i32")))?; + .map_err(|_| GremlinError::Cast(format!("Invalid usize bytes exceed i32")))?; GraphBinaryV1Serde::to_be_bytes(val_i32, buf) } @@ -107,18 +107,24 @@ impl GraphBinaryV1Serde for &GValue { buf.push(0x00); //then value bytes // {steps_length}{step_0}…​{step_n}{sources_length}{source_0}…​{source_n} - //{steps_length} is an Int value describing the amount of steps. + //{steps_length} is an Int value describing the amount of steps. //{step_i} is composed of {name}{values_length}{value_0}…​{value_n}, where: // {name} is a String. // {values_length} is an Int describing the amount values. // {value_i} is a fully qualified typed value composed of {type_code}{type_info}{value_flag}{value} describing the step argument. - fn write_instructions(instructions: &Vec, buf: &mut Vec) -> GremlinResult<()>{ + fn write_instructions( + instructions: &Vec, + buf: &mut Vec, + ) -> GremlinResult<()> { write_usize_as_i32_be_bytes(instructions.len(), buf)?; for instruction in instructions { GraphBinaryV1Serde::to_be_bytes(instruction.operator().as_str(), buf)?; write_usize_as_i32_be_bytes(instruction.args().len(), buf)?; - instruction.args().iter().try_for_each(|arg| arg.to_be_bytes(buf))?; + instruction + .args() + .iter() + .try_for_each(|arg| arg.to_be_bytes(buf))?; } Ok(()) } @@ -134,15 +140,19 @@ impl GraphBinaryV1Serde for &GValue { //Format: a fully qualified single String representing the enum value. match scope { - crate::process::traversal::Scope::Global => (&GValue::from(String::from("global"))).to_be_bytes(buf)?, - crate::process::traversal::Scope::Local => (&GValue::from(String::from("local"))).to_be_bytes(buf)?, + crate::process::traversal::Scope::Global => { + (&GValue::from(String::from("global"))).to_be_bytes(buf)? + } + crate::process::traversal::Scope::Local => { + (&GValue::from(String::from("local"))).to_be_bytes(buf)? + } } } // GValue::Order(order) => todo!(), // GValue::Bool(_) => todo!(), // GValue::TextP(text_p) => todo!(), // GValue::Pop(pop) => todo!(), - + // GValue::Cardinality(cardinality) => todo!(), // GValue::Merge(merge) => todo!(), // GValue::Direction(direction) => todo!(), @@ -328,7 +338,7 @@ pub trait GraphBinaryV1Serde: Sized { // fn to_fully_qualified_be_bytes(&self, buf: &mut Vec) -> GremlinResult<()>; // fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult; // fn from_fully_qualified_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult; - + //TODO implement a to_fully_qualified_be_bytes method & from_fully_qualified_be_bytes instead of serialize/deserialize methods // maybe this doesn't make sense, since it would require us to peek the first byte anyways and then match to the that impl to seek over the byte again } @@ -453,7 +463,9 @@ mod tests { #[case::empty_str(&[0x03, 0x00, 0x00, 0x00, 0x00, 0x00], GValue::String("".into()))] fn serde(#[case] expected_serialized: &[u8], #[case] expected: GValue) { let mut serialized = Vec::new(); - (&expected).to_be_bytes(&mut serialized).expect("Shouldn't fail parsing"); + (&expected) + .to_be_bytes(&mut serialized) + .expect("Shouldn't fail parsing"); assert_eq!(serialized, expected_serialized); let deserialized = deserialize(serialized.iter()).expect("Shouldn't fail parsing"); assert_eq!(deserialized, expected); diff --git a/gremlin-client/src/io/mod.rs b/gremlin-client/src/io/mod.rs index 240801ce..6f59a56d 100644 --- a/gremlin-client/src/io/mod.rs +++ b/gremlin-client/src/io/mod.rs @@ -45,7 +45,13 @@ impl IoProtocol { } } - pub fn build_message(&self, op: &str, processor: &str, args: HashMap, request_id: Option) -> GremlinResult<(Uuid, Vec)> { + pub fn build_message( + &self, + op: &str, + processor: &str, + args: HashMap, + request_id: Option, + ) -> GremlinResult<(Uuid, Vec)> { let content_type = self.content_type(); let request_id = request_id.unwrap_or_else(Uuid::new_v4); let message_bytes = match self { @@ -137,11 +143,13 @@ impl IoProtocol { "@value" : d.timestamp_millis() })), (IoProtocol::GraphSONV2, GValue::List(d)) => { - let elements: GremlinResult> = d.iter().map(|e| self.write_graphson(e)).collect(); + let elements: GremlinResult> = + d.iter().map(|e| self.write_graphson(e)).collect(); Ok(json!(elements?)) } (IoProtocol::GraphSONV3, GValue::List(d)) => { - let elements: GremlinResult> = d.iter().map(|e| self.write_graphson(e)).collect(); + let elements: GremlinResult> = + d.iter().map(|e| self.write_graphson(e)).collect(); Ok(json!({ "@type" : "g:List", "@value" : elements? diff --git a/gremlin-client/src/pool.rs b/gremlin-client/src/pool.rs index 2c1e519e..81f3c28e 100644 --- a/gremlin-client/src/pool.rs +++ b/gremlin-client/src/pool.rs @@ -6,7 +6,7 @@ use crate::error::GremlinError; use crate::message::{ message_with_args, message_with_args_and_uuid, message_with_args_v2, Response, }; -use crate::{GValue, IoProtocol, GremlinResult}; +use crate::{GValue, GremlinResult, IoProtocol}; use base64::encode; use std::collections::HashMap; @@ -40,8 +40,11 @@ impl ManageConnection for GremlinConnectionManager { String::from("language"), GValue::String(String::from("gremlin-groovy")), ); - - let (_, message) = self.options.serializer.build_message("eval", "", args, None)?; + + let (_, message) = self + .options + .serializer + .build_message("eval", "", args, None)?; conn.send(message)?; let result = conn.recv()?; @@ -59,7 +62,12 @@ impl ManageConnection for GremlinConnectionManager { GValue::String(encode(&format!("\0{}\0{}", c.username, c.password))), ); - let (_, message)= self.options.serializer.build_message("authentication", "traversal", args, Some(response.request_id))?; + let (_, message) = self.options.serializer.build_message( + "authentication", + "traversal", + args, + Some(response.request_id), + )?; conn.send(message)?; let result = conn.recv()?; diff --git a/gremlin-client/src/process/traversal/mod.rs b/gremlin-client/src/process/traversal/mod.rs index efed376b..e1a5d8d4 100644 --- a/gremlin-client/src/process/traversal/mod.rs +++ b/gremlin-client/src/process/traversal/mod.rs @@ -18,7 +18,7 @@ pub use order::Order; pub use remote::{traversal, SyncTerminator, Terminator}; pub use builder::TraversalBuilder; -pub use bytecode::{Bytecode, WRITE_OPERATORS, Instruction}; +pub use bytecode::{Bytecode, Instruction, WRITE_OPERATORS}; pub use graph_traversal::GraphTraversal; pub use graph_traversal_source::GraphTraversalSource; pub use scope::Scope; diff --git a/gremlin-client/tests/common.rs b/gremlin-client/tests/common.rs index c0dae324..01eaa608 100644 --- a/gremlin-client/tests/common.rs +++ b/gremlin-client/tests/common.rs @@ -11,7 +11,9 @@ pub fn assert_map_property(element_map: &Map, expected_key: &str, expected_value #[allow(dead_code)] pub mod io { - use gremlin_client::{ConnectionOptions, Edge, IoProtocol, GremlinClient, GremlinResult, Vertex}; + use gremlin_client::{ + ConnectionOptions, Edge, GremlinClient, GremlinResult, IoProtocol, Vertex, + }; pub fn connect() -> GremlinResult { GremlinClient::connect(("localhost", 8182)) @@ -113,7 +115,7 @@ pub mod io { pub mod aio { use gremlin_client::aio::GremlinClient; - use gremlin_client::{ConnectionOptions, Edge, IoProtocol, GremlinResult, Vertex}; + use gremlin_client::{ConnectionOptions, Edge, GremlinResult, IoProtocol, Vertex}; #[cfg(feature = "async-std-runtime")] use async_std::prelude::*; diff --git a/gremlin-client/tests/integration_client_v2.rs b/gremlin-client/tests/integration_client_v2.rs index 87a96282..4f771aee 100644 --- a/gremlin-client/tests/integration_client_v2.rs +++ b/gremlin-client/tests/integration_client_v2.rs @@ -1,7 +1,7 @@ mod common; use gremlin_client::{ - ConnectionOptions, IoProtocol, GremlinClient, GremlinError, List, TlsOptions, ToGValue, + ConnectionOptions, GremlinClient, GremlinError, IoProtocol, List, TlsOptions, ToGValue, TraversalExplanation, TraversalMetrics, VertexProperty, }; use gremlin_client::{Edge, GKey, GValue, Map, Vertex, GID}; diff --git a/gremlin-client/tests/integration_traversal_graph_binary.rs b/gremlin-client/tests/integration_traversal_graph_binary.rs index 08425537..b7156998 100644 --- a/gremlin-client/tests/integration_traversal_graph_binary.rs +++ b/gremlin-client/tests/integration_traversal_graph_binary.rs @@ -1,5 +1,8 @@ use common::io::graph_serializer; -use gremlin_client::{process::traversal::{traversal, Scope}, GValue, IoProtocol}; +use gremlin_client::{ + process::traversal::{traversal, Scope}, + GValue, IoProtocol, +}; mod common; From 0a3916395af118a64cc8bf63463cedeb05cc44f9 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Fri, 18 Oct 2024 21:13:44 -0500 Subject: [PATCH 09/56] Removed defunct methods and restored binary deserialization functions --- gremlin-client/src/client.rs | 5 +- gremlin-client/src/connection.rs | 2 +- gremlin-client/src/io/graph_binary_v1.rs | 468 +++++++++++------------ gremlin-client/src/io/mod.rs | 35 +- gremlin-client/src/message.rs | 39 -- gremlin-client/src/pool.rs | 4 +- 6 files changed, 232 insertions(+), 321 deletions(-) diff --git a/gremlin-client/src/client.rs b/gremlin-client/src/client.rs index 52821142..5f84b61c 100644 --- a/gremlin-client/src/client.rs +++ b/gremlin-client/src/client.rs @@ -1,7 +1,5 @@ use crate::io::IoProtocol; -use crate::message::{ - message_with_args, message_with_args_and_uuid, message_with_args_v2, Message, Response, -}; +use crate::message::Response; use crate::pool::GremlinConnectionManager; use crate::process::traversal::Bytecode; use crate::ToGValue; @@ -9,7 +7,6 @@ use crate::{ConnectionOptions, GremlinError, GremlinResult}; use crate::{GResultSet, GValue}; use base64::encode; use r2d2::Pool; -use serde::Serialize; use std::collections::{HashMap, VecDeque}; type SessionedClient = GremlinClient; diff --git a/gremlin-client/src/connection.rs b/gremlin-client/src/connection.rs index 7660448c..56854c90 100644 --- a/gremlin-client/src/connection.rs +++ b/gremlin-client/src/connection.rs @@ -1,4 +1,4 @@ -use std::{net::TcpStream, sync::Arc, time::Duration}; +use std::{net::TcpStream, time::Duration}; use crate::{GremlinError, GremlinResult, IoProtocol}; use native_tls::TlsConnector; diff --git a/gremlin-client/src/io/graph_binary_v1.rs b/gremlin-client/src/io/graph_binary_v1.rs index 004027a2..60472dbb 100644 --- a/gremlin-client/src/io/graph_binary_v1.rs +++ b/gremlin-client/src/io/graph_binary_v1.rs @@ -1,20 +1,59 @@ use std::{ collections::HashMap, convert::TryInto, - fmt::{self, Display}, - io::Read, }; -use serde::{de, ser, Serialize}; use uuid::Uuid; use crate::{process::traversal::Instruction, GKey, GValue, GremlinError, GremlinResult}; -struct RequestV1 { - version: u8, - request_id: Uuid, - op: String, - args: HashMap, +use super::IoProtocol; + +pub(crate) struct RequestMessage<'a, 'b> { + pub(crate) request_id: Uuid, + pub(crate) op: &'a str, + pub(crate) processor: &'b str, + pub(crate) args: HashMap, +} + +impl<'a, 'b> GraphBinaryV1Ser for RequestMessage<'a, 'b> { + fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { + //Need to write header first, its length is a Byte not a Int + let header = IoProtocol::GraphBinaryV1.content_type(); + let header_length: u8 = header + .len() + .try_into() + .expect("Header length should fit in u8"); + buf.push(header_length); + buf.extend_from_slice(header.as_bytes()); + + //Version byte is 0x81 for this version + buf.push(0x81); + + //Request Id + self.request_id.to_be_bytes(buf)?; + + //Op + self.op.to_be_bytes(buf)?; + + //Processor + self.processor.to_be_bytes(buf)?; + + //Args + let args_length: i32 = self + .args + .len() + .try_into() + .map_err(|_| GremlinError::Cast(format!("Args exceeds i32 length limit")))?; + GraphBinaryV1Ser::to_be_bytes(args_length, buf)?; + for (k, v) in self.args.into_iter() { + //Both keys and values need to be fully qualified here, so turn + //the keys into a GValue + GValue::from(k).to_be_bytes(buf)?; + v.to_be_bytes(buf)?; + } + Ok(()) + } } //https://tinkerpop.apache.org/docs/3.7.2/dev/io/#_data_type_codes @@ -35,10 +74,10 @@ struct RequestV1 { fn write_usize_as_i32_be_bytes(val: usize, buf: &mut Vec) -> GremlinResult<()> { let val_i32 = TryInto::::try_into(val) .map_err(|_| GremlinError::Cast(format!("Invalid usize bytes exceed i32")))?; - GraphBinaryV1Serde::to_be_bytes(val_i32, buf) + GraphBinaryV1Ser::to_be_bytes(val_i32, buf) } -impl GraphBinaryV1Serde for &GValue { +impl GraphBinaryV1Ser for &GValue { fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { match self { GValue::Null => { @@ -58,7 +97,7 @@ impl GraphBinaryV1Serde for &GValue { //Empty value flag buf.push(0x00); //then value bytes - GraphBinaryV1Serde::to_be_bytes(*value, buf)?; + GraphBinaryV1Ser::to_be_bytes(*value, buf)?; } // GValue::Int64(_) => todo!(), // GValue::Float(_) => todo!(), @@ -91,7 +130,7 @@ impl GraphBinaryV1Serde for &GValue { //Format: {length}{text_value} // {length} is an Int describing the byte length of the text. Length is a positive number or zero to represent the empty string. // {text_value} is a sequence of bytes representing the string value in UTF8 encoding. - GraphBinaryV1Serde::to_be_bytes(value.as_str(), buf)?; + GraphBinaryV1Ser::to_be_bytes(value.as_str(), buf)?; } // GValue::Path(path) => todo!(), // GValue::TraversalMetrics(traversal_metrics) => todo!(), @@ -119,7 +158,7 @@ impl GraphBinaryV1Serde for &GValue { ) -> GremlinResult<()> { write_usize_as_i32_be_bytes(instructions.len(), buf)?; for instruction in instructions { - GraphBinaryV1Serde::to_be_bytes(instruction.operator().as_str(), buf)?; + GraphBinaryV1Ser::to_be_bytes(instruction.operator().as_str(), buf)?; write_usize_as_i32_be_bytes(instruction.args().len(), buf)?; instruction .args() @@ -161,13 +200,9 @@ impl GraphBinaryV1Serde for &GValue { } Ok(()) } - - // fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { - // todo!() - // } } -impl GraphBinaryV1Serde for &GKey { +impl GraphBinaryV1Ser for &GKey { fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { match self { GKey::T(t) => todo!(), @@ -178,243 +213,206 @@ impl GraphBinaryV1Serde for &GKey { GKey::Direction(direction) => todo!(), } } - - // fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { - // todo!() - // } +} +pub trait GraphBinaryV1Ser: Sized { + fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()>; } -impl, V: Into> GraphBinaryV1Serde for HashMap { - //This represents a complicated meeting point. The request message has a non-qualified emission of a map - //for the arguments, but the contained elements needs to be fully qualified - //Ideally this would just be K: GraphBinaryV1Serde & V: GraphBinaryV1Serde - //but that exposes as a type declaration emission things like HashMap which will then - //invoke the value bytes only impl for String and not doing the qualified and then be rejected by the server - fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { - write_usize_as_i32_be_bytes(self.len(), buf)?; - for (k, v) in self { - //TODO we could just move this logic into mod.rs since it's a detail about the nature of - //how the request message is implemented, and not the generialized notion of how to write a HashMap - //That'd also duck the issue of passing through GKey here - let k: GKey = k.into(); - let v: GValue = v.into(); - k.to_be_bytes(buf)?; - v.to_be_bytes(buf)?; - } - Ok(()) - } - - // fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { - // todo!() - // } +pub trait GraphBinaryV1Deser: Sized { + fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult; } -fn deserialize<'a, T: Iterator>(mut value: T) -> GremlinResult { - let data_code = value - .next() - .ok_or_else(|| GremlinError::Cast(format!("Invalid bytes no data code byte")))?; - match data_code { - // GValue::Null => { - // buf.reserve_exact(2); - // //Type code of 0xfe: Unspecified null object - // buf.push(0xfe); - // //Then the null {value_flag} set and no sequence of bytes. - // buf.push(0x01); - // } - // GValue::Vertex(vertex) => todo!(), - // GValue::Edge(edge) => todo!(), - // GValue::VertexProperty(vertex_property) => todo!(), - // GValue::Property(property) => todo!(), - // GValue::Uuid(uuid) => todo!(), - 0x01 => { - //Type code of 0x01: Integer - //Check null flag - match value.next() { - Some(0x00) => { - //We've got a value to parse - // GraphBinaryV1Serde::from_be_bytes(&mut value).map(GValue::Int32) - todo!() +impl GraphBinaryV1Deser for GValue { + fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { + let data_code = bytes + .next() + .ok_or_else(|| GremlinError::Cast(format!("Invalid bytes no data code byte")))?; + match data_code { + // GValue::Null => { + // buf.reserve_exact(2); + // //Type code of 0xfe: Unspecified null object + // buf.push(0xfe); + // //Then the null {value_flag} set and no sequence of bytes. + // buf.push(0x01); + // } + // GValue::Vertex(vertex) => todo!(), + // GValue::Edge(edge) => todo!(), + // GValue::VertexProperty(vertex_property) => todo!(), + // GValue::Property(property) => todo!(), + // GValue::Uuid(uuid) => todo!(), + 0x01 => { + //Type code of 0x01: Integer + //Check null flag + match bytes.next() { + Some(0x00) => { + //We've got a value to parse + GraphBinaryV1Deser::from_be_bytes(bytes).map(GValue::Int32) + } + Some(0x01) => Ok(GValue::Null), + _ => Err(GremlinError::Cast(format!("Invalid bytes into i32"))), } - Some(0x01) => Ok(GValue::Null), - _ => Err(GremlinError::Cast(format!("Invalid bytes into i32"))), } - } - // GValue::Int64(_) => todo!(), - // GValue::Float(_) => todo!(), - // GValue::Double(_) => todo!(), - // GValue::Date(date_time) => todo!(), - // GValue::List(list) => todo!(), - // GValue::Set(set) => todo!(), - // GValue::Map(map) => todo!(), - // GValue::Token(token) => todo!(), - 0x03 => { - //Type code of 0x03: String - match value.next() { - Some(0x00) => { - //We've got a value to parse - // GraphBinaryV1Serde::from_be_bytes(&mut value).map(GValue::String) - todo!() + // GValue::Int64(_) => todo!(), + // GValue::Float(_) => todo!(), + // GValue::Double(_) => todo!(), + // GValue::Date(date_time) => todo!(), + // GValue::List(list) => todo!(), + // GValue::Set(set) => todo!(), + // GValue::Map(map) => todo!(), + // GValue::Token(token) => todo!(), + 0x03 => { + //Type code of 0x03: String + match bytes.next() { + Some(0x00) => { + //We've got a value to parse + GraphBinaryV1Deser::from_be_bytes(bytes).map(GValue::String) + } + Some(0x01) => Ok(GValue::Null), + _ => Err(GremlinError::Cast(format!("Invalid bytes into String"))), } - Some(0x01) => Ok(GValue::Null), - _ => Err(GremlinError::Cast(format!("Invalid bytes into String"))), - } - // GValue::String(value) => { - // + // GValue::String(value) => { + // + // //Empty value flag + // //Format: {length}{text_value} + // // {length} is an Int describing the byte length of the text. Length is a positive number or zero to represent the empty string. + // // {text_value} is a sequence of bytes representing the string value in UTF8 encoding. + } + // GValue::Path(path) => todo!(), + // GValue::TraversalMetrics(traversal_metrics) => todo!(), + // GValue::Metric(metric) => todo!(), + // GValue::TraversalExplanation(traversal_explanation) => todo!(), + // GValue::IntermediateRepr(intermediate_repr) => todo!(), + // GValue::P(p) => todo!(), + // GValue::T(t) => todo!(), + // GValue::Bytecode(code) => { + // //Type code of 0x15: Bytecode + // buf.push(0x15); // //Empty value flag - // //Format: {length}{text_value} - // // {length} is an Int describing the byte length of the text. Length is a positive number or zero to represent the empty string. - // // {text_value} is a sequence of bytes representing the string value in UTF8 encoding. + // buf.push(0x00); + // //then value bytes + // // {steps_length}{step_0}…​{step_n}{sources_length}{source_0}…​{source_n} + // //{steps_length} is an Int value describing the amount of steps. + // let steps_length: i32 = code.steps().len().try_into().expect("Number of steps should fit in i32"); + // buf.extend_from_slice(serialize(&GValue::Int32(steps_length))); + + // //{step_i} is composed of {name}{values_length}{value_0}…​{value_n}, where: + // // {name} is a String. + // // {values_length} is an Int describing the amount values. + // // {value_i} is a fully qualified typed value composed of {type_code}{type_info}{value_flag}{value} describing the step argument. + + // let steps: GremlinResult> = code + // .steps() + // .iter() + // .map(|m| { + // let mut instruction = vec![]; + // instruction.push(Value::String(m.operator().clone())); + + // let arguments: GremlinResult> = + // m.args().iter().map(|a| self.write(a)).collect(); + + // instruction.extend(arguments?); + // Ok(Value::Array(instruction)) + // }) + // .collect(); + + // let sources: GremlinResult> = code + // .sources() + // .iter() + // .map(|m| { + // let mut instruction = vec![]; + // instruction.push(Value::String(m.operator().clone())); + + // let arguments: GremlinResult> = + // m.args().iter().map(|a| self.write(a)).collect(); + + // instruction.extend(arguments?); + // Ok(Value::Array(instruction)) + // }) + // .collect(); + // } + // GValue::Traverser(traverser) => todo!(), + // GValue::Scope(scope) => todo!(), + // GValue::Order(order) => todo!(), + // GValue::Bool(_) => todo!(), + // GValue::TextP(text_p) => todo!(), + // GValue::Pop(pop) => todo!(), + // GValue::Cardinality(cardinality) => todo!(), + // GValue::Merge(merge) => todo!(), + // GValue::Direction(direction) => todo!(), + // GValue::Column(column) => todo!(), + _ => unimplemented!("TODO"), } - // GValue::Path(path) => todo!(), - // GValue::TraversalMetrics(traversal_metrics) => todo!(), - // GValue::Metric(metric) => todo!(), - // GValue::TraversalExplanation(traversal_explanation) => todo!(), - // GValue::IntermediateRepr(intermediate_repr) => todo!(), - // GValue::P(p) => todo!(), - // GValue::T(t) => todo!(), - // GValue::Bytecode(code) => { - // //Type code of 0x15: Bytecode - // buf.push(0x15); - // //Empty value flag - // buf.push(0x00); - // //then value bytes - // // {steps_length}{step_0}…​{step_n}{sources_length}{source_0}…​{source_n} - // //{steps_length} is an Int value describing the amount of steps. - // let steps_length: i32 = code.steps().len().try_into().expect("Number of steps should fit in i32"); - // buf.extend_from_slice(serialize(&GValue::Int32(steps_length))); - - // //{step_i} is composed of {name}{values_length}{value_0}…​{value_n}, where: - // // {name} is a String. - // // {values_length} is an Int describing the amount values. - // // {value_i} is a fully qualified typed value composed of {type_code}{type_info}{value_flag}{value} describing the step argument. - - // let steps: GremlinResult> = code - // .steps() - // .iter() - // .map(|m| { - // let mut instruction = vec![]; - // instruction.push(Value::String(m.operator().clone())); - - // let arguments: GremlinResult> = - // m.args().iter().map(|a| self.write(a)).collect(); - - // instruction.extend(arguments?); - // Ok(Value::Array(instruction)) - // }) - // .collect(); - - // let sources: GremlinResult> = code - // .sources() - // .iter() - // .map(|m| { - // let mut instruction = vec![]; - // instruction.push(Value::String(m.operator().clone())); - - // let arguments: GremlinResult> = - // m.args().iter().map(|a| self.write(a)).collect(); - - // instruction.extend(arguments?); - // Ok(Value::Array(instruction)) - // }) - // .collect(); - // } - // GValue::Traverser(traverser) => todo!(), - // GValue::Scope(scope) => todo!(), - // GValue::Order(order) => todo!(), - // GValue::Bool(_) => todo!(), - // GValue::TextP(text_p) => todo!(), - // GValue::Pop(pop) => todo!(), - // GValue::Cardinality(cardinality) => todo!(), - // GValue::Merge(merge) => todo!(), - // GValue::Direction(direction) => todo!(), - // GValue::Column(column) => todo!(), - _ => unimplemented!("TODO"), } } -pub trait GraphBinaryV1Serde: Sized { - fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()>; - // fn to_fully_qualified_be_bytes(&self, buf: &mut Vec) -> GremlinResult<()>; - // fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult; - // fn from_fully_qualified_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult; - - //TODO implement a to_fully_qualified_be_bytes method & from_fully_qualified_be_bytes instead of serialize/deserialize methods - // maybe this doesn't make sense, since it would require us to peek the first byte anyways and then match to the that impl to seek over the byte again -} - -impl GraphBinaryV1Serde for &str { +impl GraphBinaryV1Ser for &str { fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { let length: i32 = self .len() .try_into() .map_err(|_| GremlinError::Cast(format!("String length exceeds i32")))?; - GraphBinaryV1Serde::to_be_bytes(length, buf)?; + GraphBinaryV1Ser::to_be_bytes(length, buf)?; buf.extend_from_slice(self.as_bytes()); Ok(()) } +} - // fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { - // let string_bytes_length: i32 = GraphBinaryV1Serde::from_be_bytes(bytes) - // .map_err((|_| GremlinError::Cast(format!("Invalid bytes for string length"))))?; - // let string_bytes_length = string_bytes_length - // .try_into() - // .map_err((|_| GremlinError::Cast(format!("String length did not fit into usize"))))?; - // let string_value_bytes: Vec = bytes.take(string_bytes_length).cloned().collect(); - // if string_value_bytes.len() < string_bytes_length { - // return Err(GremlinError::Cast(format!( - // "Missing bytes for string value" - // ))); - // } - // String::from_utf8(string_value_bytes) - // .map_err((|_| GremlinError::Cast(format!("Invalid bytes for string value")))) - // } +impl GraphBinaryV1Deser for String { + fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { + let string_bytes_length: i32 = GraphBinaryV1Deser::from_be_bytes(bytes) + .map_err(|_| GremlinError::Cast(format!("Invalid bytes for String length")))?; + let string_bytes_length = string_bytes_length + .try_into() + .map_err(|_| GremlinError::Cast(format!("String length did not fit into usize")))?; + let string_value_bytes: Vec = bytes.take(string_bytes_length).cloned().collect(); + if string_value_bytes.len() < string_bytes_length { + return Err(GremlinError::Cast(format!( + "Missing bytes for String value" + ))); + } + String::from_utf8(string_value_bytes) + .map_err(|_| GremlinError::Cast(format!("Invalid bytes for String value"))) + } } -impl GraphBinaryV1Serde for i32 { +impl GraphBinaryV1Ser for i32 { fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { buf.extend_from_slice(&self.to_be_bytes()); Ok(()) } +} - // fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { - // bytes - // .take(4) - // .cloned() - // .collect::>() - // .try_into() - // .map_err(|_| GremlinError::Cast(format!("Invalid bytes into i32"))) - // .map(i32::from_be_bytes) - // } +impl GraphBinaryV1Deser for i32 { + fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { + bytes + .take(4) + .cloned() + .collect::>() + .try_into() + .map_err(|_| GremlinError::Cast(format!("Invalid bytes into i32"))) + .map(i32::from_be_bytes) + } } -impl GraphBinaryV1Serde for Uuid { +impl GraphBinaryV1Ser for Uuid { fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { buf.extend_from_slice(self.as_bytes().as_slice()); Ok(()) } - - // fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { - // bytes - // .take(16) - // .cloned() - // .collect::>() - // .try_into() - // .map_err(|_| GremlinError::Cast(format!("Invalid bytes into Uuid"))) - // .map(Uuid::from_bytes) - // } } -// fn deserialize_i32(bytes: &[u8]) -> GremlinResult { -// bytes -// .try_into() -// .map(i32::from_be_bytes) -// .map_err(|_| GremlinError::Cast(format!("Invalid bytes into i32"))) -// } - -// fn serialize_i32(value: i32) -> [u8; 4] { -// value.to_be_bytes() -// } +impl GraphBinaryV1Deser for Uuid { + fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { + bytes + .take(16) + .cloned() + .collect::>() + .try_into() + .map_err(|_| GremlinError::Cast(format!("Invalid bytes into Uuid"))) + .map(Uuid::from_bytes) + } +} #[cfg(test)] mod tests { @@ -422,34 +420,6 @@ mod tests { use super::*; - // All encodings are big-endian. - - // Quick examples, using hexadecimal notation to represent each byte: - - // 01 00 00 00 00 01: a 32-bit integer number, that represents the decimal number 1. It’s composed by the type_code 0x01, and empty flag value 0x00 and four bytes to describe the value. - - // 01 00 00 00 00 ff: a 32-bit integer, representing the number 256. - - // 01 01: a null value for a 32-bit integer. It’s composed by the type_code 0x01, and a null flag value 0x01. - - // 02 00 00 00 00 00 00 00 00 01: a 64-bit integer number 1. It’s composed by the type_code 0x02, empty flags and eight bytes to describe the value. - - //Seems like generalized flow should be, be given a slice: - //Read the first byte - //then match on it - - // {type_code}{type_info}{value_flag}{value} - - // {type_code} is a single unsigned byte representing the type number. - - // {type_info} is an optional sequence of bytes providing additional information of the type represented. This is specially useful for representing complex and custom types. - - // {value_flag} is a single byte providing information about the value. Flags have the following meaning: - - // 0x01 The value is null. When this flag is set, no bytes for {value} will be provided. - - // {value} is a sequence of bytes which content is determined by the type. - #[rstest] //Non-Null i32 Integer (01 00) #[case::int_1(&[0x01, 0x00, 0x00, 0x00, 0x00, 0x01], GValue::Int32(1))] @@ -467,13 +437,15 @@ mod tests { .to_be_bytes(&mut serialized) .expect("Shouldn't fail parsing"); assert_eq!(serialized, expected_serialized); - let deserialized = deserialize(serialized.iter()).expect("Shouldn't fail parsing"); + let deserialized: GValue = GraphBinaryV1Deser::from_be_bytes(&mut serialized.iter()) + .expect("Shouldn't fail parsing"); assert_eq!(deserialized, expected); } #[rstest] #[case::too_few_bytes( &[0x01, 0x00, 0x00, 0x00, 0x00])] fn serde_int32_invalid_bytes(#[case] bytes: &[u8]) { - deserialize(bytes.iter()).expect_err("Should have failed due invalid bytes"); + ::from_be_bytes(&mut bytes.iter()) + .expect_err("Should have failed due invalid bytes"); } } diff --git a/gremlin-client/src/io/mod.rs b/gremlin-client/src/io/mod.rs index 6f59a56d..b29a57bf 100644 --- a/gremlin-client/src/io/mod.rs +++ b/gremlin-client/src/io/mod.rs @@ -6,16 +6,15 @@ mod serializer_v3; use crate::conversion::ToGValue; use crate::message::{RequestIdV2, Response}; -use crate::process::traversal::{Bytecode, Order, Scope}; +use crate::process::traversal::{Order, Scope}; use crate::structure::{Cardinality, Direction, GValue, Merge, T}; use serde_json::{json, Map, Value}; use std::collections::HashMap; use std::convert::TryInto; -use std::f64::consts::E; use std::string::ToString; use uuid::Uuid; -use crate::{io::graph_binary_v1::GraphBinaryV1Serde, GKey, GremlinError, GremlinResult, Message}; +use crate::{io::graph_binary_v1::GraphBinaryV1Ser, GKey, GremlinError, GremlinResult, Message}; #[derive(Debug, Clone)] pub enum IoProtocol { @@ -86,29 +85,13 @@ impl IoProtocol { } IoProtocol::GraphBinaryV1 => { let mut message_bytes: Vec = Vec::new(); - //Need to write header first, its length is a Byte not a Int - let header = String::from(content_type); - let header_length: u8 = header - .len() - .try_into() - .expect("Header length should fit in u8"); - message_bytes.push(header_length); - message_bytes.extend_from_slice(header.as_bytes()); - - //Version byte - message_bytes.push(0x81); - - //Request Id - request_id.to_be_bytes(&mut message_bytes)?; - - //Op - op.to_be_bytes(&mut message_bytes)?; - - //Processor - processor.to_be_bytes(&mut message_bytes)?; - - //Args - args.to_be_bytes(&mut message_bytes)?; + graph_binary_v1::RequestMessage { + request_id, + op, + processor, + args, + } + .to_be_bytes(&mut message_bytes)?; message_bytes } }; diff --git a/gremlin-client/src/message.rs b/gremlin-client/src/message.rs index 8fc8a728..20438ebd 100644 --- a/gremlin-client/src/message.rs +++ b/gremlin-client/src/message.rs @@ -78,45 +78,6 @@ where Option::::deserialize(de).map(Option::unwrap_or_default) } -pub fn message_with_args_v2(op: String, processor: String, args: T) -> Message { - message_with_args_and_uuid_v2(op, processor, Uuid::new_v4(), args) -} - -pub fn message_with_args_and_uuid_v2( - op: String, - processor: String, - id: Uuid, - args: T, -) -> Message { - Message::V2 { - request_id: RequestIdV2 { - id_type: "g:UUID".to_string(), - value: id, - }, - op, - processor, - args, - } -} - -pub fn message_with_args(op: String, processor: String, args: T) -> Message { - message_with_args_and_uuid(op, processor, Uuid::new_v4(), args) -} - -pub fn message_with_args_and_uuid( - op: String, - processor: String, - id: Uuid, - args: T, -) -> Message { - Message::V3 { - request_id: id, - op, - processor, - args, - } -} - #[cfg(test)] mod tests { use crate::message::ReponseStatus; diff --git a/gremlin-client/src/pool.rs b/gremlin-client/src/pool.rs index 81f3c28e..d2d3338f 100644 --- a/gremlin-client/src/pool.rs +++ b/gremlin-client/src/pool.rs @@ -3,9 +3,7 @@ use r2d2::ManageConnection; use crate::connection::Connection; use crate::connection::ConnectionOptions; use crate::error::GremlinError; -use crate::message::{ - message_with_args, message_with_args_and_uuid, message_with_args_v2, Response, -}; +use crate::message::Response; use crate::{GValue, GremlinResult, IoProtocol}; use base64::encode; use std::collections::HashMap; From 36a9989b16eecfb376aa200f0a3f35e70e55b1b6 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Sat, 19 Oct 2024 10:57:40 -0500 Subject: [PATCH 10/56] Successful graph binary traversal --- gremlin-client/src/client.rs | 12 +- gremlin-client/src/io/graph_binary_v1.rs | 269 +++++++++++++++++++---- gremlin-client/src/io/mod.rs | 50 ++++- gremlin-client/src/message.rs | 9 +- gremlin-client/src/pool.rs | 4 +- 5 files changed, 280 insertions(+), 64 deletions(-) diff --git a/gremlin-client/src/client.rs b/gremlin-client/src/client.rs index 5f84b61c..297910af 100644 --- a/gremlin-client/src/client.rs +++ b/gremlin-client/src/client.rs @@ -179,15 +179,15 @@ impl GremlinClient { conn: &mut r2d2::PooledConnection, ) -> GremlinResult<(Response, VecDeque)> { let result = conn.recv()?; - let response = self.options.deserializer.read_response(&result)?; + let response = self.options.deserializer.read_response(result)?; match response.status.code { 200 | 206 => { - let results: VecDeque = self - .options - .deserializer - .read(&response.result.data)? - .map(|v| v.into()) + let results: VecDeque = response + .result + .data + .clone() + .map(Into::into) .unwrap_or_else(VecDeque::new); Ok((response, results)) diff --git a/gremlin-client/src/io/graph_binary_v1.rs b/gremlin-client/src/io/graph_binary_v1.rs index 60472dbb..e0d51ab1 100644 --- a/gremlin-client/src/io/graph_binary_v1.rs +++ b/gremlin-client/src/io/graph_binary_v1.rs @@ -1,14 +1,39 @@ -use std::{ - collections::HashMap, - convert::TryInto, -}; +use std::{collections::HashMap, convert::TryInto, iter}; +use tungstenite::http::request; use uuid::Uuid; -use crate::{process::traversal::Instruction, GKey, GValue, GremlinError, GremlinResult}; +use crate::{ + conversion::FromGValue, + io::graph_binary_v1, + message::{ReponseStatus, Response, ResponseResult}, + process::traversal::Instruction, + structure::Traverser, + GKey, GValue, GremlinError, GremlinResult, +}; use super::IoProtocol; +const VERSION_BYTE: u8 = 0x81; +const VALUE_FLAG: u8 = 0x00; +const VALUE_NULL_FLAG: u8 = 0x01; + +//Data codes (https://tinkerpop.apache.org/docs/3.7.2/dev/io/#_data_type_codes) +const INTEGER: u8 = 0x01; +const LONG: u8 = 0x02; +const STRING: u8 = 0x03; +//... +const BYTECODE: u8 = 0x15; +//... +const SCOPE: u8 = 0x1F; +//TODO fill in others +const LIST: u8 = 0x09; +const MAP: u8 = 0x0A; +//... +const TRAVERSER: u8 = 0x21; +//... +const UNSPECIFIED_NULL_OBEJECT: u8 = 0xFE; + pub(crate) struct RequestMessage<'a, 'b> { pub(crate) request_id: Uuid, pub(crate) op: &'a str, @@ -16,6 +41,109 @@ pub(crate) struct RequestMessage<'a, 'b> { pub(crate) args: HashMap, } +pub(crate) struct ResponseMessage { + //Format: {version}{request_id}{status_code}{status_message}{status_attributes}{result_meta}{result_data} + pub(crate) request_id: Uuid, + pub(crate) status_code: i16, + pub(crate) status_message: String, + pub(crate) status_attributes: HashMap, + pub(crate) result_meta: HashMap, + pub(crate) result_data: Option, +} + +impl Into for ResponseMessage { + fn into(self) -> Response { + let status = ReponseStatus { + code: self.status_code, + message: self.status_message, + }; + Response { + request_id: self.request_id, + result: ResponseResult { + data: self.result_data, + }, + status, + } + } +} + +impl GraphBinaryV1Deser for ResponseMessage { + fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { + if false { + //[ + //version: 129, + //uuid w/ 0 value flag: 0, 149, 52, 143, 202, 41, 23, 76, 216, 157, 199, 182, 172, 1, 102, 41, 11, + //status: 0, 0, 0, 200, + //status_message just 0 int length: 0, 0, 0, 0, + //status_attributes, just 0 length map?: 0, + //result_meta, just 0 length map?: 0, + //result data: + // 0, 0, 1, 3, 0, 0, 0, 0, 4, 104, 111, 115, 116, 3, 0, 0, 0, 0, 16, 47, 49, 50, 55, 46, 48, 46, 48, 46, 49, 58, 53, 49, 57, 48, 54, 0, 0, 0, 0, 9, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0] + + //[129, + //0, 241, 233, 95, 30, 32, 43, 70, 30, 160, 140, 157, 252, 20, 108, 65, 238, + //status: 0, 0, 0, 200, + //status_message w/ 0 length: 0, 0, 0, 0, + //status attributes: 0, 0, 0, 0, 1, 3, 0, 0, 0, 0, 4, 104, 111, 115, 116, 3, 0, 0, 0, 0, 16, 47, 49, 50, 55, 46, 48, 46, 48, 46, 49, 58, 53, 49, 53, 56, 52, 0, 0, 0, 0, + //9, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0] + let vec: Vec = bytes.cloned().collect(); + panic!("{:?}", vec); + } + //First confirm the version is as expected + let Some(&graph_binary_v1::VERSION_BYTE) = bytes.next() else { + return Err(GremlinError::Cast(format!("Invalid version byte"))); + }; + + //Request id is nullable + let request_id = + Uuid::from_be_bytes_nullable(bytes)?.expect("TODO what to do with null request id?"); + + let status_code = ::from_be_bytes(bytes)? + .try_into() + .expect("Status code should fit in i16"); + //Status message is nullable + let status_message = String::from_be_bytes_nullable(bytes)? + .expect("TODO what to do with null status message"); + + fn deserialize_map<'a, S: Iterator>( + bytes: &mut S, + ) -> GremlinResult> { + //first will be the map length + let map_length = ::from_be_bytes(bytes)?; + let mut map = HashMap::new(); + //Then fully qualified entry of each k/v pair + for _ in 0..map_length { + let key: GKey = GKey::from_gvalue(GValue::from_be_bytes(bytes)?) + .map_err(|_| GremlinError::Cast(format!("Invalid GKey bytes")))?; + let value = GValue::from_be_bytes(bytes)?; + + map.insert(key, value); + } + Ok(map) + } + + let status_attributes = deserialize_map(bytes)?; + + //It seems like we're going off the rails here, probably need to do a binary dump comparison with the java client again + + let result_meta = deserialize_map(bytes)?; + let result_data = GValue::from_be_bytes(bytes)?; + let result_data = if result_data == GValue::Null { + None + } else { + Some(result_data) + }; + Ok(ResponseMessage { + request_id, + status_code, + status_message, + status_attributes, + result_meta, + result_data, + }) + } +} + impl<'a, 'b> GraphBinaryV1Ser for RequestMessage<'a, 'b> { fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { //Need to write header first, its length is a Byte not a Int @@ -27,8 +155,8 @@ impl<'a, 'b> GraphBinaryV1Ser for RequestMessage<'a, 'b> { buf.push(header_length); buf.extend_from_slice(header.as_bytes()); - //Version byte is 0x81 for this version - buf.push(0x81); + //Version byte first + buf.push(VERSION_BYTE); //Request Id self.request_id.to_be_bytes(buf)?; @@ -82,9 +210,9 @@ impl GraphBinaryV1Ser for &GValue { match self { GValue::Null => { //Type code of 0xfe: Unspecified null object - buf.push(0xfe); + buf.push(UNSPECIFIED_NULL_OBEJECT); //Then the null {value_flag} set and no sequence of bytes. - buf.push(0x01); + buf.push(VALUE_NULL_FLAG); } // GValue::Vertex(vertex) => todo!(), // GValue::Edge(edge) => todo!(), @@ -93,9 +221,9 @@ impl GraphBinaryV1Ser for &GValue { // GValue::Uuid(uuid) => todo!(), GValue::Int32(value) => { //Type code of 0x01 - buf.push(0x01); + buf.push(INTEGER); //Empty value flag - buf.push(0x00); + buf.push(VALUE_FLAG); //then value bytes GraphBinaryV1Ser::to_be_bytes(*value, buf)?; } @@ -107,9 +235,9 @@ impl GraphBinaryV1Ser for &GValue { // GValue::Set(set) => todo!(), GValue::Map(map) => { //Type code of 0x0a: Map - buf.push(0x0a); + buf.push(MAP); // //Empty value flag - buf.push(0x00); + buf.push(VALUE_FLAG); //{length} is an Int describing the length of the map. write_usize_as_i32_be_bytes(map.len(), buf)?; @@ -124,9 +252,9 @@ impl GraphBinaryV1Ser for &GValue { // GValue::Token(token) => todo!(), GValue::String(value) => { //Type code of 0x03: String - buf.push(0x03); + buf.push(STRING); //Empty value flag - buf.push(0x00); + buf.push(VALUE_FLAG); //Format: {length}{text_value} // {length} is an Int describing the byte length of the text. Length is a positive number or zero to represent the empty string. // {text_value} is a sequence of bytes representing the string value in UTF8 encoding. @@ -141,9 +269,9 @@ impl GraphBinaryV1Ser for &GValue { // GValue::T(t) => todo!(), GValue::Bytecode(code) => { //Type code of 0x15: Bytecode - buf.push(0x15); + buf.push(BYTECODE); //Empty value flag - buf.push(0x00); + buf.push(VALUE_FLAG); //then value bytes // {steps_length}{step_0}…​{step_n}{sources_length}{source_0}…​{source_n} //{steps_length} is an Int value describing the amount of steps. @@ -173,9 +301,9 @@ impl GraphBinaryV1Ser for &GValue { // GValue::Traverser(traverser) => todo!(), GValue::Scope(scope) => { //Type code of 0x1f: Scope - buf.push(0x1f); + buf.push(SCOPE); //Empty value flag - buf.push(0x00); + buf.push(VALUE_FLAG); //Format: a fully qualified single String representing the enum value. match scope { @@ -220,6 +348,20 @@ pub trait GraphBinaryV1Ser: Sized { pub trait GraphBinaryV1Deser: Sized { fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult; + + fn from_be_bytes_nullable<'a, S: Iterator>( + bytes: &mut S, + ) -> GremlinResult> { + match bytes.next().cloned() { + Some(VALUE_FLAG) => Self::from_be_bytes(bytes).map(Option::Some), + Some(VALUE_NULL_FLAG) => Ok(None), + other => { + return Err(GremlinError::Cast(format!( + "Unexpected byte for nullable check: {other:?}" + ))) + } + } + } } impl GraphBinaryV1Deser for GValue { @@ -227,7 +369,7 @@ impl GraphBinaryV1Deser for GValue { let data_code = bytes .next() .ok_or_else(|| GremlinError::Cast(format!("Invalid bytes no data code byte")))?; - match data_code { + match *data_code { // GValue::Null => { // buf.reserve_exact(2); // //Type code of 0xfe: Unspecified null object @@ -240,17 +382,12 @@ impl GraphBinaryV1Deser for GValue { // GValue::VertexProperty(vertex_property) => todo!(), // GValue::Property(property) => todo!(), // GValue::Uuid(uuid) => todo!(), - 0x01 => { + INTEGER => { //Type code of 0x01: Integer - //Check null flag - match bytes.next() { - Some(0x00) => { - //We've got a value to parse - GraphBinaryV1Deser::from_be_bytes(bytes).map(GValue::Int32) - } - Some(0x01) => Ok(GValue::Null), - _ => Err(GremlinError::Cast(format!("Invalid bytes into i32"))), - } + Ok(match i32::from_be_bytes_nullable(bytes)? { + Some(value) => GValue::Int32(value), + None => GValue::Null, + }) } // GValue::Int64(_) => todo!(), // GValue::Float(_) => todo!(), @@ -260,23 +397,12 @@ impl GraphBinaryV1Deser for GValue { // GValue::Set(set) => todo!(), // GValue::Map(map) => todo!(), // GValue::Token(token) => todo!(), - 0x03 => { + STRING => { //Type code of 0x03: String - match bytes.next() { - Some(0x00) => { - //We've got a value to parse - GraphBinaryV1Deser::from_be_bytes(bytes).map(GValue::String) - } - Some(0x01) => Ok(GValue::Null), - _ => Err(GremlinError::Cast(format!("Invalid bytes into String"))), - } - - // GValue::String(value) => { - // - // //Empty value flag - // //Format: {length}{text_value} - // // {length} is an Int describing the byte length of the text. Length is a positive number or zero to represent the empty string. - // // {text_value} is a sequence of bytes representing the string value in UTF8 encoding. + Ok(match String::from_be_bytes_nullable(bytes)? { + Some(string) => GValue::String(string), + None => GValue::Null, + }) } // GValue::Path(path) => todo!(), // GValue::TraversalMetrics(traversal_metrics) => todo!(), @@ -341,7 +467,21 @@ impl GraphBinaryV1Deser for GValue { // GValue::Merge(merge) => todo!(), // GValue::Direction(direction) => todo!(), // GValue::Column(column) => todo!(), - _ => unimplemented!("TODO"), + LIST => { + let deserialized_list: Option> = + GraphBinaryV1Deser::from_be_bytes_nullable(bytes)?; + Ok(deserialized_list + .map(|val| GValue::List(val.into())) + .unwrap_or(GValue::Null)) + } + TRAVERSER => { + let traverser: Option = + GraphBinaryV1Deser::from_be_bytes_nullable(bytes)?; + Ok(traverser + .map(|val| GValue::Traverser(val)) + .unwrap_or(GValue::Null)) + } + other => unimplemented!("TODO {other}"), } } } @@ -358,6 +498,29 @@ impl GraphBinaryV1Ser for &str { } } +impl GraphBinaryV1Deser for Traverser { + fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { + Ok(Traverser::new( + GraphBinaryV1Deser::from_be_bytes(bytes)?, + GraphBinaryV1Deser::from_be_bytes(bytes)?, + )) + } +} + +impl GraphBinaryV1Deser for Vec { + fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { + let length = ::from_be_bytes(bytes)? + .try_into() + .map_err(|_| GremlinError::Cast(format!("list length exceeds usize")))?; + let mut list = Vec::new(); + list.reserve_exact(length); + for _ in 0..length { + list.push(GValue::from_be_bytes(bytes)?); + } + Ok(list) + } +} + impl GraphBinaryV1Deser for String { fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { let string_bytes_length: i32 = GraphBinaryV1Deser::from_be_bytes(bytes) @@ -395,6 +558,18 @@ impl GraphBinaryV1Deser for i32 { } } +impl GraphBinaryV1Deser for i64 { + fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { + bytes + .take(8) + .cloned() + .collect::>() + .try_into() + .map_err(|_| GremlinError::Cast(format!("Invalid bytes into i64"))) + .map(i64::from_be_bytes) + } +} + impl GraphBinaryV1Ser for Uuid { fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { buf.extend_from_slice(self.as_bytes().as_slice()); diff --git a/gremlin-client/src/io/mod.rs b/gremlin-client/src/io/mod.rs index b29a57bf..e3f53cd3 100644 --- a/gremlin-client/src/io/mod.rs +++ b/gremlin-client/src/io/mod.rs @@ -5,9 +5,12 @@ mod serializer_v2; mod serializer_v3; use crate::conversion::ToGValue; -use crate::message::{RequestIdV2, Response}; +use crate::message::{ReponseStatus, RequestIdV2, Response, ResponseResult}; use crate::process::traversal::{Order, Scope}; use crate::structure::{Cardinality, Direction, GValue, Merge, T}; +use graph_binary_v1::GraphBinaryV1Deser; +use serde::{Deserialize as SerdeDeserialize, Deserializer}; +use serde_derive::Deserialize; use serde_json::{json, Map, Value}; use std::collections::HashMap; use std::convert::TryInto; @@ -23,6 +26,21 @@ pub enum IoProtocol { GraphBinaryV1, } +//TODO these should probably be moved into their modules + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct MiddleResponse { + pub request_id: Uuid, + pub result: MiddleResponseResult, + pub status: ReponseStatus, +} + +#[derive(Debug, Deserialize)] +pub struct MiddleResponseResult { + pub data: Value, +} + impl IoProtocol { pub fn read(&self, value: &Value) -> GremlinResult> { if let Value::Null = value { @@ -35,12 +53,34 @@ impl IoProtocol { } } - pub fn read_response(&self, response: &[u8]) -> GremlinResult { + pub fn read_response(&self, response: Vec) -> GremlinResult { match self { - IoProtocol::GraphSONV2 | IoProtocol::GraphSONV3 => { - serde_json::from_slice(&response).map_err(GremlinError::from) + IoProtocol::GraphSONV2 => { + let middle_form: MiddleResponse = + serde_json::from_slice(&response).map_err(GremlinError::from)?; + Ok(Response { + request_id: middle_form.request_id, + result: ResponseResult { + data: serializer_v2::deserializer_v2(&middle_form.result.data).map(Some)?, + }, + status: middle_form.status, + }) + } + IoProtocol::GraphSONV3 => { + let middle_form: MiddleResponse = + serde_json::from_slice(&response).map_err(GremlinError::from)?; + Ok(Response { + request_id: middle_form.request_id, + result: ResponseResult { + data: serializer_v3::deserializer_v3(&middle_form.result.data).map(Some)?, + }, + status: middle_form.status, + }) + } + IoProtocol::GraphBinaryV1 => { + graph_binary_v1::ResponseMessage::from_be_bytes(&mut response.iter()) + .map(|middle| middle.into()) } - IoProtocol::GraphBinaryV1 => todo!(), } } diff --git a/gremlin-client/src/message.rs b/gremlin-client/src/message.rs index 20438ebd..919ca7ec 100644 --- a/gremlin-client/src/message.rs +++ b/gremlin-client/src/message.rs @@ -3,6 +3,8 @@ use serde_derive::{Deserialize, Serialize}; use serde_json::Value; use uuid::Uuid; +use crate::GValue; + #[derive(Serialize)] #[serde(rename_all = "camelCase")] pub struct RequestIdV2 { @@ -49,17 +51,16 @@ impl Message { } } } -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] +#[derive(Debug)] pub struct Response { pub request_id: Uuid, pub result: ResponseResult, pub status: ReponseStatus, } -#[derive(Debug, Deserialize)] +#[derive(Debug)] pub struct ResponseResult { - pub data: Value, + pub data: Option, } #[derive(Debug, Deserialize)] diff --git a/gremlin-client/src/pool.rs b/gremlin-client/src/pool.rs index d2d3338f..8a2dd2c1 100644 --- a/gremlin-client/src/pool.rs +++ b/gremlin-client/src/pool.rs @@ -46,7 +46,7 @@ impl ManageConnection for GremlinConnectionManager { conn.send(message)?; let result = conn.recv()?; - let response = self.options.deserializer.read_response(&result)?; + let response = self.options.deserializer.read_response(result)?; match response.status.code { 200 | 206 => Ok(()), @@ -69,7 +69,7 @@ impl ManageConnection for GremlinConnectionManager { conn.send(message)?; let result = conn.recv()?; - let response = self.options.deserializer.read_response(&result)?; + let response = self.options.deserializer.read_response(result)?; match response.status.code { 200 | 206 => Ok(()), From c810c07700d69602594d6f0be11551aaa2579d11 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Sun, 20 Oct 2024 11:45:52 -0500 Subject: [PATCH 11/56] Handle mapping JSON Value::Null to GValue::Null --- gremlin-client/src/io/macros.rs | 2 ++ gremlin-client/src/io/mod.rs | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gremlin-client/src/io/macros.rs b/gremlin-client/src/io/macros.rs index 3829fd70..681ed613 100644 --- a/gremlin-client/src/io/macros.rs +++ b/gremlin-client/src/io/macros.rs @@ -3,6 +3,8 @@ macro_rules! g_serializer { pub fn $name(val: &Value) -> GremlinResult { if let Value::String(ref s) = val { Ok(s.clone().into()) + } else if let Value::Null = val { + Ok(GValue::Null) } else if let Value::Bool(b) = val { Ok((*b).into()) } else { diff --git a/gremlin-client/src/io/mod.rs b/gremlin-client/src/io/mod.rs index e3f53cd3..765153f3 100644 --- a/gremlin-client/src/io/mod.rs +++ b/gremlin-client/src/io/mod.rs @@ -26,8 +26,6 @@ pub enum IoProtocol { GraphBinaryV1, } -//TODO these should probably be moved into their modules - #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct MiddleResponse { From 6de41560c315cd18449d2d7b9b7af98ebdda92b2 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Sun, 20 Oct 2024 12:10:20 -0500 Subject: [PATCH 12/56] Fix aio compiling errors --- gremlin-client/src/aio/client.rs | 17 ++++++----------- gremlin-client/src/aio/connection.rs | 9 ++++++--- gremlin-client/src/aio/pool.rs | 5 ++--- gremlin-client/src/aio/result.rs | 12 +++++------- 4 files changed, 19 insertions(+), 24 deletions(-) diff --git a/gremlin-client/src/aio/client.rs b/gremlin-client/src/aio/client.rs index ef75e9eb..d1d0f74c 100644 --- a/gremlin-client/src/aio/client.rs +++ b/gremlin-client/src/aio/client.rs @@ -1,9 +1,5 @@ use crate::aio::pool::GremlinConnectionManager; use crate::aio::GResultSet; -use crate::io::IoProtocol; -use crate::message::{ - message_with_args, message_with_args_and_uuid, message_with_args_v2, Message, -}; use crate::process::traversal::Bytecode; use crate::GValue; use crate::ToGValue; @@ -11,7 +7,6 @@ use crate::{ConnectionOptions, GremlinError, GremlinResult}; use base64::encode; use futures::future::{BoxFuture, FutureExt}; use mobc::{Connection, Pool}; -use serde::Serialize; use std::collections::{HashMap, VecDeque}; use uuid::Uuid; @@ -144,7 +139,7 @@ impl GremlinClient { self.send_message_new(conn, id, message).await } - pub(crate) fn send_message_new<'a, T: Serialize>( + pub(crate) fn send_message_new<'a>( &'a self, mut conn: Connection, id: Uuid, @@ -155,11 +150,11 @@ impl GremlinClient { let (response, results) = match response.status.code { 200 | 206 => { - let results: VecDeque = self - .options - .deserializer - .read(&response.result.data)? - .map(|v| v.into()) + let results: VecDeque = response + .result + .data + .clone() + .map(Into::into) .unwrap_or_else(VecDeque::new); Ok((response, results)) } diff --git a/gremlin-client/src/aio/connection.rs b/gremlin-client/src/aio/connection.rs index a0f311e0..8ebece2d 100644 --- a/gremlin-client/src/aio/connection.rs +++ b/gremlin-client/src/aio/connection.rs @@ -1,4 +1,4 @@ -use crate::{GremlinError, GremlinResult, WebSocketOptions}; +use crate::{GremlinError, GremlinResult, IoProtocol}; use crate::connection::ConnectionOptions; @@ -165,7 +165,7 @@ impl Conn { sender_loop(sink, requests.clone(), receiver); - receiver_loop(stream, requests.clone(), sender.clone()); + receiver_loop(stream, requests.clone(), sender.clone(), opts.deserializer); Ok(Conn { sender, @@ -266,6 +266,7 @@ fn receiver_loop( mut stream: SplitStream, requests: Arc>>>>, mut sender: Sender, + deserializer: IoProtocol, ) { task::spawn(async move { loop { @@ -283,7 +284,9 @@ fn receiver_loop( } Some(Ok(item)) => match item { Message::Binary(data) => { - let response: Response = serde_json::from_slice(&data).unwrap(); + let response = deserializer + .read_response(data) + .expect("Unable to parse message"); let mut guard = requests.lock().await; if response.status.code != 206 { let item = guard.remove(&response.request_id); diff --git a/gremlin-client/src/aio/pool.rs b/gremlin-client/src/aio/pool.rs index 43d53193..d6003292 100644 --- a/gremlin-client/src/aio/pool.rs +++ b/gremlin-client/src/aio/pool.rs @@ -3,8 +3,7 @@ use mobc::Manager; use crate::aio::connection::Conn; use crate::connection::ConnectionOptions; use crate::error::GremlinError; -use crate::message::{message_with_args, message_with_args_and_uuid, message_with_args_v2}; -use crate::{GValue, IoProtocol}; +use crate::GValue; use async_trait::async_trait; use base64::encode; use std::collections::HashMap; @@ -46,7 +45,7 @@ impl Manager for GremlinConnectionManager { .serializer .build_message("eval", "", args, None)?; - let (response, _receiver) = conn.send(id, binary).await?; + let (response, _receiver) = conn.send(id, message).await?; match response.status.code { 200 | 206 => Ok(conn), diff --git a/gremlin-client/src/aio/result.rs b/gremlin-client/src/aio/result.rs index ddcb195c..daed9fc6 100644 --- a/gremlin-client/src/aio/result.rs +++ b/gremlin-client/src/aio/result.rs @@ -59,14 +59,12 @@ impl Stream for GResultSet { if this.response.status.code == 206 { match futures::ready!(this.receiver.as_mut().poll_next(cx)) { Some(Ok(response)) => { - let results: VecDeque = this - .client - .options - .serializer - .read(&response.result.data)? - .map(|v| v.into()) + let results: VecDeque = response + .result + .data + .clone() + .map(Into::into) .unwrap_or_else(VecDeque::new); - *this.results = results; *this.response = response; } From 5330d8f16784f4cfca0a9b14d23298ad117d010f Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Sun, 20 Oct 2024 12:18:24 -0500 Subject: [PATCH 13/56] Set IT client v2 test's deserializer --- gremlin-client/tests/integration_client_v2.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/gremlin-client/tests/integration_client_v2.rs b/gremlin-client/tests/integration_client_v2.rs index 4f771aee..bdc3f966 100644 --- a/gremlin-client/tests/integration_client_v2.rs +++ b/gremlin-client/tests/integration_client_v2.rs @@ -36,6 +36,7 @@ fn test_ok_credentials_v2() { accept_invalid_certs: true, }) .serializer(IoProtocol::GraphSONV2) + .deserializer(IoProtocol::GraphSONV2) .build(), ) .expect("Cannot connect"); From ee6086fe8b47a85100d25f93e0a635ad143d4f24 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Sun, 20 Oct 2024 12:18:57 -0500 Subject: [PATCH 14/56] Handle null in GraphSONV2 g_serializer_2 --- gremlin-client/src/io/macros.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gremlin-client/src/io/macros.rs b/gremlin-client/src/io/macros.rs index 681ed613..5ab6b6a1 100644 --- a/gremlin-client/src/io/macros.rs +++ b/gremlin-client/src/io/macros.rs @@ -28,7 +28,9 @@ macro_rules! g_serializer_2 { pub fn $name(val: &Value) -> GremlinResult { if let Value::String(ref s) = val { return Ok(s.clone().into()) - } + } else if let Value::Null = val { + return Ok(GValue::Null) + } if let Value::Array(_) = val { let _type = "g:List"; let _value = &val; From f273b14ae98dd6fbed3a441659971296221ea60b Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Sun, 20 Oct 2024 12:20:27 -0500 Subject: [PATCH 15/56] Formatting --- gremlin-client/src/io/macros.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gremlin-client/src/io/macros.rs b/gremlin-client/src/io/macros.rs index 5ab6b6a1..f787df39 100644 --- a/gremlin-client/src/io/macros.rs +++ b/gremlin-client/src/io/macros.rs @@ -30,7 +30,7 @@ macro_rules! g_serializer_2 { return Ok(s.clone().into()) } else if let Value::Null = val { return Ok(GValue::Null) - } + } if let Value::Array(_) = val { let _type = "g:List"; let _value = &val; From 5b5108cbef91f38e8ba85cb304b401bacf85a9d1 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Sun, 20 Oct 2024 12:26:01 -0500 Subject: [PATCH 16/56] Fix graph binary demo IT --- gremlin-client/src/io/graph_binary_v1.rs | 6 ++++++ gremlin-client/tests/integration_traversal_graph_binary.rs | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/gremlin-client/src/io/graph_binary_v1.rs b/gremlin-client/src/io/graph_binary_v1.rs index e0d51ab1..a74e1358 100644 --- a/gremlin-client/src/io/graph_binary_v1.rs +++ b/gremlin-client/src/io/graph_binary_v1.rs @@ -389,6 +389,12 @@ impl GraphBinaryV1Deser for GValue { None => GValue::Null, }) } + LONG => { + Ok(match i64::from_be_bytes_nullable(bytes)? { + Some(value) => GValue::Int64(value), + None => GValue::Null, + }) + } // GValue::Int64(_) => todo!(), // GValue::Float(_) => todo!(), // GValue::Double(_) => todo!(), diff --git a/gremlin-client/tests/integration_traversal_graph_binary.rs b/gremlin-client/tests/integration_traversal_graph_binary.rs index b7156998..d4e82c1c 100644 --- a/gremlin-client/tests/integration_traversal_graph_binary.rs +++ b/gremlin-client/tests/integration_traversal_graph_binary.rs @@ -10,5 +10,5 @@ mod common; fn demo() { let g = traversal().with_remote(graph_serializer(IoProtocol::GraphBinaryV1)); let y = g.inject(1).sum(Scope::Global).next().unwrap(); - panic!("Got {:?}", y); + assert_eq!(y, Some(GValue::Int64(1))) } From 678792f6191f581da14a2be38fa9272711aa0f42 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Sun, 20 Oct 2024 12:28:26 -0500 Subject: [PATCH 17/56] Formatting --- gremlin-client/src/io/graph_binary_v1.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/gremlin-client/src/io/graph_binary_v1.rs b/gremlin-client/src/io/graph_binary_v1.rs index a74e1358..b99fbb90 100644 --- a/gremlin-client/src/io/graph_binary_v1.rs +++ b/gremlin-client/src/io/graph_binary_v1.rs @@ -389,12 +389,10 @@ impl GraphBinaryV1Deser for GValue { None => GValue::Null, }) } - LONG => { - Ok(match i64::from_be_bytes_nullable(bytes)? { - Some(value) => GValue::Int64(value), - None => GValue::Null, - }) - } + LONG => Ok(match i64::from_be_bytes_nullable(bytes)? { + Some(value) => GValue::Int64(value), + None => GValue::Null, + }), // GValue::Int64(_) => todo!(), // GValue::Float(_) => todo!(), // GValue::Double(_) => todo!(), From 4ca0eb5300535b88d5383f99778e58937aa79515 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Tue, 22 Oct 2024 17:49:36 -0500 Subject: [PATCH 18/56] Additional value binary serdes --- gremlin-client/src/io/graph_binary_v1.rs | 423 +++++++++++++---------- gremlin-client/src/structure/set.rs | 4 + 2 files changed, 239 insertions(+), 188 deletions(-) diff --git a/gremlin-client/src/io/graph_binary_v1.rs b/gremlin-client/src/io/graph_binary_v1.rs index b99fbb90..7b646db7 100644 --- a/gremlin-client/src/io/graph_binary_v1.rs +++ b/gremlin-client/src/io/graph_binary_v1.rs @@ -1,5 +1,6 @@ use std::{collections::HashMap, convert::TryInto, iter}; +use chrono::{DateTime, TimeZone, Utc}; use tungstenite::http::request; use uuid::Uuid; @@ -18,17 +19,32 @@ const VERSION_BYTE: u8 = 0x81; const VALUE_FLAG: u8 = 0x00; const VALUE_NULL_FLAG: u8 = 0x01; -//Data codes (https://tinkerpop.apache.org/docs/3.7.2/dev/io/#_data_type_codes) +//Data codes (https://tinkerpop.apache.org/docs/current/dev/io/#_data_type_codes) const INTEGER: u8 = 0x01; const LONG: u8 = 0x02; const STRING: u8 = 0x03; -//... +const DATE: u8 = 0x04; +// const TIMESTAMP: u8 = 0x05; +// const CLASS: u8 = 0x06; +const DOUBLE: u8 = 0x07; +const FLOAT: u8 = 0x08; +const LIST: u8 = 0x09; +const MAP: u8 = 0x0A; +const SET: u8 = 0x0B; +const UUID: u8 = 0x0C; +const EDGE: u8 = 0x0D; +const PATH: u8 = 0x0E; +const PROPERTY: u8 = 0x0F; +// const TINKERGRAPH: u8 = 0x10; +const VERTEX: u8 = 0x11; +const VERTEX_PROPERTY: u8 = 0x12; +// const BARRIER: u8 = 0x13; +// const BINDING: u8 = 0x14; const BYTECODE: u8 = 0x15; //... const SCOPE: u8 = 0x1F; //TODO fill in others -const LIST: u8 = 0x09; -const MAP: u8 = 0x0A; + //... const TRAVERSER: u8 = 0x21; //... @@ -67,28 +83,25 @@ impl Into for ResponseMessage { } } -impl GraphBinaryV1Deser for ResponseMessage { +impl GraphBinaryV1Deser for HashMap { fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { - if false { - //[ - //version: 129, - //uuid w/ 0 value flag: 0, 149, 52, 143, 202, 41, 23, 76, 216, 157, 199, 182, 172, 1, 102, 41, 11, - //status: 0, 0, 0, 200, - //status_message just 0 int length: 0, 0, 0, 0, - //status_attributes, just 0 length map?: 0, - //result_meta, just 0 length map?: 0, - //result data: - // 0, 0, 1, 3, 0, 0, 0, 0, 4, 104, 111, 115, 116, 3, 0, 0, 0, 0, 16, 47, 49, 50, 55, 46, 48, 46, 48, 46, 49, 58, 53, 49, 57, 48, 54, 0, 0, 0, 0, 9, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0] - - //[129, - //0, 241, 233, 95, 30, 32, 43, 70, 30, 160, 140, 157, 252, 20, 108, 65, 238, - //status: 0, 0, 0, 200, - //status_message w/ 0 length: 0, 0, 0, 0, - //status attributes: 0, 0, 0, 0, 1, 3, 0, 0, 0, 0, 4, 104, 111, 115, 116, 3, 0, 0, 0, 0, 16, 47, 49, 50, 55, 46, 48, 46, 48, 46, 49, 58, 53, 49, 53, 56, 52, 0, 0, 0, 0, - //9, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0] - let vec: Vec = bytes.cloned().collect(); - panic!("{:?}", vec); + //first will be the map length + let map_length = ::from_be_bytes(bytes)?; + let mut map = HashMap::new(); + //Then fully qualified entry of each k/v pair + for _ in 0..map_length { + let key: GKey = GKey::from_gvalue(GValue::from_be_bytes(bytes)?) + .map_err(|_| GremlinError::Cast(format!("Invalid GKey bytes")))?; + let value = GValue::from_be_bytes(bytes)?; + + map.insert(key, value); } + Ok(map) + } +} + +impl GraphBinaryV1Deser for ResponseMessage { + fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { //First confirm the version is as expected let Some(&graph_binary_v1::VERSION_BYTE) = bytes.next() else { return Err(GremlinError::Cast(format!("Invalid version byte"))); @@ -105,28 +118,8 @@ impl GraphBinaryV1Deser for ResponseMessage { let status_message = String::from_be_bytes_nullable(bytes)? .expect("TODO what to do with null status message"); - fn deserialize_map<'a, S: Iterator>( - bytes: &mut S, - ) -> GremlinResult> { - //first will be the map length - let map_length = ::from_be_bytes(bytes)?; - let mut map = HashMap::new(); - //Then fully qualified entry of each k/v pair - for _ in 0..map_length { - let key: GKey = GKey::from_gvalue(GValue::from_be_bytes(bytes)?) - .map_err(|_| GremlinError::Cast(format!("Invalid GKey bytes")))?; - let value = GValue::from_be_bytes(bytes)?; - - map.insert(key, value); - } - Ok(map) - } - - let status_attributes = deserialize_map(bytes)?; - - //It seems like we're going off the rails here, probably need to do a binary dump comparison with the java client again - - let result_meta = deserialize_map(bytes)?; + let status_attributes = GraphBinaryV1Deser::from_be_bytes(bytes)?; + let result_meta: HashMap = GraphBinaryV1Deser::from_be_bytes(bytes)?; let result_data = GValue::from_be_bytes(bytes)?; let result_data = if result_data == GValue::Null { None @@ -184,7 +177,7 @@ impl<'a, 'b> GraphBinaryV1Ser for RequestMessage<'a, 'b> { } } -//https://tinkerpop.apache.org/docs/3.7.2/dev/io/#_data_type_codes +//https://tinkerpop.apache.org/docs/current/dev/io/#_data_type_codes //Each type has a "fully qualified" serialized form usually: {type_code}{type_info}{value_flag}{value} //{type_code} is a single unsigned byte representing the type number. //{type_info} is an optional sequence of bytes providing additional information of the type represented. This is specially useful for representing complex and custom types. @@ -208,17 +201,6 @@ fn write_usize_as_i32_be_bytes(val: usize, buf: &mut Vec) -> GremlinResult<( impl GraphBinaryV1Ser for &GValue { fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { match self { - GValue::Null => { - //Type code of 0xfe: Unspecified null object - buf.push(UNSPECIFIED_NULL_OBEJECT); - //Then the null {value_flag} set and no sequence of bytes. - buf.push(VALUE_NULL_FLAG); - } - // GValue::Vertex(vertex) => todo!(), - // GValue::Edge(edge) => todo!(), - // GValue::VertexProperty(vertex_property) => todo!(), - // GValue::Property(property) => todo!(), - // GValue::Uuid(uuid) => todo!(), GValue::Int32(value) => { //Type code of 0x01 buf.push(INTEGER); @@ -227,12 +209,45 @@ impl GraphBinaryV1Ser for &GValue { //then value bytes GraphBinaryV1Ser::to_be_bytes(*value, buf)?; } - // GValue::Int64(_) => todo!(), - // GValue::Float(_) => todo!(), - // GValue::Double(_) => todo!(), - // GValue::Date(date_time) => todo!(), - // GValue::List(list) => todo!(), - // GValue::Set(set) => todo!(), + GValue::Int64(value) => { + buf.push(LONG); + buf.push(VALUE_FLAG); + GraphBinaryV1Ser::to_be_bytes(*value, buf)?; + } + GValue::String(value) => { + //Type code of 0x03: String + buf.push(STRING); + //Empty value flag + buf.push(VALUE_FLAG); + GraphBinaryV1Ser::to_be_bytes(value.as_str(), buf)?; + } + GValue::Date(value) => { + buf.push(DATE); + buf.push(VALUE_FLAG); + value.to_be_bytes(buf)?; + } + GValue::Double(value) => { + buf.push(DOUBLE); + buf.push(VALUE_FLAG); + GraphBinaryV1Ser::to_be_bytes(*value, buf)?; + } + GValue::Float(value) => { + buf.push(FLOAT); + buf.push(VALUE_FLAG); + GraphBinaryV1Ser::to_be_bytes(*value, buf)?; + } + GValue::List(value) => { + buf.push(LIST); + buf.push(VALUE_FLAG); + + //{length} is an Int describing the length of the collection. + write_usize_as_i32_be_bytes(value.len(), buf)?; + + //{item_0}…​{item_n} are the items of the list. {item_i} is a fully qualified typed value composed of {type_code}{type_info}{value_flag}{value}. + for item in value.iter() { + item.to_be_bytes(buf)?; + } + } GValue::Map(map) => { //Type code of 0x0a: Map buf.push(MAP); @@ -249,24 +264,23 @@ impl GraphBinaryV1Ser for &GValue { v.to_be_bytes(buf)?; } } - // GValue::Token(token) => todo!(), - GValue::String(value) => { - //Type code of 0x03: String - buf.push(STRING); - //Empty value flag + GValue::Set(value) => { + buf.push(SET); buf.push(VALUE_FLAG); - //Format: {length}{text_value} - // {length} is an Int describing the byte length of the text. Length is a positive number or zero to represent the empty string. - // {text_value} is a sequence of bytes representing the string value in UTF8 encoding. - GraphBinaryV1Ser::to_be_bytes(value.as_str(), buf)?; + + //{length} is an Int describing the length of the collection. + write_usize_as_i32_be_bytes(value.len(), buf)?; + + //{item_0}…​{item_n} are the items of the list. {item_i} is a fully qualified typed value composed of {type_code}{type_info}{value_flag}{value}. + for item in value.iter() { + item.to_be_bytes(buf)?; + } + } + GValue::Uuid(value) => { + buf.push(UUID); + buf.push(VALUE_FLAG); + value.to_be_bytes(buf)?; } - // GValue::Path(path) => todo!(), - // GValue::TraversalMetrics(traversal_metrics) => todo!(), - // GValue::Metric(metric) => todo!(), - // GValue::TraversalExplanation(traversal_explanation) => todo!(), - // GValue::IntermediateRepr(intermediate_repr) => todo!(), - // GValue::P(p) => todo!(), - // GValue::T(t) => todo!(), GValue::Bytecode(code) => { //Type code of 0x15: Bytecode buf.push(BYTECODE); @@ -298,6 +312,12 @@ impl GraphBinaryV1Ser for &GValue { write_instructions(code.steps(), buf)?; write_instructions(code.sources(), buf)?; } + GValue::Null => { + //Type code of 0xfe: Unspecified null object + buf.push(UNSPECIFIED_NULL_OBEJECT); + //Then the null {value_flag} set and no sequence of bytes. + buf.push(VALUE_NULL_FLAG); + } // GValue::Traverser(traverser) => todo!(), GValue::Scope(scope) => { //Type code of 0x1f: Scope @@ -315,15 +335,6 @@ impl GraphBinaryV1Ser for &GValue { } } } - // GValue::Order(order) => todo!(), - // GValue::Bool(_) => todo!(), - // GValue::TextP(text_p) => todo!(), - // GValue::Pop(pop) => todo!(), - - // GValue::Cardinality(cardinality) => todo!(), - // GValue::Merge(merge) => todo!(), - // GValue::Direction(direction) => todo!(), - // GValue::Column(column) => todo!(), other => unimplemented!("TODO {other:?}"), } Ok(()) @@ -370,107 +381,33 @@ impl GraphBinaryV1Deser for GValue { .next() .ok_or_else(|| GremlinError::Cast(format!("Invalid bytes no data code byte")))?; match *data_code { - // GValue::Null => { - // buf.reserve_exact(2); - // //Type code of 0xfe: Unspecified null object - // buf.push(0xfe); - // //Then the null {value_flag} set and no sequence of bytes. - // buf.push(0x01); - // } - // GValue::Vertex(vertex) => todo!(), - // GValue::Edge(edge) => todo!(), - // GValue::VertexProperty(vertex_property) => todo!(), - // GValue::Property(property) => todo!(), - // GValue::Uuid(uuid) => todo!(), - INTEGER => { - //Type code of 0x01: Integer - Ok(match i32::from_be_bytes_nullable(bytes)? { - Some(value) => GValue::Int32(value), - None => GValue::Null, - }) - } + INTEGER => Ok(match i32::from_be_bytes_nullable(bytes)? { + Some(value) => GValue::Int32(value), + None => GValue::Null, + }), LONG => Ok(match i64::from_be_bytes_nullable(bytes)? { Some(value) => GValue::Int64(value), None => GValue::Null, }), - // GValue::Int64(_) => todo!(), - // GValue::Float(_) => todo!(), - // GValue::Double(_) => todo!(), - // GValue::Date(date_time) => todo!(), - // GValue::List(list) => todo!(), - // GValue::Set(set) => todo!(), - // GValue::Map(map) => todo!(), - // GValue::Token(token) => todo!(), - STRING => { - //Type code of 0x03: String - Ok(match String::from_be_bytes_nullable(bytes)? { - Some(string) => GValue::String(string), - None => GValue::Null, - }) - } - // GValue::Path(path) => todo!(), - // GValue::TraversalMetrics(traversal_metrics) => todo!(), - // GValue::Metric(metric) => todo!(), - // GValue::TraversalExplanation(traversal_explanation) => todo!(), - // GValue::IntermediateRepr(intermediate_repr) => todo!(), - // GValue::P(p) => todo!(), - // GValue::T(t) => todo!(), - // GValue::Bytecode(code) => { - // //Type code of 0x15: Bytecode - // buf.push(0x15); - // //Empty value flag - // buf.push(0x00); - // //then value bytes - // // {steps_length}{step_0}…​{step_n}{sources_length}{source_0}…​{source_n} - // //{steps_length} is an Int value describing the amount of steps. - // let steps_length: i32 = code.steps().len().try_into().expect("Number of steps should fit in i32"); - // buf.extend_from_slice(serialize(&GValue::Int32(steps_length))); - - // //{step_i} is composed of {name}{values_length}{value_0}…​{value_n}, where: - // // {name} is a String. - // // {values_length} is an Int describing the amount values. - // // {value_i} is a fully qualified typed value composed of {type_code}{type_info}{value_flag}{value} describing the step argument. - - // let steps: GremlinResult> = code - // .steps() - // .iter() - // .map(|m| { - // let mut instruction = vec![]; - // instruction.push(Value::String(m.operator().clone())); - - // let arguments: GremlinResult> = - // m.args().iter().map(|a| self.write(a)).collect(); - - // instruction.extend(arguments?); - // Ok(Value::Array(instruction)) - // }) - // .collect(); - - // let sources: GremlinResult> = code - // .sources() - // .iter() - // .map(|m| { - // let mut instruction = vec![]; - // instruction.push(Value::String(m.operator().clone())); - - // let arguments: GremlinResult> = - // m.args().iter().map(|a| self.write(a)).collect(); - - // instruction.extend(arguments?); - // Ok(Value::Array(instruction)) - // }) - // .collect(); - // } - // GValue::Traverser(traverser) => todo!(), - // GValue::Scope(scope) => todo!(), - // GValue::Order(order) => todo!(), - // GValue::Bool(_) => todo!(), - // GValue::TextP(text_p) => todo!(), - // GValue::Pop(pop) => todo!(), - // GValue::Cardinality(cardinality) => todo!(), - // GValue::Merge(merge) => todo!(), - // GValue::Direction(direction) => todo!(), - // GValue::Column(column) => todo!(), + STRING => Ok(match String::from_be_bytes_nullable(bytes)? { + Some(string) => GValue::String(string), + None => GValue::Null, + }), + DATE => match i64::from_be_bytes_nullable(bytes)? { + Some(value) => match Utc.timestamp_millis_opt(value) { + chrono::LocalResult::Single(valid) => Ok(GValue::Date(valid)), + _ => Err(GremlinError::Cast(format!("Invalid timestamp millis"))), + }, + None => Ok(GValue::Null), + }, + DOUBLE => Ok(match f64::from_be_bytes_nullable(bytes)? { + Some(value) => GValue::Double(value), + None => GValue::Null, + }), + FLOAT => Ok(match f32::from_be_bytes_nullable(bytes)? { + Some(value) => GValue::Float(value), + None => GValue::Null, + }), LIST => { let deserialized_list: Option> = GraphBinaryV1Deser::from_be_bytes_nullable(bytes)?; @@ -478,6 +415,33 @@ impl GraphBinaryV1Deser for GValue { .map(|val| GValue::List(val.into())) .unwrap_or(GValue::Null)) } + MAP => { + let deserialized_map: Option> = + GraphBinaryV1Deser::from_be_bytes_nullable(bytes)?; + Ok(deserialized_map + .map(|val| GValue::Map(val.into())) + .unwrap_or(GValue::Null)) + } + SET => { + let deserialized_set: Option> = + GraphBinaryV1Deser::from_be_bytes_nullable(bytes)?; + Ok(deserialized_set + .map(|val| GValue::Set(val.into())) + .unwrap_or(GValue::Null)) + } + UUID => Ok(match Uuid::from_be_bytes_nullable(bytes)? { + Some(value) => GValue::Uuid(value), + None => GValue::Null, + }), + EDGE => { + todo!() + } + PATH => { + todo!() + } + PROPERTY => { + todo!() + } TRAVERSER => { let traverser: Option = GraphBinaryV1Deser::from_be_bytes_nullable(bytes)?; @@ -492,6 +456,9 @@ impl GraphBinaryV1Deser for GValue { impl GraphBinaryV1Ser for &str { fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { + //Format: {length}{text_value} + // {length} is an Int describing the byte length of the text. Length is a positive number or zero to represent the empty string. + // {text_value} is a sequence of bytes representing the string value in UTF8 encoding. let length: i32 = self .len() .try_into() @@ -502,6 +469,27 @@ impl GraphBinaryV1Ser for &str { } } +impl GraphBinaryV1Ser for &DateTime { + fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { + //Format: An 8-byte two’s complement signed integer representing a millisecond-precision offset from the unix epoch. + GraphBinaryV1Ser::to_be_bytes(self.timestamp_millis(), buf) + } +} + +impl GraphBinaryV1Ser for f32 { + fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { + buf.extend_from_slice(&self.to_be_bytes()); + Ok(()) + } +} + +impl GraphBinaryV1Ser for f64 { + fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { + buf.extend_from_slice(&self.to_be_bytes()); + Ok(()) + } +} + impl GraphBinaryV1Deser for Traverser { fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { Ok(Traverser::new( @@ -545,6 +533,15 @@ impl GraphBinaryV1Deser for String { impl GraphBinaryV1Ser for i32 { fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { + //Format: 4-byte two’s complement integer + buf.extend_from_slice(&self.to_be_bytes()); + Ok(()) + } +} + +impl GraphBinaryV1Ser for i64 { + fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { + //Format: 8-byte two’s complement integer buf.extend_from_slice(&self.to_be_bytes()); Ok(()) } @@ -564,6 +561,7 @@ impl GraphBinaryV1Deser for i32 { impl GraphBinaryV1Deser for i64 { fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { + //Format: 8-byte two’s complement integer bytes .take(8) .cloned() @@ -574,7 +572,31 @@ impl GraphBinaryV1Deser for i64 { } } -impl GraphBinaryV1Ser for Uuid { +impl GraphBinaryV1Deser for f64 { + fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { + bytes + .take(8) + .cloned() + .collect::>() + .try_into() + .map_err(|_| GremlinError::Cast(format!("Invalid bytes into f64"))) + .map(f64::from_be_bytes) + } +} + +impl GraphBinaryV1Deser for f32 { + fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { + bytes + .take(4) + .cloned() + .collect::>() + .try_into() + .map_err(|_| GremlinError::Cast(format!("Invalid bytes into f32"))) + .map(f32::from_be_bytes) + } +} + +impl GraphBinaryV1Ser for &Uuid { fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { buf.extend_from_slice(self.as_bytes().as_slice()); Ok(()) @@ -595,7 +617,9 @@ impl GraphBinaryV1Deser for Uuid { #[cfg(test)] mod tests { + use chrono::DateTime; use rstest::rstest; + use uuid::uuid; use super::*; @@ -606,11 +630,34 @@ mod tests { #[case::int_257(&[0x01, 0x00, 0x00, 0x00, 0x01, 0x01], GValue::Int32(257))] #[case::int_neg_1(&[0x01, 0x00, 0xFF, 0xFF, 0xFF, 0xFF], GValue::Int32(-1))] #[case::int_neg_2(&[0x01, 0x00, 0xFF, 0xFF, 0xFF, 0xFE], GValue::Int32(-2))] + //Non-Null i64 Long (02 00) + #[case::long_1(&[0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01], GValue::Int64(1))] + #[case::long_neg_2(&[0x02, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE], GValue::Int64(-2))] //Non-Null Strings (03 00) #[case::str_abc(&[0x03, 0x00, 0x00, 0x00, 0x00, 0x03, 0x61, 0x62, 0x63], GValue::String("abc".into()))] #[case::str_abcd(&[0x03, 0x00, 0x00, 0x00, 0x00, 0x04, 0x61, 0x62, 0x63, 0x64], GValue::String("abcd".into()))] #[case::empty_str(&[0x03, 0x00, 0x00, 0x00, 0x00, 0x00], GValue::String("".into()))] - fn serde(#[case] expected_serialized: &[u8], #[case] expected: GValue) { + //Non-Null Date (04 00) + #[case::date_epoch(&[0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], GValue::Date(DateTime::parse_from_rfc3339("1970-01-01T00:00:00.000Z").unwrap().into()))] + #[case::date_before_epoch(&[0x04, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF], GValue::Date(DateTime::parse_from_rfc3339("1969-12-31T23:59:59.999Z").unwrap().into()))] + //Non-Null Timestamp (05 00), no GValue at this time + //Non-Null Class (06 00), no GValue at this time + //Non-Null Double (07 00) + #[case::double_1(&[0x07, 0x00, 0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], GValue::Double(1f64))] + #[case::double_fractional(&[0x07, 0x00, 0x3F, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], GValue::Double(0.00390625))] + #[case::double_0_dot_1(&[0x07, 0x00, 0x3F, 0xB9, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9A], GValue::Double(0.1))] + //Non-Null Float (08 00) + #[case::double_fractional(&[0x08, 0x00, 0x3F, 0x80, 0x00, 0x00], GValue::Float(1f32))] + #[case::double_0_dot_1(&[0x08, 0x00, 0x3E, 0xC0, 0x00, 0x00], GValue::Float(0.375f32))] + //Non-Null List (09 00) + #[case::list_single_int(&[0x09, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01], GValue::List(Vec::from([GValue::Int32(1)]).into()))] + //Non-Null Map (0A 00) + #[case::map_single_str_int_pair(&[0x0A, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00, 0x03, 0x61, 0x62, 0x63, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01], GValue::Map(iter::once((GKey::String("abc".into()), GValue::Int32(1))).collect::>().into()))] + //Non-Null Set (0B 00) + #[case::set_single_int(&[0x0B, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01], GValue::Set(Vec::from([GValue::Int32(1)]).into()))] + //Non-Null UUID (0C 00) + #[case::uuid(&[0x0C, 0x00, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF], GValue::Uuid(uuid!("00112233-4455-6677-8899-aabbccddeeff")))] + fn serde_values(#[case] expected_serialized: &[u8], #[case] expected: GValue) { let mut serialized = Vec::new(); (&expected) .to_be_bytes(&mut serialized) diff --git a/gremlin-client/src/structure/set.rs b/gremlin-client/src/structure/set.rs index b0235ba9..dd0d9a18 100644 --- a/gremlin-client/src/structure/set.rs +++ b/gremlin-client/src/structure/set.rs @@ -14,6 +14,10 @@ impl Set { pub fn iter(&self) -> impl Iterator { self.0.iter() } + + pub fn len(&self) -> usize { + self.0.len() + } } impl Into for Vec { From 455e329579e1360540a2eb45ef4eb2aaed36a051 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Wed, 23 Oct 2024 11:18:51 -0500 Subject: [PATCH 19/56] Created graph binary rw tests --- .../tests/graph_binary_read_write_cycle.rs | 74 +++++++++++++++++++ .../integration_traversal_graph_binary.rs | 14 ---- 2 files changed, 74 insertions(+), 14 deletions(-) create mode 100644 gremlin-client/tests/graph_binary_read_write_cycle.rs delete mode 100644 gremlin-client/tests/integration_traversal_graph_binary.rs diff --git a/gremlin-client/tests/graph_binary_read_write_cycle.rs b/gremlin-client/tests/graph_binary_read_write_cycle.rs new file mode 100644 index 00000000..6b1db8a2 --- /dev/null +++ b/gremlin-client/tests/graph_binary_read_write_cycle.rs @@ -0,0 +1,74 @@ +use std::array::IntoIter; +use std::collections::{HashSet, HashMap}; + +use chrono::{DateTime, TimeZone, Utc}; +use common::io::graph_serializer; +use gremlin_client::{ + process::traversal::{ + traversal, Bytecode, GraphTraversal, GraphTraversalSource, Scope, SyncTerminator, + }, + GValue, IoProtocol, +}; +use rstest::rstest; +use uuid::Uuid; +use std::iter::FromIterator; + +mod common; + +fn get_graph_source() -> GraphTraversalSource { + traversal().with_remote(graph_serializer(IoProtocol::GraphBinaryV1)) +} + +#[rstest] +#[case::int(1i32)] +#[case::long(1i64)] +#[case::string("abc")] +#[case::date(Utc.timestamp_millis(9001))] +#[case::double(0.1f64)] +#[case::float(0.1f32)] +#[case::int_list(Vec::from_iter([GValue::Int64(2)]))] +#[case::map_str_int(HashMap::<_, _>::from_iter(IntoIter::new([(String::from("abc"), GValue::Int32(1))])))] +#[case::int_set(GValue::Set(Vec::from_iter([GValue::Int64(2)]).into()))] +#[case::uuid(Uuid::new_v4())] +fn simple_value_rw_cycle>(#[case] payload: T) { + let payload = payload.into(); + assert_eq!( + get_graph_source().inject(payload.clone()).next().unwrap(), + Some(payload) + ) +} + +// #[test] +// fn edge_rw_cycle() { +// todo!() +// } + +// #[test] +// fn path_rw_cycle() { +// todo!() +// } + +// #[test] +// fn property_rw_cycle() { +// todo!() +// } + +// #[test] +// fn vertex_rw_cycle() { +// todo!() +// } + +// #[test] +// fn vertex_property_rw_cycle() { +// todo!() +// } + +// #[test] +// fn scope_rw_cycle() { +// todo!() +// } + +// #[test] +// fn traverser_rw_cycle() { +// todo!() +// } diff --git a/gremlin-client/tests/integration_traversal_graph_binary.rs b/gremlin-client/tests/integration_traversal_graph_binary.rs deleted file mode 100644 index d4e82c1c..00000000 --- a/gremlin-client/tests/integration_traversal_graph_binary.rs +++ /dev/null @@ -1,14 +0,0 @@ -use common::io::graph_serializer; -use gremlin_client::{ - process::traversal::{traversal, Scope}, - GValue, IoProtocol, -}; - -mod common; - -#[test] -fn demo() { - let g = traversal().with_remote(graph_serializer(IoProtocol::GraphBinaryV1)); - let y = g.inject(1).sum(Scope::Global).next().unwrap(); - assert_eq!(y, Some(GValue::Int64(1))) -} From 4a42319306b93af73badff2ba1c4cdfa0610afa2 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Thu, 24 Oct 2024 16:33:05 -0500 Subject: [PATCH 20/56] Adding omni test class --- .../tests/integration_traversal_omni.rs | 2681 +++++++++++++++++ 1 file changed, 2681 insertions(+) create mode 100644 gremlin-client/tests/integration_traversal_omni.rs diff --git a/gremlin-client/tests/integration_traversal_omni.rs b/gremlin-client/tests/integration_traversal_omni.rs new file mode 100644 index 00000000..8ef168da --- /dev/null +++ b/gremlin-client/tests/integration_traversal_omni.rs @@ -0,0 +1,2681 @@ +use core::panic; +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, Column, List, Map, Pop, TextP, Vertex, VertexProperty, P, T, +}; + +use gremlin_client::{utils, GKey, GValue, GremlinClient, IoProtocol}; + +mod common; + +use rstest::rstest; +use rstest_reuse::{self, *}; + +use serial_test::serial; + +use common::io::{ + create_edge, create_vertex, create_vertex_with_label, drop_edges, drop_vertices, + graph_serializer, +}; + +#[template] +#[rstest] +#[case::graphson_v2(graph_serializer(IoProtocol::GraphSONV2))] +#[case::graphson_v3(graph_serializer(IoProtocol::GraphSONV3))] +#[case::graph_binary_v1(graph_serializer(IoProtocol::GraphBinaryV1))] +fn serializers(#[case] client: GremlinClient) {} + +#[cfg(feature = "merge_tests")] +mod merge_tests { + use super::*; + use gremlin_client::{ + process::traversal::{GraphTraversalSource, SyncTerminator}, + structure::{Direction, Merge}, + Edge, GValue, ToGValue, + }; + use std::collections::HashMap; + + #[apply(serializers)] + #[serial(test_merge_v_no_options)] + fn test_merge_v_no_options(client: GremlinClient) { + 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 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()); + injection_map.insert("lookup".into(), lookup_map.into()); + injection_map.insert("properties".into(), property_map.into()); + + let vertex_properties = g + .inject(vec![injection_map.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"); + + assert_map_property(&vertex_properties, "propertyKey", "propertyValue"); + } + + #[apply(serializers)] + #[serial(test_merge_v_options)] + fn test_merge_v_options(client: GremlinClient) { + 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()); + + 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"); + + assert_map_property(&on_create_vertex_map, "label", expected_label); + + 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 + .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"); + + assert_map_property(&on_match_vertex_map, "label", expected_label); + + assert_map_property(&on_match_vertex_map, prop_key, expected_on_match_prop_value); + } + + #[apply(serializers)] + #[serial(test_merge_v_start_step)] + fn test_merge_v_start_step(client: GremlinClient) { + 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::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"); + + assert_eq!(expected_label, actual_vertex.label()) + } + + #[apply(serializers)] + #[serial(test_merge_v_anonymous_traversal)] + fn test_merge_v_anonymous_traversal(client: GremlinClient) { + 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()) + } + + #[apply(serializers)] + #[serial(test_merge_e_start_step)] + fn test_merge_e_start_step(client: GremlinClient) { + 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"); + + 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) + .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()); + } + + #[apply(serializers)] + #[serial(test_merge_e_no_options)] + fn test_merge_e_no_options(client: GremlinClient) { + 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"); + + 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) + .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()); + } + + #[apply(serializers)] + #[serial(test_merge_e_options)] + fn test_merge_e_options(client: GremlinClient) { + 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 + assert_map_property( + &on_create_edge_properties, + expected_edge_property_key, + "on_create", + ); + + let on_match_edge_properties = do_merge_edge(g); + assert_map_property( + &on_match_edge_properties, + expected_edge_property_key, + "on_match", + ); + } + + #[apply(serializers)] + #[serial(test_merge_e_anonymous_traversal)] + fn test_merge_e_anonymous_traversal(client: GremlinClient) { + 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()); + } + + #[apply(serializers)] + #[serial(test_merge_v_into_merge_e)] + fn test_merge_v_into_merge_e(client: GremlinClient) { + //Based on the reference doc's combo example + + 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)); + + assert_map_property(&combo_merge_edge_properties, "label", expected_edge_label); + } +} + +#[apply(serializers)] +#[serial(test_simple_vertex_traversal)] +fn test_simple_vertex_traversal(client: GremlinClient) { + let g = traversal().with_remote(client); + + let results = g.v(()).to_list().unwrap(); + + assert!(results.len() > 0); +} + +#[apply(serializers)] +#[serial(test_inject)] +fn test_inject(client: GremlinClient) { + let g = traversal().with_remote(client); + 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); +} + +#[apply(serializers)] +#[serial(test_simple_vertex_traversal_with_id)] +fn test_simple_vertex_traversal_with_id(client: GremlinClient) { + let vertex = create_vertex(&client, "Traversal"); + + let g = traversal().with_remote(client); + + let results = g.v(vertex.id()).to_list().unwrap(); + + assert_eq!(1, results.len()); + + assert_eq!(vertex.id(), results[0].id()); +} + +#[apply(serializers)] +#[serial(test_simple_vertex_traversal_with_multiple_id)] +fn test_simple_vertex_traversal_with_multiple_id(client: GremlinClient) { + drop_vertices(&client, "test_simple_vertex_traversal").unwrap(); + + let vertex = create_vertex_with_label(&client, "test_simple_vertex_traversal", "Traversal"); + let vertex2 = create_vertex_with_label(&client, "test_simple_vertex_traversal", "Traversal"); + + let g = traversal().with_remote(client); + + let results = g.v(vec![vertex.id(), vertex2.id()]).to_list().unwrap(); + + assert_eq!(2, results.len()); + + assert_eq!(vertex.id(), results[0].id()); + assert_eq!(vertex2.id(), results[1].id()); +} + +#[apply(serializers)] +#[serial(test_simple_vertex_traversal_with_label)] +fn test_simple_vertex_traversal_with_label(client: GremlinClient) { + drop_vertices(&client, "test_simple_vertex_traversal_with_label").unwrap(); + + let vertex = create_vertex_with_label( + &client, + "test_simple_vertex_traversal_with_label", + "Traversal", + ); + + let g = traversal().with_remote(client); + + let results = g + .v(()) + .has_label("test_simple_vertex_traversal_with_label") + .to_list() + .unwrap(); + + assert_eq!(1, results.len()); + + assert_eq!(vertex.id(), results[0].id()); +} + +#[apply(serializers)] +#[serial(test_simple_vertex_traversal_with_label_and_has)] +fn test_simple_vertex_traversal_with_label_and_has(client: GremlinClient) { + drop_vertices(&client, "test_simple_vertex_traversal_with_label_and_has").unwrap(); + + let vertex = create_vertex_with_label( + &client, + "test_simple_vertex_traversal_with_label_and_has", + "Traversal", + ); + + let g = traversal().with_remote(client); + + let results = g + .v(()) + .has_label("test_simple_vertex_traversal_with_label_and_has") + .has(("name", "Traversal")) + .to_list() + .unwrap(); + + assert_eq!(1, results.len()); + + assert_eq!(vertex.id(), results[0].id()); + + // with 3 params + + let results = g + .v(()) + .has(( + "test_simple_vertex_traversal_with_label_and_has", + "name", + "Traversal", + )) + .to_list() + .unwrap(); + + assert_eq!(1, results.len()); + + assert_eq!(vertex.id(), results[0].id()); + + // with 1 param + + let results = g + .v(()) + .has_label("test_simple_vertex_traversal_with_label_and_has") + .has("name") + .to_list() + .unwrap(); + + assert_eq!(1, results.len()); + + assert_eq!(vertex.id(), results[0].id()); + + // hasNot + + let results = g + .v(()) + .has_label("test_simple_vertex_traversal_with_label_and_has") + .has_not("surname") + .to_list() + .unwrap(); + + assert_eq!(1, results.len()); + + assert_eq!(vertex.id(), results[0].id()); +} + +#[apply(serializers)] +#[serial(test_simple_edge_traversal)] +fn test_simple_edge_traversal(client: GremlinClient) { + let g = traversal().with_remote(client); + + let results = g.e(()).to_list().unwrap(); + + assert!(results.len() > 0); +} + +#[apply(serializers)] +#[serial(test_simple_edge_traversal_id)] +fn test_simple_edge_traversal_id(client: GremlinClient) { + let v = create_vertex(&client, "Traversal"); + let v1 = create_vertex(&client, "Traversal"); + + let e = create_edge(&client, &v, &v1, "TraversalEdge"); + + let g = traversal().with_remote(client); + + let results = g.e(e.id()).to_list().unwrap(); + + assert_eq!(1, results.len()); + + assert_eq!(e.id(), results[0].id()); +} + +#[apply(serializers)] +#[serial(test_simple_edge_traversal_with_label)] +fn test_simple_edge_traversal_with_label(client: GremlinClient) { + drop_edges(&client, "test_simple_edge_traversal_with_label").unwrap(); + + let v = create_vertex(&client, "Traversal"); + let v1 = create_vertex(&client, "Traversal"); + + let e = create_edge(&client, &v, &v1, "test_simple_edge_traversal_with_label"); + + let g = traversal().with_remote(client); + + let results = g + .e(()) + .has_label("test_simple_edge_traversal_with_label") + .to_list() + .unwrap(); + + assert_eq!(1, results.len()); + + assert_eq!(e.id(), results[0].id()); +} + +#[apply(serializers)] +#[serial(test_traversal)] +fn test_traversal(client: GremlinClient) { + drop_edges(&client, "test_vertex_out_traversal").unwrap(); + + let v = create_vertex(&client, "Traversal"); + let v1 = create_vertex(&client, "Traversal"); + let v2 = create_vertex(&client, "Traversal"); + + let _e = create_edge(&client, &v, &v1, "test_vertex_out_traversal"); + let _e2 = create_edge(&client, &v2, &v, "test_vertex_out_traversal"); + + let g = traversal().with_remote(client); + + // OUT + let results = g + .v(v.id()) + .out("test_vertex_out_traversal") + .to_list() + .unwrap(); + + assert_eq!(1, results.len()); + + assert_eq!(v1.id(), results[0].id()); + + let results = g.v(v.id()).out("fake").to_list().unwrap(); + + assert_eq!(0, results.len()); + + // OUT_E + + let results = g + .v(v.id()) + .out_e("test_vertex_out_traversal") + .to_list() + .unwrap(); + + assert_eq!(1, results.len()); + + assert_eq!("test_vertex_out_traversal", results[0].label()); + + assert_eq!(v.id(), results[0].out_v().id()); + assert_eq!(v1.id(), results[0].in_v().id()); + + // OUT_E -> IN_V + let results = g + .v(v.id()) + .out_e("test_vertex_out_traversal") + .in_v() + .to_list() + .unwrap(); + + assert_eq!(1, results.len()); + + assert_eq!(v1.id(), results[0].id()); + + let results = g.v(v.id()).out("fake").to_list().unwrap(); + + assert_eq!(0, results.len()); + + // IN + let results = g + .v(v1.id()) + .in_("test_vertex_out_traversal") + .to_list() + .unwrap(); + + assert_eq!(1, results.len()); + + assert_eq!(v.id(), results[0].id()); + + let results = g.v(v1.id()).in_("fake").to_list().unwrap(); + + assert_eq!(0, results.len()); + + // IN_E + + let results = g + .v(v1.id()) + .in_e("test_vertex_out_traversal") + .to_list() + .unwrap(); + + assert_eq!(1, results.len()); + + assert_eq!("test_vertex_out_traversal", results[0].label()); + + assert_eq!(v.id(), results[0].out_v().id()); + assert_eq!(v1.id(), results[0].in_v().id()); + + // IN_E -> OUT_V + let results = g + .v(v1.id()) + .in_e("test_vertex_out_traversal") + .out_v() + .to_list() + .unwrap(); + + assert_eq!(1, results.len()); + + assert_eq!(v.id(), results[0].id()); + + let results = g.v(v1.id()).in_("fake").to_list().unwrap(); + + assert_eq!(0, results.len()); + + // BOTH + + let results = g + .v(v.id()) + .both("test_vertex_out_traversal") + .to_list() + .unwrap(); + + assert_eq!(2, results.len()); + + assert_eq!(v1.id(), results[0].id()); + assert_eq!(v2.id(), results[1].id()); + + let results = g.v(v1.id()).in_("fake").to_list().unwrap(); + + assert_eq!(0, results.len()); + + // BOTH_E -> OTHER_V + + let results = g + .v(v.id()) + .both_e("test_vertex_out_traversal") + .other_v() + .to_list() + .unwrap(); + + assert_eq!(2, results.len()); + + assert_eq!(v1.id(), results[0].id()); + assert_eq!(v2.id(), results[1].id()); + + let results = g.v(v1.id()).in_("fake").to_list().unwrap(); + + assert_eq!(0, results.len()); +} + +#[apply(serializers)] +#[serial(test_add_v)] +fn test_add_v(client: GremlinClient) { + let g = traversal().with_remote(client); + + let results = g.add_v("person").to_list().unwrap(); + + assert!(results.len() > 0); + + assert_eq!("person", results[0].label()); + + let results = g.add_v("person").add_v(()).to_list().unwrap(); + + assert!(results.len() > 0); + + //default label + assert_eq!("vertex", results[0].label()); +} + +#[apply(serializers)] +#[serial(test_add_v_with_properties)] +fn test_add_v_with_properties(client: GremlinClient) { + let g = traversal().with_remote(client.clone()); + + let results = g + .add_v("person") + .property("name", "marko") + .property("age", 29) + .to_list() + .unwrap(); + + assert!(results.len() > 0); + + assert_eq!("person", results[0].label()); + + let results = client + .execute("g.V(_id).propertyMap()", &[("_id", results[0].id())]) + .expect("it should execute addV") + .filter_map(Result::ok) + .map(|f| f.take::()) + .collect::, _>>() + .expect("It should be ok"); + + let properties = &results[0]; + + assert_eq!( + &29, + properties["age"].get::().unwrap()[0] + .get::() + .unwrap() + .get::() + .unwrap() + ); + + assert_eq!( + &"marko", + properties["name"].get::().unwrap()[0] + .get::() + .unwrap() + .get::() + .unwrap() + ); +} + +#[apply(serializers)] +#[serial(test_add_v_with_property_many)] +fn test_add_v_with_property_many(client: GremlinClient) { + drop_vertices(&client, "test_add_v_with_property_many").unwrap(); + + let g = traversal().with_remote(client.clone()); + + let results = g + .add_v("test_add_v_with_property_many") + .property_many(vec![ + (String::from("name"), "marko"), + (String::from("age"), "29"), + ]) + .to_list() + .unwrap(); + + assert!(results.len() > 0); + + assert_eq!("test_add_v_with_property_many", results[0].label()); + + let results = client + .execute("g.V(_id).propertyMap()", &[("_id", results[0].id())]) + .expect("it should execute addV") + .filter_map(Result::ok) + .map(|f| f.take::()) + .collect::, _>>() + .expect("It should be ok"); + + let properties = &results[0]; + + assert_eq!( + &"29".to_string(), + properties["age"].get::().unwrap()[0] + .get::() + .unwrap() + .get::() + .unwrap() + ); + + assert_eq!( + &"marko", + properties["name"].get::().unwrap()[0] + .get::() + .unwrap() + .get::() + .unwrap() + ); +} + +#[apply(serializers)] +#[serial(test_has_many)] +fn test_has_many(client: GremlinClient) { + drop_vertices(&client, "test_has_many").unwrap(); + + let g = traversal().with_remote(client.clone()); + + let results = g + .add_v("test_has_many") + .property_many(vec![ + (String::from("name"), "josh"), + (String::from("age"), "21"), + ]) + .to_list() + .unwrap(); + + assert!(results.len() > 0); + + assert_eq!("test_has_many", results[0].label()); + + let results = g + .v(()) + .has_many(vec![ + (String::from("name"), "josh"), + (String::from("age"), "21"), + ]) + .to_list() + .unwrap(); + + assert_eq!(results.len(), 1); +} + +#[apply(serializers)] +#[serial(test_add_e)] +fn test_add_e(client: GremlinClient) { + let g = traversal().with_remote(client.clone()); + + let v = g + .add_v("person") + .property("name", "marko") + .property("age", 29) + .to_list() + .unwrap(); + + let v1 = g + .add_v("person") + .property("name", "jon") + .property("age", 29) + .to_list() + .unwrap(); + + let edges = g.add_e("knows").from(&v[0]).to(&v1[0]).to_list().unwrap(); + + assert!(edges.len() > 0); + + assert_eq!("knows", edges[0].label()); + + let edges = g + .v(v[0].id()) + .as_("a") + .out("knows") + .add_e("livesNear") + .from("a") + .property("year", 2009) + .to_list() + .unwrap(); + + assert!(edges.len() > 0); + + assert_eq!("livesNear", edges[0].label()); + + let edges = g + .v(()) + .as_("a") + .out("created") + .add_e("createdBy") + .to("a") + .property("acl", "public") + .to_list() + .unwrap(); + + assert_eq!("createdBy", edges[0].label()); + + let edges = g + .add_e("knows") + .from(__.v(()).has(("name", "marko"))) + .to(__.v(()).has(("name", "jon"))) + .to_list() + .unwrap(); + + assert!(edges.len() > 0); + + assert_eq!("knows", edges[0].label()); +} + +#[apply(serializers)] +#[serial(test_label_step)] +fn test_label_step(client: GremlinClient) { + let vertex = create_vertex(&client, "Traversal"); + + let g = traversal().with_remote(client); + + let results = g.v(vertex.id()).label().to_list().unwrap(); + + assert_eq!(1, results.len()); + + assert_eq!("person", results[0]); +} + +#[apply(serializers)] +#[serial(test_properties_step)] +fn test_properties_step(client: GremlinClient) { + let vertex = create_vertex(&client, "Traversal"); + + let g = traversal().with_remote(client); + + let results = g.v(vertex.id()).properties(()).to_list().unwrap(); + + assert_eq!(1, results.len()); + + assert_eq!("Traversal", results[0].get::().unwrap()); + + let results = g.v(vertex.id()).properties("name").to_list().unwrap(); + + assert_eq!(1, results.len()); + + assert_eq!("Traversal", results[0].get::().unwrap()); + + let results = g.v(vertex.id()).properties("fake").to_list().unwrap(); + + assert_eq!(0, results.len()); +} + +#[apply(serializers)] +#[serial(test_property_map)] +fn test_property_map(client: GremlinClient) { + let vertex = create_vertex(&client, "Traversal"); + + let g = traversal().with_remote(client); + + let results = g.v(vertex.id()).property_map(()).to_list().unwrap(); + + assert_eq!(1, results.len()); + + let properties = &results[0]; + + assert_eq!( + "Traversal", + properties["name"].get::().unwrap()[0] + .get::() + .unwrap() + .get::() + .unwrap() + ); + + let results = g.v(vertex.id()).property_map("name").to_list().unwrap(); + + assert_eq!(1, results.len()); + + let properties = &results[0]; + + assert_eq!( + "Traversal", + properties["name"].get::().unwrap()[0] + .get::() + .unwrap() + .get::() + .unwrap() + ); + + let results = g.v(vertex.id()).property_map("fake").to_list().unwrap(); + + assert_eq!(1, results.len()); + + let properties = &results[0]; + + assert_eq!(0, properties.len()); +} + +#[apply(serializers)] +#[serial(test_values)] +fn test_values(client: GremlinClient) { + let vertex = create_vertex(&client, "Traversal"); + + let g = traversal().with_remote(client); + + let results = g.v(vertex.id()).values(()).to_list().unwrap(); + + assert_eq!(1, results.len()); + + let value = &results[0]; + + assert_eq!("Traversal", value.get::().unwrap()); + + let results = g.v(vertex.id()).values("name").to_list().unwrap(); + + assert_eq!(1, results.len()); + + let value = &results[0]; + + assert_eq!("Traversal", value.get::().unwrap()); + + let results = g.v(vertex.id()).values("fake").to_list().unwrap(); + + assert_eq!(0, results.len()); +} + +#[apply(serializers)] +#[serial(test_value_map)] +fn test_value_map(client: GremlinClient) { + let g = traversal().with_remote(client); + + let vertices = g + .add_v("test_value_map") + .property("name", "test") + .to_list() + .unwrap(); + + let vertex = &vertices[0]; + + let results = g.v(vertex.id()).value_map(()).to_list().unwrap(); + + assert_eq!(1, results.len()); + + let value = &results[0]; + + assert_eq!( + "test", + value["name"].get::().unwrap()[0] + .get::() + .unwrap() + ); + + let results = g.v(vertex.id()).value_map("name").to_list().unwrap(); + + assert_eq!(1, results.len()); + + let value = &results[0]; + + assert_eq!( + "test", + value["name"].get::().unwrap()[0] + .get::() + .unwrap() + ); + + let results = g.v(vertex.id()).value_map("fake").to_list().unwrap(); + + assert_eq!(0, results[0].len()); + + let results = g.v(vertex.id()).value_map(true).to_list().unwrap(); + + assert_eq!(true, results[0].get("id").is_some()); + assert_eq!(true, results[0].get("label").is_some()); + assert_eq!(true, results[0].get("name").is_some()); +} + +#[apply(serializers)] +#[serial(test_unwrap_map)] +fn test_unwrap_map(client: GremlinClient) { + let g = traversal().with_remote(client); + + let vertices = g + .add_v("test_value_map") + .property("name", "test") + .to_list() + .unwrap(); + + let vertex = &vertices[0]; + + let results = g.v(vertex.id()).value_map(true).next().unwrap().unwrap(); + let v_id = vertex.id().get::().unwrap(); + + let id = utils::unwrap_map::(&results, "id", 0); + let property = utils::unwrap_map::(&results, "name", 0); + let label = utils::unwrap_map::(&results, "label", 0); + + assert_eq!(id.is_ok(), true); + assert_eq!(property.is_ok(), true); + assert_eq!(label.is_ok(), true); + + assert_eq!(id.unwrap(), v_id); + assert_eq!(property.unwrap(), "test"); + assert_eq!(label.unwrap(), "test_value_map"); +} + +#[apply(serializers)] +#[serial(test_element_map)] +fn test_element_map(client: GremlinClient) { + let g = traversal().with_remote(client); + + let vertices = g + .add_v("test_element_map") + .property("name", "test") + .to_list() + .unwrap(); + + let vertex = &vertices[0]; + + let results = g.v(vertex.id()).element_map(()).to_list().unwrap(); + + assert_eq!(1, results.len()); + + let value = &results[0]; + + assert_eq!("test", value["name"].get::().unwrap()); + + let results = g.v(vertex.id()).element_map("name").to_list().unwrap(); + + assert_eq!(1, results.len()); + + let value = &results[0]; + + assert_eq!("test", value["name"].get::().unwrap()); + + let results = g.v(vertex.id()).element_map("fake").to_list().unwrap(); + + assert_eq!(2, results[0].len()); + assert_eq!(true, results[0].get("id").is_some()); + assert_eq!(true, results[0].get("label").is_some()); + + let results = g.v(vertex.id()).element_map(()).to_list().unwrap(); + + assert_eq!(true, results[0].get("id").is_some()); + assert_eq!(true, results[0].get("label").is_some()); + assert_eq!(true, results[0].get("name").is_some()); +} + +#[apply(serializers)] +#[serial(test_count)] +fn test_count(client: GremlinClient) { + let vertex = create_vertex_with_label(&client, "test_count", "Count"); + + let g = traversal().with_remote(client); + + let results = g.v(vertex.id()).count().to_list().unwrap(); + + assert_eq!(1, results.len()); + + let value = &results[0]; + + assert_eq!(&1, value); +} + +#[apply(serializers)] +#[serial(test_group_count_step)] +fn test_group_count_step(client: GremlinClient) { + drop_vertices(&client, "test_group_count").unwrap(); + + let vertex = create_vertex_with_label(&client, "test_group_count", "Count"); + + let g = traversal().with_remote(client); + + let results = g + .v(()) + .has_label("test_group_count") + .group_count() + //Normalize the keys to a common type of Id across serializers + .by(T::Id) + .next() + .unwrap(); + + let value = results.expect("Should have returned a map"); + assert_eq!(1, value.len(), "Should only have 1 entry in map"); + let (actual_key, actual_value) = value.into_iter().next().unwrap(); + + //The actual key may come back as either an int or a string depending + //on the serializer, so normalize it and the vertex gid to a string + let normalized_actual_key = match actual_key { + GKey::String(v) => v, + GKey::Int64(v) => v.to_string(), + other => panic!("Unexpected key type: {:?}", other), + }; + + let normalized_vertex_id = match vertex.id() { + gremlin_client::GID::String(v) => v.clone(), + gremlin_client::GID::Int32(v) => v.to_string(), + gremlin_client::GID::Int64(v) => v.to_string(), + }; + + assert_eq!(normalized_actual_key, normalized_vertex_id); + + assert_eq!( + actual_value, + GValue::Int64(1), + "Group count should have been the single vertex" + ); + + let results = g + .v(()) + .has_label("test_group_count") + .group_count() + .by("name") + .to_list() + .unwrap(); + + assert_eq!(1, results.len()); + + let value = &results[0]; + + assert_eq!(&1, value["Count"].get::().unwrap()); + + let results = g + .v(()) + .has_label("test_group_count") + .group_count() + .by(T::Label) + .to_list() + .unwrap(); + + assert_eq!(1, results.len()); + + let value = &results[0]; + + assert_eq!(&1, value["test_group_count"].get::().unwrap()); +} + +#[apply(serializers)] +#[serial(test_group_by_step)] +fn test_group_by_step(client: GremlinClient) { + drop_vertices(&client, "test_group_by_step").unwrap(); + + create_vertex_with_label(&client, "test_group_by_step", "Count"); + + let g = traversal().with_remote(client); + + let results = g + .v(()) + .has_label("test_group_by_step") + .group() + .by("name") + .to_list() + .unwrap(); + + assert_eq!(1, results.len()); + + let value = &results[0]; + + assert_eq!(1, value["Count"].get::().unwrap().len()); + + let results = g + .v(()) + .has_label("test_group_by_step") + .group() + .by(T::Label) + .to_list() + .unwrap(); + + assert_eq!(1, results.len()); + + let value = &results[0]; + + assert_eq!(1, value["test_group_by_step"].get::().unwrap().len()); + + // + + let results = g + .v(()) + .has_label("test_group_by_step") + .group() + .by(T::Label) + .by(__.count()) + .to_list() + .unwrap(); + + assert_eq!(1, results.len()); + + let value = &results[0]; + + assert_eq!(&1, value["test_group_by_step"].get::().unwrap()); +} + +#[apply(serializers)] +#[serial(test_select_step)] +fn test_select_step(client: GremlinClient) { + drop_vertices(&client, "test_select_step").unwrap(); + + create_vertex_with_label(&client, "test_select_step", "Count"); + + let g = traversal().with_remote(client); + + let results = g + .v(()) + .has_label("test_select_step") + .group_count() + .by("name") + .select("Count") + .to_list() + .unwrap(); + + assert_eq!(1, results.len()); + + let value = &results[0]; + + assert_eq!(&1, value.get::().unwrap()); +} + +#[apply(serializers)] +#[serial(test_fold_step)] +fn test_fold_step(client: GremlinClient) { + drop_vertices(&client, "test_fold_step").unwrap(); + + create_vertex_with_label(&client, "test_fold_step", "Count"); + + let g = traversal().with_remote(client); + + let results = g + .v(()) + .has_label("test_fold_step") + .values("name") + .fold() + .to_list() + .unwrap(); + + assert_eq!(1, results.len()); + + let value = &results[0]; + + assert_eq!("Count", value[0].get::().unwrap()); +} + +#[apply(serializers)] +#[serial(test_unfold_step)] +fn test_unfold_step(client: GremlinClient) { + drop_vertices(&client, "test_unfold_step").unwrap(); + + let vertex = create_vertex_with_label(&client, "test_unfold_step", "Count"); + + let g = traversal().with_remote(client); + + let results = g + .v(vertex.id()) + .property_map(()) + .unfold() + .to_list() + .unwrap(); + + assert_eq!(1, results.len()); + + let value = &results[0]; + + assert_eq!( + "Count", + value["name"].get::().unwrap()[0] + .get::() + .unwrap() + .get::() + .unwrap() + ); +} + +#[apply(serializers)] +#[serial(test_path_step)] +fn test_path_step(client: GremlinClient) { + drop_vertices(&client, "test_path_step").unwrap(); + + let v = create_vertex_with_label(&client, "test_path_step", "Count"); + + let g = traversal().with_remote(client); + + let results = g + .v(()) + .has_label("test_path_step") + .path() + .to_list() + .unwrap(); + + assert_eq!(1, results.len()); + + let value = &results[0]; + + assert_eq!(v.id(), value.objects()[0].get::().unwrap().id()); +} + +#[apply(serializers)] +#[serial(test_limit_step)] +fn test_limit_step(client: GremlinClient) { + drop_vertices(&client, "test_limit_step").unwrap(); + + create_vertex_with_label(&client, "test_limit_step", "Count"); + create_vertex_with_label(&client, "test_limit_step", "Count"); + + let g = traversal().with_remote(client); + + let results = g + .v(()) + .has_label("test_limit_step") + .limit(1) + .to_list() + .unwrap(); + + assert_eq!(1, results.len()); +} + +#[apply(serializers)] +#[serial(test_dedup_step)] +fn test_dedup_step(client: GremlinClient) { + drop_vertices(&client, "test_limit_step").unwrap(); + + create_vertex_with_label(&client, "test_limit_step", "Count"); + create_vertex_with_label(&client, "test_limit_step", "Count"); + + let g = traversal().with_remote(client); + + let results = g + .v(()) + .has_label("test_limit_step") + .dedup(()) + .by(T::Label) + .to_list() + .unwrap(); + + assert_eq!(1, results.len()); +} + +#[apply(serializers)] +#[serial(test_numerical_steps)] +fn test_numerical_steps(client: GremlinClient) { + drop_vertices(&client, "test_numerical_steps").unwrap(); + + let g = traversal().with_remote(client); + + g.add_v("test_numerical_steps") + .property("age", 26) + .to_list() + .unwrap(); + g.add_v("test_numerical_steps") + .property("age", 20) + .to_list() + .unwrap(); + + // sum + let results = g + .v(()) + .has_label("test_numerical_steps") + .values("age") + .sum(()) + .to_list() + .unwrap(); + + assert_eq!(1, results.len()); + + assert_eq!(&46, results[0].get::().unwrap()); + + // max + let results = g + .v(()) + .has_label("test_numerical_steps") + .values("age") + .max(()) + .to_list() + .unwrap(); + + assert_eq!(1, results.len()); + + assert_eq!(&26, results[0].get::().unwrap()); + + // mean + + let results = g + .v(()) + .has_label("test_numerical_steps") + .values("age") + .mean(()) + .to_list() + .unwrap(); + + assert_eq!(1, results.len()); + + assert_eq!(&23.0, results[0].get::().unwrap()); + + // min + + let results = g + .v(()) + .has_label("test_numerical_steps") + .values("age") + .min(()) + .to_list() + .unwrap(); + + assert_eq!(1, results.len()); + + assert_eq!(&20, results[0].get::().unwrap()); +} + +#[apply(serializers)] +#[serial(test_has_with_p_steps)] +fn test_has_with_p_steps(client: GremlinClient) { + drop_vertices(&client, "test_has_with_p_steps").unwrap(); + + let g = traversal().with_remote(client); + + g.add_v("test_has_with_p_steps") + .property("age", 26) + .to_list() + .unwrap(); + let vertices = g + .add_v("test_has_with_p_steps") + .property("age", 20) + .to_list() + .unwrap(); + + let results = g + .v(()) + .has(("test_has_with_p_steps", "age", P::within(vec![19, 20]))) + .to_list() + .unwrap(); + + assert_eq!(1, results.len()); + + assert_eq!(vertices[0].id(), results[0].id()); + + let results = g + .v(()) + .has_label("test_has_with_p_steps") + .values("age") + .is(20) + .to_list() + .unwrap(); + + assert_eq!(1, results.len()); + + assert_eq!(&20, results[0].get::().unwrap()); + + let results = g + .v(()) + .has_label("test_has_with_p_steps") + .values("age") + .is(P::within(vec![19, 20])) + .to_list() + .unwrap(); + + assert_eq!(1, results.len()); + + assert_eq!(&20, results[0].get::().unwrap()); +} + +#[apply(serializers)] +#[serial(test_has_with_text_p_step)] +fn test_has_with_text_p_step(client: GremlinClient) { + drop_vertices(&client, "test_has_with_text_p_step").unwrap(); + + let g = traversal().with_remote(client); + + g.add_v("test_has_with_text_p_step") + .property("name", "Jon") + .to_list() + .unwrap(); + + let vertices = g + .add_v("test_has_with_text_p_step") + .property("name", "Alice") + .to_list() + .unwrap(); + + let results = g + .v(()) + .has(("test_has_with_text_p_step", "name", TextP::containing("A"))) + .to_list() + .unwrap(); + + assert_eq!(1, results.len()); + + assert_eq!(vertices[0].id(), results[0].id()); + + let results = g + .v(()) + .has_label("test_has_with_text_p_step") + .values("name") + .is("Alice") + .to_list() + .unwrap(); + + assert_eq!(1, results.len()); + + assert_eq!("Alice", results[0].get::().unwrap()); + + let results = g + .v(()) + .has_label("test_has_with_text_p_step") + .values("name") + .is(TextP::containing("Al")) + .to_list() + .unwrap(); + + assert_eq!(1, results.len()); + + assert_eq!("Alice", results[0].get::().unwrap()); + + g.add_v("test_has_with_text_p_step") + .property("name", "Alice2") + .to_list() + .unwrap(); + + let results = g + .v(()) + .has(("test_has_with_text_p_step", "name", TextP::containing("A"))) + .to_list() + .unwrap(); + + assert_eq!(2, results.len()); +} + +#[apply(serializers)] +#[serial(where_step_test)] +fn where_step_test(client: GremlinClient) { + drop_vertices(&client, "where_step_test").unwrap(); + + let g = traversal().with_remote(client); + + let v = g + .add_v("where_step_test") + .property("age", 26) + .to_list() + .unwrap(); + + let results = g + .v(()) + .has_label("where_step_test") + .where_(__.values("age").is(26)) + .to_list() + .unwrap(); + + assert_eq!(1, results.len()); + + assert_eq!(v[0].id(), results[0].id()); +} + +#[apply(serializers)] +#[serial(not_step_test)] +fn not_step_test(client: GremlinClient) { + drop_vertices(&client, "not_step_test").unwrap(); + + let g = traversal().with_remote(client); + + g.add_v("not_step_test") + .property("age", 26) + .to_list() + .unwrap(); + + let results = g + .v(()) + .has_label("not_step_test") + .not(__.values("age").is(26)) + .to_list() + .unwrap(); + + assert_eq!(0, results.len()); +} + +#[apply(serializers)] +#[serial(order_step_test)] +fn order_step_test(client: GremlinClient) { + drop_vertices(&client, "order_step_test").unwrap(); + + let g = traversal().with_remote(client); + + g.add_v("order_step_test") + .property("name", "b") + .to_list() + .unwrap(); + + g.add_v("order_step_test") + .property("name", "a") + .to_list() + .unwrap(); + + let results = g + .v(()) + .has_label("order_step_test") + .values("name") + .order(()) + .to_list() + .unwrap(); + + assert_eq!(2, results.len()); + + assert_eq!("a", results[0].get::().unwrap()); + + let results = g + .v(()) + .has_label("order_step_test") + .values("name") + .order(()) + .by(Order::Desc) + .to_list() + .unwrap(); + + assert_eq!(2, results.len()); + + assert_eq!("b", results[0].get::().unwrap()); +} + +#[apply(serializers)] +#[serial(match_step_test)] +fn match_step_test(client: GremlinClient) { + drop_vertices(&client, "match_step_test").unwrap(); + + drop_edges(&client, "match_step_test_edge").unwrap(); + + let g = traversal().with_remote(client); + + let v1 = g + .add_v("match_step_test") + .property("name", "a") + .to_list() + .unwrap(); + + let v2 = g + .add_v("match_step_test") + .property("name", "b") + .to_list() + .unwrap(); + + let v3 = g + .add_v("match_step_test") + .property("name", "c") + .to_list() + .unwrap(); + + g.add_e("match_step_test_edge") + .from(&v1[0]) + .to(&v2[0]) + .to_list() + .unwrap(); + + g.add_e("match_step_test_edge") + .from(&v2[0]) + .to(&v3[0]) + .to_list() + .unwrap(); + + let results = g + .v(()) + .has_label("match_step_test") + .match_(vec![ + __.as_("a") + .has(("name", "a")) + .out("match_step_test_edge") + .as_("b"), + __.as_("b").out("match_step_test_edge").as_("c"), + ]) + .select(vec!["a", "c"]) + .to_list() + .unwrap(); + + assert_eq!(1, results.len()); + + let first = &results[0].get::().unwrap(); + + assert_eq!(&v1[0], first["a"].get::().unwrap()); + assert_eq!(&v3[0], first["c"].get::().unwrap()); +} + +#[apply(serializers)] +#[serial(drop_step_test)] +fn drop_step_test(client: GremlinClient) { + drop_vertices(&client, "drop_step_test").unwrap(); + + let g = traversal().with_remote(client); + + g.add_v("drop_step_test") + .property("name", "a") + .next() + .unwrap(); + + g.add_v("drop_step_test") + .property("name", "b") + .next() + .unwrap(); + + let results = g.v(()).has_label("drop_step_test").count().next().unwrap(); + + assert_eq!(Some(2), results); + + g.v(()) + .has_label("drop_step_test") + .drop() + .to_list() + .unwrap(); + + let results = g.v(()).has_label("drop_step_test").has_next().unwrap(); + + assert_eq!(false, results); +} + +#[apply(serializers)] +#[serial(or_step_test)] +fn or_step_test(client: GremlinClient) { + drop_vertices(&client, "or_step_test").unwrap(); + + let g = traversal().with_remote(client); + + g.add_v("or_step_test") + .property("foo", "bar") + .property("bar", "foo") + .next() + .unwrap(); + + g.add_v("or_step_test") + .property("foo", "nobar") + .property("bar", "nofoo") + .next() + .unwrap(); + + let result = g + .v(()) + .has_label("or_step_test") + .has(("foo", "bar")) + .or(()) + .has(("bar", "foo")) + .to_list() + .unwrap(); + assert_eq!(result.len(), 1); + + let result = g + .v(()) + .has_label("or_step_test") + .has(("foo", "bar")) + .or(()) + .has(("bar", "nofoo")) + .to_list() + .unwrap(); + assert_eq!(result.len(), 2); +} + +#[apply(serializers)] +#[serial(iter_terminator_test)] +fn iter_terminator_test(client: GremlinClient) { + drop_vertices(&client, "iter_terminator_test").unwrap(); + + let g = traversal().with_remote(client); + + g.add_v("iter_terminator_test") + .property("name", "a") + .next() + .unwrap(); + + g.add_v("iter_terminator_test") + .property("name", "b") + .next() + .unwrap(); + + let results: Vec = g + .v(()) + .has_label("iter_terminator_test") + .iter() + .unwrap() + .filter_map(Result::ok) + .collect(); + + assert_eq!(2, results.len()) +} + +#[apply(serializers)] +#[serial(test_select_pop)] +fn test_select_pop(client: GremlinClient) { + drop_vertices(&client, "test_select_pop").unwrap(); + drop_vertices(&client, "test_select_pop_child").unwrap(); + + let g = traversal().with_remote(client); + + let v1 = g + .add_v("test_select_pop") + .property("name", "a") + .to_list() + .unwrap(); + + let v2 = g + .add_v("test_select_pop") + .property("name", "b") + .to_list() + .unwrap(); + + let e1 = g + .add_v("test_select_pop_child") + .property("name", "a") + .to_list() + .unwrap(); + + let e2 = g + .add_v("test_select_pop_child") + .property("name", "b") + .to_list() + .unwrap(); + + g.add_e("child").from(&v1[0]).to(&e1[0]).to_list().unwrap(); + + g.add_e("child").from(&v2[0]).to(&e2[0]).to_list().unwrap(); + + let results = g + .v(()) + .has_label("test_select_pop") + .has(("name", "a")) + .out("child") + .as_("v") + .v(()) + .has_label("test_select_pop") + .has(("name", "b")) + .out("child") + .as_("v") + .select((Pop::All, "v")) + .unfold() + .to_list() + .unwrap(); + assert_eq!(results.len(), 2); + + let results = g + .v(()) + .has_label("test_select_pop") + .has(("name", "a")) + .out("child") + .as_("v") + .v(()) + .has_label("test_select_pop") + .has(("name", "b")) + .out("child") + .as_("v") + .select((Pop::Last, "v")) + .unfold() + .to_list() + .unwrap(); + assert_eq!(results.len(), 1); + + let results = g + .v(()) + .has_label("test_select_pop") + .has(("name", "a")) + .out("child") + .as_("v") + .v(()) + .has_label("test_select_pop") + .has(("name", "b")) + .out("child") + .as_("v") + .select((Pop::First, "v")) + .unfold() + .to_list() + .unwrap(); + assert_eq!(results.len(), 1); +} + +#[apply(serializers)] +#[serial(test_repeat_until_loops_loops)] +fn test_repeat_until_loops_loops(client: GremlinClient) { + drop_vertices(&client, "test_repeat_until_loops").unwrap(); + drop_vertices(&client, "test_repeat_until_loops_child").unwrap(); + + let g = traversal().with_remote(client); + + let v1 = g + .add_v("test_repeat_until_loops") + .property("name", "a") + .to_list() + .unwrap(); + + let e1 = g + .add_v("test_repeat_until_loops_child") + .property("name", "b") + .to_list() + .unwrap(); + + let e2 = g + .add_v("test_repeat_until_loops_child") + .property("name", "c") + .to_list() + .unwrap(); + + g.add_e("child").from(&v1[0]).to(&e1[0]).to_list().unwrap(); + g.add_e("child").from(&e1[0]).to(&e2[0]).to_list().unwrap(); + + let results = g + .v(v1[0].id()) + .repeat(__.out("child")) + .until(__.loops(()).is(2)) + .to_list() + .unwrap(); + + assert_eq!(results.len(), 1); + assert_eq!(results[0], e2[0]); +} + +#[apply(serializers)] +#[serial(test_simple_path)] +fn test_simple_path(client: GremlinClient) { + drop_vertices(&client, "test_simple_path").unwrap(); + drop_vertices(&client, "test_simple_path_child").unwrap(); + + let g = traversal().with_remote(client); + + let v1 = g + .add_v("test_simple_path") + .property("name", "a") + .to_list() + .unwrap(); + + let e1 = g + .add_v("test_simple_path_child") + .property("name", "b") + .to_list() + .unwrap(); + + let e2 = g + .add_v("test_simple_path_child") + .property("name", "c") + .to_list() + .unwrap(); + + g.add_e("child").from(&v1[0]).to(&e1[0]).to_list().unwrap(); + g.add_e("child").from(&e1[0]).to(&e2[0]).to_list().unwrap(); + g.add_e("child").from(&e2[0]).to(&v1[0]).to_list().unwrap(); + + let results = g + .v(v1[0].id()) + .repeat(__.out("child").simple_path()) + .until(__.loops(()).is(2)) + .to_list() + .unwrap(); + + assert_eq!(results.len(), 1); + assert_eq!(results[0], e2[0]); +} + +#[apply(serializers)] +#[serial(test_sample)] +fn test_sample(client: GremlinClient) { + drop_vertices(&client, "test_sample").unwrap(); + drop_vertices(&client, "test_sample_child").unwrap(); + + let g = traversal().with_remote(client); + + let v1 = g + .add_v("test_sample") + .property("name", "a") + .to_list() + .unwrap(); + + let e1 = g + .add_v("test_sample_child") + .property("name", "b") + .to_list() + .unwrap(); + + let e2 = g + .add_v("test_sample_child") + .property("name", "b") + .to_list() + .unwrap(); + + g.add_e("child").from(&v1[0]).to(&e1[0]).to_list().unwrap(); + g.add_e("child").from(&v1[0]).to(&e2[0]).to_list().unwrap(); + let results = g.v(v1[0].id()).out("child").sample(1).to_list().unwrap(); + assert_eq!(results.len(), 1); +} + +#[apply(serializers)] +#[serial(test_local)] +fn test_local(client: GremlinClient) { + drop_vertices(&client, "test_local").unwrap(); + drop_vertices(&client, "test_local_child").unwrap(); + drop_vertices(&client, "test_local_child_child").unwrap(); + + let g = traversal().with_remote(client); + + let v1 = g + .add_v("test_local") + .property("name", "a") + .to_list() + .unwrap(); + + let e1 = g + .add_v("test_local_child") + .property("name", "b") + .to_list() + .unwrap(); + + let e2 = g + .add_v("test_local_child") + .property("name", "b") + .to_list() + .unwrap(); + + let e3 = g + .add_v("test_local_child_child") + .property("name", "c") + .to_list() + .unwrap(); + + let e4 = g + .add_v("test_local_child_child") + .property("name", "d") + .to_list() + .unwrap(); + + let e5 = g + .add_v("test_local_child_child") + .property("name", "e") + .to_list() + .unwrap(); + + g.add_e("child").from(&v1[0]).to(&e1[0]).to_list().unwrap(); + g.add_e("child").from(&v1[0]).to(&e2[0]).to_list().unwrap(); + + g.add_e("child_child") + .from(&e1[0]) + .to(&e3[0]) + .to_list() + .unwrap(); + g.add_e("child_child") + .from(&e1[0]) + .to(&e4[0]) + .to_list() + .unwrap(); + + g.add_e("child_child") + .from(&e2[0]) + .to(&e5[0]) + .to_list() + .unwrap(); + + let results = g + .v(v1[0].id()) + .out("child") + .local(__.out("child_child").sample(1)) //Local used here to only get one vertices from each child + .to_list() + .unwrap(); + + assert_eq!(results.len(), 2); +} + +#[apply(serializers)] +#[serial(test_side_effect)] +fn test_side_effect(client: GremlinClient) { + 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"); + + assert_map_property( + &element_map, + expected_side_effect_key, + expected_side_effect_value, + ); +} + +#[apply(serializers)] +#[serial(test_anonymous_traversal_properties_drop)] +fn test_anonymous_traversal_properties_drop(client: GremlinClient) { + 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" + ); +} + +#[apply(serializers)] +#[serial(test_by_columns)] +fn test_by_columns(client: GremlinClient) { + 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_map_property( + &element_map, + expected_side_effect_key_b, + expected_side_effect_value_b, + ); +} + +#[apply(serializers)] +#[serial(test_property_cardinality)] +fn test_property_cardinality(client: GremlinClient) { + drop_vertices(&client, "test_property_cardinality").unwrap(); + + let g = traversal().with_remote(client); + + let v1 = g + .add_v("test_property_cardinality") + .property("name", "a") + .to_list() + .unwrap(); + + assert!(v1.len() > 0); + + g.v(v1[0].id()) + .property_with_cardinality(Cardinality::List, "name", "b") + .next() + .unwrap(); + let new_v = g.v(v1[0].id()).property_map(()).next().unwrap().unwrap(); + assert_eq!(2, new_v["name"].get::().unwrap().len()); + + g.v(v1[0].id()) + .property_with_cardinality(Cardinality::Single, "name", "b") + .next() + .unwrap(); + let new_v = g.v(v1[0].id()).property_map(()).next().unwrap().unwrap(); + assert_eq!(1, new_v["name"].get::().unwrap().len()); +} + +#[apply(serializers)] +#[serial(test_choose)] +fn test_choose(client: GremlinClient) { + drop_vertices(&client, "test_choose").unwrap(); + + let g = traversal().with_remote(client); + + g.add_v("test_choose") + .property("name", "a") + .as_("source") + .choose(( + __.select("source").has("name"), + __.add_v("test_choose_success"), + __.add_v("test_choose_failure"), + )) + .next() + .unwrap(); + + let success_vertices = g.v(()).has_label("test_choose_success").next().unwrap(); + assert_eq!(success_vertices.is_some(), true); + + let success_vertices = g.v(()).has_label("test_choose_failure").next().unwrap(); + assert_eq!(success_vertices.is_some(), false); + + g.add_v("test_choose") + .property("name", "b") + .as_("source") + .choose(( + __.select("source").has("name"), + __.add_v("test_choose_success2"), + )) + .next() + .unwrap(); + + let success_vertices = g.v(()).has_label("test_choose_failure2").next().unwrap(); + assert_eq!(success_vertices.is_some(), false); + + let success_vertices = g.v(()).has_label("test_choose_success2").next().unwrap(); + assert_eq!(success_vertices.is_some(), true); +} + +#[apply(serializers)] +#[serial(test_choose_by_literal_options)] +fn test_choose_by_literal_options(client: GremlinClient) { + 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())); +} + +#[apply(serializers)] +#[serial()] +fn test_coalesce(client: GremlinClient) { + use gremlin_client::GValue; + + drop_vertices(&client, "test_coalesce").unwrap(); + + let g = traversal().with_remote(client); + + g.add_v("test_coalesce") + .property("name", "a") + .to_list() + .unwrap(); + + g.add_v("test_coalesce") + .property("nickname", "b") + .to_list() + .unwrap(); + + let v = g + .v(()) + .has_label("test_coalesce") + .coalesce::([__.values("nickname"), __.values("name")]) + .to_list() + .unwrap(); + + let values = v + .into_iter() + .map(|e| e.take::().unwrap()) + .collect::>(); + + assert!(values.contains(&String::from("a"))); + assert!(values.contains(&String::from("b"))); +} + +#[apply(serializers)] +#[serial(test_coalesce_unfold)] +fn test_coalesce_unfold(client: GremlinClient) { + drop_vertices(&client, "test_coalesce_unfold").unwrap(); + + let g = traversal().with_remote(client); + + g.v(()) + .has(("test_coalesce_unfold", "name", "unfold")) + .fold() + .coalesce::([__.unfold(), __.add_v("test_coalesce_unfold")]) + .property("name", "unfold") + .next() + .expect("It should create a vertex with coalesce"); + + let v = g + .v(()) + .has_label("test_coalesce_unfold") + .value_map(()) + .to_list() + .unwrap(); + + let values = v.into_iter().collect::>(); + + assert_eq!(1, values.len()); + + assert_eq!( + "unfold", + utils::unwrap_map::(&values[0], "name", 0).unwrap() + ); + + g.v(()) + .has(("test_coalesce_unfold", "name", "unfold")) + .fold() + .coalesce::([__.unfold(), __.add_v("test_coalesce_unfold")]) + .property("name", "unfold") + .next() + .expect("It should create a vertex with coalesce"); + + let v = g + .v(()) + .has_label("test_coalesce_unfold") + .value_map(()) + .to_list() + .unwrap(); + + let values = v.into_iter().collect::>(); + + assert_eq!(1, values.len()); + + assert_eq!( + "unfold", + utils::unwrap_map::(&values[0], "name", 0).unwrap() + ); +} + +#[apply(serializers)] +#[serial(test_none_step)] +fn test_none_step(client: GremlinClient) { + 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 + g.add_v("test_none_step") + .none() + .iter() + .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 + .v(()) + .has_label("test_none_step") + .count() + .next() + .ok() + .flatten() + .expect("Should have gotten a response"); + assert_eq!(1, vertex_count); +} + +#[apply(serializers)] +#[serial(test_traversal_vertex_mapping)] +#[cfg(feature = "derive")] +fn test_traversal_vertex_mapping(client: GremlinClient) { + use chrono::{DateTime, TimeZone, Utc}; + use gremlin_client::derive::FromGMap; + use std::convert::TryFrom; + + drop_vertices(&client, "test_vertex_mapping").unwrap(); + + let g = traversal().with_remote(client); + + let uuid = uuid::Uuid::new_v4(); + let mark = g + .add_v("person") + .property("name", "Mark") + .property("age", 22) + .property("time", 22 as i64) + .property("score", 3.2) + .property("uuid", uuid.clone()) + .property("datetime", chrono::Utc.timestamp(1551825863, 0)) + .property("date", 1551825863 as i64) + .value_map(true) + .by(TraversalBuilder::new(Bytecode::new()).unfold()) + .next(); + assert_eq!(mark.is_ok(), true); + + #[derive(Debug, PartialEq, FromGMap)] + struct Person { + name: String, + age: i32, + time: i64, + datetime: DateTime, + uuid: uuid::Uuid, + optional: Option, + } + let person = Person::try_from(mark.unwrap().unwrap()); + assert_eq!(person.is_ok(), true); + + assert_eq!( + Person { + name: String::from("Mark"), + age: 22, + time: 22, + datetime: chrono::Utc.timestamp(1551825863, 0), + uuid: uuid, + optional: None + }, + person.unwrap() + ); +} From 32b47acf628857a4f47d048631b18200bc5c6cef Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Thu, 24 Oct 2024 18:54:37 -0500 Subject: [PATCH 21/56] Handling more test required serialization types --- gremlin-client/Cargo.toml | 2 + gremlin-client/src/conversion.rs | 2 + gremlin-client/src/io/graph_binary_v1.rs | 83 +++++++++++++++++++++++- gremlin-client/src/structure/gid.rs | 17 +++++ gremlin-client/src/structure/map.rs | 14 ++++ 5 files changed, 116 insertions(+), 2 deletions(-) diff --git a/gremlin-client/Cargo.toml b/gremlin-client/Cargo.toml index 94149bbd..dc66d561 100644 --- a/gremlin-client/Cargo.toml +++ b/gremlin-client/Cargo.toml @@ -71,6 +71,8 @@ version = "1.1.2" [dev-dependencies] rstest = "0.23.0" +rstest_reuse = "0.7.0" +serial_test = "3.1.1" [[example]] name = "traversal_async" diff --git a/gremlin-client/src/conversion.rs b/gremlin-client/src/conversion.rs index fd2102dd..538455af 100644 --- a/gremlin-client/src/conversion.rs +++ b/gremlin-client/src/conversion.rs @@ -155,6 +155,8 @@ impl FromGValue for GKey { GValue::Token(s) => Ok(GKey::String(s.value().clone())), GValue::Vertex(s) => Ok(GKey::Vertex(s)), GValue::Edge(s) => Ok(GKey::Edge(s)), + GValue::Int64(v) => Ok(GKey::Int64(v)), + GValue::Int32(v) => Ok(GKey::Int32(v)), _ => Err(GremlinError::Cast(format!( "Cannot convert {:?} to {}", v, "GKey" diff --git a/gremlin-client/src/io/graph_binary_v1.rs b/gremlin-client/src/io/graph_binary_v1.rs index 7b646db7..f66994ed 100644 --- a/gremlin-client/src/io/graph_binary_v1.rs +++ b/gremlin-client/src/io/graph_binary_v1.rs @@ -9,8 +9,8 @@ use crate::{ io::graph_binary_v1, message::{ReponseStatus, Response, ResponseResult}, process::traversal::Instruction, - structure::Traverser, - GKey, GValue, GremlinError, GremlinResult, + structure::{Traverser, T}, + GKey, GValue, GremlinError, GremlinResult, Vertex, GID, }; use super::IoProtocol; @@ -46,8 +46,11 @@ const SCOPE: u8 = 0x1F; //TODO fill in others //... +const T: u8 = 0x20; const TRAVERSER: u8 = 0x21; //... +const BOOLEAN: u8 = 0x27; +//... const UNSPECIFIED_NULL_OBEJECT: u8 = 0xFE; pub(crate) struct RequestMessage<'a, 'b> { @@ -335,6 +338,11 @@ impl GraphBinaryV1Ser for &GValue { } } } + GValue::Bool(bool) => { + buf.push(BOOLEAN); + buf.push(VALUE_FLAG); + bool.to_be_bytes(buf)?; + } other => unimplemented!("TODO {other:?}"), } Ok(()) @@ -350,6 +358,8 @@ impl GraphBinaryV1Ser for &GKey { GKey::Vertex(vertex) => todo!(), GKey::Edge(edge) => todo!(), GKey::Direction(direction) => todo!(), + GKey::Int64(_) => todo!(), + GKey::Int32(_) => todo!(), } } } @@ -375,6 +385,27 @@ pub trait GraphBinaryV1Deser: Sized { } } +impl GraphBinaryV1Ser for bool { + fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { + //Format: A single byte containing the value 0x01 when it’s true and 0 otherwise. + match self { + true => buf.push(0x01), + false => buf.push(0x00), + } + Ok(()) + } +} + +impl GraphBinaryV1Deser for bool { + fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { + match bytes.next() { + Some(0x00) => Ok(false), + Some(0x01) => Ok(true), + other => Err(GremlinError::Cast(format!("No boolean value byte {other:?}"))), + } + } +} + impl GraphBinaryV1Deser for GValue { fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { let data_code = bytes @@ -442,6 +473,14 @@ impl GraphBinaryV1Deser for GValue { PROPERTY => { todo!() } + VERTEX => Ok(match Vertex::from_be_bytes_nullable(bytes)? { + Some(value) => GValue::Vertex(value), + None => GValue::Null, + }), + T => Ok(match T::from_be_bytes_nullable(bytes)? { + Some(value) => GValue::T(value), + None => GValue::Null, + }), TRAVERSER => { let traverser: Option = GraphBinaryV1Deser::from_be_bytes_nullable(bytes)?; @@ -449,11 +488,51 @@ impl GraphBinaryV1Deser for GValue { .map(|val| GValue::Traverser(val)) .unwrap_or(GValue::Null)) } + UNSPECIFIED_NULL_OBEJECT => { + //Need to confirm the null-ness with the next byte being a 1 + match bytes.next().cloned() { + Some(VALUE_NULL_FLAG) => Ok(GValue::Null), + other => Err(GremlinError::Cast(format!("Not expected null value byte {other:?}"))), + } + } other => unimplemented!("TODO {other}"), } } } +impl GraphBinaryV1Deser for T { + fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { + let literal = GValue::from_be_bytes_nullable(bytes)?; + match literal { + Some(GValue::String(literal)) if literal.eq_ignore_ascii_case("id") => Ok(T::Id), + Some(GValue::String(literal)) if literal.eq_ignore_ascii_case("key") => Ok(T::Id), + Some(GValue::String(literal)) if literal.eq_ignore_ascii_case("label") => Ok(T::Id), + Some(GValue::String(literal)) if literal.eq_ignore_ascii_case("value") => Ok(T::Id), + other => Err(GremlinError::Cast(format!("Unexpected T literal {other:?}"))), + } + } +} + +impl GraphBinaryV1Deser for Vertex { + fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { + //Format: {id}{label}{properties} + //{id} is a fully qualified typed value composed of {type_code}{type_info}{value_flag}{value}. + let id: GValue = GraphBinaryV1Deser::from_be_bytes(bytes)?; + //{label} is a String value. + let label: String = GraphBinaryV1Deser::from_be_bytes(bytes)?; + //{properties} is a fully qualified typed value composed of {type_code}{type_info}{value_flag}{value} which contains properties. + //Note that as TinkerPop currently send "references" only, this value will always be null. + //properties: HashMap>, + let properties: GValue = GraphBinaryV1Deser::from_be_bytes(bytes)?; + if properties == GValue::Null { + //TODO How do vertices get properties then? + Ok(Vertex::new(id.try_into()?, label, HashMap::new())) + } else { + panic!("Remainder: {:?}", properties); + } + } +} + impl GraphBinaryV1Ser for &str { fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { //Format: {length}{text_value} diff --git a/gremlin-client/src/structure/gid.rs b/gremlin-client/src/structure/gid.rs index 049ab4b5..7e3eb1ea 100644 --- a/gremlin-client/src/structure/gid.rs +++ b/gremlin-client/src/structure/gid.rs @@ -1,6 +1,10 @@ +use std::convert::TryFrom; + use crate::{GremlinError, GremlinResult}; use uuid::Uuid; +use super::GValue; + #[derive(Debug, Clone)] pub struct GIDs(pub(crate) Vec); @@ -29,6 +33,19 @@ pub enum GID { Int64(i64), } +impl TryFrom for GID { + type Error = GremlinError; + + fn try_from(value: GValue) -> Result { + match value { + GValue::Int32(v) => Ok(GID::Int32(v)), + GValue::Int64(v) => Ok(GID::Int64(v)), + GValue::String(v) => Ok(GID::String(v)), + other => Err(GremlinError::Cast(format!("Invalid GValue for GID {other:?}"))) + } + } +} + impl GID { pub fn get<'a, T>(&'a self) -> GremlinResult<&'a T> where diff --git a/gremlin-client/src/structure/map.rs b/gremlin-client/src/structure/map.rs index 10e491e0..cc56caa6 100644 --- a/gremlin-client/src/structure/map.rs +++ b/gremlin-client/src/structure/map.rs @@ -145,6 +145,8 @@ pub enum GKey { Vertex(Vertex), Edge(Edge), Direction(Direction), + Int64(i64), + Int32(i32), } impl From for GKey { @@ -153,6 +155,18 @@ impl From for GKey { } } +impl From for GKey { + fn from(value: i64) -> Self { + GKey::Int64(value) + } +} + +impl From for GKey { + fn from(value: i32) -> Self { + GKey::Int32(value) + } +} + impl From for GKey { fn from(value: Direction) -> Self { GKey::Direction(value) From 815767ad973f59c575100374fd57dafba51a49c4 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Thu, 24 Oct 2024 21:53:40 -0500 Subject: [PATCH 22/56] Formatting --- gremlin-client/src/io/graph_binary_v1.rs | 12 +++++++++--- gremlin-client/src/structure/gid.rs | 4 +++- gremlin-client/src/structure/value.rs | 2 ++ .../tests/graph_binary_read_write_cycle.rs | 4 ++-- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/gremlin-client/src/io/graph_binary_v1.rs b/gremlin-client/src/io/graph_binary_v1.rs index f66994ed..a7de09db 100644 --- a/gremlin-client/src/io/graph_binary_v1.rs +++ b/gremlin-client/src/io/graph_binary_v1.rs @@ -401,7 +401,9 @@ impl GraphBinaryV1Deser for bool { match bytes.next() { Some(0x00) => Ok(false), Some(0x01) => Ok(true), - other => Err(GremlinError::Cast(format!("No boolean value byte {other:?}"))), + other => Err(GremlinError::Cast(format!( + "No boolean value byte {other:?}" + ))), } } } @@ -492,7 +494,9 @@ impl GraphBinaryV1Deser for GValue { //Need to confirm the null-ness with the next byte being a 1 match bytes.next().cloned() { Some(VALUE_NULL_FLAG) => Ok(GValue::Null), - other => Err(GremlinError::Cast(format!("Not expected null value byte {other:?}"))), + other => Err(GremlinError::Cast(format!( + "Not expected null value byte {other:?}" + ))), } } other => unimplemented!("TODO {other}"), @@ -508,7 +512,9 @@ impl GraphBinaryV1Deser for T { Some(GValue::String(literal)) if literal.eq_ignore_ascii_case("key") => Ok(T::Id), Some(GValue::String(literal)) if literal.eq_ignore_ascii_case("label") => Ok(T::Id), Some(GValue::String(literal)) if literal.eq_ignore_ascii_case("value") => Ok(T::Id), - other => Err(GremlinError::Cast(format!("Unexpected T literal {other:?}"))), + other => Err(GremlinError::Cast(format!( + "Unexpected T literal {other:?}" + ))), } } } diff --git a/gremlin-client/src/structure/gid.rs b/gremlin-client/src/structure/gid.rs index 7e3eb1ea..2b699dce 100644 --- a/gremlin-client/src/structure/gid.rs +++ b/gremlin-client/src/structure/gid.rs @@ -41,7 +41,9 @@ impl TryFrom for GID { GValue::Int32(v) => Ok(GID::Int32(v)), GValue::Int64(v) => Ok(GID::Int64(v)), GValue::String(v) => Ok(GID::String(v)), - other => Err(GremlinError::Cast(format!("Invalid GValue for GID {other:?}"))) + other => Err(GremlinError::Cast(format!( + "Invalid GValue for GID {other:?}" + ))), } } } diff --git a/gremlin-client/src/structure/value.rs b/gremlin-client/src/structure/value.rs index 5ad9b4d5..c611be7b 100644 --- a/gremlin-client/src/structure/value.rs +++ b/gremlin-client/src/structure/value.rs @@ -266,6 +266,8 @@ impl From for GValue { GKey::Token(s) => GValue::String(s.value().clone()), GKey::Vertex(v) => GValue::Vertex(v), GKey::Edge(v) => GValue::Edge(v), + GKey::Int64(v) => GValue::Int64(v), + GKey::Int32(v) => GValue::Int32(v), } } } diff --git a/gremlin-client/tests/graph_binary_read_write_cycle.rs b/gremlin-client/tests/graph_binary_read_write_cycle.rs index 6b1db8a2..fbdf6ce2 100644 --- a/gremlin-client/tests/graph_binary_read_write_cycle.rs +++ b/gremlin-client/tests/graph_binary_read_write_cycle.rs @@ -1,5 +1,5 @@ use std::array::IntoIter; -use std::collections::{HashSet, HashMap}; +use std::collections::{HashMap, HashSet}; use chrono::{DateTime, TimeZone, Utc}; use common::io::graph_serializer; @@ -10,8 +10,8 @@ use gremlin_client::{ GValue, IoProtocol, }; use rstest::rstest; -use uuid::Uuid; use std::iter::FromIterator; +use uuid::Uuid; mod common; From 753dbf74a0962957868c9190f16b94177831aa11 Mon Sep 17 00:00:00 2001 From: criminosis Date: Thu, 31 Oct 2024 18:45:53 -0500 Subject: [PATCH 23/56] Made test_unwrap_map pass --- gremlin-client/src/conversion.rs | 1 + gremlin-client/src/io/graph_binary_v1.rs | 10 +++--- .../tests/integration_traversal_omni.rs | 36 +++++++++++++------ 3 files changed, 31 insertions(+), 16 deletions(-) diff --git a/gremlin-client/src/conversion.rs b/gremlin-client/src/conversion.rs index 538455af..7d2f3fdc 100644 --- a/gremlin-client/src/conversion.rs +++ b/gremlin-client/src/conversion.rs @@ -157,6 +157,7 @@ impl FromGValue for GKey { GValue::Edge(s) => Ok(GKey::Edge(s)), GValue::Int64(v) => Ok(GKey::Int64(v)), GValue::Int32(v) => Ok(GKey::Int32(v)), + GValue::T(t) => Ok(GKey::T(t)), _ => Err(GremlinError::Cast(format!( "Cannot convert {:?} to {}", v, "GKey" diff --git a/gremlin-client/src/io/graph_binary_v1.rs b/gremlin-client/src/io/graph_binary_v1.rs index a7de09db..d5557793 100644 --- a/gremlin-client/src/io/graph_binary_v1.rs +++ b/gremlin-client/src/io/graph_binary_v1.rs @@ -506,12 +506,12 @@ impl GraphBinaryV1Deser for GValue { impl GraphBinaryV1Deser for T { fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { - let literal = GValue::from_be_bytes_nullable(bytes)?; + let literal = GValue::from_be_bytes(bytes)?; match literal { - Some(GValue::String(literal)) if literal.eq_ignore_ascii_case("id") => Ok(T::Id), - Some(GValue::String(literal)) if literal.eq_ignore_ascii_case("key") => Ok(T::Id), - Some(GValue::String(literal)) if literal.eq_ignore_ascii_case("label") => Ok(T::Id), - Some(GValue::String(literal)) if literal.eq_ignore_ascii_case("value") => Ok(T::Id), + GValue::String(literal) if literal.eq_ignore_ascii_case("id") => Ok(T::Id), + GValue::String(literal) if literal.eq_ignore_ascii_case("key") => Ok(T::Key), + GValue::String(literal) if literal.eq_ignore_ascii_case("label") => Ok(T::Label), + GValue::String(literal) if literal.eq_ignore_ascii_case("value") => Ok(T::Value), other => Err(GremlinError::Cast(format!( "Unexpected T literal {other:?}" ))), diff --git a/gremlin-client/tests/integration_traversal_omni.rs b/gremlin-client/tests/integration_traversal_omni.rs index 8ef168da..d6fc98eb 100644 --- a/gremlin-client/tests/integration_traversal_omni.rs +++ b/gremlin-client/tests/integration_traversal_omni.rs @@ -8,7 +8,9 @@ use gremlin_client::structure::{ Cardinality, Column, List, Map, Pop, TextP, Vertex, VertexProperty, P, T, }; -use gremlin_client::{utils, GKey, GValue, GremlinClient, IoProtocol}; +use gremlin_client::{ + utils, BorrowFromGValue, GKey, GValue, GremlinClient, GremlinError, IoProtocol, +}; mod common; @@ -1213,17 +1215,16 @@ fn test_unwrap_map(client: GremlinClient) { let results = g.v(vertex.id()).value_map(true).next().unwrap().unwrap(); let v_id = vertex.id().get::().unwrap(); - let id = utils::unwrap_map::(&results, "id", 0); - let property = utils::unwrap_map::(&results, "name", 0); - let label = utils::unwrap_map::(&results, "label", 0); - - assert_eq!(id.is_ok(), true); - assert_eq!(property.is_ok(), true); - assert_eq!(label.is_ok(), true); - - assert_eq!(id.unwrap(), v_id); + let id = get_map::(&results, "id") + .unwrap() + .or_else(|| get_map::(&results, T::Id).unwrap()); + let property = get_map::(&results, "name").unwrap(); + let label = get_map::(&results, "label") + .unwrap() + .or_else(|| get_map::(&results, T::Label).unwrap()); + assert_eq!(id, Some(v_id)); assert_eq!(property.unwrap(), "test"); - assert_eq!(label.unwrap(), "test_value_map"); + assert_eq!(label, Some(vertex.label())); } #[apply(serializers)] @@ -2628,6 +2629,19 @@ fn test_none_step(client: GremlinClient) { assert_eq!(1, vertex_count); } +fn get_map<'a, T, K>(map: &'a Map, key: K) -> Result, GremlinError> +where + T: BorrowFromGValue, + K: Into, +{ + map.get(key) + .map(|val| match val { + GValue::List(list) => list[0].get::(), + other => other.get::(), + }) + .transpose() +} + #[apply(serializers)] #[serial(test_traversal_vertex_mapping)] #[cfg(feature = "derive")] From 0368454efc7ed0d8f7cbf3f79909d9f5fabb9609 Mon Sep 17 00:00:00 2001 From: criminosis Date: Thu, 31 Oct 2024 19:28:33 -0500 Subject: [PATCH 24/56] Made test_value_map pass --- .../tests/integration_traversal_omni.rs | 48 +++++++++++-------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/gremlin-client/tests/integration_traversal_omni.rs b/gremlin-client/tests/integration_traversal_omni.rs index d6fc98eb..19a2360e 100644 --- a/gremlin-client/tests/integration_traversal_omni.rs +++ b/gremlin-client/tests/integration_traversal_omni.rs @@ -1168,12 +1168,7 @@ fn test_value_map(client: GremlinClient) { let value = &results[0]; - assert_eq!( - "test", - value["name"].get::().unwrap()[0] - .get::() - .unwrap() - ); + assert_eq!("test", get_map::(value, "name").unwrap().unwrap()); let results = g.v(vertex.id()).value_map("name").to_list().unwrap(); @@ -1181,22 +1176,19 @@ fn test_value_map(client: GremlinClient) { let value = &results[0]; - assert_eq!( - "test", - value["name"].get::().unwrap()[0] - .get::() - .unwrap() - ); + assert_eq!("test", get_map::(value, "name").unwrap().unwrap()); let results = g.v(vertex.id()).value_map("fake").to_list().unwrap(); assert_eq!(0, results[0].len()); let results = g.v(vertex.id()).value_map(true).to_list().unwrap(); + assert_eq!(1, results.len()); + let value = &results[0]; - assert_eq!(true, results[0].get("id").is_some()); - assert_eq!(true, results[0].get("label").is_some()); - assert_eq!(true, results[0].get("name").is_some()); + assert_eq!(Some(vertex.id().get().unwrap()), get_map_id(&value).unwrap()); + assert_eq!(Some(vertex.label()), get_map_label(&value).unwrap()); + assert_eq!(Some("test".to_owned()).as_ref(), get_map(&value, "name").unwrap()); } #[apply(serializers)] @@ -1215,13 +1207,9 @@ fn test_unwrap_map(client: GremlinClient) { let results = g.v(vertex.id()).value_map(true).next().unwrap().unwrap(); let v_id = vertex.id().get::().unwrap(); - let id = get_map::(&results, "id") - .unwrap() - .or_else(|| get_map::(&results, T::Id).unwrap()); + let id = get_map_id(&results).unwrap(); let property = get_map::(&results, "name").unwrap(); - let label = get_map::(&results, "label") - .unwrap() - .or_else(|| get_map::(&results, T::Label).unwrap()); + let label = get_map_label(&results).unwrap(); assert_eq!(id, Some(v_id)); assert_eq!(property.unwrap(), "test"); assert_eq!(label, Some(vertex.label())); @@ -2629,6 +2617,24 @@ fn test_none_step(client: GremlinClient) { assert_eq!(1, vertex_count); } +fn get_map_id<'a>(map: &'a Map) -> Result, GremlinError>{ + let string_keyed = get_map(map, "id")?; + if string_keyed.is_some() { + Ok(string_keyed) + } else { + get_map(map, T::Id) + } +} + +fn get_map_label<'a>(map: &'a Map) -> Result, GremlinError>{ + let string_keyed = get_map(map, "label")?; + if string_keyed.is_some() { + Ok(string_keyed) + } else { + get_map(map, T::Label) + } +} + fn get_map<'a, T, K>(map: &'a Map, key: K) -> Result, GremlinError> where T: BorrowFromGValue, From 54b245787a312d248e9527b89a3404f51911edc6 Mon Sep 17 00:00:00 2001 From: criminosis Date: Thu, 31 Oct 2024 20:36:27 -0500 Subject: [PATCH 25/56] Made test_element_map pass --- gremlin-client/tests/integration_traversal_omni.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/gremlin-client/tests/integration_traversal_omni.rs b/gremlin-client/tests/integration_traversal_omni.rs index 19a2360e..712864d0 100644 --- a/gremlin-client/tests/integration_traversal_omni.rs +++ b/gremlin-client/tests/integration_traversal_omni.rs @@ -1245,16 +1245,18 @@ fn test_element_map(client: GremlinClient) { assert_eq!("test", value["name"].get::().unwrap()); let results = g.v(vertex.id()).element_map("fake").to_list().unwrap(); + let value = &results[0]; - assert_eq!(2, results[0].len()); - assert_eq!(true, results[0].get("id").is_some()); - assert_eq!(true, results[0].get("label").is_some()); + assert_eq!(2, value.len()); + assert_eq!(Some(vertex.id().get().unwrap()), get_map_id(&value).unwrap()); + assert_eq!(Some(vertex.label()), get_map_label(&value).unwrap()); let results = g.v(vertex.id()).element_map(()).to_list().unwrap(); + let value = &results[0]; - assert_eq!(true, results[0].get("id").is_some()); - assert_eq!(true, results[0].get("label").is_some()); - assert_eq!(true, results[0].get("name").is_some()); + assert_eq!(Some(vertex.id().get().unwrap()), get_map_id(&value).unwrap()); + assert_eq!(Some(vertex.label()), get_map_label(&value).unwrap()); + assert_eq!(Some("test".to_owned()).as_ref(), get_map(&value, "name").unwrap()); } #[apply(serializers)] From 35d22ed31fd8b662292970312d1b50233c61cc38 Mon Sep 17 00:00:00 2001 From: criminosis Date: Thu, 31 Oct 2024 20:47:02 -0500 Subject: [PATCH 26/56] test_anonymous_traversal_properties_drop --- gremlin-client/tests/integration_traversal_omni.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gremlin-client/tests/integration_traversal_omni.rs b/gremlin-client/tests/integration_traversal_omni.rs index 712864d0..9b3eaca7 100644 --- a/gremlin-client/tests/integration_traversal_omni.rs +++ b/gremlin-client/tests/integration_traversal_omni.rs @@ -2316,7 +2316,7 @@ fn test_anonymous_traversal_properties_drop(client: GremlinClient) { //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 created_vertex_id = element_map.get("id").or(element_map.get(T::Id)).expect("Should have id property"); let GValue::Int64(id) = created_vertex_id else { panic!("Not expected id type"); }; From 490e9255f41f074d00b888afffde1a8b359208cf Mon Sep 17 00:00:00 2001 From: criminosis Date: Mon, 4 Nov 2024 22:06:11 -0600 Subject: [PATCH 27/56] Saving vertex serde progress --- gremlin-client/src/io/graph_binary_v1.rs | 53 ++++++++++++++++++- gremlin-client/src/structure/vertex.rs | 2 +- .../tests/integration_traversal_omni.rs | 48 ++++++++++++----- 3 files changed, 89 insertions(+), 14 deletions(-) diff --git a/gremlin-client/src/io/graph_binary_v1.rs b/gremlin-client/src/io/graph_binary_v1.rs index d5557793..3a818612 100644 --- a/gremlin-client/src/io/graph_binary_v1.rs +++ b/gremlin-client/src/io/graph_binary_v1.rs @@ -10,7 +10,7 @@ use crate::{ message::{ReponseStatus, Response, ResponseResult}, process::traversal::Instruction, structure::{Traverser, T}, - GKey, GValue, GremlinError, GremlinResult, Vertex, GID, + GKey, GValue, GremlinError, GremlinResult, ToGValue, Vertex, VertexProperty, GID, }; use super::IoProtocol; @@ -284,6 +284,10 @@ impl GraphBinaryV1Ser for &GValue { buf.push(VALUE_FLAG); value.to_be_bytes(buf)?; } + GValue::Vertex(value) => { + buf.push(VERTEX); + buf.push(VALUE_FLAG); + } GValue::Bytecode(code) => { //Type code of 0x15: Bytecode buf.push(BYTECODE); @@ -688,6 +692,53 @@ impl GraphBinaryV1Ser for &Uuid { } } +impl GraphBinaryV1Ser for &Vertex { + fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { + //Format: {id}{label}{properties} + + //{id} is a fully qualified typed value composed of {type_code}{type_info}{value_flag}{value}. + self.id().to_be_bytes(buf)?; + + //{label} is a String value + self.label().to_be_bytes(buf)?; + + //{properties} is a fully qualified typed value composed of {type_code}{type_info}{value_flag}{value} which contains properties. + self.properties.len(); + todo!() + } +} + +impl GraphBinaryV1Ser for &VertexProperty { + fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { + //Format: {id}{label}{value}{parent}{properties} + + //{id} is a fully qualified typed value composed of {type_code}{type_info}{value_flag}{value}. + self.id().to_be_bytes(buf)?; + + //{label} is a String value. + self.label().to_be_bytes(buf)?; + + //{value} is a fully qualified typed value composed of {type_code}{type_info}{value_flag}{value}. + //??????? + + //{parent} is a fully qualified typed value composed of {type_code}{type_info}{value_flag}{value} which contains the parent Vertex. Note that as TinkerPop currently send "references" only, this value will always be null.} + + //{properties} is a fully qualified typed value composed of {type_code}{type_info}{value_flag}{value} which contains properties. + + todo!() + } +} + +impl GraphBinaryV1Ser for &GID { + fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { + match self { + GID::String(s) => s.to_gvalue().to_be_bytes(buf), + GID::Int32(i) => i.to_gvalue().to_be_bytes(buf), + GID::Int64(i) => i.to_gvalue().to_be_bytes(buf), + } + } +} + impl GraphBinaryV1Deser for Uuid { fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { bytes diff --git a/gremlin-client/src/structure/vertex.rs b/gremlin-client/src/structure/vertex.rs index fdd2a4df..b0001579 100644 --- a/gremlin-client/src/structure/vertex.rs +++ b/gremlin-client/src/structure/vertex.rs @@ -8,7 +8,7 @@ use std::hash::Hasher; pub struct Vertex { id: GID, label: String, - properties: HashMap>, + pub(crate) properties: HashMap>, } impl Vertex { diff --git a/gremlin-client/tests/integration_traversal_omni.rs b/gremlin-client/tests/integration_traversal_omni.rs index 9b3eaca7..fd6cdd0e 100644 --- a/gremlin-client/tests/integration_traversal_omni.rs +++ b/gremlin-client/tests/integration_traversal_omni.rs @@ -1168,7 +1168,10 @@ fn test_value_map(client: GremlinClient) { let value = &results[0]; - assert_eq!("test", get_map::(value, "name").unwrap().unwrap()); + assert_eq!( + "test", + get_map::(value, "name").unwrap().unwrap() + ); let results = g.v(vertex.id()).value_map("name").to_list().unwrap(); @@ -1176,7 +1179,10 @@ fn test_value_map(client: GremlinClient) { let value = &results[0]; - assert_eq!("test", get_map::(value, "name").unwrap().unwrap()); + assert_eq!( + "test", + get_map::(value, "name").unwrap().unwrap() + ); let results = g.v(vertex.id()).value_map("fake").to_list().unwrap(); @@ -1186,9 +1192,15 @@ fn test_value_map(client: GremlinClient) { assert_eq!(1, results.len()); let value = &results[0]; - assert_eq!(Some(vertex.id().get().unwrap()), get_map_id(&value).unwrap()); + assert_eq!( + Some(vertex.id().get().unwrap()), + get_map_id(&value).unwrap() + ); assert_eq!(Some(vertex.label()), get_map_label(&value).unwrap()); - assert_eq!(Some("test".to_owned()).as_ref(), get_map(&value, "name").unwrap()); + assert_eq!( + Some("test".to_owned()).as_ref(), + get_map(&value, "name").unwrap() + ); } #[apply(serializers)] @@ -1248,15 +1260,24 @@ fn test_element_map(client: GremlinClient) { let value = &results[0]; assert_eq!(2, value.len()); - assert_eq!(Some(vertex.id().get().unwrap()), get_map_id(&value).unwrap()); + assert_eq!( + Some(vertex.id().get().unwrap()), + get_map_id(&value).unwrap() + ); assert_eq!(Some(vertex.label()), get_map_label(&value).unwrap()); let results = g.v(vertex.id()).element_map(()).to_list().unwrap(); let value = &results[0]; - assert_eq!(Some(vertex.id().get().unwrap()), get_map_id(&value).unwrap()); + assert_eq!( + Some(vertex.id().get().unwrap()), + get_map_id(&value).unwrap() + ); assert_eq!(Some(vertex.label()), get_map_label(&value).unwrap()); - assert_eq!(Some("test".to_owned()).as_ref(), get_map(&value, "name").unwrap()); + assert_eq!( + Some("test".to_owned()).as_ref(), + get_map(&value, "name").unwrap() + ); } #[apply(serializers)] @@ -2316,7 +2337,10 @@ fn test_anonymous_traversal_properties_drop(client: GremlinClient) { //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").or(element_map.get(T::Id)).expect("Should have id property"); + let created_vertex_id = element_map + .get("id") + .or(element_map.get(T::Id)) + .expect("Should have id property"); let GValue::Int64(id) = created_vertex_id else { panic!("Not expected id type"); }; @@ -2619,21 +2643,21 @@ fn test_none_step(client: GremlinClient) { assert_eq!(1, vertex_count); } -fn get_map_id<'a>(map: &'a Map) -> Result, GremlinError>{ +fn get_map_id<'a>(map: &'a Map) -> Result, GremlinError> { let string_keyed = get_map(map, "id")?; if string_keyed.is_some() { Ok(string_keyed) } else { - get_map(map, T::Id) + get_map(map, T::Id) } } -fn get_map_label<'a>(map: &'a Map) -> Result, GremlinError>{ +fn get_map_label<'a>(map: &'a Map) -> Result, GremlinError> { let string_keyed = get_map(map, "label")?; if string_keyed.is_some() { Ok(string_keyed) } else { - get_map(map, T::Label) + get_map(map, T::Label) } } From 69da91e3b3847d191f6524017eb4ebcdfb95a3bf Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Tue, 5 Nov 2024 15:14:23 -0600 Subject: [PATCH 28/56] Express possibly null response request id & made vertex serde tests pass --- gremlin-client/src/aio/client.rs | 6 +- gremlin-client/src/aio/connection.rs | 16 +- gremlin-client/src/aio/pool.rs | 6 +- gremlin-client/src/client.rs | 6 +- gremlin-client/src/io/graph_binary_v1.rs | 196 ++++++++++++++++-- gremlin-client/src/io/mod.rs | 4 +- gremlin-client/src/message.rs | 2 +- gremlin-client/src/pool.rs | 6 +- gremlin-client/src/structure/list.rs | 4 + gremlin-client/src/structure/map.rs | 4 + .../tests/integration_traversal_omni.rs | 19 ++ 11 files changed, 238 insertions(+), 31 deletions(-) diff --git a/gremlin-client/src/aio/client.rs b/gremlin-client/src/aio/client.rs index d1d0f74c..fbd3286c 100644 --- a/gremlin-client/src/aio/client.rs +++ b/gremlin-client/src/aio/client.rs @@ -172,7 +172,11 @@ impl GremlinClient { "authentication", "traversal", args, - Some(response.request_id), + Some( + response + .request_id + .expect("Auth challenge requires response id"), + ), )?; return self.send_message_new(conn, id, message).await; diff --git a/gremlin-client/src/aio/connection.rs b/gremlin-client/src/aio/connection.rs index 8ebece2d..7854f0e5 100644 --- a/gremlin-client/src/aio/connection.rs +++ b/gremlin-client/src/aio/connection.rs @@ -288,8 +288,20 @@ fn receiver_loop( .read_response(data) .expect("Unable to parse message"); let mut guard = requests.lock().await; + + //GraphBinary permits a null response request id, so in lieu of a request id assume + //a single entry in the requests to be the one we should respond to given connection + //multiplexing isn't currently implemented + let request_id = response.request_id.unwrap_or_else(|| { + if guard.len() == 1 { + guard.keys().next().expect("Should have had only 1 key").clone() + } else { + panic!("Request response without request id was received, but there isn't only 1 request currently submitted"); + } + }); + if response.status.code != 206 { - let item = guard.remove(&response.request_id); + let item = guard.remove(&request_id); drop(guard); if let Some(mut s) = item { match s.send(Ok(response)).await { @@ -298,7 +310,7 @@ fn receiver_loop( }; } } else { - let item = guard.get_mut(&response.request_id); + let item = guard.get_mut(&request_id); if let Some(s) = item { match s.send(Ok(response)).await { Ok(_r) => {} diff --git a/gremlin-client/src/aio/pool.rs b/gremlin-client/src/aio/pool.rs index d6003292..e4ca5c38 100644 --- a/gremlin-client/src/aio/pool.rs +++ b/gremlin-client/src/aio/pool.rs @@ -63,7 +63,11 @@ impl Manager for GremlinConnectionManager { "authentication", "traversal", args, - Some(response.request_id), + Some( + response + .request_id + .expect("Auth challenge requires response id"), + ), )?; let (response, _receiver) = conn.send(id, message).await?; match response.status.code { diff --git a/gremlin-client/src/client.rs b/gremlin-client/src/client.rs index 297910af..b3e49e48 100644 --- a/gremlin-client/src/client.rs +++ b/gremlin-client/src/client.rs @@ -206,7 +206,11 @@ impl GremlinClient { "authentication", "traversal", args, - Some(response.request_id), + Some( + response + .request_id + .expect("Auth challenge requires response id"), + ), )?; conn.send(message)?; diff --git a/gremlin-client/src/io/graph_binary_v1.rs b/gremlin-client/src/io/graph_binary_v1.rs index 3a818612..917bc269 100644 --- a/gremlin-client/src/io/graph_binary_v1.rs +++ b/gremlin-client/src/io/graph_binary_v1.rs @@ -10,7 +10,7 @@ use crate::{ message::{ReponseStatus, Response, ResponseResult}, process::traversal::Instruction, structure::{Traverser, T}, - GKey, GValue, GremlinError, GremlinResult, ToGValue, Vertex, VertexProperty, GID, + Edge, GKey, GValue, GremlinError, GremlinResult, ToGValue, Vertex, VertexProperty, GID, }; use super::IoProtocol; @@ -42,6 +42,7 @@ const VERTEX_PROPERTY: u8 = 0x12; // const BINDING: u8 = 0x14; const BYTECODE: u8 = 0x15; //... +const P: u8 = 0x1E; const SCOPE: u8 = 0x1F; //TODO fill in others @@ -62,7 +63,7 @@ pub(crate) struct RequestMessage<'a, 'b> { pub(crate) struct ResponseMessage { //Format: {version}{request_id}{status_code}{status_message}{status_attributes}{result_meta}{result_data} - pub(crate) request_id: Uuid, + pub(crate) request_id: Option, pub(crate) status_code: i16, pub(crate) status_message: String, pub(crate) status_attributes: HashMap, @@ -111,8 +112,7 @@ impl GraphBinaryV1Deser for ResponseMessage { }; //Request id is nullable - let request_id = - Uuid::from_be_bytes_nullable(bytes)?.expect("TODO what to do with null request id?"); + let request_id = Uuid::from_be_bytes_nullable(bytes)?; let status_code = ::from_be_bytes(bytes)? .try_into() @@ -284,9 +284,18 @@ impl GraphBinaryV1Ser for &GValue { buf.push(VALUE_FLAG); value.to_be_bytes(buf)?; } - GValue::Vertex(value) => { + GValue::Property(property) => { + unimplemented!("") + } + GValue::Vertex(vertex) => { buf.push(VERTEX); buf.push(VALUE_FLAG); + vertex.id().to_be_bytes(buf)?; + vertex.label().to_be_bytes(buf)?; + GValue::Null.to_be_bytes(buf)?; + } + GValue::VertexProperty(vertex_property) => { + todo!() } GValue::Bytecode(code) => { //Type code of 0x15: Bytecode @@ -319,13 +328,15 @@ impl GraphBinaryV1Ser for &GValue { write_instructions(code.steps(), buf)?; write_instructions(code.sources(), buf)?; } - GValue::Null => { - //Type code of 0xfe: Unspecified null object - buf.push(UNSPECIFIED_NULL_OBEJECT); - //Then the null {value_flag} set and no sequence of bytes. - buf.push(VALUE_NULL_FLAG); + GValue::P(p) => { + //Type code of 0x1e: P + buf.push(P); + buf.push(VALUE_FLAG); + p.operator().to_be_bytes(buf)?; + //Seems we only support 1 parameter predicates? + GraphBinaryV1Ser::to_be_bytes(1i32, buf)?; + p.value().to_be_bytes(buf)?; } - // GValue::Traverser(traverser) => todo!(), GValue::Scope(scope) => { //Type code of 0x1f: Scope buf.push(SCOPE); @@ -347,6 +358,12 @@ impl GraphBinaryV1Ser for &GValue { buf.push(VALUE_FLAG); bool.to_be_bytes(buf)?; } + GValue::Null => { + //Type code of 0xfe: Unspecified null object + buf.push(UNSPECIFIED_NULL_OBEJECT); + //Then the null {value_flag} set and no sequence of bytes. + buf.push(VALUE_NULL_FLAG); + } other => unimplemented!("TODO {other:?}"), } Ok(()) @@ -470,9 +487,10 @@ impl GraphBinaryV1Deser for GValue { Some(value) => GValue::Uuid(value), None => GValue::Null, }), - EDGE => { - todo!() - } + EDGE => Ok(match Edge::from_be_bytes_nullable(bytes)? { + Some(value) => GValue::Edge(value), + None => GValue::Null, + }), PATH => { todo!() } @@ -483,6 +501,10 @@ impl GraphBinaryV1Deser for GValue { Some(value) => GValue::Vertex(value), None => GValue::Null, }), + VERTEX_PROPERTY => Ok(match VertexProperty::from_be_bytes_nullable(bytes)? { + Some(value) => GValue::VertexProperty(value), + None => GValue::Null, + }), T => Ok(match T::from_be_bytes_nullable(bytes)? { Some(value) => GValue::T(value), None => GValue::Null, @@ -523,6 +545,87 @@ impl GraphBinaryV1Deser for T { } } +fn consume_expected_null_reference_bytes<'a, S: Iterator>( + bytes: &mut S, + null_reference_descriptor: &str, +) -> GremlinResult<()> { + let GValue::Null = GraphBinaryV1Deser::from_be_bytes(bytes)? else { + //Anything else is erroneous + return Err(GremlinError::Cast(format!( + "{null_reference_descriptor} is supposed to be a \"null\" reference" + ))); + }; + + Ok(()) +} + +impl GraphBinaryV1Deser for Edge { + fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { + //Format: {id}{label}{inVId}{inVLabel}{outVId}{outVLabel}{parent}{properties} + + //{id} is a fully qualified typed value composed of {type_code}{type_info}{value_flag}{value} + let id: GValue = GraphBinaryV1Deser::from_be_bytes(bytes)?; + + //{label} is a String value. + let label: String = GraphBinaryV1Deser::from_be_bytes(bytes)?; + + //Ideally we'd just do something like Vertex::from_be_bytes(bytes) for the in/out vertices + //however only the id & label is submitted, the "null" properties byte is not + + //{inVId} is a fully qualified typed value composed of {type_code}{type_info}{value_flag}{value}. + let in_v_id: GValue = GraphBinaryV1Deser::from_be_bytes(bytes)?; + + //{inVLabel} is a String value. + let in_v_label: String = GraphBinaryV1Deser::from_be_bytes(bytes)?; + + //{outVId} is a fully qualified typed value composed of {type_code}{type_info}{value_flag}{value}. + let out_v_id: GValue = GraphBinaryV1Deser::from_be_bytes(bytes)?; + + //{outVLabel} is a String value. + let out_v_label: String = GraphBinaryV1Deser::from_be_bytes(bytes)?; + + //{parent} is a fully qualified typed value composed of {type_code}{type_info}{value_flag}{value} which contains the parent Vertex. Note that as TinkerPop currently send "references" only, this value will always be null. + consume_expected_null_reference_bytes(bytes, "Parent")?; + + //{properties} is a fully qualified typed value composed of {type_code}{type_info}{value_flag}{value} which contains the properties for the edge. Note that as TinkerPop currently send "references" only this value will always be null. + consume_expected_null_reference_bytes(bytes, "Properties")?; + Ok(Edge::new( + id.try_into()?, + label, + in_v_id.try_into()?, + in_v_label, + out_v_id.try_into()?, + out_v_label, + HashMap::new(), + )) + } +} + +impl GraphBinaryV1Deser for VertexProperty { + fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { + //Format: {id}{label}{value}{parent}{properties} + //{id} is a fully qualified typed value composed of {type_code}{type_info}{value_flag}{value}. + let id: GValue = GraphBinaryV1Deser::from_be_bytes_nullable(bytes)? + .ok_or(GremlinError::Cast(format!("Id bytes not present")))?; + let id: GID = id.try_into()?; + //{label} is a String value. + let label: String = GraphBinaryV1Deser::from_be_bytes(bytes)?; + + //{value} is a fully qualified typed value composed of {type_code}{type_info}{value_flag}{value}. + let value: GValue = + GraphBinaryV1Deser::from_be_bytes_nullable(bytes)?.unwrap_or(GValue::Null); + + //{parent} is a fully qualified typed value composed of {type_code}{type_info}{value_flag}{value} which contains the parent Vertex. + //Note that as TinkerPop currently send "references" only, this value will always be null. + consume_expected_null_reference_bytes(bytes, "Parent vertex")?; + + //{properties} is a fully qualified typed value composed of {type_code}{type_info}{value_flag}{value} which contains properties. + //Note that as TinkerPop currently send "references" only, this value will always be null. + consume_expected_null_reference_bytes(bytes, "Properties")?; + Ok(VertexProperty::new(id, label, value)) + } +} + impl GraphBinaryV1Deser for Vertex { fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { //Format: {id}{label}{properties} @@ -534,11 +637,58 @@ impl GraphBinaryV1Deser for Vertex { //Note that as TinkerPop currently send "references" only, this value will always be null. //properties: HashMap>, let properties: GValue = GraphBinaryV1Deser::from_be_bytes(bytes)?; - if properties == GValue::Null { - //TODO How do vertices get properties then? - Ok(Vertex::new(id.try_into()?, label, HashMap::new())) - } else { - panic!("Remainder: {:?}", properties); + match properties { + //Should always be null + GValue::Null => Ok(Vertex::new(id.try_into()?, label, HashMap::new())), + // GValue::Map(map) => { + // let properties = map + // .into_iter() + // .map(|(k, v)| { + // let key = match k { + // GKey::T(t) => match t { + // T::Id => "id".to_owned(), + // T::Key => "key".to_owned(), + // T::Label => "label".to_owned(), + // T::Value => "value".to_owned(), + // }, + // GKey::String(s) => s, + // GKey::Int64(i) => i.to_string(), + // GKey::Int32(i) => i.to_string(), + // _ => { + // return Err(GremlinError::Cast(format!( + // "Unsupported vertex property key type" + // ))) + // } + // }; + + // fn unfurl_gvalue_to_vertex_property( + // v: GValue, + // ) -> Result, GremlinError> { + // match v { + // GValue::VertexProperty(vertex_property) => { + // Ok(vec![vertex_property]) + // } + // GValue::List(list) => Ok(list + // .into_iter() + // .map(|value| unfurl_gvalue_to_vertex_property(value)) + // .collect::>, GremlinError>>()? + // .into_iter() + // .flat_map(|vec| vec.into_iter()) + // .collect()), + // _ => Err(GremlinError::Cast(format!( + // "Unsupported vertex property value type" + // ))), + // } + // } + + // Ok((key, unfurl_gvalue_to_vertex_property(v)?)) + // }) + // .collect::>, GremlinError>>()?; + // Ok(Vertex::new(id.try_into()?, label, properties)) + // } + other => Err(GremlinError::Cast(format!( + "Unsupported vertex property type: {other:?}" + ))), } } } @@ -588,7 +738,7 @@ impl GraphBinaryV1Deser for Traverser { } } -impl GraphBinaryV1Deser for Vec { +impl GraphBinaryV1Deser for Vec { fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { let length = ::from_be_bytes(bytes)? .try_into() @@ -596,7 +746,7 @@ impl GraphBinaryV1Deser for Vec { let mut list = Vec::new(); list.reserve_exact(length); for _ in 0..length { - list.push(GValue::from_be_bytes(bytes)?); + list.push(T::from_be_bytes(bytes)?); } Ok(list) } @@ -612,7 +762,9 @@ impl GraphBinaryV1Deser for String { let string_value_bytes: Vec = bytes.take(string_bytes_length).cloned().collect(); if string_value_bytes.len() < string_bytes_length { return Err(GremlinError::Cast(format!( - "Missing bytes for String value" + "Missing bytes for String value. Expected {} only retrieved {}", + string_bytes_length, + string_value_bytes.len(), ))); } String::from_utf8(string_value_bytes) diff --git a/gremlin-client/src/io/mod.rs b/gremlin-client/src/io/mod.rs index 765153f3..4b59f61c 100644 --- a/gremlin-client/src/io/mod.rs +++ b/gremlin-client/src/io/mod.rs @@ -57,7 +57,7 @@ impl IoProtocol { let middle_form: MiddleResponse = serde_json::from_slice(&response).map_err(GremlinError::from)?; Ok(Response { - request_id: middle_form.request_id, + request_id: Some(middle_form.request_id), result: ResponseResult { data: serializer_v2::deserializer_v2(&middle_form.result.data).map(Some)?, }, @@ -68,7 +68,7 @@ impl IoProtocol { let middle_form: MiddleResponse = serde_json::from_slice(&response).map_err(GremlinError::from)?; Ok(Response { - request_id: middle_form.request_id, + request_id: Some(middle_form.request_id), result: ResponseResult { data: serializer_v3::deserializer_v3(&middle_form.result.data).map(Some)?, }, diff --git a/gremlin-client/src/message.rs b/gremlin-client/src/message.rs index 919ca7ec..39a69cc2 100644 --- a/gremlin-client/src/message.rs +++ b/gremlin-client/src/message.rs @@ -53,7 +53,7 @@ impl Message { } #[derive(Debug)] pub struct Response { - pub request_id: Uuid, + pub request_id: Option, pub result: ResponseResult, pub status: ReponseStatus, } diff --git a/gremlin-client/src/pool.rs b/gremlin-client/src/pool.rs index 8a2dd2c1..b0c72347 100644 --- a/gremlin-client/src/pool.rs +++ b/gremlin-client/src/pool.rs @@ -64,7 +64,11 @@ impl ManageConnection for GremlinConnectionManager { "authentication", "traversal", args, - Some(response.request_id), + Some( + response + .request_id + .expect("Auth challenge requires response id"), + ), )?; conn.send(message)?; diff --git a/gremlin-client/src/structure/list.rs b/gremlin-client/src/structure/list.rs index e7aa7f8a..715deabd 100644 --- a/gremlin-client/src/structure/list.rs +++ b/gremlin-client/src/structure/list.rs @@ -16,6 +16,10 @@ impl List { self.0.iter() } + pub fn into_iter(self) -> std::vec::IntoIter { + self.0.into_iter() + } + pub fn len(&self) -> usize { self.0.len() } diff --git a/gremlin-client/src/structure/map.rs b/gremlin-client/src/structure/map.rs index cc56caa6..b4c90537 100644 --- a/gremlin-client/src/structure/map.rs +++ b/gremlin-client/src/structure/map.rs @@ -77,6 +77,10 @@ impl Map { self.0.iter() } + pub fn into_iter(self) -> IntoIter { + self.0.into_iter() + } + ///Returns a reference to the value corresponding to the key. pub fn get(&self, key: T) -> Option<&GValue> where diff --git a/gremlin-client/tests/integration_traversal_omni.rs b/gremlin-client/tests/integration_traversal_omni.rs index fd6cdd0e..40eedefc 100644 --- a/gremlin-client/tests/integration_traversal_omni.rs +++ b/gremlin-client/tests/integration_traversal_omni.rs @@ -2135,6 +2135,25 @@ fn test_repeat_until_loops_loops(client: GremlinClient) { assert_eq!(results[0], e2[0]); } +#[apply(serializers)] +#[serial(test_simple_path)] +fn test_simple_vertex_property(client: GremlinClient) { + drop_vertices(&client, "test_simple_vertex_property").unwrap(); + + let g = traversal().with_remote(client); + + let v = g + .add_v("test_simple_vertex_property") + .property("name", "a") + .element_map(()) + .next() + .unwrap() + .unwrap(); + + let actual_property: &String = v.get("name").expect("Should have property").get().unwrap(); + assert_eq!(actual_property, "a"); +} + #[apply(serializers)] #[serial(test_simple_path)] fn test_simple_path(client: GremlinClient) { From 6db17500ce7a610923bf0aa563b1562414b16732 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Thu, 7 Nov 2024 16:13:24 -0600 Subject: [PATCH 29/56] Fix passing test_has_with_p_steps --- gremlin-client/src/io/graph_binary_v1.rs | 68 +++++++++++++++++-- .../traversal/anonymous_traversal_source.rs | 6 +- .../src/process/traversal/builder.rs | 6 +- .../src/process/traversal/graph_traversal.rs | 6 +- .../src/process/traversal/step/has.rs | 14 ++-- .../tests/integration_traversal_omni.rs | 23 +++++++ 6 files changed, 100 insertions(+), 23 deletions(-) diff --git a/gremlin-client/src/io/graph_binary_v1.rs b/gremlin-client/src/io/graph_binary_v1.rs index 917bc269..6639b9a1 100644 --- a/gremlin-client/src/io/graph_binary_v1.rs +++ b/gremlin-client/src/io/graph_binary_v1.rs @@ -1,7 +1,6 @@ -use std::{collections::HashMap, convert::TryInto, iter}; +use std::{collections::HashMap, convert::TryInto}; use chrono::{DateTime, TimeZone, Utc}; -use tungstenite::http::request; use uuid::Uuid; use crate::{ @@ -332,10 +331,7 @@ impl GraphBinaryV1Ser for &GValue { //Type code of 0x1e: P buf.push(P); buf.push(VALUE_FLAG); - p.operator().to_be_bytes(buf)?; - //Seems we only support 1 parameter predicates? - GraphBinaryV1Ser::to_be_bytes(1i32, buf)?; - p.value().to_be_bytes(buf)?; + p.to_be_bytes(buf)?; } GValue::Scope(scope) => { //Type code of 0x1f: Scope @@ -353,6 +349,11 @@ impl GraphBinaryV1Ser for &GValue { } } } + GValue::T(t) => { + buf.push(T); + buf.push(VALUE_FLAG); + t.to_be_bytes(buf)?; + } GValue::Bool(bool) => { buf.push(BOOLEAN); buf.push(VALUE_FLAG); @@ -370,6 +371,43 @@ impl GraphBinaryV1Ser for &GValue { } } +impl GraphBinaryV1Ser for &crate::structure::P { + fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { + self.operator().to_be_bytes(buf)?; + match self.value() { + //Singular values have a length of 1 + //But still need to be written fully qualified + scalar @ GValue::Uuid(_) | + scalar @ GValue::Int32(_) | + scalar @ GValue::Int64(_) | + scalar @ GValue::Float(_) | + scalar @ GValue::Double(_) | + scalar @ GValue::String(_) | + scalar @ GValue::Date(_) + => { + GraphBinaryV1Ser::to_be_bytes(1i32, buf)?; + scalar.to_be_bytes(buf)?; + } + //"Collections" need to be unfurled, we don't write the collection but + //instead just its lengths and then the fully qualified form of each element + GValue::List(list) => { + write_usize_as_i32_be_bytes(list.len(), buf)?; + for item in list.iter() { + item.to_be_bytes(buf)?; + } + } + GValue::Set(set) => { + write_usize_as_i32_be_bytes(set.len(), buf)?; + for item in set.iter() { + item.to_be_bytes(buf)?; + } + } + other => unimplemented!("P serialization of {other:?} not implemented"), + } + Ok(()) + } +} + impl GraphBinaryV1Ser for &GKey { fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { match self { @@ -525,7 +563,10 @@ impl GraphBinaryV1Deser for GValue { ))), } } - other => unimplemented!("TODO {other}"), + other => { + let remainder: Vec = bytes.cloned().collect(); + unimplemented!("TODO {other}. Remainder: {remainder:?}"); + } } } } @@ -545,6 +586,18 @@ impl GraphBinaryV1Deser for T { } } +impl GraphBinaryV1Ser for &T { + fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { + let literal = match self { + T::Id => "id", + T::Key => "key", + T::Label => "label", + T::Value => "value", + }; + GValue::String(literal.to_owned()).to_be_bytes(buf) + } +} + fn consume_expected_null_reference_bytes<'a, S: Iterator>( bytes: &mut S, null_reference_descriptor: &str, @@ -905,6 +958,7 @@ impl GraphBinaryV1Deser for Uuid { #[cfg(test)] mod tests { + use std::iter; use chrono::DateTime; use rstest::rstest; use uuid::uuid; diff --git a/gremlin-client/src/process/traversal/anonymous_traversal_source.rs b/gremlin-client/src/process/traversal/anonymous_traversal_source.rs index 8765697a..9a13fcaf 100644 --- a/gremlin-client/src/process/traversal/anonymous_traversal_source.rs +++ b/gremlin-client/src/process/traversal/anonymous_traversal_source.rs @@ -7,8 +7,8 @@ use crate::process::traversal::step::select::SelectStep; use crate::process::traversal::step::until::UntilStep; use crate::process::traversal::step::where_step::WhereStep; use crate::process::traversal::TraversalBuilder; -use crate::structure::{Either2, GIDs, IntoPredicate, Labels, T}; -use crate::GValue; +use crate::structure::{Either2, GIDs, Labels, T}; +use crate::{GValue, ToGValue}; use super::merge_edge::MergeEdgeStep; use super::merge_vertex::MergeVertexStep; @@ -211,7 +211,7 @@ impl AnonymousTraversalSource { pub fn is(&self, val: A) -> TraversalBuilder where - A: IntoPredicate, + A: ToGValue, { self.traversal.clone().is(val) } diff --git a/gremlin-client/src/process/traversal/builder.rs b/gremlin-client/src/process/traversal/builder.rs index 49491aea..b901207b 100644 --- a/gremlin-client/src/process/traversal/builder.rs +++ b/gremlin-client/src/process/traversal/builder.rs @@ -18,7 +18,7 @@ use crate::process::traversal::step::until::UntilStep; use crate::process::traversal::step::where_step::WhereStep; use crate::process::traversal::{Bytecode, Scope}; -use crate::structure::{Cardinality, GIDs, IntoPredicate, Labels}; +use crate::structure::{Cardinality, GIDs, Labels}; use crate::GValue; use super::merge_edge::MergeEdgeStep; @@ -505,10 +505,10 @@ impl TraversalBuilder { pub fn is(mut self, val: A) -> Self where - A: IntoPredicate, + A: ToGValue, { self.bytecode - .add_step(String::from("is"), vec![val.into_predicate().into()]); + .add_step(String::from("is"), vec![val.to_gvalue()]); self } diff --git a/gremlin-client/src/process/traversal/graph_traversal.rs b/gremlin-client/src/process/traversal/graph_traversal.rs index be734946..7cbabdf3 100644 --- a/gremlin-client/src/process/traversal/graph_traversal.rs +++ b/gremlin-client/src/process/traversal/graph_traversal.rs @@ -24,8 +24,8 @@ use crate::process::traversal::strategies::{ use crate::process::traversal::{Bytecode, Scope, TraversalBuilder, WRITE_OPERATORS}; use crate::structure::{Cardinality, Labels, Null}; use crate::{ - structure::GIDs, structure::GProperty, structure::IntoPredicate, Edge, GValue, GremlinClient, - List, Map, Path, Vertex, + structure::GIDs, structure::GProperty, Edge, GValue, GremlinClient, List, Map, Path, ToGValue, + Vertex, }; use std::marker::PhantomData; @@ -537,7 +537,7 @@ impl> GraphTraversal { pub fn is(mut self, val: A) -> Self where - A: IntoPredicate, + A: ToGValue, { self.builder = self.builder.is(val); diff --git a/gremlin-client/src/process/traversal/step/has.rs b/gremlin-client/src/process/traversal/step/has.rs index e250838b..34c8d52b 100644 --- a/gremlin-client/src/process/traversal/step/has.rs +++ b/gremlin-client/src/process/traversal/step/has.rs @@ -1,6 +1,6 @@ use crate::structure::GValue; -use crate::structure::{Either2, TextP, T}; -use crate::structure::{IntoPredicate, P}; +use crate::structure::T; +use crate::ToGValue; pub enum HasStepKey { Str(String), @@ -28,7 +28,7 @@ impl Into for &str { pub struct HasStep { label: Option, key: HasStepKey, - predicate: Option>, + predicate: Option, } impl From for Vec { @@ -55,13 +55,13 @@ impl From for Vec { impl From<(A, B)> for HasStep where A: Into, - B: IntoPredicate, + B: ToGValue, { fn from(param: (A, B)) -> Self { HasStep { label: None, key: param.0.into(), - predicate: Some(param.1.into_predicate()), + predicate: Some(param.1.to_gvalue()), } } } @@ -70,13 +70,13 @@ impl From<(A, B, C)> for HasStep where A: Into, B: Into, - C: IntoPredicate, + C: ToGValue, { fn from(param: (A, B, C)) -> Self { HasStep { label: Some(param.0.into()), key: param.1.into(), - predicate: Some(param.2.into_predicate()), + predicate: Some(param.2.to_gvalue()), } } } diff --git a/gremlin-client/tests/integration_traversal_omni.rs b/gremlin-client/tests/integration_traversal_omni.rs index 40eedefc..00faee91 100644 --- a/gremlin-client/tests/integration_traversal_omni.rs +++ b/gremlin-client/tests/integration_traversal_omni.rs @@ -1677,6 +1677,29 @@ fn test_has_with_p_steps(client: GremlinClient) { assert_eq!(&20, results[0].get::().unwrap()); + let results = g + .v(()) + .has(("test_has_with_p_steps", "age", 20)) + .values("age") + .to_list() + .unwrap(); + + assert_eq!(1, results.len()); + + assert_eq!(&20, results[0].get::().unwrap()); + + let results = g + .v(()) + .has_label("test_has_with_p_steps") + .values("age") + .where_(__.is(P::eq(20))) + .to_list() + .unwrap(); + + assert_eq!(1, results.len()); + + assert_eq!(&20, results[0].get::().unwrap()); + let results = g .v(()) .has_label("test_has_with_p_steps") From e5709085fdfac478cfbb51365e91845b85d8c79a Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Fri, 8 Nov 2024 14:19:53 -0600 Subject: [PATCH 30/56] Made test_add_v_with_properties pass --- gremlin-client/src/io/graph_binary_v1.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/gremlin-client/src/io/graph_binary_v1.rs b/gremlin-client/src/io/graph_binary_v1.rs index 6639b9a1..406ddfdd 100644 --- a/gremlin-client/src/io/graph_binary_v1.rs +++ b/gremlin-client/src/io/graph_binary_v1.rs @@ -658,15 +658,13 @@ impl GraphBinaryV1Deser for VertexProperty { fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { //Format: {id}{label}{value}{parent}{properties} //{id} is a fully qualified typed value composed of {type_code}{type_info}{value_flag}{value}. - let id: GValue = GraphBinaryV1Deser::from_be_bytes_nullable(bytes)? - .ok_or(GremlinError::Cast(format!("Id bytes not present")))?; + let id: GValue = GValue::from_be_bytes(bytes)?; let id: GID = id.try_into()?; //{label} is a String value. let label: String = GraphBinaryV1Deser::from_be_bytes(bytes)?; //{value} is a fully qualified typed value composed of {type_code}{type_info}{value_flag}{value}. - let value: GValue = - GraphBinaryV1Deser::from_be_bytes_nullable(bytes)?.unwrap_or(GValue::Null); + let value: GValue = GValue::from_be_bytes(bytes)?; //{parent} is a fully qualified typed value composed of {type_code}{type_info}{value_flag}{value} which contains the parent Vertex. //Note that as TinkerPop currently send "references" only, this value will always be null. From f2880a0a34e4f523b8015436c23af1072d432f3d Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Fri, 8 Nov 2024 14:36:46 -0600 Subject: [PATCH 31/56] Made test_by_columns pass --- gremlin-client/src/io/graph_binary_v1.rs | 57 +++++++++++++++++------- 1 file changed, 41 insertions(+), 16 deletions(-) diff --git a/gremlin-client/src/io/graph_binary_v1.rs b/gremlin-client/src/io/graph_binary_v1.rs index 406ddfdd..5840cf96 100644 --- a/gremlin-client/src/io/graph_binary_v1.rs +++ b/gremlin-client/src/io/graph_binary_v1.rs @@ -7,8 +7,8 @@ use crate::{ conversion::FromGValue, io::graph_binary_v1, message::{ReponseStatus, Response, ResponseResult}, - process::traversal::Instruction, - structure::{Traverser, T}, + process::traversal::{Instruction, Scope}, + structure::{Column, Traverser, P, T}, Edge, GKey, GValue, GremlinError, GremlinResult, ToGValue, Vertex, VertexProperty, GID, }; @@ -40,6 +40,8 @@ const VERTEX_PROPERTY: u8 = 0x12; // const BARRIER: u8 = 0x13; // const BINDING: u8 = 0x14; const BYTECODE: u8 = 0x15; +// const CARDINALITY: u8 = 0x16; +const COLUMN: u8 = 0x17; //... const P: u8 = 0x1E; const SCOPE: u8 = 0x1F; @@ -217,11 +219,7 @@ impl GraphBinaryV1Ser for &GValue { GraphBinaryV1Ser::to_be_bytes(*value, buf)?; } GValue::String(value) => { - //Type code of 0x03: String - buf.push(STRING); - //Empty value flag - buf.push(VALUE_FLAG); - GraphBinaryV1Ser::to_be_bytes(value.as_str(), buf)?; + write_fully_qualified_str(value, buf)?; } GValue::Date(value) => { buf.push(DATE); @@ -327,6 +325,11 @@ impl GraphBinaryV1Ser for &GValue { write_instructions(code.steps(), buf)?; write_instructions(code.sources(), buf)?; } + GValue::Column(c) => { + buf.push(COLUMN); + buf.push(VALUE_FLAG); + c.to_be_bytes(buf)?; + } GValue::P(p) => { //Type code of 0x1e: P buf.push(P); @@ -339,15 +342,7 @@ impl GraphBinaryV1Ser for &GValue { //Empty value flag buf.push(VALUE_FLAG); - //Format: a fully qualified single String representing the enum value. - match scope { - crate::process::traversal::Scope::Global => { - (&GValue::from(String::from("global"))).to_be_bytes(buf)? - } - crate::process::traversal::Scope::Local => { - (&GValue::from(String::from("local"))).to_be_bytes(buf)? - } - } + scope.to_be_bytes(buf)?; } GValue::T(t) => { buf.push(T); @@ -408,6 +403,36 @@ impl GraphBinaryV1Ser for &crate::structure::P { } } +fn write_fully_qualified_str(value: &str, buf: &mut Vec) -> GremlinResult<()> { + //Extracted from the GValue implementation so it can be used by enum string values + + //Type code of 0x03: String + buf.push(STRING); + //Empty value flag + buf.push(VALUE_FLAG); + value.to_be_bytes(buf) +} + +impl GraphBinaryV1Ser for &Scope { + fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { + //Format: a fully qualified single String representing the enum value. + match self { + Scope::Global => write_fully_qualified_str("global", buf), + Scope::Local => write_fully_qualified_str("local", buf), + } + } +} + +impl GraphBinaryV1Ser for &Column { + fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { + //Format: a fully qualified single String representing the enum value. + match self { + Column::Keys => write_fully_qualified_str("keys", buf), + Column::Values => write_fully_qualified_str("values", buf), + } + } +} + impl GraphBinaryV1Ser for &GKey { fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { match self { From 2492d09e657c6f083467945d137cc24d15bb499d Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Fri, 8 Nov 2024 14:43:43 -0600 Subject: [PATCH 32/56] Made test_select_pop pass --- gremlin-client/src/io/graph_binary_v1.rs | 28 ++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/gremlin-client/src/io/graph_binary_v1.rs b/gremlin-client/src/io/graph_binary_v1.rs index 5840cf96..d5496bcd 100644 --- a/gremlin-client/src/io/graph_binary_v1.rs +++ b/gremlin-client/src/io/graph_binary_v1.rs @@ -8,7 +8,7 @@ use crate::{ io::graph_binary_v1, message::{ReponseStatus, Response, ResponseResult}, process::traversal::{Instruction, Scope}, - structure::{Column, Traverser, P, T}, + structure::{Column, Pop, Traverser, P, T}, Edge, GKey, GValue, GremlinError, GremlinResult, ToGValue, Vertex, VertexProperty, GID, }; @@ -42,7 +42,12 @@ const VERTEX_PROPERTY: u8 = 0x12; const BYTECODE: u8 = 0x15; // const CARDINALITY: u8 = 0x16; const COLUMN: u8 = 0x17; -//... +// const DIRECTION: u8 = 0x18; +// const OPERATOR: u8 = 0x19; +// const ORDER: u8 = 0x1A; +// const PICK: u8 = 0x1B; +const POP: u8 = 0x1C; +// const LAMBDA: u8 = 0x1D; const P: u8 = 0x1E; const SCOPE: u8 = 0x1F; //TODO fill in others @@ -330,6 +335,11 @@ impl GraphBinaryV1Ser for &GValue { buf.push(VALUE_FLAG); c.to_be_bytes(buf)?; } + GValue::Pop(pop) => { + buf.push(POP); + buf.push(VALUE_FLAG); + pop.to_be_bytes(buf)?; + } GValue::P(p) => { //Type code of 0x1e: P buf.push(P); @@ -405,6 +415,8 @@ impl GraphBinaryV1Ser for &crate::structure::P { fn write_fully_qualified_str(value: &str, buf: &mut Vec) -> GremlinResult<()> { //Extracted from the GValue implementation so it can be used by enum string values + //There are times we want the fully qualified prefix bytes outside of the normal + //GValue based route, without having to allocate the string again inside the GValue //Type code of 0x03: String buf.push(STRING); @@ -423,6 +435,18 @@ impl GraphBinaryV1Ser for &Scope { } } +impl GraphBinaryV1Ser for &Pop { + fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { + //Format: a fully qualified single String representing the enum value. + match self { + Pop::All => write_fully_qualified_str("all", buf), + Pop::First => write_fully_qualified_str("first", buf), + Pop::Last => write_fully_qualified_str("last", buf), + Pop::Mixed => write_fully_qualified_str("mixed", buf), + } + } +} + impl GraphBinaryV1Ser for &Column { fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { //Format: a fully qualified single String representing the enum value. From e9a0c6da5bdcdb73b634e8d8ccd86cf5abe5ff61 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Fri, 8 Nov 2024 15:05:43 -0600 Subject: [PATCH 33/56] Implemented string literal GraphBinaryV1Ser --- gremlin-client/src/io/graph_binary_v1.rs | 76 ++++++++++++++++++------ 1 file changed, 58 insertions(+), 18 deletions(-) diff --git a/gremlin-client/src/io/graph_binary_v1.rs b/gremlin-client/src/io/graph_binary_v1.rs index d5496bcd..f8ee1140 100644 --- a/gremlin-client/src/io/graph_binary_v1.rs +++ b/gremlin-client/src/io/graph_binary_v1.rs @@ -4,12 +4,7 @@ use chrono::{DateTime, TimeZone, Utc}; use uuid::Uuid; use crate::{ - conversion::FromGValue, - io::graph_binary_v1, - message::{ReponseStatus, Response, ResponseResult}, - process::traversal::{Instruction, Scope}, - structure::{Column, Pop, Traverser, P, T}, - Edge, GKey, GValue, GremlinError, GremlinResult, ToGValue, Vertex, VertexProperty, GID, + conversion::FromGValue, io::graph_binary_v1, message::{ReponseStatus, Response, ResponseResult}, process::traversal::{Instruction, Order, Scope}, structure::{Column, Direction, Pop, Traverser, P, T}, Cardinality, Edge, GKey, GValue, GremlinError, GremlinResult, ToGValue, Vertex, VertexProperty, GID }; use super::IoProtocol; @@ -40,11 +35,11 @@ const VERTEX_PROPERTY: u8 = 0x12; // const BARRIER: u8 = 0x13; // const BINDING: u8 = 0x14; const BYTECODE: u8 = 0x15; -// const CARDINALITY: u8 = 0x16; +const CARDINALITY: u8 = 0x16; const COLUMN: u8 = 0x17; -// const DIRECTION: u8 = 0x18; +const DIRECTION: u8 = 0x18; // const OPERATOR: u8 = 0x19; -// const ORDER: u8 = 0x1A; +const ORDER: u8 = 0x1A; // const PICK: u8 = 0x1B; const POP: u8 = 0x1C; // const LAMBDA: u8 = 0x1D; @@ -330,10 +325,25 @@ impl GraphBinaryV1Ser for &GValue { write_instructions(code.steps(), buf)?; write_instructions(code.sources(), buf)?; } - GValue::Column(c) => { + GValue::Cardinality(cardinality) => { + buf.push(CARDINALITY); + buf.push(VALUE_FLAG); + cardinality.to_be_bytes(buf)?; + } + GValue::Column(column) => { buf.push(COLUMN); buf.push(VALUE_FLAG); - c.to_be_bytes(buf)?; + column.to_be_bytes(buf)?; + } + GValue::Direction(direction) => { + buf.push(DIRECTION); + buf.push(VALUE_FLAG); + direction.to_be_bytes(buf)?; + } + GValue::Order(order) => { + buf.push(ORDER); + buf.push(VALUE_FLAG); + order.to_be_bytes(buf)?; } GValue::Pop(pop) => { buf.push(POP); @@ -435,6 +445,37 @@ impl GraphBinaryV1Ser for &Scope { } } +impl GraphBinaryV1Ser for &Cardinality { + fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { + match self { + Cardinality::List => write_fully_qualified_str("list", buf), + Cardinality::Set => write_fully_qualified_str("set", buf), + Cardinality::Single => write_fully_qualified_str("single", buf), + } + } +} + +impl GraphBinaryV1Ser for &Direction { + fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { + match self { + Direction::Out => write_fully_qualified_str("out", buf), + Direction::In => write_fully_qualified_str("in", buf), + Direction::From => write_fully_qualified_str("from", buf), + Direction::To => write_fully_qualified_str("to", buf), + } + } +} + +impl GraphBinaryV1Ser for &Order { + fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { + match self { + Order::Asc => write_fully_qualified_str("asc", buf), + Order::Desc => write_fully_qualified_str("desc", buf), + Order::Shuffle => write_fully_qualified_str("shuffle", buf), + } + } +} + impl GraphBinaryV1Ser for &Pop { fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { //Format: a fully qualified single String representing the enum value. @@ -637,13 +678,12 @@ impl GraphBinaryV1Deser for T { impl GraphBinaryV1Ser for &T { fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { - let literal = match self { - T::Id => "id", - T::Key => "key", - T::Label => "label", - T::Value => "value", - }; - GValue::String(literal.to_owned()).to_be_bytes(buf) + match self { + T::Id => write_fully_qualified_str("id", buf), + T::Key => write_fully_qualified_str("key", buf), + T::Label => write_fully_qualified_str("label", buf), + T::Value => write_fully_qualified_str("value", buf), + } } } From baccdd5ef6796d4dd22d8f1f524554c2b865e14b Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Fri, 8 Nov 2024 15:54:19 -0600 Subject: [PATCH 34/56] All omni tests passing except merge and test_has_with_text_p_step --- gremlin-client/src/io/graph_binary_v1.rs | 114 ++++++++++++------ .../tests/integration_traversal_omni.rs | 18 +++ 2 files changed, 94 insertions(+), 38 deletions(-) diff --git a/gremlin-client/src/io/graph_binary_v1.rs b/gremlin-client/src/io/graph_binary_v1.rs index f8ee1140..2e853dab 100644 --- a/gremlin-client/src/io/graph_binary_v1.rs +++ b/gremlin-client/src/io/graph_binary_v1.rs @@ -4,7 +4,13 @@ use chrono::{DateTime, TimeZone, Utc}; use uuid::Uuid; use crate::{ - conversion::FromGValue, io::graph_binary_v1, message::{ReponseStatus, Response, ResponseResult}, process::traversal::{Instruction, Order, Scope}, structure::{Column, Direction, Pop, Traverser, P, T}, Cardinality, Edge, GKey, GValue, GremlinError, GremlinResult, ToGValue, Vertex, VertexProperty, GID + conversion::FromGValue, + io::graph_binary_v1, + message::{ReponseStatus, Response, ResponseResult}, + process::traversal::{Instruction, Order, Scope}, + structure::{Column, Direction, Pop, Set, TextP, Traverser, P, T}, + Cardinality, Edge, GKey, GValue, GremlinError, GremlinResult, Path, ToGValue, Vertex, + VertexProperty, GID, }; use super::IoProtocol; @@ -52,6 +58,7 @@ const T: u8 = 0x20; const TRAVERSER: u8 = 0x21; //... const BOOLEAN: u8 = 0x27; +const TEXTP: u8 = 0x28; //... const UNSPECIFIED_NULL_OBEJECT: u8 = 0xFE; @@ -374,6 +381,12 @@ impl GraphBinaryV1Ser for &GValue { buf.push(VALUE_FLAG); bool.to_be_bytes(buf)?; } + GValue::TextP(text_p) => { + todo!() + // buf.push(BOOLEAN); + // buf.push(VALUE_FLAG); + // text_p.to_be_bytes(buf)?; + } GValue::Null => { //Type code of 0xfe: Unspecified null object buf.push(UNSPECIFIED_NULL_OBEJECT); @@ -386,40 +399,47 @@ impl GraphBinaryV1Ser for &GValue { } } -impl GraphBinaryV1Ser for &crate::structure::P { - fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { - self.operator().to_be_bytes(buf)?; - match self.value() { - //Singular values have a length of 1 - //But still need to be written fully qualified - scalar @ GValue::Uuid(_) | - scalar @ GValue::Int32(_) | - scalar @ GValue::Int64(_) | - scalar @ GValue::Float(_) | - scalar @ GValue::Double(_) | - scalar @ GValue::String(_) | - scalar @ GValue::Date(_) - => { - GraphBinaryV1Ser::to_be_bytes(1i32, buf)?; - scalar.to_be_bytes(buf)?; - } - //"Collections" need to be unfurled, we don't write the collection but - //instead just its lengths and then the fully qualified form of each element - GValue::List(list) => { - write_usize_as_i32_be_bytes(list.len(), buf)?; - for item in list.iter() { - item.to_be_bytes(buf)?; - } +fn predicate_to_be_bytes( + operator: &String, + value: &GValue, + buf: &mut Vec, +) -> GremlinResult<()> { + operator.to_be_bytes(buf)?; + match value { + //Singular values have a length of 1 + //But still need to be written fully qualified + scalar @ GValue::Uuid(_) + | scalar @ GValue::Int32(_) + | scalar @ GValue::Int64(_) + | scalar @ GValue::Float(_) + | scalar @ GValue::Double(_) + | scalar @ GValue::String(_) + | scalar @ GValue::Date(_) => { + GraphBinaryV1Ser::to_be_bytes(1i32, buf)?; + scalar.to_be_bytes(buf)?; + } + //"Collections" need to be unfurled, we don't write the collection but + //instead just its lengths and then the fully qualified form of each element + GValue::List(list) => { + write_usize_as_i32_be_bytes(list.len(), buf)?; + for item in list.iter() { + item.to_be_bytes(buf)?; } - GValue::Set(set) => { - write_usize_as_i32_be_bytes(set.len(), buf)?; - for item in set.iter() { - item.to_be_bytes(buf)?; - } + } + GValue::Set(set) => { + write_usize_as_i32_be_bytes(set.len(), buf)?; + for item in set.iter() { + item.to_be_bytes(buf)?; } - other => unimplemented!("P serialization of {other:?} not implemented"), } - Ok(()) + other => unimplemented!("Predicate serialization of {other:?} not implemented"), + } + Ok(()) +} + +impl GraphBinaryV1Ser for &crate::structure::P { + fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { + predicate_to_be_bytes(&self.operator, &self.value, buf) } } @@ -526,9 +546,10 @@ pub trait GraphBinaryV1Deser: Sized { Some(VALUE_FLAG) => Self::from_be_bytes(bytes).map(Option::Some), Some(VALUE_NULL_FLAG) => Ok(None), other => { + let remainder: Vec = bytes.cloned().collect(); return Err(GremlinError::Cast(format!( - "Unexpected byte for nullable check: {other:?}" - ))) + "Unexpected byte for nullable check: {other:?}. Remainder: {remainder:?}" + ))); } } } @@ -545,6 +566,12 @@ impl GraphBinaryV1Ser for bool { } } +impl GraphBinaryV1Ser for &TextP { + fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { + predicate_to_be_bytes(&self.operator, &self.value, buf) + } +} + impl GraphBinaryV1Deser for bool { fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { match bytes.next() { @@ -619,9 +646,10 @@ impl GraphBinaryV1Deser for GValue { Some(value) => GValue::Edge(value), None => GValue::Null, }), - PATH => { - todo!() - } + PATH => Ok(match Path::from_be_bytes_nullable(bytes)? { + Some(value) => GValue::Path(value), + None => GValue::Null, + }), PROPERTY => { todo!() } @@ -743,6 +771,16 @@ impl GraphBinaryV1Deser for Edge { } } +impl GraphBinaryV1Deser for Path { + fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { + let labels = GValue::from_be_bytes(bytes)?; + let GValue::List(objects) = GValue::from_be_bytes(bytes)? else { + return Err(GremlinError::Cast(format!("Path objects should be a list"))); + }; + Ok(Path::new(labels, objects)) + } +} + impl GraphBinaryV1Deser for VertexProperty { fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { //Format: {id}{label}{value}{parent}{properties} @@ -1045,9 +1083,9 @@ impl GraphBinaryV1Deser for Uuid { #[cfg(test)] mod tests { - use std::iter; use chrono::DateTime; use rstest::rstest; + use std::iter; use uuid::uuid; use super::*; diff --git a/gremlin-client/tests/integration_traversal_omni.rs b/gremlin-client/tests/integration_traversal_omni.rs index 00faee91..be9d93e8 100644 --- a/gremlin-client/tests/integration_traversal_omni.rs +++ b/gremlin-client/tests/integration_traversal_omni.rs @@ -1201,6 +1201,12 @@ fn test_value_map(client: GremlinClient) { Some("test".to_owned()).as_ref(), get_map(&value, "name").unwrap() ); + assert!(results[0].get("id").or(results[0].get(T::Id)).is_some()); + assert!(results[0] + .get("label") + .or(results[0].get(T::Label)) + .is_some()); + assert_eq!(true, results[0].get("name").is_some()); } #[apply(serializers)] @@ -1265,6 +1271,12 @@ fn test_element_map(client: GremlinClient) { get_map_id(&value).unwrap() ); assert_eq!(Some(vertex.label()), get_map_label(&value).unwrap()); + assert_eq!(2, results[0].len()); + assert!(results[0].get("id").or(results[0].get(T::Id)).is_some()); + assert!(results[0] + .get("label") + .or(results[0].get(T::Label)) + .is_some()); let results = g.v(vertex.id()).element_map(()).to_list().unwrap(); let value = &results[0]; @@ -1278,6 +1290,12 @@ fn test_element_map(client: GremlinClient) { Some("test".to_owned()).as_ref(), get_map(&value, "name").unwrap() ); + assert!(results[0].get("id").or(results[0].get(T::Id)).is_some()); + assert!(results[0] + .get("label") + .or(results[0].get(T::Label)) + .is_some()); + assert_eq!(true, results[0].get("name").is_some()); } #[apply(serializers)] From 6626be398edde332bbc4640a6dde31ba42f981c1 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Fri, 8 Nov 2024 16:04:26 -0600 Subject: [PATCH 35/56] test_has_with_text_p_step passing --- gremlin-client/src/io/graph_binary_v1.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/gremlin-client/src/io/graph_binary_v1.rs b/gremlin-client/src/io/graph_binary_v1.rs index 2e853dab..a0c1e501 100644 --- a/gremlin-client/src/io/graph_binary_v1.rs +++ b/gremlin-client/src/io/graph_binary_v1.rs @@ -382,10 +382,9 @@ impl GraphBinaryV1Ser for &GValue { bool.to_be_bytes(buf)?; } GValue::TextP(text_p) => { - todo!() - // buf.push(BOOLEAN); - // buf.push(VALUE_FLAG); - // text_p.to_be_bytes(buf)?; + buf.push(TEXTP); + buf.push(VALUE_FLAG); + text_p.to_be_bytes(buf)?; } GValue::Null => { //Type code of 0xfe: Unspecified null object From d07679e201ce331bd92daa8e803fd36a15e2e760 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Fri, 8 Nov 2024 16:22:55 -0600 Subject: [PATCH 36/56] Modifed test to reflect has clause not needing predicate for equality testing --- .../src/process/traversal/graph_traversal_source.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/gremlin-client/src/process/traversal/graph_traversal_source.rs b/gremlin-client/src/process/traversal/graph_traversal_source.rs index ac3a3199..452b9f8f 100644 --- a/gremlin-client/src/process/traversal/graph_traversal_source.rs +++ b/gremlin-client/src/process/traversal/graph_traversal_source.rs @@ -221,14 +221,11 @@ mod tests { code.add_step(String::from("V"), vec![1.into()]); code.add_step( String::from("has"), - vec![ - String::from("name").into(), - P::new("eq", String::from("marko").into()).into(), - ], + vec![String::from("name").into(), String::from("marko").into()], ); code.add_step( String::from("has"), - vec![String::from("age").into(), P::new("eq", 23.into()).into()], + vec![String::from("age").into(), 23.into()], ); assert_eq!( @@ -246,7 +243,7 @@ mod tests { vec![ String::from("person").into(), String::from("name").into(), - P::new("eq", String::from("marko").into()).into(), + String::from("marko").into(), ], ); @@ -359,7 +356,7 @@ mod tests { code.add_step(String::from("V"), vec![1.into()]); code.add_step(String::from("values"), vec!["age".into()]); - code.add_step(String::from("is"), vec![P::eq(23).into()]); + code.add_step(String::from("is"), vec![23i32.into()]); assert_eq!(&code, g.v(1).values("age").is(23).bytecode()); } From fe2c0dedc0315f86a50b3381120269feced7e010 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Fri, 8 Nov 2024 19:17:26 -0600 Subject: [PATCH 37/56] Fixed merge tests --- gremlin-client/src/io/graph_binary_v1.rs | 138 +++++++++++------- gremlin-client/tests/common.rs | 8 +- .../tests/integration_traversal_omni.rs | 34 +++-- 3 files changed, 115 insertions(+), 65 deletions(-) diff --git a/gremlin-client/src/io/graph_binary_v1.rs b/gremlin-client/src/io/graph_binary_v1.rs index a0c1e501..45c07209 100644 --- a/gremlin-client/src/io/graph_binary_v1.rs +++ b/gremlin-client/src/io/graph_binary_v1.rs @@ -8,7 +8,7 @@ use crate::{ io::graph_binary_v1, message::{ReponseStatus, Response, ResponseResult}, process::traversal::{Instruction, Order, Scope}, - structure::{Column, Direction, Pop, Set, TextP, Traverser, P, T}, + structure::{Column, Direction, Merge, Pop, TextP, Traverser, T}, Cardinality, Edge, GKey, GValue, GremlinError, GremlinResult, Path, ToGValue, Vertex, VertexProperty, GID, }; @@ -34,7 +34,7 @@ const SET: u8 = 0x0B; const UUID: u8 = 0x0C; const EDGE: u8 = 0x0D; const PATH: u8 = 0x0E; -const PROPERTY: u8 = 0x0F; +// const PROPERTY: u8 = 0x0F; // const TINKERGRAPH: u8 = 0x10; const VERTEX: u8 = 0x11; const VERTEX_PROPERTY: u8 = 0x12; @@ -60,6 +60,7 @@ const TRAVERSER: u8 = 0x21; const BOOLEAN: u8 = 0x27; const TEXTP: u8 = 0x28; //... +const MERGE: u8 = 0x2E; const UNSPECIFIED_NULL_OBEJECT: u8 = 0xFE; pub(crate) struct RequestMessage<'a, 'b> { @@ -288,9 +289,6 @@ impl GraphBinaryV1Ser for &GValue { buf.push(VALUE_FLAG); value.to_be_bytes(buf)?; } - GValue::Property(property) => { - unimplemented!("") - } GValue::Vertex(vertex) => { buf.push(VERTEX); buf.push(VALUE_FLAG); @@ -298,9 +296,6 @@ impl GraphBinaryV1Ser for &GValue { vertex.label().to_be_bytes(buf)?; GValue::Null.to_be_bytes(buf)?; } - GValue::VertexProperty(vertex_property) => { - todo!() - } GValue::Bytecode(code) => { //Type code of 0x15: Bytecode buf.push(BYTECODE); @@ -386,13 +381,18 @@ impl GraphBinaryV1Ser for &GValue { buf.push(VALUE_FLAG); text_p.to_be_bytes(buf)?; } + GValue::Merge(merge) => { + buf.push(MERGE); + buf.push(VALUE_FLAG); + merge.to_be_bytes(buf)?; + } GValue::Null => { //Type code of 0xfe: Unspecified null object buf.push(UNSPECIFIED_NULL_OBEJECT); //Then the null {value_flag} set and no sequence of bytes. buf.push(VALUE_NULL_FLAG); } - other => unimplemented!("TODO {other:?}"), + other => unimplemented!("Serializing GValue {other:?}"), } Ok(()) } @@ -454,80 +454,106 @@ fn write_fully_qualified_str(value: &str, buf: &mut Vec) -> GremlinResult<() value.to_be_bytes(buf) } +impl GraphBinaryV1Ser for &Merge { + fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { + let literal = match self { + Merge::OnCreate => "onCreate", + Merge::OnMatch => "onMatch", + Merge::OutV => "outV", + Merge::InV => "inV", + }; + write_fully_qualified_str(literal, buf) + } +} + impl GraphBinaryV1Ser for &Scope { fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { //Format: a fully qualified single String representing the enum value. - match self { - Scope::Global => write_fully_qualified_str("global", buf), - Scope::Local => write_fully_qualified_str("local", buf), - } + let literal = match self { + Scope::Global => "global", + Scope::Local => "local", + }; + write_fully_qualified_str(literal, buf) } } impl GraphBinaryV1Ser for &Cardinality { fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { - match self { - Cardinality::List => write_fully_qualified_str("list", buf), - Cardinality::Set => write_fully_qualified_str("set", buf), - Cardinality::Single => write_fully_qualified_str("single", buf), + let literal = match self { + Cardinality::List => "list", + Cardinality::Set => "set", + Cardinality::Single => "single", + }; + write_fully_qualified_str(literal, buf) + } +} + +impl GraphBinaryV1Deser for Direction { + fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { + match GValue::from_be_bytes(bytes)? { + GValue::String(literal) if literal.eq_ignore_ascii_case("out") => Ok(Direction::Out), + GValue::String(literal) if literal.eq_ignore_ascii_case("in") => Ok(Direction::In), + other => Err(GremlinError::Cast(format!( + "Unexpected direction literal {other:?}" + ))), } } } impl GraphBinaryV1Ser for &Direction { fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { - match self { - Direction::Out => write_fully_qualified_str("out", buf), - Direction::In => write_fully_qualified_str("in", buf), - Direction::From => write_fully_qualified_str("from", buf), - Direction::To => write_fully_qualified_str("to", buf), - } + let literal = match self { + Direction::Out | Direction::From => "OUT", + Direction::In | Direction::To => "IN", + }; + write_fully_qualified_str(literal, buf) } } impl GraphBinaryV1Ser for &Order { fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { - match self { - Order::Asc => write_fully_qualified_str("asc", buf), - Order::Desc => write_fully_qualified_str("desc", buf), - Order::Shuffle => write_fully_qualified_str("shuffle", buf), - } + let literal = match self { + Order::Asc => "asc", + Order::Desc => "desc", + Order::Shuffle => "shuffle", + }; + write_fully_qualified_str(literal, buf) } } impl GraphBinaryV1Ser for &Pop { fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { //Format: a fully qualified single String representing the enum value. - match self { - Pop::All => write_fully_qualified_str("all", buf), - Pop::First => write_fully_qualified_str("first", buf), - Pop::Last => write_fully_qualified_str("last", buf), - Pop::Mixed => write_fully_qualified_str("mixed", buf), - } + let literal = match self { + Pop::All => "all", + Pop::First => "first", + Pop::Last => "last", + Pop::Mixed => "mixed", + }; + write_fully_qualified_str(literal, buf) } } impl GraphBinaryV1Ser for &Column { fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { //Format: a fully qualified single String representing the enum value. - match self { - Column::Keys => write_fully_qualified_str("keys", buf), - Column::Values => write_fully_qualified_str("values", buf), - } + let literal = match self { + Column::Keys => "keys", + Column::Values => "values", + }; + write_fully_qualified_str(literal, buf) } } impl GraphBinaryV1Ser for &GKey { fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { match self { - GKey::T(t) => todo!(), - GKey::String(str) => (&GValue::from(str.clone())).to_be_bytes(buf), - GKey::Token(token) => todo!(), - GKey::Vertex(vertex) => todo!(), - GKey::Edge(edge) => todo!(), - GKey::Direction(direction) => todo!(), - GKey::Int64(_) => todo!(), - GKey::Int32(_) => todo!(), + GKey::T(t) => GValue::T(t.clone()).to_be_bytes(buf), + GKey::String(str) => write_fully_qualified_str(str, buf), + GKey::Direction(direction) => GValue::Direction(direction.clone()).to_be_bytes(buf), + GKey::Int64(i) => GValue::Int64(*i).to_be_bytes(buf), + GKey::Int32(i) => GValue::Int32(*i).to_be_bytes(buf), + other => unimplemented!("Unimplemented GKey serialization requested {other:?}"), } } } @@ -649,9 +675,6 @@ impl GraphBinaryV1Deser for GValue { Some(value) => GValue::Path(value), None => GValue::Null, }), - PROPERTY => { - todo!() - } VERTEX => Ok(match Vertex::from_be_bytes_nullable(bytes)? { Some(value) => GValue::Vertex(value), None => GValue::Null, @@ -660,6 +683,10 @@ impl GraphBinaryV1Deser for GValue { Some(value) => GValue::VertexProperty(value), None => GValue::Null, }), + DIRECTION => Ok(match Direction::from_be_bytes_nullable(bytes)? { + Some(value) => GValue::Direction(value), + None => GValue::Null, + }), T => Ok(match T::from_be_bytes_nullable(bytes)? { Some(value) => GValue::T(value), None => GValue::Null, @@ -705,12 +732,13 @@ impl GraphBinaryV1Deser for T { impl GraphBinaryV1Ser for &T { fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { - match self { - T::Id => write_fully_qualified_str("id", buf), - T::Key => write_fully_qualified_str("key", buf), - T::Label => write_fully_qualified_str("label", buf), - T::Value => write_fully_qualified_str("value", buf), - } + let literal = match self { + T::Id => "id", + T::Key => "key", + T::Label => "label", + T::Value => "value", + }; + write_fully_qualified_str(literal, buf) } } diff --git a/gremlin-client/tests/common.rs b/gremlin-client/tests/common.rs index 01eaa608..099a2c75 100644 --- a/gremlin-client/tests/common.rs +++ b/gremlin-client/tests/common.rs @@ -1,8 +1,14 @@ -use gremlin_client::Map; +use gremlin_client::{structure::T, 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) + .or(match expected_key { + "id" => element_map.get(T::Id), + "key" => element_map.get(T::Key), + "label" => element_map.get(T::Label), + _ => None, + }) .unwrap_or_else(|| panic!("Didn't have expected key {}", expected_key)) .get() .expect("Should be String"); diff --git a/gremlin-client/tests/integration_traversal_omni.rs b/gremlin-client/tests/integration_traversal_omni.rs index be9d93e8..f3bf4fe4 100644 --- a/gremlin-client/tests/integration_traversal_omni.rs +++ b/gremlin-client/tests/integration_traversal_omni.rs @@ -24,6 +24,14 @@ use common::io::{ graph_serializer, }; +//GraphSONV2 doesn't appear to support merge steps, so ommit it from +//being one of the serializers tested for those tests +#[template] +#[rstest] +#[case::graphson_v3(graph_serializer(IoProtocol::GraphSONV3))] +#[case::graph_binary_v1(graph_serializer(IoProtocol::GraphBinaryV1))] +fn merge_capable_serializers(#[case] client: GremlinClient) {} + #[template] #[rstest] #[case::graphson_v2(graph_serializer(IoProtocol::GraphSONV2))] @@ -41,7 +49,7 @@ mod merge_tests { }; use std::collections::HashMap; - #[apply(serializers)] + #[apply(merge_capable_serializers)] #[serial(test_merge_v_no_options)] fn test_merge_v_no_options(client: GremlinClient) { let test_vertex_label = "test_merge_v_no_options"; @@ -76,7 +84,7 @@ mod merge_tests { assert_map_property(&vertex_properties, "propertyKey", "propertyValue"); } - #[apply(serializers)] + #[apply(merge_capable_serializers)] #[serial(test_merge_v_options)] fn test_merge_v_options(client: GremlinClient) { let expected_label = "test_merge_v_options"; @@ -127,7 +135,7 @@ mod merge_tests { assert_map_property(&on_match_vertex_map, prop_key, expected_on_match_prop_value); } - #[apply(serializers)] + #[apply(merge_capable_serializers)] #[serial(test_merge_v_start_step)] fn test_merge_v_start_step(client: GremlinClient) { let expected_label = "test_merge_v_start_step"; @@ -144,7 +152,7 @@ mod merge_tests { assert_eq!(expected_label, actual_vertex.label()) } - #[apply(serializers)] + #[apply(merge_capable_serializers)] #[serial(test_merge_v_anonymous_traversal)] fn test_merge_v_anonymous_traversal(client: GremlinClient) { let expected_label = "test_merge_v_anonymous_traversal"; @@ -162,7 +170,7 @@ mod merge_tests { assert_eq!(expected_label, actual_vertex.label()) } - #[apply(serializers)] + #[apply(merge_capable_serializers)] #[serial(test_merge_e_start_step)] fn test_merge_e_start_step(client: GremlinClient) { let expected_vertex_label = "test_merge_e_start_step_vertex"; @@ -215,6 +223,7 @@ mod merge_tests { let incoming_vertex_id = incoming_vertex .get("id") + .or(incoming_vertex.get(T::Id)) .expect("Should have returned vertex id"); assert_eq!(incoming_vertex_id, &vertex_a.id().to_gvalue()); @@ -225,11 +234,12 @@ mod merge_tests { .unwrap(); let outgoing_vertex_id = outgoing_vertex .get("id") + .or(outgoing_vertex.get(T::Id)) .expect("Should have returned vertex id"); assert_eq!(outgoing_vertex_id, &vertex_b.id().to_gvalue()); } - #[apply(serializers)] + #[apply(merge_capable_serializers)] #[serial(test_merge_e_no_options)] fn test_merge_e_no_options(client: GremlinClient) { let expected_vertex_label = "test_merge_e_no_options_vertex"; @@ -284,6 +294,7 @@ mod merge_tests { .unwrap(); let incoming_vertex_id = incoming_vertex .get("id") + .or(incoming_vertex.get(T::Id)) .expect("Should have returned vertex id"); assert_eq!(incoming_vertex_id, &vertex_a.id().to_gvalue()); @@ -294,11 +305,12 @@ mod merge_tests { .unwrap(); let outgoing_vertex_id = outgoing_vertex .get("id") + .or(outgoing_vertex.get(T::Id)) .expect("Should have returned vertex id"); assert_eq!(outgoing_vertex_id, &vertex_b.id().to_gvalue()); } - #[apply(serializers)] + #[apply(merge_capable_serializers)] #[serial(test_merge_e_options)] fn test_merge_e_options(client: GremlinClient) { let expected_vertex_label = "test_merge_e_options_vertex"; @@ -368,7 +380,7 @@ mod merge_tests { ); } - #[apply(serializers)] + #[apply(merge_capable_serializers)] #[serial(test_merge_e_anonymous_traversal)] fn test_merge_e_anonymous_traversal(client: GremlinClient) { let expected_vertex_label = "test_merge_e_options_vertex"; @@ -409,6 +421,7 @@ mod merge_tests { .unwrap(); let incoming_vertex_id = incoming_vertex .get("id") + .or(incoming_vertex.get(T::Id)) .expect("Should have returned vertex id"); assert_eq!(incoming_vertex_id, &vertex_a.id().to_gvalue()); @@ -419,11 +432,12 @@ mod merge_tests { .unwrap(); let outgoing_vertex_id = outgoing_vertex .get("id") + .or(outgoing_vertex.get(T::Id)) .expect("Should have returned vertex id"); assert_eq!(outgoing_vertex_id, &vertex_b.id().to_gvalue()); } - #[apply(serializers)] + #[apply(merge_capable_serializers)] #[serial(test_merge_v_into_merge_e)] fn test_merge_v_into_merge_e(client: GremlinClient) { //Based on the reference doc's combo example @@ -472,6 +486,7 @@ mod merge_tests { .unwrap(); let brandy_vertex_id = brandy_vertex .get("id") + .or(brandy_vertex.get(T::Id)) .expect("Should have returned vertex id"); assert_eq!(*brandy_vertex_id, GValue::Int64(expected_brandy_id)); @@ -482,6 +497,7 @@ mod merge_tests { .unwrap(); let toby_vertex_id = toby_vertex .get("id") + .or(toby_vertex.get(T::Id)) .expect("Should have returned vertex id"); assert_eq!(*toby_vertex_id, GValue::Int64(expected_toby_id)); From 2083461192eaf88d66392f5d907d528868bb7417 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Fri, 8 Nov 2024 19:23:00 -0600 Subject: [PATCH 38/56] Supplanted integration_traversal tests into single parameterized test --- gremlin-client/tests/integration_traversal.rs | 654 ++-- .../tests/integration_traversal_omni.rs | 2803 ----------------- .../tests/integration_traversal_v2.rs | 1837 ----------- 3 files changed, 386 insertions(+), 4908 deletions(-) delete mode 100644 gremlin-client/tests/integration_traversal_omni.rs delete mode 100644 gremlin-client/tests/integration_traversal_v2.rs diff --git a/gremlin-client/tests/integration_traversal.rs b/gremlin-client/tests/integration_traversal.rs index 78c4725a..f3bf4fe4 100644 --- a/gremlin-client/tests/integration_traversal.rs +++ b/gremlin-client/tests/integration_traversal.rs @@ -1,3 +1,4 @@ +use core::panic; use std::collections::HashMap; use std::convert::TryInto; @@ -7,14 +8,37 @@ use gremlin_client::structure::{ Cardinality, Column, List, Map, Pop, TextP, Vertex, VertexProperty, P, T, }; -use gremlin_client::{utils, GKey, GValue}; +use gremlin_client::{ + utils, BorrowFromGValue, GKey, GValue, GremlinClient, GremlinError, IoProtocol, +}; mod common; +use rstest::rstest; +use rstest_reuse::{self, *}; + +use serial_test::serial; + use common::io::{ - create_edge, create_vertex, create_vertex_with_label, drop_edges, drop_vertices, graph, + create_edge, create_vertex, create_vertex_with_label, drop_edges, drop_vertices, + graph_serializer, }; +//GraphSONV2 doesn't appear to support merge steps, so ommit it from +//being one of the serializers tested for those tests +#[template] +#[rstest] +#[case::graphson_v3(graph_serializer(IoProtocol::GraphSONV3))] +#[case::graph_binary_v1(graph_serializer(IoProtocol::GraphBinaryV1))] +fn merge_capable_serializers(#[case] client: GremlinClient) {} + +#[template] +#[rstest] +#[case::graphson_v2(graph_serializer(IoProtocol::GraphSONV2))] +#[case::graphson_v3(graph_serializer(IoProtocol::GraphSONV3))] +#[case::graph_binary_v1(graph_serializer(IoProtocol::GraphBinaryV1))] +fn serializers(#[case] client: GremlinClient) {} + #[cfg(feature = "merge_tests")] mod merge_tests { use super::*; @@ -25,9 +49,9 @@ mod merge_tests { }; use std::collections::HashMap; - #[test] - fn test_merge_v_no_options() { - let client = graph(); + #[apply(merge_capable_serializers)] + #[serial(test_merge_v_no_options)] + fn test_merge_v_no_options(client: GremlinClient) { let test_vertex_label = "test_merge_v_no_options"; drop_vertices(&client, test_vertex_label) .expect("Failed to drop vertices in case of rerun"); @@ -60,9 +84,9 @@ mod merge_tests { assert_map_property(&vertex_properties, "propertyKey", "propertyValue"); } - #[test] - fn test_merge_v_options() { - let client = graph(); + #[apply(merge_capable_serializers)] + #[serial(test_merge_v_options)] + fn test_merge_v_options(client: GremlinClient) { let expected_label = "test_merge_v_options"; drop_vertices(&client, expected_label).expect("Failed to drop vertices"); let g = traversal().with_remote(client); @@ -111,9 +135,9 @@ mod merge_tests { assert_map_property(&on_match_vertex_map, prop_key, expected_on_match_prop_value); } - #[test] - fn test_merge_v_start_step() { - let client = graph(); + #[apply(merge_capable_serializers)] + #[serial(test_merge_v_start_step)] + fn test_merge_v_start_step(client: GremlinClient) { let expected_label = "test_merge_v_start_step"; drop_vertices(&client, &expected_label).expect("Failed to drop vertiecs"); let g = traversal().with_remote(client); @@ -128,9 +152,9 @@ mod merge_tests { assert_eq!(expected_label, actual_vertex.label()) } - #[test] - fn test_merge_v_anonymous_traversal() { - let client = graph(); + #[apply(merge_capable_serializers)] + #[serial(test_merge_v_anonymous_traversal)] + fn test_merge_v_anonymous_traversal(client: GremlinClient) { let expected_label = "test_merge_v_anonymous_traversal"; drop_vertices(&client, &expected_label).expect("Failed to drop vertiecs"); let g = traversal().with_remote(client); @@ -146,9 +170,9 @@ mod merge_tests { assert_eq!(expected_label, actual_vertex.label()) } - #[test] - fn test_merge_e_start_step() { - let client = graph(); + #[apply(merge_capable_serializers)] + #[serial(test_merge_e_start_step)] + fn test_merge_e_start_step(client: GremlinClient) { 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"; @@ -199,6 +223,7 @@ mod merge_tests { let incoming_vertex_id = incoming_vertex .get("id") + .or(incoming_vertex.get(T::Id)) .expect("Should have returned vertex id"); assert_eq!(incoming_vertex_id, &vertex_a.id().to_gvalue()); @@ -209,13 +234,14 @@ mod merge_tests { .unwrap(); let outgoing_vertex_id = outgoing_vertex .get("id") + .or(outgoing_vertex.get(T::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(); + #[apply(merge_capable_serializers)] + #[serial(test_merge_e_no_options)] + fn test_merge_e_no_options(client: GremlinClient) { 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"; @@ -268,6 +294,7 @@ mod merge_tests { .unwrap(); let incoming_vertex_id = incoming_vertex .get("id") + .or(incoming_vertex.get(T::Id)) .expect("Should have returned vertex id"); assert_eq!(incoming_vertex_id, &vertex_a.id().to_gvalue()); @@ -278,13 +305,14 @@ mod merge_tests { .unwrap(); let outgoing_vertex_id = outgoing_vertex .get("id") + .or(outgoing_vertex.get(T::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(); + #[apply(merge_capable_serializers)] + #[serial(test_merge_e_options)] + fn test_merge_e_options(client: GremlinClient) { 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"; @@ -352,9 +380,9 @@ mod merge_tests { ); } - #[test] - fn test_merge_e_anonymous_traversal() { - let client = graph(); + #[apply(merge_capable_serializers)] + #[serial(test_merge_e_anonymous_traversal)] + fn test_merge_e_anonymous_traversal(client: GremlinClient) { 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"); @@ -393,6 +421,7 @@ mod merge_tests { .unwrap(); let incoming_vertex_id = incoming_vertex .get("id") + .or(incoming_vertex.get(T::Id)) .expect("Should have returned vertex id"); assert_eq!(incoming_vertex_id, &vertex_a.id().to_gvalue()); @@ -403,14 +432,16 @@ mod merge_tests { .unwrap(); let outgoing_vertex_id = outgoing_vertex .get("id") + .or(outgoing_vertex.get(T::Id)) .expect("Should have returned vertex id"); assert_eq!(outgoing_vertex_id, &vertex_b.id().to_gvalue()); } - #[test] - fn test_merge_v_into_merge_e() { + #[apply(merge_capable_serializers)] + #[serial(test_merge_v_into_merge_e)] + fn test_merge_v_into_merge_e(client: GremlinClient) { //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"); @@ -455,6 +486,7 @@ mod merge_tests { .unwrap(); let brandy_vertex_id = brandy_vertex .get("id") + .or(brandy_vertex.get(T::Id)) .expect("Should have returned vertex id"); assert_eq!(*brandy_vertex_id, GValue::Int64(expected_brandy_id)); @@ -465,6 +497,7 @@ mod merge_tests { .unwrap(); let toby_vertex_id = toby_vertex .get("id") + .or(toby_vertex.get(T::Id)) .expect("Should have returned vertex id"); assert_eq!(*toby_vertex_id, GValue::Int64(expected_toby_id)); @@ -472,18 +505,20 @@ mod merge_tests { } } -#[test] -fn test_simple_vertex_traversal() { - let g = traversal().with_remote(graph()); +#[apply(serializers)] +#[serial(test_simple_vertex_traversal)] +fn test_simple_vertex_traversal(client: GremlinClient) { + let g = traversal().with_remote(client); let results = g.v(()).to_list().unwrap(); assert!(results.len() > 0); } -#[test] -fn test_inject() { - let g = traversal().with_remote(graph()); +#[apply(serializers)] +#[serial(test_inject)] +fn test_inject(client: GremlinClient) { + let g = traversal().with_remote(client); let expected_value = "foo"; let response: String = g .inject(vec![expected_value.into()]) @@ -495,10 +530,9 @@ fn test_inject() { assert_eq!(expected_value, response); } -#[test] -fn test_simple_vertex_traversal_with_id() { - let client = graph(); - +#[apply(serializers)] +#[serial(test_simple_vertex_traversal_with_id)] +fn test_simple_vertex_traversal_with_id(client: GremlinClient) { let vertex = create_vertex(&client, "Traversal"); let g = traversal().with_remote(client); @@ -510,9 +544,9 @@ fn test_simple_vertex_traversal_with_id() { assert_eq!(vertex.id(), results[0].id()); } -#[test] -fn test_simple_vertex_traversal_with_multiple_id() { - let client = graph(); +#[apply(serializers)] +#[serial(test_simple_vertex_traversal_with_multiple_id)] +fn test_simple_vertex_traversal_with_multiple_id(client: GremlinClient) { drop_vertices(&client, "test_simple_vertex_traversal").unwrap(); let vertex = create_vertex_with_label(&client, "test_simple_vertex_traversal", "Traversal"); @@ -528,10 +562,9 @@ fn test_simple_vertex_traversal_with_multiple_id() { assert_eq!(vertex2.id(), results[1].id()); } -#[test] -fn test_simple_vertex_traversal_with_label() { - let client = graph(); - +#[apply(serializers)] +#[serial(test_simple_vertex_traversal_with_label)] +fn test_simple_vertex_traversal_with_label(client: GremlinClient) { drop_vertices(&client, "test_simple_vertex_traversal_with_label").unwrap(); let vertex = create_vertex_with_label( @@ -553,10 +586,9 @@ fn test_simple_vertex_traversal_with_label() { assert_eq!(vertex.id(), results[0].id()); } -#[test] -fn test_simple_vertex_traversal_with_label_and_has() { - let client = graph(); - +#[apply(serializers)] +#[serial(test_simple_vertex_traversal_with_label_and_has)] +fn test_simple_vertex_traversal_with_label_and_has(client: GremlinClient) { drop_vertices(&client, "test_simple_vertex_traversal_with_label_and_has").unwrap(); let vertex = create_vertex_with_label( @@ -621,19 +653,19 @@ fn test_simple_vertex_traversal_with_label_and_has() { assert_eq!(vertex.id(), results[0].id()); } -#[test] -fn test_simple_edge_traversal() { - let g = traversal().with_remote(graph()); +#[apply(serializers)] +#[serial(test_simple_edge_traversal)] +fn test_simple_edge_traversal(client: GremlinClient) { + let g = traversal().with_remote(client); let results = g.e(()).to_list().unwrap(); assert!(results.len() > 0); } -#[test] -fn test_simple_edge_traversal_id() { - let client = graph(); - +#[apply(serializers)] +#[serial(test_simple_edge_traversal_id)] +fn test_simple_edge_traversal_id(client: GremlinClient) { let v = create_vertex(&client, "Traversal"); let v1 = create_vertex(&client, "Traversal"); @@ -648,10 +680,9 @@ fn test_simple_edge_traversal_id() { assert_eq!(e.id(), results[0].id()); } -#[test] -fn test_simple_edge_traversal_with_label() { - let client = graph(); - +#[apply(serializers)] +#[serial(test_simple_edge_traversal_with_label)] +fn test_simple_edge_traversal_with_label(client: GremlinClient) { drop_edges(&client, "test_simple_edge_traversal_with_label").unwrap(); let v = create_vertex(&client, "Traversal"); @@ -672,10 +703,9 @@ fn test_simple_edge_traversal_with_label() { assert_eq!(e.id(), results[0].id()); } -#[test] -fn test_traversal() { - let client = graph(); - +#[apply(serializers)] +#[serial(test_traversal)] +fn test_traversal(client: GremlinClient) { drop_edges(&client, "test_vertex_out_traversal").unwrap(); let v = create_vertex(&client, "Traversal"); @@ -815,9 +845,10 @@ fn test_traversal() { assert_eq!(0, results.len()); } -#[test] -fn test_add_v() { - let g = traversal().with_remote(graph()); +#[apply(serializers)] +#[serial(test_add_v)] +fn test_add_v(client: GremlinClient) { + let g = traversal().with_remote(client); let results = g.add_v("person").to_list().unwrap(); @@ -833,9 +864,9 @@ fn test_add_v() { assert_eq!("vertex", results[0].label()); } -#[test] -fn test_add_v_with_properties() { - let client = graph(); +#[apply(serializers)] +#[serial(test_add_v_with_properties)] +fn test_add_v_with_properties(client: GremlinClient) { let g = traversal().with_remote(client.clone()); let results = g @@ -878,10 +909,9 @@ fn test_add_v_with_properties() { ); } -#[test] -fn test_add_v_with_property_many() { - let client = graph(); - +#[apply(serializers)] +#[serial(test_add_v_with_property_many)] +fn test_add_v_with_property_many(client: GremlinClient) { drop_vertices(&client, "test_add_v_with_property_many").unwrap(); let g = traversal().with_remote(client.clone()); @@ -928,10 +958,9 @@ fn test_add_v_with_property_many() { ); } -#[test] -fn test_has_many() { - let client = graph(); - +#[apply(serializers)] +#[serial(test_has_many)] +fn test_has_many(client: GremlinClient) { drop_vertices(&client, "test_has_many").unwrap(); let g = traversal().with_remote(client.clone()); @@ -961,9 +990,9 @@ fn test_has_many() { assert_eq!(results.len(), 1); } -#[test] -fn test_add_e() { - let client = graph(); +#[apply(serializers)] +#[serial(test_add_e)] +fn test_add_e(client: GremlinClient) { let g = traversal().with_remote(client.clone()); let v = g @@ -1024,10 +1053,9 @@ fn test_add_e() { assert_eq!("knows", edges[0].label()); } -#[test] -fn test_label_step() { - let client = graph(); - +#[apply(serializers)] +#[serial(test_label_step)] +fn test_label_step(client: GremlinClient) { let vertex = create_vertex(&client, "Traversal"); let g = traversal().with_remote(client); @@ -1039,10 +1067,9 @@ fn test_label_step() { assert_eq!("person", results[0]); } -#[test] -fn test_properties_step() { - let client = graph(); - +#[apply(serializers)] +#[serial(test_properties_step)] +fn test_properties_step(client: GremlinClient) { let vertex = create_vertex(&client, "Traversal"); let g = traversal().with_remote(client); @@ -1064,10 +1091,9 @@ fn test_properties_step() { assert_eq!(0, results.len()); } -#[test] -fn test_property_map() { - let client = graph(); - +#[apply(serializers)] +#[serial(test_property_map)] +fn test_property_map(client: GremlinClient) { let vertex = create_vertex(&client, "Traversal"); let g = traversal().with_remote(client); @@ -1111,10 +1137,9 @@ fn test_property_map() { assert_eq!(0, properties.len()); } -#[test] -fn test_values() { - let client = graph(); - +#[apply(serializers)] +#[serial(test_values)] +fn test_values(client: GremlinClient) { let vertex = create_vertex(&client, "Traversal"); let g = traversal().with_remote(client); @@ -1140,10 +1165,9 @@ fn test_values() { assert_eq!(0, results.len()); } -#[test] -fn test_value_map() { - let client = graph(); - +#[apply(serializers)] +#[serial(test_value_map)] +fn test_value_map(client: GremlinClient) { let g = traversal().with_remote(client); let vertices = g @@ -1162,9 +1186,7 @@ fn test_value_map() { assert_eq!( "test", - value["name"].get::().unwrap()[0] - .get::() - .unwrap() + get_map::(value, "name").unwrap().unwrap() ); let results = g.v(vertex.id()).value_map("name").to_list().unwrap(); @@ -1175,9 +1197,7 @@ fn test_value_map() { assert_eq!( "test", - value["name"].get::().unwrap()[0] - .get::() - .unwrap() + get_map::(value, "name").unwrap().unwrap() ); let results = g.v(vertex.id()).value_map("fake").to_list().unwrap(); @@ -1185,16 +1205,29 @@ fn test_value_map() { assert_eq!(0, results[0].len()); let results = g.v(vertex.id()).value_map(true).to_list().unwrap(); + assert_eq!(1, results.len()); + let value = &results[0]; - assert_eq!(true, results[0].get("id").is_some()); - assert_eq!(true, results[0].get("label").is_some()); + assert_eq!( + Some(vertex.id().get().unwrap()), + get_map_id(&value).unwrap() + ); + assert_eq!(Some(vertex.label()), get_map_label(&value).unwrap()); + assert_eq!( + Some("test".to_owned()).as_ref(), + get_map(&value, "name").unwrap() + ); + assert!(results[0].get("id").or(results[0].get(T::Id)).is_some()); + assert!(results[0] + .get("label") + .or(results[0].get(T::Label)) + .is_some()); assert_eq!(true, results[0].get("name").is_some()); } -#[test] -fn test_unwrap_map() { - let client = graph(); - +#[apply(serializers)] +#[serial(test_unwrap_map)] +fn test_unwrap_map(client: GremlinClient) { let g = traversal().with_remote(client); let vertices = g @@ -1208,23 +1241,17 @@ fn test_unwrap_map() { let results = g.v(vertex.id()).value_map(true).next().unwrap().unwrap(); let v_id = vertex.id().get::().unwrap(); - let id = utils::unwrap_map::(&results, "id", 0); - let property = utils::unwrap_map::(&results, "name", 0); - let label = utils::unwrap_map::(&results, "label", 0); - - assert_eq!(id.is_ok(), true); - assert_eq!(property.is_ok(), true); - assert_eq!(label.is_ok(), true); - - assert_eq!(id.unwrap(), v_id); + let id = get_map_id(&results).unwrap(); + let property = get_map::(&results, "name").unwrap(); + let label = get_map_label(&results).unwrap(); + assert_eq!(id, Some(v_id)); assert_eq!(property.unwrap(), "test"); - assert_eq!(label.unwrap(), "test_value_map"); + assert_eq!(label, Some(vertex.label())); } -#[test] -fn test_element_map() { - let client = graph(); - +#[apply(serializers)] +#[serial(test_element_map)] +fn test_element_map(client: GremlinClient) { let g = traversal().with_remote(client); let vertices = g @@ -1252,22 +1279,44 @@ fn test_element_map() { assert_eq!("test", value["name"].get::().unwrap()); let results = g.v(vertex.id()).element_map("fake").to_list().unwrap(); + let value = &results[0]; + assert_eq!(2, value.len()); + assert_eq!( + Some(vertex.id().get().unwrap()), + get_map_id(&value).unwrap() + ); + assert_eq!(Some(vertex.label()), get_map_label(&value).unwrap()); assert_eq!(2, results[0].len()); - assert_eq!(true, results[0].get("id").is_some()); - assert_eq!(true, results[0].get("label").is_some()); + assert!(results[0].get("id").or(results[0].get(T::Id)).is_some()); + assert!(results[0] + .get("label") + .or(results[0].get(T::Label)) + .is_some()); let results = g.v(vertex.id()).element_map(()).to_list().unwrap(); + let value = &results[0]; - assert_eq!(true, results[0].get("id").is_some()); - assert_eq!(true, results[0].get("label").is_some()); + assert_eq!( + Some(vertex.id().get().unwrap()), + get_map_id(&value).unwrap() + ); + assert_eq!(Some(vertex.label()), get_map_label(&value).unwrap()); + assert_eq!( + Some("test".to_owned()).as_ref(), + get_map(&value, "name").unwrap() + ); + assert!(results[0].get("id").or(results[0].get(T::Id)).is_some()); + assert!(results[0] + .get("label") + .or(results[0].get(T::Label)) + .is_some()); assert_eq!(true, results[0].get("name").is_some()); } -#[test] -fn test_count() { - let client = graph(); - +#[apply(serializers)] +#[serial(test_count)] +fn test_count(client: GremlinClient) { let vertex = create_vertex_with_label(&client, "test_count", "Count"); let g = traversal().with_remote(client); @@ -1281,10 +1330,9 @@ fn test_count() { assert_eq!(&1, value); } -#[test] -fn test_group_count_step() { - let client = graph(); - +#[apply(serializers)] +#[serial(test_group_count_step)] +fn test_group_count_step(client: GremlinClient) { drop_vertices(&client, "test_group_count").unwrap(); let vertex = create_vertex_with_label(&client, "test_group_count", "Count"); @@ -1295,14 +1343,36 @@ fn test_group_count_step() { .v(()) .has_label("test_group_count") .group_count() - .to_list() + //Normalize the keys to a common type of Id across serializers + .by(T::Id) + .next() .unwrap(); - assert_eq!(1, results.len()); + let value = results.expect("Should have returned a map"); + assert_eq!(1, value.len(), "Should only have 1 entry in map"); + let (actual_key, actual_value) = value.into_iter().next().unwrap(); - let value = &results[0]; + //The actual key may come back as either an int or a string depending + //on the serializer, so normalize it and the vertex gid to a string + let normalized_actual_key = match actual_key { + GKey::String(v) => v, + GKey::Int64(v) => v.to_string(), + other => panic!("Unexpected key type: {:?}", other), + }; + + let normalized_vertex_id = match vertex.id() { + gremlin_client::GID::String(v) => v.clone(), + gremlin_client::GID::Int32(v) => v.to_string(), + gremlin_client::GID::Int64(v) => v.to_string(), + }; - assert_eq!(&1, value[&vertex].get::().unwrap()); + assert_eq!(normalized_actual_key, normalized_vertex_id); + + assert_eq!( + actual_value, + GValue::Int64(1), + "Group count should have been the single vertex" + ); let results = g .v(()) @@ -1333,10 +1403,9 @@ fn test_group_count_step() { assert_eq!(&1, value["test_group_count"].get::().unwrap()); } -#[test] -fn test_group_by_step() { - let client = graph(); - +#[apply(serializers)] +#[serial(test_group_by_step)] +fn test_group_by_step(client: GremlinClient) { drop_vertices(&client, "test_group_by_step").unwrap(); create_vertex_with_label(&client, "test_group_by_step", "Count"); @@ -1389,10 +1458,9 @@ fn test_group_by_step() { assert_eq!(&1, value["test_group_by_step"].get::().unwrap()); } -#[test] -fn test_select_step() { - let client = graph(); - +#[apply(serializers)] +#[serial(test_select_step)] +fn test_select_step(client: GremlinClient) { drop_vertices(&client, "test_select_step").unwrap(); create_vertex_with_label(&client, "test_select_step", "Count"); @@ -1415,10 +1483,9 @@ fn test_select_step() { assert_eq!(&1, value.get::().unwrap()); } -#[test] -fn test_fold_step() { - let client = graph(); - +#[apply(serializers)] +#[serial(test_fold_step)] +fn test_fold_step(client: GremlinClient) { drop_vertices(&client, "test_fold_step").unwrap(); create_vertex_with_label(&client, "test_fold_step", "Count"); @@ -1440,10 +1507,9 @@ fn test_fold_step() { assert_eq!("Count", value[0].get::().unwrap()); } -#[test] -fn test_unfold_step() { - let client = graph(); - +#[apply(serializers)] +#[serial(test_unfold_step)] +fn test_unfold_step(client: GremlinClient) { drop_vertices(&client, "test_unfold_step").unwrap(); let vertex = create_vertex_with_label(&client, "test_unfold_step", "Count"); @@ -1471,10 +1537,9 @@ fn test_unfold_step() { ); } -#[test] -fn test_path_step() { - let client = graph(); - +#[apply(serializers)] +#[serial(test_path_step)] +fn test_path_step(client: GremlinClient) { drop_vertices(&client, "test_path_step").unwrap(); let v = create_vertex_with_label(&client, "test_path_step", "Count"); @@ -1495,10 +1560,9 @@ fn test_path_step() { assert_eq!(v.id(), value.objects()[0].get::().unwrap().id()); } -#[test] -fn test_limit_step() { - let client = graph(); - +#[apply(serializers)] +#[serial(test_limit_step)] +fn test_limit_step(client: GremlinClient) { drop_vertices(&client, "test_limit_step").unwrap(); create_vertex_with_label(&client, "test_limit_step", "Count"); @@ -1516,10 +1580,9 @@ fn test_limit_step() { assert_eq!(1, results.len()); } -#[test] -fn test_dedup_step() { - let client = graph(); - +#[apply(serializers)] +#[serial(test_dedup_step)] +fn test_dedup_step(client: GremlinClient) { drop_vertices(&client, "test_limit_step").unwrap(); create_vertex_with_label(&client, "test_limit_step", "Count"); @@ -1538,10 +1601,9 @@ fn test_dedup_step() { assert_eq!(1, results.len()); } -#[test] -fn test_numerical_steps() { - let client = graph(); - +#[apply(serializers)] +#[serial(test_numerical_steps)] +fn test_numerical_steps(client: GremlinClient) { drop_vertices(&client, "test_numerical_steps").unwrap(); let g = traversal().with_remote(client); @@ -1610,10 +1672,9 @@ fn test_numerical_steps() { assert_eq!(&20, results[0].get::().unwrap()); } -#[test] -fn test_has_with_p_steps() { - let client = graph(); - +#[apply(serializers)] +#[serial(test_has_with_p_steps)] +fn test_has_with_p_steps(client: GremlinClient) { drop_vertices(&client, "test_has_with_p_steps").unwrap(); let g = traversal().with_remote(client); @@ -1650,6 +1711,29 @@ fn test_has_with_p_steps() { assert_eq!(&20, results[0].get::().unwrap()); + let results = g + .v(()) + .has(("test_has_with_p_steps", "age", 20)) + .values("age") + .to_list() + .unwrap(); + + assert_eq!(1, results.len()); + + assert_eq!(&20, results[0].get::().unwrap()); + + let results = g + .v(()) + .has_label("test_has_with_p_steps") + .values("age") + .where_(__.is(P::eq(20))) + .to_list() + .unwrap(); + + assert_eq!(1, results.len()); + + assert_eq!(&20, results[0].get::().unwrap()); + let results = g .v(()) .has_label("test_has_with_p_steps") @@ -1663,10 +1747,9 @@ fn test_has_with_p_steps() { assert_eq!(&20, results[0].get::().unwrap()); } -#[test] -fn test_has_with_text_p_step() { - let client = graph(); - +#[apply(serializers)] +#[serial(test_has_with_text_p_step)] +fn test_has_with_text_p_step(client: GremlinClient) { drop_vertices(&client, "test_has_with_text_p_step").unwrap(); let g = traversal().with_remote(client); @@ -1730,10 +1813,9 @@ fn test_has_with_text_p_step() { assert_eq!(2, results.len()); } -#[test] -fn where_step_test() { - let client = graph(); - +#[apply(serializers)] +#[serial(where_step_test)] +fn where_step_test(client: GremlinClient) { drop_vertices(&client, "where_step_test").unwrap(); let g = traversal().with_remote(client); @@ -1756,10 +1838,9 @@ fn where_step_test() { assert_eq!(v[0].id(), results[0].id()); } -#[test] -fn not_step_test() { - let client = graph(); - +#[apply(serializers)] +#[serial(not_step_test)] +fn not_step_test(client: GremlinClient) { drop_vertices(&client, "not_step_test").unwrap(); let g = traversal().with_remote(client); @@ -1779,10 +1860,9 @@ fn not_step_test() { assert_eq!(0, results.len()); } -#[test] -fn order_step_test() { - let client = graph(); - +#[apply(serializers)] +#[serial(order_step_test)] +fn order_step_test(client: GremlinClient) { drop_vertices(&client, "order_step_test").unwrap(); let g = traversal().with_remote(client); @@ -1823,10 +1903,9 @@ fn order_step_test() { assert_eq!("b", results[0].get::().unwrap()); } -#[test] -fn match_step_test() { - let client = graph(); - +#[apply(serializers)] +#[serial(match_step_test)] +fn match_step_test(client: GremlinClient) { drop_vertices(&client, "match_step_test").unwrap(); drop_edges(&client, "match_step_test_edge").unwrap(); @@ -1885,10 +1964,9 @@ fn match_step_test() { assert_eq!(&v3[0], first["c"].get::().unwrap()); } -#[test] -fn drop_step_test() { - let client = graph(); - +#[apply(serializers)] +#[serial(drop_step_test)] +fn drop_step_test(client: GremlinClient) { drop_vertices(&client, "drop_step_test").unwrap(); let g = traversal().with_remote(client); @@ -1918,10 +1996,9 @@ fn drop_step_test() { assert_eq!(false, results); } -#[test] -fn or_step_test() { - let client = graph(); - +#[apply(serializers)] +#[serial(or_step_test)] +fn or_step_test(client: GremlinClient) { drop_vertices(&client, "or_step_test").unwrap(); let g = traversal().with_remote(client); @@ -1959,10 +2036,9 @@ fn or_step_test() { assert_eq!(result.len(), 2); } -#[test] -fn iter_terminator_test() { - let client = graph(); - +#[apply(serializers)] +#[serial(iter_terminator_test)] +fn iter_terminator_test(client: GremlinClient) { drop_vertices(&client, "iter_terminator_test").unwrap(); let g = traversal().with_remote(client); @@ -1988,10 +2064,9 @@ fn iter_terminator_test() { assert_eq!(2, results.len()) } -#[test] -fn test_select_pop() { - let client = graph(); - +#[apply(serializers)] +#[serial(test_select_pop)] +fn test_select_pop(client: GremlinClient) { drop_vertices(&client, "test_select_pop").unwrap(); drop_vertices(&client, "test_select_pop_child").unwrap(); @@ -2077,10 +2152,9 @@ fn test_select_pop() { assert_eq!(results.len(), 1); } -#[test] -fn test_repeat_until_loops_loops() { - let client = graph(); - +#[apply(serializers)] +#[serial(test_repeat_until_loops_loops)] +fn test_repeat_until_loops_loops(client: GremlinClient) { drop_vertices(&client, "test_repeat_until_loops").unwrap(); drop_vertices(&client, "test_repeat_until_loops_child").unwrap(); @@ -2118,10 +2192,28 @@ fn test_repeat_until_loops_loops() { assert_eq!(results[0], e2[0]); } -#[test] -fn test_simple_path() { - let client = graph(); +#[apply(serializers)] +#[serial(test_simple_path)] +fn test_simple_vertex_property(client: GremlinClient) { + drop_vertices(&client, "test_simple_vertex_property").unwrap(); + + let g = traversal().with_remote(client); + + let v = g + .add_v("test_simple_vertex_property") + .property("name", "a") + .element_map(()) + .next() + .unwrap() + .unwrap(); + + let actual_property: &String = v.get("name").expect("Should have property").get().unwrap(); + assert_eq!(actual_property, "a"); +} +#[apply(serializers)] +#[serial(test_simple_path)] +fn test_simple_path(client: GremlinClient) { drop_vertices(&client, "test_simple_path").unwrap(); drop_vertices(&client, "test_simple_path_child").unwrap(); @@ -2160,10 +2252,9 @@ fn test_simple_path() { assert_eq!(results[0], e2[0]); } -#[test] -fn test_sample() { - let client = graph(); - +#[apply(serializers)] +#[serial(test_sample)] +fn test_sample(client: GremlinClient) { drop_vertices(&client, "test_sample").unwrap(); drop_vertices(&client, "test_sample_child").unwrap(); @@ -2193,10 +2284,9 @@ fn test_sample() { assert_eq!(results.len(), 1); } -#[test] -fn test_local() { - let client = graph(); - +#[apply(serializers)] +#[serial(test_local)] +fn test_local(client: GremlinClient) { drop_vertices(&client, "test_local").unwrap(); drop_vertices(&client, "test_local_child").unwrap(); drop_vertices(&client, "test_local_child_child").unwrap(); @@ -2269,9 +2359,9 @@ fn test_local() { assert_eq!(results.len(), 2); } -#[test] -fn test_side_effect() { - let client = graph(); +#[apply(serializers)] +#[serial(test_side_effect)] +fn test_side_effect(client: GremlinClient) { let test_vertex_label = "test_side_effect"; let expected_side_effect_key = "prop_key"; let expected_side_effect_value = "prop_val"; @@ -2298,9 +2388,9 @@ fn test_side_effect() { ); } -#[test] -fn test_anonymous_traversal_properties_drop() { - let client = graph(); +#[apply(serializers)] +#[serial(test_anonymous_traversal_properties_drop)] +fn test_anonymous_traversal_properties_drop(client: GremlinClient) { let test_vertex_label = "test_anonymous_traversal_properties_drop"; let pre_drop_prop_key = "pre_drop_prop_key"; let expected_prop_value = "prop_val"; @@ -2323,7 +2413,10 @@ fn test_anonymous_traversal_properties_drop() { //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 created_vertex_id = element_map + .get("id") + .or(element_map.get(T::Id)) + .expect("Should have id property"); let GValue::Int64(id) = created_vertex_id else { panic!("Not expected id type"); }; @@ -2353,9 +2446,9 @@ fn test_anonymous_traversal_properties_drop() { ); } -#[test] -fn test_by_columns() { - let client = graph(); +#[apply(serializers)] +#[serial(test_by_columns)] +fn test_by_columns(client: GremlinClient) { 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"; @@ -2409,10 +2502,9 @@ fn test_by_columns() { ); } -#[test] -fn test_property_cardinality() { - let client = graph(); - +#[apply(serializers)] +#[serial(test_property_cardinality)] +fn test_property_cardinality(client: GremlinClient) { drop_vertices(&client, "test_property_cardinality").unwrap(); let g = traversal().with_remote(client); @@ -2440,10 +2532,9 @@ fn test_property_cardinality() { assert_eq!(1, new_v["name"].get::().unwrap().len()); } -#[test] -fn test_choose() { - let client = graph(); - +#[apply(serializers)] +#[serial(test_choose)] +fn test_choose(client: GremlinClient) { drop_vertices(&client, "test_choose").unwrap(); let g = traversal().with_remote(client); @@ -2482,9 +2573,9 @@ fn test_choose() { assert_eq!(success_vertices.is_some(), true); } -#[test] -fn test_choose_by_literal_options() { - let client = graph(); +#[apply(serializers)] +#[serial(test_choose_by_literal_options)] +fn test_choose_by_literal_options(client: GremlinClient) { let g = traversal().with_remote(client); let choosen_literal_a = g @@ -2510,10 +2601,9 @@ fn test_choose_by_literal_options() { assert_eq!(choosen_literal_b, Some("option-b".into())); } -#[test] -fn test_coalesce() { - let client = graph(); - +#[apply(serializers)] +#[serial()] +fn test_coalesce(client: GremlinClient) { use gremlin_client::GValue; drop_vertices(&client, "test_coalesce").unwrap(); @@ -2546,10 +2636,9 @@ fn test_coalesce() { assert!(values.contains(&String::from("b"))); } -#[test] -fn test_coalesce_unfold() { - let client = graph(); - +#[apply(serializers)] +#[serial(test_coalesce_unfold)] +fn test_coalesce_unfold(client: GremlinClient) { drop_vertices(&client, "test_coalesce_unfold").unwrap(); let g = traversal().with_remote(client); @@ -2603,10 +2692,9 @@ fn test_coalesce_unfold() { ); } -#[test] -fn test_none_step() { - let client = graph(); - +#[apply(serializers)] +#[serial(test_none_step)] +fn test_none_step(client: GremlinClient) { drop_vertices(&client, "test_none_step").unwrap(); let g = traversal().with_remote(client); @@ -2631,15 +2719,45 @@ fn test_none_step() { assert_eq!(1, vertex_count); } -#[test] +fn get_map_id<'a>(map: &'a Map) -> Result, GremlinError> { + let string_keyed = get_map(map, "id")?; + if string_keyed.is_some() { + Ok(string_keyed) + } else { + get_map(map, T::Id) + } +} + +fn get_map_label<'a>(map: &'a Map) -> Result, GremlinError> { + let string_keyed = get_map(map, "label")?; + if string_keyed.is_some() { + Ok(string_keyed) + } else { + get_map(map, T::Label) + } +} + +fn get_map<'a, T, K>(map: &'a Map, key: K) -> Result, GremlinError> +where + T: BorrowFromGValue, + K: Into, +{ + map.get(key) + .map(|val| match val { + GValue::List(list) => list[0].get::(), + other => other.get::(), + }) + .transpose() +} + +#[apply(serializers)] +#[serial(test_traversal_vertex_mapping)] #[cfg(feature = "derive")] -fn test_traversal_vertex_mapping() { +fn test_traversal_vertex_mapping(client: GremlinClient) { use chrono::{DateTime, TimeZone, Utc}; use gremlin_client::derive::FromGMap; use std::convert::TryFrom; - let client = graph(); - drop_vertices(&client, "test_vertex_mapping").unwrap(); let g = traversal().with_remote(client); diff --git a/gremlin-client/tests/integration_traversal_omni.rs b/gremlin-client/tests/integration_traversal_omni.rs deleted file mode 100644 index f3bf4fe4..00000000 --- a/gremlin-client/tests/integration_traversal_omni.rs +++ /dev/null @@ -1,2803 +0,0 @@ -use core::panic; -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, Column, List, Map, Pop, TextP, Vertex, VertexProperty, P, T, -}; - -use gremlin_client::{ - utils, BorrowFromGValue, GKey, GValue, GremlinClient, GremlinError, IoProtocol, -}; - -mod common; - -use rstest::rstest; -use rstest_reuse::{self, *}; - -use serial_test::serial; - -use common::io::{ - create_edge, create_vertex, create_vertex_with_label, drop_edges, drop_vertices, - graph_serializer, -}; - -//GraphSONV2 doesn't appear to support merge steps, so ommit it from -//being one of the serializers tested for those tests -#[template] -#[rstest] -#[case::graphson_v3(graph_serializer(IoProtocol::GraphSONV3))] -#[case::graph_binary_v1(graph_serializer(IoProtocol::GraphBinaryV1))] -fn merge_capable_serializers(#[case] client: GremlinClient) {} - -#[template] -#[rstest] -#[case::graphson_v2(graph_serializer(IoProtocol::GraphSONV2))] -#[case::graphson_v3(graph_serializer(IoProtocol::GraphSONV3))] -#[case::graph_binary_v1(graph_serializer(IoProtocol::GraphBinaryV1))] -fn serializers(#[case] client: GremlinClient) {} - -#[cfg(feature = "merge_tests")] -mod merge_tests { - use super::*; - use gremlin_client::{ - process::traversal::{GraphTraversalSource, SyncTerminator}, - structure::{Direction, Merge}, - Edge, GValue, ToGValue, - }; - use std::collections::HashMap; - - #[apply(merge_capable_serializers)] - #[serial(test_merge_v_no_options)] - fn test_merge_v_no_options(client: GremlinClient) { - 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 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()); - injection_map.insert("lookup".into(), lookup_map.into()); - injection_map.insert("properties".into(), property_map.into()); - - let vertex_properties = g - .inject(vec![injection_map.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"); - - assert_map_property(&vertex_properties, "propertyKey", "propertyValue"); - } - - #[apply(merge_capable_serializers)] - #[serial(test_merge_v_options)] - fn test_merge_v_options(client: GremlinClient) { - 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()); - - 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"); - - assert_map_property(&on_create_vertex_map, "label", expected_label); - - 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 - .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"); - - assert_map_property(&on_match_vertex_map, "label", expected_label); - - assert_map_property(&on_match_vertex_map, prop_key, expected_on_match_prop_value); - } - - #[apply(merge_capable_serializers)] - #[serial(test_merge_v_start_step)] - fn test_merge_v_start_step(client: GremlinClient) { - 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::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"); - - assert_eq!(expected_label, actual_vertex.label()) - } - - #[apply(merge_capable_serializers)] - #[serial(test_merge_v_anonymous_traversal)] - fn test_merge_v_anonymous_traversal(client: GremlinClient) { - 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()) - } - - #[apply(merge_capable_serializers)] - #[serial(test_merge_e_start_step)] - fn test_merge_e_start_step(client: GremlinClient) { - 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"); - - 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) - .expect("Should have returned incoming vertex info") - .get() - .unwrap(); - - let incoming_vertex_id = incoming_vertex - .get("id") - .or(incoming_vertex.get(T::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") - .or(outgoing_vertex.get(T::Id)) - .expect("Should have returned vertex id"); - assert_eq!(outgoing_vertex_id, &vertex_b.id().to_gvalue()); - } - - #[apply(merge_capable_serializers)] - #[serial(test_merge_e_no_options)] - fn test_merge_e_no_options(client: GremlinClient) { - 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"); - - 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) - .expect("Should have returned incoming vertex info") - .get() - .unwrap(); - let incoming_vertex_id = incoming_vertex - .get("id") - .or(incoming_vertex.get(T::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") - .or(outgoing_vertex.get(T::Id)) - .expect("Should have returned vertex id"); - assert_eq!(outgoing_vertex_id, &vertex_b.id().to_gvalue()); - } - - #[apply(merge_capable_serializers)] - #[serial(test_merge_e_options)] - fn test_merge_e_options(client: GremlinClient) { - 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 - assert_map_property( - &on_create_edge_properties, - expected_edge_property_key, - "on_create", - ); - - let on_match_edge_properties = do_merge_edge(g); - assert_map_property( - &on_match_edge_properties, - expected_edge_property_key, - "on_match", - ); - } - - #[apply(merge_capable_serializers)] - #[serial(test_merge_e_anonymous_traversal)] - fn test_merge_e_anonymous_traversal(client: GremlinClient) { - 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") - .or(incoming_vertex.get(T::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") - .or(outgoing_vertex.get(T::Id)) - .expect("Should have returned vertex id"); - assert_eq!(outgoing_vertex_id, &vertex_b.id().to_gvalue()); - } - - #[apply(merge_capable_serializers)] - #[serial(test_merge_v_into_merge_e)] - fn test_merge_v_into_merge_e(client: GremlinClient) { - //Based on the reference doc's combo example - - 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") - .or(brandy_vertex.get(T::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") - .or(toby_vertex.get(T::Id)) - .expect("Should have returned vertex id"); - assert_eq!(*toby_vertex_id, GValue::Int64(expected_toby_id)); - - assert_map_property(&combo_merge_edge_properties, "label", expected_edge_label); - } -} - -#[apply(serializers)] -#[serial(test_simple_vertex_traversal)] -fn test_simple_vertex_traversal(client: GremlinClient) { - let g = traversal().with_remote(client); - - let results = g.v(()).to_list().unwrap(); - - assert!(results.len() > 0); -} - -#[apply(serializers)] -#[serial(test_inject)] -fn test_inject(client: GremlinClient) { - let g = traversal().with_remote(client); - 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); -} - -#[apply(serializers)] -#[serial(test_simple_vertex_traversal_with_id)] -fn test_simple_vertex_traversal_with_id(client: GremlinClient) { - let vertex = create_vertex(&client, "Traversal"); - - let g = traversal().with_remote(client); - - let results = g.v(vertex.id()).to_list().unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(vertex.id(), results[0].id()); -} - -#[apply(serializers)] -#[serial(test_simple_vertex_traversal_with_multiple_id)] -fn test_simple_vertex_traversal_with_multiple_id(client: GremlinClient) { - drop_vertices(&client, "test_simple_vertex_traversal").unwrap(); - - let vertex = create_vertex_with_label(&client, "test_simple_vertex_traversal", "Traversal"); - let vertex2 = create_vertex_with_label(&client, "test_simple_vertex_traversal", "Traversal"); - - let g = traversal().with_remote(client); - - let results = g.v(vec![vertex.id(), vertex2.id()]).to_list().unwrap(); - - assert_eq!(2, results.len()); - - assert_eq!(vertex.id(), results[0].id()); - assert_eq!(vertex2.id(), results[1].id()); -} - -#[apply(serializers)] -#[serial(test_simple_vertex_traversal_with_label)] -fn test_simple_vertex_traversal_with_label(client: GremlinClient) { - drop_vertices(&client, "test_simple_vertex_traversal_with_label").unwrap(); - - let vertex = create_vertex_with_label( - &client, - "test_simple_vertex_traversal_with_label", - "Traversal", - ); - - let g = traversal().with_remote(client); - - let results = g - .v(()) - .has_label("test_simple_vertex_traversal_with_label") - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(vertex.id(), results[0].id()); -} - -#[apply(serializers)] -#[serial(test_simple_vertex_traversal_with_label_and_has)] -fn test_simple_vertex_traversal_with_label_and_has(client: GremlinClient) { - drop_vertices(&client, "test_simple_vertex_traversal_with_label_and_has").unwrap(); - - let vertex = create_vertex_with_label( - &client, - "test_simple_vertex_traversal_with_label_and_has", - "Traversal", - ); - - let g = traversal().with_remote(client); - - let results = g - .v(()) - .has_label("test_simple_vertex_traversal_with_label_and_has") - .has(("name", "Traversal")) - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(vertex.id(), results[0].id()); - - // with 3 params - - let results = g - .v(()) - .has(( - "test_simple_vertex_traversal_with_label_and_has", - "name", - "Traversal", - )) - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(vertex.id(), results[0].id()); - - // with 1 param - - let results = g - .v(()) - .has_label("test_simple_vertex_traversal_with_label_and_has") - .has("name") - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(vertex.id(), results[0].id()); - - // hasNot - - let results = g - .v(()) - .has_label("test_simple_vertex_traversal_with_label_and_has") - .has_not("surname") - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(vertex.id(), results[0].id()); -} - -#[apply(serializers)] -#[serial(test_simple_edge_traversal)] -fn test_simple_edge_traversal(client: GremlinClient) { - let g = traversal().with_remote(client); - - let results = g.e(()).to_list().unwrap(); - - assert!(results.len() > 0); -} - -#[apply(serializers)] -#[serial(test_simple_edge_traversal_id)] -fn test_simple_edge_traversal_id(client: GremlinClient) { - let v = create_vertex(&client, "Traversal"); - let v1 = create_vertex(&client, "Traversal"); - - let e = create_edge(&client, &v, &v1, "TraversalEdge"); - - let g = traversal().with_remote(client); - - let results = g.e(e.id()).to_list().unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(e.id(), results[0].id()); -} - -#[apply(serializers)] -#[serial(test_simple_edge_traversal_with_label)] -fn test_simple_edge_traversal_with_label(client: GremlinClient) { - drop_edges(&client, "test_simple_edge_traversal_with_label").unwrap(); - - let v = create_vertex(&client, "Traversal"); - let v1 = create_vertex(&client, "Traversal"); - - let e = create_edge(&client, &v, &v1, "test_simple_edge_traversal_with_label"); - - let g = traversal().with_remote(client); - - let results = g - .e(()) - .has_label("test_simple_edge_traversal_with_label") - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(e.id(), results[0].id()); -} - -#[apply(serializers)] -#[serial(test_traversal)] -fn test_traversal(client: GremlinClient) { - drop_edges(&client, "test_vertex_out_traversal").unwrap(); - - let v = create_vertex(&client, "Traversal"); - let v1 = create_vertex(&client, "Traversal"); - let v2 = create_vertex(&client, "Traversal"); - - let _e = create_edge(&client, &v, &v1, "test_vertex_out_traversal"); - let _e2 = create_edge(&client, &v2, &v, "test_vertex_out_traversal"); - - let g = traversal().with_remote(client); - - // OUT - let results = g - .v(v.id()) - .out("test_vertex_out_traversal") - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(v1.id(), results[0].id()); - - let results = g.v(v.id()).out("fake").to_list().unwrap(); - - assert_eq!(0, results.len()); - - // OUT_E - - let results = g - .v(v.id()) - .out_e("test_vertex_out_traversal") - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!("test_vertex_out_traversal", results[0].label()); - - assert_eq!(v.id(), results[0].out_v().id()); - assert_eq!(v1.id(), results[0].in_v().id()); - - // OUT_E -> IN_V - let results = g - .v(v.id()) - .out_e("test_vertex_out_traversal") - .in_v() - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(v1.id(), results[0].id()); - - let results = g.v(v.id()).out("fake").to_list().unwrap(); - - assert_eq!(0, results.len()); - - // IN - let results = g - .v(v1.id()) - .in_("test_vertex_out_traversal") - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(v.id(), results[0].id()); - - let results = g.v(v1.id()).in_("fake").to_list().unwrap(); - - assert_eq!(0, results.len()); - - // IN_E - - let results = g - .v(v1.id()) - .in_e("test_vertex_out_traversal") - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!("test_vertex_out_traversal", results[0].label()); - - assert_eq!(v.id(), results[0].out_v().id()); - assert_eq!(v1.id(), results[0].in_v().id()); - - // IN_E -> OUT_V - let results = g - .v(v1.id()) - .in_e("test_vertex_out_traversal") - .out_v() - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(v.id(), results[0].id()); - - let results = g.v(v1.id()).in_("fake").to_list().unwrap(); - - assert_eq!(0, results.len()); - - // BOTH - - let results = g - .v(v.id()) - .both("test_vertex_out_traversal") - .to_list() - .unwrap(); - - assert_eq!(2, results.len()); - - assert_eq!(v1.id(), results[0].id()); - assert_eq!(v2.id(), results[1].id()); - - let results = g.v(v1.id()).in_("fake").to_list().unwrap(); - - assert_eq!(0, results.len()); - - // BOTH_E -> OTHER_V - - let results = g - .v(v.id()) - .both_e("test_vertex_out_traversal") - .other_v() - .to_list() - .unwrap(); - - assert_eq!(2, results.len()); - - assert_eq!(v1.id(), results[0].id()); - assert_eq!(v2.id(), results[1].id()); - - let results = g.v(v1.id()).in_("fake").to_list().unwrap(); - - assert_eq!(0, results.len()); -} - -#[apply(serializers)] -#[serial(test_add_v)] -fn test_add_v(client: GremlinClient) { - let g = traversal().with_remote(client); - - let results = g.add_v("person").to_list().unwrap(); - - assert!(results.len() > 0); - - assert_eq!("person", results[0].label()); - - let results = g.add_v("person").add_v(()).to_list().unwrap(); - - assert!(results.len() > 0); - - //default label - assert_eq!("vertex", results[0].label()); -} - -#[apply(serializers)] -#[serial(test_add_v_with_properties)] -fn test_add_v_with_properties(client: GremlinClient) { - let g = traversal().with_remote(client.clone()); - - let results = g - .add_v("person") - .property("name", "marko") - .property("age", 29) - .to_list() - .unwrap(); - - assert!(results.len() > 0); - - assert_eq!("person", results[0].label()); - - let results = client - .execute("g.V(_id).propertyMap()", &[("_id", results[0].id())]) - .expect("it should execute addV") - .filter_map(Result::ok) - .map(|f| f.take::()) - .collect::, _>>() - .expect("It should be ok"); - - let properties = &results[0]; - - assert_eq!( - &29, - properties["age"].get::().unwrap()[0] - .get::() - .unwrap() - .get::() - .unwrap() - ); - - assert_eq!( - &"marko", - properties["name"].get::().unwrap()[0] - .get::() - .unwrap() - .get::() - .unwrap() - ); -} - -#[apply(serializers)] -#[serial(test_add_v_with_property_many)] -fn test_add_v_with_property_many(client: GremlinClient) { - drop_vertices(&client, "test_add_v_with_property_many").unwrap(); - - let g = traversal().with_remote(client.clone()); - - let results = g - .add_v("test_add_v_with_property_many") - .property_many(vec![ - (String::from("name"), "marko"), - (String::from("age"), "29"), - ]) - .to_list() - .unwrap(); - - assert!(results.len() > 0); - - assert_eq!("test_add_v_with_property_many", results[0].label()); - - let results = client - .execute("g.V(_id).propertyMap()", &[("_id", results[0].id())]) - .expect("it should execute addV") - .filter_map(Result::ok) - .map(|f| f.take::()) - .collect::, _>>() - .expect("It should be ok"); - - let properties = &results[0]; - - assert_eq!( - &"29".to_string(), - properties["age"].get::().unwrap()[0] - .get::() - .unwrap() - .get::() - .unwrap() - ); - - assert_eq!( - &"marko", - properties["name"].get::().unwrap()[0] - .get::() - .unwrap() - .get::() - .unwrap() - ); -} - -#[apply(serializers)] -#[serial(test_has_many)] -fn test_has_many(client: GremlinClient) { - drop_vertices(&client, "test_has_many").unwrap(); - - let g = traversal().with_remote(client.clone()); - - let results = g - .add_v("test_has_many") - .property_many(vec![ - (String::from("name"), "josh"), - (String::from("age"), "21"), - ]) - .to_list() - .unwrap(); - - assert!(results.len() > 0); - - assert_eq!("test_has_many", results[0].label()); - - let results = g - .v(()) - .has_many(vec![ - (String::from("name"), "josh"), - (String::from("age"), "21"), - ]) - .to_list() - .unwrap(); - - assert_eq!(results.len(), 1); -} - -#[apply(serializers)] -#[serial(test_add_e)] -fn test_add_e(client: GremlinClient) { - let g = traversal().with_remote(client.clone()); - - let v = g - .add_v("person") - .property("name", "marko") - .property("age", 29) - .to_list() - .unwrap(); - - let v1 = g - .add_v("person") - .property("name", "jon") - .property("age", 29) - .to_list() - .unwrap(); - - let edges = g.add_e("knows").from(&v[0]).to(&v1[0]).to_list().unwrap(); - - assert!(edges.len() > 0); - - assert_eq!("knows", edges[0].label()); - - let edges = g - .v(v[0].id()) - .as_("a") - .out("knows") - .add_e("livesNear") - .from("a") - .property("year", 2009) - .to_list() - .unwrap(); - - assert!(edges.len() > 0); - - assert_eq!("livesNear", edges[0].label()); - - let edges = g - .v(()) - .as_("a") - .out("created") - .add_e("createdBy") - .to("a") - .property("acl", "public") - .to_list() - .unwrap(); - - assert_eq!("createdBy", edges[0].label()); - - let edges = g - .add_e("knows") - .from(__.v(()).has(("name", "marko"))) - .to(__.v(()).has(("name", "jon"))) - .to_list() - .unwrap(); - - assert!(edges.len() > 0); - - assert_eq!("knows", edges[0].label()); -} - -#[apply(serializers)] -#[serial(test_label_step)] -fn test_label_step(client: GremlinClient) { - let vertex = create_vertex(&client, "Traversal"); - - let g = traversal().with_remote(client); - - let results = g.v(vertex.id()).label().to_list().unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!("person", results[0]); -} - -#[apply(serializers)] -#[serial(test_properties_step)] -fn test_properties_step(client: GremlinClient) { - let vertex = create_vertex(&client, "Traversal"); - - let g = traversal().with_remote(client); - - let results = g.v(vertex.id()).properties(()).to_list().unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!("Traversal", results[0].get::().unwrap()); - - let results = g.v(vertex.id()).properties("name").to_list().unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!("Traversal", results[0].get::().unwrap()); - - let results = g.v(vertex.id()).properties("fake").to_list().unwrap(); - - assert_eq!(0, results.len()); -} - -#[apply(serializers)] -#[serial(test_property_map)] -fn test_property_map(client: GremlinClient) { - let vertex = create_vertex(&client, "Traversal"); - - let g = traversal().with_remote(client); - - let results = g.v(vertex.id()).property_map(()).to_list().unwrap(); - - assert_eq!(1, results.len()); - - let properties = &results[0]; - - assert_eq!( - "Traversal", - properties["name"].get::().unwrap()[0] - .get::() - .unwrap() - .get::() - .unwrap() - ); - - let results = g.v(vertex.id()).property_map("name").to_list().unwrap(); - - assert_eq!(1, results.len()); - - let properties = &results[0]; - - assert_eq!( - "Traversal", - properties["name"].get::().unwrap()[0] - .get::() - .unwrap() - .get::() - .unwrap() - ); - - let results = g.v(vertex.id()).property_map("fake").to_list().unwrap(); - - assert_eq!(1, results.len()); - - let properties = &results[0]; - - assert_eq!(0, properties.len()); -} - -#[apply(serializers)] -#[serial(test_values)] -fn test_values(client: GremlinClient) { - let vertex = create_vertex(&client, "Traversal"); - - let g = traversal().with_remote(client); - - let results = g.v(vertex.id()).values(()).to_list().unwrap(); - - assert_eq!(1, results.len()); - - let value = &results[0]; - - assert_eq!("Traversal", value.get::().unwrap()); - - let results = g.v(vertex.id()).values("name").to_list().unwrap(); - - assert_eq!(1, results.len()); - - let value = &results[0]; - - assert_eq!("Traversal", value.get::().unwrap()); - - let results = g.v(vertex.id()).values("fake").to_list().unwrap(); - - assert_eq!(0, results.len()); -} - -#[apply(serializers)] -#[serial(test_value_map)] -fn test_value_map(client: GremlinClient) { - let g = traversal().with_remote(client); - - let vertices = g - .add_v("test_value_map") - .property("name", "test") - .to_list() - .unwrap(); - - let vertex = &vertices[0]; - - let results = g.v(vertex.id()).value_map(()).to_list().unwrap(); - - assert_eq!(1, results.len()); - - let value = &results[0]; - - assert_eq!( - "test", - get_map::(value, "name").unwrap().unwrap() - ); - - let results = g.v(vertex.id()).value_map("name").to_list().unwrap(); - - assert_eq!(1, results.len()); - - let value = &results[0]; - - assert_eq!( - "test", - get_map::(value, "name").unwrap().unwrap() - ); - - let results = g.v(vertex.id()).value_map("fake").to_list().unwrap(); - - assert_eq!(0, results[0].len()); - - let results = g.v(vertex.id()).value_map(true).to_list().unwrap(); - assert_eq!(1, results.len()); - let value = &results[0]; - - assert_eq!( - Some(vertex.id().get().unwrap()), - get_map_id(&value).unwrap() - ); - assert_eq!(Some(vertex.label()), get_map_label(&value).unwrap()); - assert_eq!( - Some("test".to_owned()).as_ref(), - get_map(&value, "name").unwrap() - ); - assert!(results[0].get("id").or(results[0].get(T::Id)).is_some()); - assert!(results[0] - .get("label") - .or(results[0].get(T::Label)) - .is_some()); - assert_eq!(true, results[0].get("name").is_some()); -} - -#[apply(serializers)] -#[serial(test_unwrap_map)] -fn test_unwrap_map(client: GremlinClient) { - let g = traversal().with_remote(client); - - let vertices = g - .add_v("test_value_map") - .property("name", "test") - .to_list() - .unwrap(); - - let vertex = &vertices[0]; - - let results = g.v(vertex.id()).value_map(true).next().unwrap().unwrap(); - let v_id = vertex.id().get::().unwrap(); - - let id = get_map_id(&results).unwrap(); - let property = get_map::(&results, "name").unwrap(); - let label = get_map_label(&results).unwrap(); - assert_eq!(id, Some(v_id)); - assert_eq!(property.unwrap(), "test"); - assert_eq!(label, Some(vertex.label())); -} - -#[apply(serializers)] -#[serial(test_element_map)] -fn test_element_map(client: GremlinClient) { - let g = traversal().with_remote(client); - - let vertices = g - .add_v("test_element_map") - .property("name", "test") - .to_list() - .unwrap(); - - let vertex = &vertices[0]; - - let results = g.v(vertex.id()).element_map(()).to_list().unwrap(); - - assert_eq!(1, results.len()); - - let value = &results[0]; - - assert_eq!("test", value["name"].get::().unwrap()); - - let results = g.v(vertex.id()).element_map("name").to_list().unwrap(); - - assert_eq!(1, results.len()); - - let value = &results[0]; - - assert_eq!("test", value["name"].get::().unwrap()); - - let results = g.v(vertex.id()).element_map("fake").to_list().unwrap(); - let value = &results[0]; - - assert_eq!(2, value.len()); - assert_eq!( - Some(vertex.id().get().unwrap()), - get_map_id(&value).unwrap() - ); - assert_eq!(Some(vertex.label()), get_map_label(&value).unwrap()); - assert_eq!(2, results[0].len()); - assert!(results[0].get("id").or(results[0].get(T::Id)).is_some()); - assert!(results[0] - .get("label") - .or(results[0].get(T::Label)) - .is_some()); - - let results = g.v(vertex.id()).element_map(()).to_list().unwrap(); - let value = &results[0]; - - assert_eq!( - Some(vertex.id().get().unwrap()), - get_map_id(&value).unwrap() - ); - assert_eq!(Some(vertex.label()), get_map_label(&value).unwrap()); - assert_eq!( - Some("test".to_owned()).as_ref(), - get_map(&value, "name").unwrap() - ); - assert!(results[0].get("id").or(results[0].get(T::Id)).is_some()); - assert!(results[0] - .get("label") - .or(results[0].get(T::Label)) - .is_some()); - assert_eq!(true, results[0].get("name").is_some()); -} - -#[apply(serializers)] -#[serial(test_count)] -fn test_count(client: GremlinClient) { - let vertex = create_vertex_with_label(&client, "test_count", "Count"); - - let g = traversal().with_remote(client); - - let results = g.v(vertex.id()).count().to_list().unwrap(); - - assert_eq!(1, results.len()); - - let value = &results[0]; - - assert_eq!(&1, value); -} - -#[apply(serializers)] -#[serial(test_group_count_step)] -fn test_group_count_step(client: GremlinClient) { - drop_vertices(&client, "test_group_count").unwrap(); - - let vertex = create_vertex_with_label(&client, "test_group_count", "Count"); - - let g = traversal().with_remote(client); - - let results = g - .v(()) - .has_label("test_group_count") - .group_count() - //Normalize the keys to a common type of Id across serializers - .by(T::Id) - .next() - .unwrap(); - - let value = results.expect("Should have returned a map"); - assert_eq!(1, value.len(), "Should only have 1 entry in map"); - let (actual_key, actual_value) = value.into_iter().next().unwrap(); - - //The actual key may come back as either an int or a string depending - //on the serializer, so normalize it and the vertex gid to a string - let normalized_actual_key = match actual_key { - GKey::String(v) => v, - GKey::Int64(v) => v.to_string(), - other => panic!("Unexpected key type: {:?}", other), - }; - - let normalized_vertex_id = match vertex.id() { - gremlin_client::GID::String(v) => v.clone(), - gremlin_client::GID::Int32(v) => v.to_string(), - gremlin_client::GID::Int64(v) => v.to_string(), - }; - - assert_eq!(normalized_actual_key, normalized_vertex_id); - - assert_eq!( - actual_value, - GValue::Int64(1), - "Group count should have been the single vertex" - ); - - let results = g - .v(()) - .has_label("test_group_count") - .group_count() - .by("name") - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - let value = &results[0]; - - assert_eq!(&1, value["Count"].get::().unwrap()); - - let results = g - .v(()) - .has_label("test_group_count") - .group_count() - .by(T::Label) - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - let value = &results[0]; - - assert_eq!(&1, value["test_group_count"].get::().unwrap()); -} - -#[apply(serializers)] -#[serial(test_group_by_step)] -fn test_group_by_step(client: GremlinClient) { - drop_vertices(&client, "test_group_by_step").unwrap(); - - create_vertex_with_label(&client, "test_group_by_step", "Count"); - - let g = traversal().with_remote(client); - - let results = g - .v(()) - .has_label("test_group_by_step") - .group() - .by("name") - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - let value = &results[0]; - - assert_eq!(1, value["Count"].get::().unwrap().len()); - - let results = g - .v(()) - .has_label("test_group_by_step") - .group() - .by(T::Label) - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - let value = &results[0]; - - assert_eq!(1, value["test_group_by_step"].get::().unwrap().len()); - - // - - let results = g - .v(()) - .has_label("test_group_by_step") - .group() - .by(T::Label) - .by(__.count()) - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - let value = &results[0]; - - assert_eq!(&1, value["test_group_by_step"].get::().unwrap()); -} - -#[apply(serializers)] -#[serial(test_select_step)] -fn test_select_step(client: GremlinClient) { - drop_vertices(&client, "test_select_step").unwrap(); - - create_vertex_with_label(&client, "test_select_step", "Count"); - - let g = traversal().with_remote(client); - - let results = g - .v(()) - .has_label("test_select_step") - .group_count() - .by("name") - .select("Count") - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - let value = &results[0]; - - assert_eq!(&1, value.get::().unwrap()); -} - -#[apply(serializers)] -#[serial(test_fold_step)] -fn test_fold_step(client: GremlinClient) { - drop_vertices(&client, "test_fold_step").unwrap(); - - create_vertex_with_label(&client, "test_fold_step", "Count"); - - let g = traversal().with_remote(client); - - let results = g - .v(()) - .has_label("test_fold_step") - .values("name") - .fold() - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - let value = &results[0]; - - assert_eq!("Count", value[0].get::().unwrap()); -} - -#[apply(serializers)] -#[serial(test_unfold_step)] -fn test_unfold_step(client: GremlinClient) { - drop_vertices(&client, "test_unfold_step").unwrap(); - - let vertex = create_vertex_with_label(&client, "test_unfold_step", "Count"); - - let g = traversal().with_remote(client); - - let results = g - .v(vertex.id()) - .property_map(()) - .unfold() - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - let value = &results[0]; - - assert_eq!( - "Count", - value["name"].get::().unwrap()[0] - .get::() - .unwrap() - .get::() - .unwrap() - ); -} - -#[apply(serializers)] -#[serial(test_path_step)] -fn test_path_step(client: GremlinClient) { - drop_vertices(&client, "test_path_step").unwrap(); - - let v = create_vertex_with_label(&client, "test_path_step", "Count"); - - let g = traversal().with_remote(client); - - let results = g - .v(()) - .has_label("test_path_step") - .path() - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - let value = &results[0]; - - assert_eq!(v.id(), value.objects()[0].get::().unwrap().id()); -} - -#[apply(serializers)] -#[serial(test_limit_step)] -fn test_limit_step(client: GremlinClient) { - drop_vertices(&client, "test_limit_step").unwrap(); - - create_vertex_with_label(&client, "test_limit_step", "Count"); - create_vertex_with_label(&client, "test_limit_step", "Count"); - - let g = traversal().with_remote(client); - - let results = g - .v(()) - .has_label("test_limit_step") - .limit(1) - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); -} - -#[apply(serializers)] -#[serial(test_dedup_step)] -fn test_dedup_step(client: GremlinClient) { - drop_vertices(&client, "test_limit_step").unwrap(); - - create_vertex_with_label(&client, "test_limit_step", "Count"); - create_vertex_with_label(&client, "test_limit_step", "Count"); - - let g = traversal().with_remote(client); - - let results = g - .v(()) - .has_label("test_limit_step") - .dedup(()) - .by(T::Label) - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); -} - -#[apply(serializers)] -#[serial(test_numerical_steps)] -fn test_numerical_steps(client: GremlinClient) { - drop_vertices(&client, "test_numerical_steps").unwrap(); - - let g = traversal().with_remote(client); - - g.add_v("test_numerical_steps") - .property("age", 26) - .to_list() - .unwrap(); - g.add_v("test_numerical_steps") - .property("age", 20) - .to_list() - .unwrap(); - - // sum - let results = g - .v(()) - .has_label("test_numerical_steps") - .values("age") - .sum(()) - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(&46, results[0].get::().unwrap()); - - // max - let results = g - .v(()) - .has_label("test_numerical_steps") - .values("age") - .max(()) - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(&26, results[0].get::().unwrap()); - - // mean - - let results = g - .v(()) - .has_label("test_numerical_steps") - .values("age") - .mean(()) - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(&23.0, results[0].get::().unwrap()); - - // min - - let results = g - .v(()) - .has_label("test_numerical_steps") - .values("age") - .min(()) - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(&20, results[0].get::().unwrap()); -} - -#[apply(serializers)] -#[serial(test_has_with_p_steps)] -fn test_has_with_p_steps(client: GremlinClient) { - drop_vertices(&client, "test_has_with_p_steps").unwrap(); - - let g = traversal().with_remote(client); - - g.add_v("test_has_with_p_steps") - .property("age", 26) - .to_list() - .unwrap(); - let vertices = g - .add_v("test_has_with_p_steps") - .property("age", 20) - .to_list() - .unwrap(); - - let results = g - .v(()) - .has(("test_has_with_p_steps", "age", P::within(vec![19, 20]))) - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(vertices[0].id(), results[0].id()); - - let results = g - .v(()) - .has_label("test_has_with_p_steps") - .values("age") - .is(20) - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(&20, results[0].get::().unwrap()); - - let results = g - .v(()) - .has(("test_has_with_p_steps", "age", 20)) - .values("age") - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(&20, results[0].get::().unwrap()); - - let results = g - .v(()) - .has_label("test_has_with_p_steps") - .values("age") - .where_(__.is(P::eq(20))) - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(&20, results[0].get::().unwrap()); - - let results = g - .v(()) - .has_label("test_has_with_p_steps") - .values("age") - .is(P::within(vec![19, 20])) - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(&20, results[0].get::().unwrap()); -} - -#[apply(serializers)] -#[serial(test_has_with_text_p_step)] -fn test_has_with_text_p_step(client: GremlinClient) { - drop_vertices(&client, "test_has_with_text_p_step").unwrap(); - - let g = traversal().with_remote(client); - - g.add_v("test_has_with_text_p_step") - .property("name", "Jon") - .to_list() - .unwrap(); - - let vertices = g - .add_v("test_has_with_text_p_step") - .property("name", "Alice") - .to_list() - .unwrap(); - - let results = g - .v(()) - .has(("test_has_with_text_p_step", "name", TextP::containing("A"))) - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(vertices[0].id(), results[0].id()); - - let results = g - .v(()) - .has_label("test_has_with_text_p_step") - .values("name") - .is("Alice") - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!("Alice", results[0].get::().unwrap()); - - let results = g - .v(()) - .has_label("test_has_with_text_p_step") - .values("name") - .is(TextP::containing("Al")) - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!("Alice", results[0].get::().unwrap()); - - g.add_v("test_has_with_text_p_step") - .property("name", "Alice2") - .to_list() - .unwrap(); - - let results = g - .v(()) - .has(("test_has_with_text_p_step", "name", TextP::containing("A"))) - .to_list() - .unwrap(); - - assert_eq!(2, results.len()); -} - -#[apply(serializers)] -#[serial(where_step_test)] -fn where_step_test(client: GremlinClient) { - drop_vertices(&client, "where_step_test").unwrap(); - - let g = traversal().with_remote(client); - - let v = g - .add_v("where_step_test") - .property("age", 26) - .to_list() - .unwrap(); - - let results = g - .v(()) - .has_label("where_step_test") - .where_(__.values("age").is(26)) - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(v[0].id(), results[0].id()); -} - -#[apply(serializers)] -#[serial(not_step_test)] -fn not_step_test(client: GremlinClient) { - drop_vertices(&client, "not_step_test").unwrap(); - - let g = traversal().with_remote(client); - - g.add_v("not_step_test") - .property("age", 26) - .to_list() - .unwrap(); - - let results = g - .v(()) - .has_label("not_step_test") - .not(__.values("age").is(26)) - .to_list() - .unwrap(); - - assert_eq!(0, results.len()); -} - -#[apply(serializers)] -#[serial(order_step_test)] -fn order_step_test(client: GremlinClient) { - drop_vertices(&client, "order_step_test").unwrap(); - - let g = traversal().with_remote(client); - - g.add_v("order_step_test") - .property("name", "b") - .to_list() - .unwrap(); - - g.add_v("order_step_test") - .property("name", "a") - .to_list() - .unwrap(); - - let results = g - .v(()) - .has_label("order_step_test") - .values("name") - .order(()) - .to_list() - .unwrap(); - - assert_eq!(2, results.len()); - - assert_eq!("a", results[0].get::().unwrap()); - - let results = g - .v(()) - .has_label("order_step_test") - .values("name") - .order(()) - .by(Order::Desc) - .to_list() - .unwrap(); - - assert_eq!(2, results.len()); - - assert_eq!("b", results[0].get::().unwrap()); -} - -#[apply(serializers)] -#[serial(match_step_test)] -fn match_step_test(client: GremlinClient) { - drop_vertices(&client, "match_step_test").unwrap(); - - drop_edges(&client, "match_step_test_edge").unwrap(); - - let g = traversal().with_remote(client); - - let v1 = g - .add_v("match_step_test") - .property("name", "a") - .to_list() - .unwrap(); - - let v2 = g - .add_v("match_step_test") - .property("name", "b") - .to_list() - .unwrap(); - - let v3 = g - .add_v("match_step_test") - .property("name", "c") - .to_list() - .unwrap(); - - g.add_e("match_step_test_edge") - .from(&v1[0]) - .to(&v2[0]) - .to_list() - .unwrap(); - - g.add_e("match_step_test_edge") - .from(&v2[0]) - .to(&v3[0]) - .to_list() - .unwrap(); - - let results = g - .v(()) - .has_label("match_step_test") - .match_(vec![ - __.as_("a") - .has(("name", "a")) - .out("match_step_test_edge") - .as_("b"), - __.as_("b").out("match_step_test_edge").as_("c"), - ]) - .select(vec!["a", "c"]) - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - let first = &results[0].get::().unwrap(); - - assert_eq!(&v1[0], first["a"].get::().unwrap()); - assert_eq!(&v3[0], first["c"].get::().unwrap()); -} - -#[apply(serializers)] -#[serial(drop_step_test)] -fn drop_step_test(client: GremlinClient) { - drop_vertices(&client, "drop_step_test").unwrap(); - - let g = traversal().with_remote(client); - - g.add_v("drop_step_test") - .property("name", "a") - .next() - .unwrap(); - - g.add_v("drop_step_test") - .property("name", "b") - .next() - .unwrap(); - - let results = g.v(()).has_label("drop_step_test").count().next().unwrap(); - - assert_eq!(Some(2), results); - - g.v(()) - .has_label("drop_step_test") - .drop() - .to_list() - .unwrap(); - - let results = g.v(()).has_label("drop_step_test").has_next().unwrap(); - - assert_eq!(false, results); -} - -#[apply(serializers)] -#[serial(or_step_test)] -fn or_step_test(client: GremlinClient) { - drop_vertices(&client, "or_step_test").unwrap(); - - let g = traversal().with_remote(client); - - g.add_v("or_step_test") - .property("foo", "bar") - .property("bar", "foo") - .next() - .unwrap(); - - g.add_v("or_step_test") - .property("foo", "nobar") - .property("bar", "nofoo") - .next() - .unwrap(); - - let result = g - .v(()) - .has_label("or_step_test") - .has(("foo", "bar")) - .or(()) - .has(("bar", "foo")) - .to_list() - .unwrap(); - assert_eq!(result.len(), 1); - - let result = g - .v(()) - .has_label("or_step_test") - .has(("foo", "bar")) - .or(()) - .has(("bar", "nofoo")) - .to_list() - .unwrap(); - assert_eq!(result.len(), 2); -} - -#[apply(serializers)] -#[serial(iter_terminator_test)] -fn iter_terminator_test(client: GremlinClient) { - drop_vertices(&client, "iter_terminator_test").unwrap(); - - let g = traversal().with_remote(client); - - g.add_v("iter_terminator_test") - .property("name", "a") - .next() - .unwrap(); - - g.add_v("iter_terminator_test") - .property("name", "b") - .next() - .unwrap(); - - let results: Vec = g - .v(()) - .has_label("iter_terminator_test") - .iter() - .unwrap() - .filter_map(Result::ok) - .collect(); - - assert_eq!(2, results.len()) -} - -#[apply(serializers)] -#[serial(test_select_pop)] -fn test_select_pop(client: GremlinClient) { - drop_vertices(&client, "test_select_pop").unwrap(); - drop_vertices(&client, "test_select_pop_child").unwrap(); - - let g = traversal().with_remote(client); - - let v1 = g - .add_v("test_select_pop") - .property("name", "a") - .to_list() - .unwrap(); - - let v2 = g - .add_v("test_select_pop") - .property("name", "b") - .to_list() - .unwrap(); - - let e1 = g - .add_v("test_select_pop_child") - .property("name", "a") - .to_list() - .unwrap(); - - let e2 = g - .add_v("test_select_pop_child") - .property("name", "b") - .to_list() - .unwrap(); - - g.add_e("child").from(&v1[0]).to(&e1[0]).to_list().unwrap(); - - g.add_e("child").from(&v2[0]).to(&e2[0]).to_list().unwrap(); - - let results = g - .v(()) - .has_label("test_select_pop") - .has(("name", "a")) - .out("child") - .as_("v") - .v(()) - .has_label("test_select_pop") - .has(("name", "b")) - .out("child") - .as_("v") - .select((Pop::All, "v")) - .unfold() - .to_list() - .unwrap(); - assert_eq!(results.len(), 2); - - let results = g - .v(()) - .has_label("test_select_pop") - .has(("name", "a")) - .out("child") - .as_("v") - .v(()) - .has_label("test_select_pop") - .has(("name", "b")) - .out("child") - .as_("v") - .select((Pop::Last, "v")) - .unfold() - .to_list() - .unwrap(); - assert_eq!(results.len(), 1); - - let results = g - .v(()) - .has_label("test_select_pop") - .has(("name", "a")) - .out("child") - .as_("v") - .v(()) - .has_label("test_select_pop") - .has(("name", "b")) - .out("child") - .as_("v") - .select((Pop::First, "v")) - .unfold() - .to_list() - .unwrap(); - assert_eq!(results.len(), 1); -} - -#[apply(serializers)] -#[serial(test_repeat_until_loops_loops)] -fn test_repeat_until_loops_loops(client: GremlinClient) { - drop_vertices(&client, "test_repeat_until_loops").unwrap(); - drop_vertices(&client, "test_repeat_until_loops_child").unwrap(); - - let g = traversal().with_remote(client); - - let v1 = g - .add_v("test_repeat_until_loops") - .property("name", "a") - .to_list() - .unwrap(); - - let e1 = g - .add_v("test_repeat_until_loops_child") - .property("name", "b") - .to_list() - .unwrap(); - - let e2 = g - .add_v("test_repeat_until_loops_child") - .property("name", "c") - .to_list() - .unwrap(); - - g.add_e("child").from(&v1[0]).to(&e1[0]).to_list().unwrap(); - g.add_e("child").from(&e1[0]).to(&e2[0]).to_list().unwrap(); - - let results = g - .v(v1[0].id()) - .repeat(__.out("child")) - .until(__.loops(()).is(2)) - .to_list() - .unwrap(); - - assert_eq!(results.len(), 1); - assert_eq!(results[0], e2[0]); -} - -#[apply(serializers)] -#[serial(test_simple_path)] -fn test_simple_vertex_property(client: GremlinClient) { - drop_vertices(&client, "test_simple_vertex_property").unwrap(); - - let g = traversal().with_remote(client); - - let v = g - .add_v("test_simple_vertex_property") - .property("name", "a") - .element_map(()) - .next() - .unwrap() - .unwrap(); - - let actual_property: &String = v.get("name").expect("Should have property").get().unwrap(); - assert_eq!(actual_property, "a"); -} - -#[apply(serializers)] -#[serial(test_simple_path)] -fn test_simple_path(client: GremlinClient) { - drop_vertices(&client, "test_simple_path").unwrap(); - drop_vertices(&client, "test_simple_path_child").unwrap(); - - let g = traversal().with_remote(client); - - let v1 = g - .add_v("test_simple_path") - .property("name", "a") - .to_list() - .unwrap(); - - let e1 = g - .add_v("test_simple_path_child") - .property("name", "b") - .to_list() - .unwrap(); - - let e2 = g - .add_v("test_simple_path_child") - .property("name", "c") - .to_list() - .unwrap(); - - g.add_e("child").from(&v1[0]).to(&e1[0]).to_list().unwrap(); - g.add_e("child").from(&e1[0]).to(&e2[0]).to_list().unwrap(); - g.add_e("child").from(&e2[0]).to(&v1[0]).to_list().unwrap(); - - let results = g - .v(v1[0].id()) - .repeat(__.out("child").simple_path()) - .until(__.loops(()).is(2)) - .to_list() - .unwrap(); - - assert_eq!(results.len(), 1); - assert_eq!(results[0], e2[0]); -} - -#[apply(serializers)] -#[serial(test_sample)] -fn test_sample(client: GremlinClient) { - drop_vertices(&client, "test_sample").unwrap(); - drop_vertices(&client, "test_sample_child").unwrap(); - - let g = traversal().with_remote(client); - - let v1 = g - .add_v("test_sample") - .property("name", "a") - .to_list() - .unwrap(); - - let e1 = g - .add_v("test_sample_child") - .property("name", "b") - .to_list() - .unwrap(); - - let e2 = g - .add_v("test_sample_child") - .property("name", "b") - .to_list() - .unwrap(); - - g.add_e("child").from(&v1[0]).to(&e1[0]).to_list().unwrap(); - g.add_e("child").from(&v1[0]).to(&e2[0]).to_list().unwrap(); - let results = g.v(v1[0].id()).out("child").sample(1).to_list().unwrap(); - assert_eq!(results.len(), 1); -} - -#[apply(serializers)] -#[serial(test_local)] -fn test_local(client: GremlinClient) { - drop_vertices(&client, "test_local").unwrap(); - drop_vertices(&client, "test_local_child").unwrap(); - drop_vertices(&client, "test_local_child_child").unwrap(); - - let g = traversal().with_remote(client); - - let v1 = g - .add_v("test_local") - .property("name", "a") - .to_list() - .unwrap(); - - let e1 = g - .add_v("test_local_child") - .property("name", "b") - .to_list() - .unwrap(); - - let e2 = g - .add_v("test_local_child") - .property("name", "b") - .to_list() - .unwrap(); - - let e3 = g - .add_v("test_local_child_child") - .property("name", "c") - .to_list() - .unwrap(); - - let e4 = g - .add_v("test_local_child_child") - .property("name", "d") - .to_list() - .unwrap(); - - let e5 = g - .add_v("test_local_child_child") - .property("name", "e") - .to_list() - .unwrap(); - - g.add_e("child").from(&v1[0]).to(&e1[0]).to_list().unwrap(); - g.add_e("child").from(&v1[0]).to(&e2[0]).to_list().unwrap(); - - g.add_e("child_child") - .from(&e1[0]) - .to(&e3[0]) - .to_list() - .unwrap(); - g.add_e("child_child") - .from(&e1[0]) - .to(&e4[0]) - .to_list() - .unwrap(); - - g.add_e("child_child") - .from(&e2[0]) - .to(&e5[0]) - .to_list() - .unwrap(); - - let results = g - .v(v1[0].id()) - .out("child") - .local(__.out("child_child").sample(1)) //Local used here to only get one vertices from each child - .to_list() - .unwrap(); - - assert_eq!(results.len(), 2); -} - -#[apply(serializers)] -#[serial(test_side_effect)] -fn test_side_effect(client: GremlinClient) { - 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"); - - assert_map_property( - &element_map, - expected_side_effect_key, - expected_side_effect_value, - ); -} - -#[apply(serializers)] -#[serial(test_anonymous_traversal_properties_drop)] -fn test_anonymous_traversal_properties_drop(client: GremlinClient) { - 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") - .or(element_map.get(T::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" - ); -} - -#[apply(serializers)] -#[serial(test_by_columns)] -fn test_by_columns(client: GremlinClient) { - 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_map_property( - &element_map, - expected_side_effect_key_b, - expected_side_effect_value_b, - ); -} - -#[apply(serializers)] -#[serial(test_property_cardinality)] -fn test_property_cardinality(client: GremlinClient) { - drop_vertices(&client, "test_property_cardinality").unwrap(); - - let g = traversal().with_remote(client); - - let v1 = g - .add_v("test_property_cardinality") - .property("name", "a") - .to_list() - .unwrap(); - - assert!(v1.len() > 0); - - g.v(v1[0].id()) - .property_with_cardinality(Cardinality::List, "name", "b") - .next() - .unwrap(); - let new_v = g.v(v1[0].id()).property_map(()).next().unwrap().unwrap(); - assert_eq!(2, new_v["name"].get::().unwrap().len()); - - g.v(v1[0].id()) - .property_with_cardinality(Cardinality::Single, "name", "b") - .next() - .unwrap(); - let new_v = g.v(v1[0].id()).property_map(()).next().unwrap().unwrap(); - assert_eq!(1, new_v["name"].get::().unwrap().len()); -} - -#[apply(serializers)] -#[serial(test_choose)] -fn test_choose(client: GremlinClient) { - drop_vertices(&client, "test_choose").unwrap(); - - let g = traversal().with_remote(client); - - g.add_v("test_choose") - .property("name", "a") - .as_("source") - .choose(( - __.select("source").has("name"), - __.add_v("test_choose_success"), - __.add_v("test_choose_failure"), - )) - .next() - .unwrap(); - - let success_vertices = g.v(()).has_label("test_choose_success").next().unwrap(); - assert_eq!(success_vertices.is_some(), true); - - let success_vertices = g.v(()).has_label("test_choose_failure").next().unwrap(); - assert_eq!(success_vertices.is_some(), false); - - g.add_v("test_choose") - .property("name", "b") - .as_("source") - .choose(( - __.select("source").has("name"), - __.add_v("test_choose_success2"), - )) - .next() - .unwrap(); - - let success_vertices = g.v(()).has_label("test_choose_failure2").next().unwrap(); - assert_eq!(success_vertices.is_some(), false); - - let success_vertices = g.v(()).has_label("test_choose_success2").next().unwrap(); - assert_eq!(success_vertices.is_some(), true); -} - -#[apply(serializers)] -#[serial(test_choose_by_literal_options)] -fn test_choose_by_literal_options(client: GremlinClient) { - 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())); -} - -#[apply(serializers)] -#[serial()] -fn test_coalesce(client: GremlinClient) { - use gremlin_client::GValue; - - drop_vertices(&client, "test_coalesce").unwrap(); - - let g = traversal().with_remote(client); - - g.add_v("test_coalesce") - .property("name", "a") - .to_list() - .unwrap(); - - g.add_v("test_coalesce") - .property("nickname", "b") - .to_list() - .unwrap(); - - let v = g - .v(()) - .has_label("test_coalesce") - .coalesce::([__.values("nickname"), __.values("name")]) - .to_list() - .unwrap(); - - let values = v - .into_iter() - .map(|e| e.take::().unwrap()) - .collect::>(); - - assert!(values.contains(&String::from("a"))); - assert!(values.contains(&String::from("b"))); -} - -#[apply(serializers)] -#[serial(test_coalesce_unfold)] -fn test_coalesce_unfold(client: GremlinClient) { - drop_vertices(&client, "test_coalesce_unfold").unwrap(); - - let g = traversal().with_remote(client); - - g.v(()) - .has(("test_coalesce_unfold", "name", "unfold")) - .fold() - .coalesce::([__.unfold(), __.add_v("test_coalesce_unfold")]) - .property("name", "unfold") - .next() - .expect("It should create a vertex with coalesce"); - - let v = g - .v(()) - .has_label("test_coalesce_unfold") - .value_map(()) - .to_list() - .unwrap(); - - let values = v.into_iter().collect::>(); - - assert_eq!(1, values.len()); - - assert_eq!( - "unfold", - utils::unwrap_map::(&values[0], "name", 0).unwrap() - ); - - g.v(()) - .has(("test_coalesce_unfold", "name", "unfold")) - .fold() - .coalesce::([__.unfold(), __.add_v("test_coalesce_unfold")]) - .property("name", "unfold") - .next() - .expect("It should create a vertex with coalesce"); - - let v = g - .v(()) - .has_label("test_coalesce_unfold") - .value_map(()) - .to_list() - .unwrap(); - - let values = v.into_iter().collect::>(); - - assert_eq!(1, values.len()); - - assert_eq!( - "unfold", - utils::unwrap_map::(&values[0], "name", 0).unwrap() - ); -} - -#[apply(serializers)] -#[serial(test_none_step)] -fn test_none_step(client: GremlinClient) { - 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 - g.add_v("test_none_step") - .none() - .iter() - .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 - .v(()) - .has_label("test_none_step") - .count() - .next() - .ok() - .flatten() - .expect("Should have gotten a response"); - assert_eq!(1, vertex_count); -} - -fn get_map_id<'a>(map: &'a Map) -> Result, GremlinError> { - let string_keyed = get_map(map, "id")?; - if string_keyed.is_some() { - Ok(string_keyed) - } else { - get_map(map, T::Id) - } -} - -fn get_map_label<'a>(map: &'a Map) -> Result, GremlinError> { - let string_keyed = get_map(map, "label")?; - if string_keyed.is_some() { - Ok(string_keyed) - } else { - get_map(map, T::Label) - } -} - -fn get_map<'a, T, K>(map: &'a Map, key: K) -> Result, GremlinError> -where - T: BorrowFromGValue, - K: Into, -{ - map.get(key) - .map(|val| match val { - GValue::List(list) => list[0].get::(), - other => other.get::(), - }) - .transpose() -} - -#[apply(serializers)] -#[serial(test_traversal_vertex_mapping)] -#[cfg(feature = "derive")] -fn test_traversal_vertex_mapping(client: GremlinClient) { - use chrono::{DateTime, TimeZone, Utc}; - use gremlin_client::derive::FromGMap; - use std::convert::TryFrom; - - drop_vertices(&client, "test_vertex_mapping").unwrap(); - - let g = traversal().with_remote(client); - - let uuid = uuid::Uuid::new_v4(); - let mark = g - .add_v("person") - .property("name", "Mark") - .property("age", 22) - .property("time", 22 as i64) - .property("score", 3.2) - .property("uuid", uuid.clone()) - .property("datetime", chrono::Utc.timestamp(1551825863, 0)) - .property("date", 1551825863 as i64) - .value_map(true) - .by(TraversalBuilder::new(Bytecode::new()).unfold()) - .next(); - assert_eq!(mark.is_ok(), true); - - #[derive(Debug, PartialEq, FromGMap)] - struct Person { - name: String, - age: i32, - time: i64, - datetime: DateTime, - uuid: uuid::Uuid, - optional: Option, - } - let person = Person::try_from(mark.unwrap().unwrap()); - assert_eq!(person.is_ok(), true); - - assert_eq!( - Person { - name: String::from("Mark"), - age: 22, - time: 22, - datetime: chrono::Utc.timestamp(1551825863, 0), - uuid: uuid, - optional: None - }, - person.unwrap() - ); -} diff --git a/gremlin-client/tests/integration_traversal_v2.rs b/gremlin-client/tests/integration_traversal_v2.rs deleted file mode 100644 index 2cae261a..00000000 --- a/gremlin-client/tests/integration_traversal_v2.rs +++ /dev/null @@ -1,1837 +0,0 @@ -use gremlin_client::process::traversal::{traversal, Order, __}; -use gremlin_client::structure::{ - Cardinality, GKey, GValue, List, Map, Pop, TextP, Vertex, VertexProperty, GID, P, T, -}; -use gremlin_client::{utils, IoProtocol}; - -mod common; - -use common::io::{ - create_edge, create_vertex, create_vertex_with_label, drop_edges, drop_vertices, - graph_serializer, -}; - -#[test] -fn test_simple_vertex_traversal_v2() { - let g = traversal().with_remote(graph_serializer(IoProtocol::GraphSONV2)); - - let results = g.v(()).to_list().unwrap(); - - assert!(results.len() > 0); -} - -#[test] -fn test_simple_vertex_traversal_with_id_v2() { - let client = graph_serializer(IoProtocol::GraphSONV2); - - let vertex = create_vertex(&client, "Traversal"); - - let g = traversal().with_remote(client); - - let results = g.v(vertex.id()).to_list().unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(vertex.id(), results[0].id()); -} - -#[test] -fn test_simple_vertex_traversal_with_multiple_id_v2() { - let client = graph_serializer(IoProtocol::GraphSONV2); - drop_vertices(&client, "test_simple_vertex_traversal").unwrap(); - - let vertex = create_vertex_with_label(&client, "test_simple_vertex_traversal", "Traversal"); - let vertex2 = create_vertex_with_label(&client, "test_simple_vertex_traversal", "Traversal"); - - let g = traversal().with_remote(client); - - let results = g.v(vec![vertex.id(), vertex2.id()]).to_list().unwrap(); - - assert_eq!(2, results.len()); - - assert_eq!(vertex.id(), results[0].id()); - assert_eq!(vertex2.id(), results[1].id()); -} - -#[test] -fn test_simple_vertex_traversal_with_label_v2() { - let client = graph_serializer(IoProtocol::GraphSONV2); - - drop_vertices(&client, "test_simple_vertex_traversal_with_label").unwrap(); - - let vertex = create_vertex_with_label( - &client, - "test_simple_vertex_traversal_with_label", - "Traversal", - ); - - let g = traversal().with_remote(client); - - let results = g - .v(()) - .has_label("test_simple_vertex_traversal_with_label") - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(vertex.id(), results[0].id()); -} - -#[test] -fn test_simple_vertex_traversal_with_label_and_has_v2() { - let client = graph_serializer(IoProtocol::GraphSONV2); - - drop_vertices(&client, "test_simple_vertex_traversal_with_label_and_has").unwrap(); - - let vertex = create_vertex_with_label( - &client, - "test_simple_vertex_traversal_with_label_and_has", - "Traversal", - ); - - let g = traversal().with_remote(client); - - let results = g - .v(()) - .has_label("test_simple_vertex_traversal_with_label_and_has") - .has(("name", "Traversal")) - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(vertex.id(), results[0].id()); - - // with 3 params - - let results = g - .v(()) - .has(( - "test_simple_vertex_traversal_with_label_and_has", - "name", - "Traversal", - )) - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(vertex.id(), results[0].id()); - - // with 1 param - - let results = g - .v(()) - .has_label("test_simple_vertex_traversal_with_label_and_has") - .has("name") - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(vertex.id(), results[0].id()); - - // hasNot - - let results = g - .v(()) - .has_label("test_simple_vertex_traversal_with_label_and_has") - .has_not("surname") - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(vertex.id(), results[0].id()); -} - -#[test] -fn test_simple_edge_traversal_v2() { - let g = traversal().with_remote(graph_serializer(IoProtocol::GraphSONV2)); - - let results = g.e(()).to_list().unwrap(); - - assert!(results.len() > 0); -} - -#[test] -fn test_simple_edge_traversal_id_v2() { - let client = graph_serializer(IoProtocol::GraphSONV2); - - let v = create_vertex(&client, "Traversal"); - let v1 = create_vertex(&client, "Traversal"); - - let e = create_edge(&client, &v, &v1, "TraversalEdge"); - - let g = traversal().with_remote(client); - - let results = g.e(e.id()).to_list().unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(e.id(), results[0].id()); -} - -#[test] -fn test_simple_edge_traversal_with_label_v2() { - let client = graph_serializer(IoProtocol::GraphSONV2); - - drop_edges(&client, "test_simple_edge_traversal_with_label").unwrap(); - - let v = create_vertex(&client, "Traversal"); - let v1 = create_vertex(&client, "Traversal"); - - let e = create_edge(&client, &v, &v1, "test_simple_edge_traversal_with_label"); - - let g = traversal().with_remote(client); - - let results = g - .e(()) - .has_label("test_simple_edge_traversal_with_label") - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(e.id(), results[0].id()); -} - -#[test] -fn test_traversal_v2() { - let client = graph_serializer(IoProtocol::GraphSONV2); - - drop_edges(&client, "test_vertex_out_traversal").unwrap(); - - let v = create_vertex(&client, "Traversal"); - let v1 = create_vertex(&client, "Traversal"); - let v2 = create_vertex(&client, "Traversal"); - - let _e = create_edge(&client, &v, &v1, "test_vertex_out_traversal"); - let _e2 = create_edge(&client, &v2, &v, "test_vertex_out_traversal"); - - let g = traversal().with_remote(client); - - // OUT - let results = g - .v(v.id()) - .out("test_vertex_out_traversal") - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(v1.id(), results[0].id()); - - let results = g.v(v.id()).out("fake").to_list().unwrap(); - - assert_eq!(0, results.len()); - - // OUT_E - - let results = g - .v(v.id()) - .out_e("test_vertex_out_traversal") - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!("test_vertex_out_traversal", results[0].label()); - - assert_eq!(v.id(), results[0].out_v().id()); - assert_eq!(v1.id(), results[0].in_v().id()); - - // OUT_E -> IN_V - let results = g - .v(v.id()) - .out_e("test_vertex_out_traversal") - .in_v() - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(v1.id(), results[0].id()); - - let results = g.v(v.id()).out("fake").to_list().unwrap(); - - assert_eq!(0, results.len()); - - // IN - let results = g - .v(v1.id()) - .in_("test_vertex_out_traversal") - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(v.id(), results[0].id()); - - let results = g.v(v1.id()).in_("fake").to_list().unwrap(); - - assert_eq!(0, results.len()); - - // IN_E - - let results = g - .v(v1.id()) - .in_e("test_vertex_out_traversal") - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!("test_vertex_out_traversal", results[0].label()); - - assert_eq!(v.id(), results[0].out_v().id()); - assert_eq!(v1.id(), results[0].in_v().id()); - - // IN_E -> OUT_V - let results = g - .v(v1.id()) - .in_e("test_vertex_out_traversal") - .out_v() - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(v.id(), results[0].id()); - - let results = g.v(v1.id()).in_("fake").to_list().unwrap(); - - assert_eq!(0, results.len()); - - // BOTH - - let results = g - .v(v.id()) - .both("test_vertex_out_traversal") - .to_list() - .unwrap(); - - assert_eq!(2, results.len()); - - assert_eq!(v1.id(), results[0].id()); - assert_eq!(v2.id(), results[1].id()); - - let results = g.v(v1.id()).in_("fake").to_list().unwrap(); - - assert_eq!(0, results.len()); - - // BOTH_E -> OTHER_V - - let results = g - .v(v.id()) - .both_e("test_vertex_out_traversal") - .other_v() - .to_list() - .unwrap(); - - assert_eq!(2, results.len()); - - assert_eq!(v1.id(), results[0].id()); - assert_eq!(v2.id(), results[1].id()); - - let results = g.v(v1.id()).in_("fake").to_list().unwrap(); - - assert_eq!(0, results.len()); -} - -#[test] -fn test_add_v_v2() { - let g = traversal().with_remote(graph_serializer(IoProtocol::GraphSONV2)); - - let results = g.add_v("person").to_list().unwrap(); - - assert!(results.len() > 0); - - assert_eq!("person", results[0].label()); - - let results = g.add_v("person").add_v(()).to_list().unwrap(); - - assert!(results.len() > 0); - - //default label - assert_eq!("vertex", results[0].label()); -} - -#[test] -fn test_add_v_with_properties_v2() { - let client = graph_serializer(IoProtocol::GraphSONV2); - let g = traversal().with_remote(client.clone()); - - let results = g - .add_v("person") - .property("name", "marko") - .property("age", 29) - .to_list() - .unwrap(); - - assert!(results.len() > 0); - - assert_eq!("person", results[0].label()); - - let results = client - .execute("g.V(_id).propertyMap()", &[("_id", results[0].id())]) - .expect("it should execute addV") - .filter_map(Result::ok) - .map(|f| f.take::()) - .collect::, _>>() - .expect("It should be ok"); - - let properties = &results[0]; - - assert_eq!( - &29, - properties["age"].get::().unwrap()[0] - .get::() - .unwrap() - .get::() - .unwrap() - ); - - assert_eq!( - &"marko", - properties["name"].get::().unwrap()[0] - .get::() - .unwrap() - .get::() - .unwrap() - ); -} - -#[test] -fn test_add_v_with_property_many_v2() { - let client = graph_serializer(IoProtocol::GraphSONV2); - - drop_vertices(&client, "test_add_v_with_property_many").unwrap(); - - let g = traversal().with_remote(client.clone()); - - let results = g - .add_v("test_add_v_with_property_many") - .property_many(vec![ - (String::from("name"), "marko"), - (String::from("age"), "29"), - ]) - .to_list() - .unwrap(); - - assert!(results.len() > 0); - - assert_eq!("test_add_v_with_property_many", results[0].label()); - - let results = client - .execute("g.V(_id).propertyMap()", &[("_id", results[0].id())]) - .expect("it should execute addV") - .filter_map(Result::ok) - .map(|f| f.take::()) - .collect::, _>>() - .expect("It should be ok"); - - let properties = &results[0]; - - assert_eq!( - &"29".to_string(), - properties["age"].get::().unwrap()[0] - .get::() - .unwrap() - .get::() - .unwrap() - ); - - assert_eq!( - &"marko", - properties["name"].get::().unwrap()[0] - .get::() - .unwrap() - .get::() - .unwrap() - ); -} - -#[test] -fn test_has_many_v2() { - let client = graph_serializer(IoProtocol::GraphSONV2); - - drop_vertices(&client, "test_has_many").unwrap(); - - let g = traversal().with_remote(client.clone()); - - let results = g - .add_v("test_has_many") - .property_many(vec![ - (String::from("name"), "josh"), - (String::from("age"), "21"), - ]) - .to_list() - .unwrap(); - - assert!(results.len() > 0); - - assert_eq!("test_has_many", results[0].label()); - - let results = g - .v(()) - .has_many(vec![ - (String::from("name"), "josh"), - (String::from("age"), "21"), - ]) - .to_list() - .unwrap(); - - assert_eq!(results.len(), 1); -} - -#[test] -fn test_add_e_v2() { - let client = graph_serializer(IoProtocol::GraphSONV2); - let g = traversal().with_remote(client.clone()); - - let v = g - .add_v("person") - .property("name", "marko") - .property("age", 29) - .to_list() - .unwrap(); - - let v1 = g - .add_v("person") - .property("name", "jon") - .property("age", 29) - .to_list() - .unwrap(); - - let edges = g.add_e("knows").from(&v[0]).to(&v1[0]).to_list().unwrap(); - - assert!(edges.len() > 0); - - assert_eq!("knows", edges[0].label()); - - let edges = g - .v(v[0].id()) - .as_("a") - .out("knows") - .add_e("livesNear") - .from("a") - .property("year", 2009) - .to_list() - .unwrap(); - - assert!(edges.len() > 0); - - assert_eq!("livesNear", edges[0].label()); - - let edges = g - .v(()) - .as_("a") - .out("created") - .add_e("createdBy") - .to("a") - .property("acl", "public") - .to_list() - .unwrap(); - - assert_eq!("createdBy", edges[0].label()); - - let edges = g - .add_e("knows") - .from(__.v(()).has(("name", "marko"))) - .to(__.v(()).has(("name", "jon"))) - .to_list() - .unwrap(); - - assert!(edges.len() > 0); - - assert_eq!("knows", edges[0].label()); -} - -#[test] -fn test_label_step_v2() { - let client = graph_serializer(IoProtocol::GraphSONV2); - - let vertex = create_vertex(&client, "Traversal"); - - let g = traversal().with_remote(client); - - let results = g.v(vertex.id()).label().to_list().unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!("person", results[0]); -} - -#[test] -fn test_properties_step_v2() { - let client = graph_serializer(IoProtocol::GraphSONV2); - - let vertex = create_vertex(&client, "Traversal"); - - let g = traversal().with_remote(client); - - let results = g.v(vertex.id()).properties(()).to_list().unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!("Traversal", results[0].get::().unwrap()); - - let results = g.v(vertex.id()).properties("name").to_list().unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!("Traversal", results[0].get::().unwrap()); - - let results = g.v(vertex.id()).properties("fake").to_list().unwrap(); - - assert_eq!(0, results.len()); -} - -#[test] -fn test_property_map_v2() { - let client = graph_serializer(IoProtocol::GraphSONV2); - - let vertex = create_vertex(&client, "Traversal"); - - let g = traversal().with_remote(client); - - let results = g.v(vertex.id()).property_map(()).to_list().unwrap(); - - assert_eq!(1, results.len()); - - let properties = &results[0]; - - assert_eq!( - "Traversal", - properties["name"].get::().unwrap()[0] - .get::() - .unwrap() - .get::() - .unwrap() - ); - - let results = g.v(vertex.id()).property_map("name").to_list().unwrap(); - - assert_eq!(1, results.len()); - - let properties = &results[0]; - - assert_eq!( - "Traversal", - properties["name"].get::().unwrap()[0] - .get::() - .unwrap() - .get::() - .unwrap() - ); - - let results = g.v(vertex.id()).property_map("fake").to_list().unwrap(); - - assert_eq!(1, results.len()); - - let properties = &results[0]; - - assert_eq!(0, properties.len()); -} - -#[test] -fn test_values_v2() { - let client = graph_serializer(IoProtocol::GraphSONV2); - - let vertex = create_vertex(&client, "Traversal"); - - let g = traversal().with_remote(client); - - let results = g.v(vertex.id()).values(()).to_list().unwrap(); - - assert_eq!(1, results.len()); - - let value = &results[0]; - - assert_eq!("Traversal", value.get::().unwrap()); - - let results = g.v(vertex.id()).values("name").to_list().unwrap(); - - assert_eq!(1, results.len()); - - let value = &results[0]; - - assert_eq!("Traversal", value.get::().unwrap()); - - let results = g.v(vertex.id()).values("fake").to_list().unwrap(); - - assert_eq!(0, results.len()); -} - -#[test] -fn test_value_map_v2() { - let client = graph_serializer(IoProtocol::GraphSONV2); - - let g = traversal().with_remote(client); - - let vertices = g - .add_v("test_value_map") - .property("name", "test") - .to_list() - .unwrap(); - - let vertex = &vertices[0]; - - let results = g.v(vertex.id()).value_map(()).to_list().unwrap(); - - assert_eq!(1, results.len()); - - let value = &results[0]; - - assert_eq!( - "test", - value["name"].get::().unwrap()[0] - .get::() - .unwrap() - ); - - let results = g.v(vertex.id()).value_map("name").to_list().unwrap(); - - assert_eq!(1, results.len()); - - let value = &results[0]; - - assert_eq!( - "test", - value["name"].get::().unwrap()[0] - .get::() - .unwrap() - ); - - let results = g.v(vertex.id()).value_map("fake").to_list().unwrap(); - - assert_eq!(0, results[0].len()); - - let results = g.v(vertex.id()).value_map(true).to_list().unwrap(); - - assert_eq!(true, results[0].get("id").is_some()); - assert_eq!(true, results[0].get("label").is_some()); - assert_eq!(true, results[0].get("name").is_some()); -} - -#[test] -fn test_unwrap_map_v2() { - let client = graph_serializer(IoProtocol::GraphSONV2); - - let g = traversal().with_remote(client); - - let vertices = g - .add_v("test_value_map") - .property("name", "test") - .to_list() - .unwrap(); - - let vertex = &vertices[0]; - - let results = g.v(vertex.id()).value_map(true).next().unwrap().unwrap(); - let v_id = vertex.id().get::().unwrap(); - - let id = utils::unwrap_map::(&results, "id", 0); - let property = utils::unwrap_map::(&results, "name", 0); - let label = utils::unwrap_map::(&results, "label", 0); - - assert_eq!(id.is_ok(), true); - assert_eq!(property.is_ok(), true); - assert_eq!(label.is_ok(), true); - - assert_eq!(id.unwrap(), v_id); - assert_eq!(property.unwrap(), "test"); - assert_eq!(label.unwrap(), "test_value_map"); -} - -#[test] -fn test_element_map_v2() { - let client = graph_serializer(IoProtocol::GraphSONV2); - - let g = traversal().with_remote(client); - - let vertices = g - .add_v("test_element_map") - .property("name", "test") - .to_list() - .unwrap(); - - let vertex = &vertices[0]; - - let results = g.v(vertex.id()).element_map(()).to_list().unwrap(); - - assert_eq!(1, results.len()); - - let value = &results[0]; - - assert_eq!("test", value["name"].get::().unwrap()); - - let results = g.v(vertex.id()).element_map("name").to_list().unwrap(); - - assert_eq!(1, results.len()); - - let value = &results[0]; - - assert_eq!("test", value["name"].get::().unwrap()); - - let results = g.v(vertex.id()).element_map("fake").to_list().unwrap(); - - assert_eq!(2, results[0].len()); - assert_eq!(true, results[0].get("id").is_some()); - assert_eq!(true, results[0].get("label").is_some()); - - let results = g.v(vertex.id()).element_map(()).to_list().unwrap(); - - assert_eq!(true, results[0].get("id").is_some()); - assert_eq!(true, results[0].get("label").is_some()); - assert_eq!(true, results[0].get("name").is_some()); -} - -#[test] -fn test_count_v2() { - let client = graph_serializer(IoProtocol::GraphSONV2); - - let vertex = create_vertex_with_label(&client, "test_count", "Count"); - - let g = traversal().with_remote(client); - - let results = g.v(vertex.id()).count().to_list().unwrap(); - - assert_eq!(1, results.len()); - - let value = &results[0]; - - assert_eq!(&1, value); -} - -#[test] -fn test_group_count_step_v2() { - let client = graph_serializer(IoProtocol::GraphSONV2); - - drop_vertices(&client, "test_group_count").unwrap(); - - let vertex = create_vertex_with_label(&client, "test_group_count", "Count"); - - let g = traversal().with_remote(client); - - let results = g - .v(()) - .has_label("test_group_count") - .group_count() - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - let value = &results[0]; - - assert_eq!( - &GValue::Int64(1), - value - .get(GKey::String(match vertex.id() { - GID::String(s) => s.to_string(), - GID::Int32(i) => i.to_string(), - GID::Int64(i) => i.to_string(), - })) - .unwrap() - ); - //println - //value[&vertex].get::().unwrap()); - - let results = g - .v(()) - .has_label("test_group_count") - .group_count() - .by("name") - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - let value = &results[0]; - - assert_eq!(&1, value["Count"].get::().unwrap()); - - let results = g - .v(()) - .has_label("test_group_count") - .group_count() - .by(T::Label) - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - let value = &results[0]; - - assert_eq!(&1, value["test_group_count"].get::().unwrap()); -} - -#[test] -fn test_group_by_step_v2() { - let client = graph_serializer(IoProtocol::GraphSONV2); - - drop_vertices(&client, "test_group_by_step").unwrap(); - - create_vertex_with_label(&client, "test_group_by_step", "Count"); - - let g = traversal().with_remote(client); - - let results = g - .v(()) - .has_label("test_group_by_step") - .group() - .by("name") - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - let value = &results[0]; - - assert_eq!(1, value["Count"].get::().unwrap().len()); - - let results = g - .v(()) - .has_label("test_group_by_step") - .group() - .by(T::Label) - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - let value = &results[0]; - - assert_eq!(1, value["test_group_by_step"].get::().unwrap().len()); - - // - - let results = g - .v(()) - .has_label("test_group_by_step") - .group() - .by(T::Label) - .by(__.count()) - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - let value = &results[0]; - - assert_eq!(&1, value["test_group_by_step"].get::().unwrap()); -} - -#[test] -fn test_select_step_v2() { - let client = graph_serializer(IoProtocol::GraphSONV2); - - drop_vertices(&client, "test_select_step").unwrap(); - - create_vertex_with_label(&client, "test_select_step", "Count"); - - let g = traversal().with_remote(client); - - let results = g - .v(()) - .has_label("test_select_step") - .group_count() - .by("name") - .select("Count") - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - let value = &results[0]; - - assert_eq!(&1, value.get::().unwrap()); -} - -#[test] -fn test_fold_step_v2() { - let client = graph_serializer(IoProtocol::GraphSONV2); - - drop_vertices(&client, "test_fold_step").unwrap(); - - create_vertex_with_label(&client, "test_fold_step", "Count"); - - let g = traversal().with_remote(client); - - let results = g - .v(()) - .has_label("test_fold_step") - .values("name") - .fold() - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - let value = &results[0]; - - assert_eq!("Count", value[0].get::().unwrap()); -} - -#[test] -fn test_unfold_step_v2() { - let client = graph_serializer(IoProtocol::GraphSONV2); - - drop_vertices(&client, "test_unfold_step").unwrap(); - - let vertex = create_vertex_with_label(&client, "test_unfold_step", "Count"); - - let g = traversal().with_remote(client); - - let results = g - .v(vertex.id()) - .property_map(()) - .unfold() - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - let value = &results[0]; - - assert_eq!( - "Count", - value["name"].get::().unwrap()[0] - .get::() - .unwrap() - .get::() - .unwrap() - ); -} - -#[test] -fn test_path_step_v2() { - let client = graph_serializer(IoProtocol::GraphSONV2); - - drop_vertices(&client, "test_path_step").unwrap(); - - let v = create_vertex_with_label(&client, "test_path_step", "Count"); - - let g = traversal().with_remote(client); - - let results = g - .v(()) - .has_label("test_path_step") - .path() - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - let value = &results[0]; - - assert_eq!(v.id(), value.objects()[0].get::().unwrap().id()); -} - -#[test] -fn test_limit_step_v2() { - let client = graph_serializer(IoProtocol::GraphSONV2); - - drop_vertices(&client, "test_limit_step").unwrap(); - - create_vertex_with_label(&client, "test_limit_step", "Count"); - create_vertex_with_label(&client, "test_limit_step", "Count"); - - let g = traversal().with_remote(client); - - let results = g - .v(()) - .has_label("test_limit_step") - .limit(1) - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); -} - -#[test] -fn test_dedup_step_v2() { - let client = graph_serializer(IoProtocol::GraphSONV2); - - drop_vertices(&client, "test_limit_step").unwrap(); - - create_vertex_with_label(&client, "test_limit_step", "Count"); - create_vertex_with_label(&client, "test_limit_step", "Count"); - - let g = traversal().with_remote(client); - - let results = g - .v(()) - .has_label("test_limit_step") - .dedup(()) - .by(T::Label) - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); -} - -#[test] -fn test_numerical_steps_v2() { - let client = graph_serializer(IoProtocol::GraphSONV2); - - drop_vertices(&client, "test_numerical_steps").unwrap(); - - let g = traversal().with_remote(client); - - g.add_v("test_numerical_steps") - .property("age", 26) - .to_list() - .unwrap(); - g.add_v("test_numerical_steps") - .property("age", 20) - .to_list() - .unwrap(); - - // sum - let results = g - .v(()) - .has_label("test_numerical_steps") - .values("age") - .sum(()) - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(&46, results[0].get::().unwrap()); - - // max - let results = g - .v(()) - .has_label("test_numerical_steps") - .values("age") - .max(()) - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(&26, results[0].get::().unwrap()); - - // mean - - let results = g - .v(()) - .has_label("test_numerical_steps") - .values("age") - .mean(()) - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(&23.0, results[0].get::().unwrap()); - - // min - - let results = g - .v(()) - .has_label("test_numerical_steps") - .values("age") - .min(()) - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(&20, results[0].get::().unwrap()); -} - -#[test] -fn test_has_with_p_steps_v2() { - let client = graph_serializer(IoProtocol::GraphSONV2); - - drop_vertices(&client, "test_has_with_p_steps").unwrap(); - - let g = traversal().with_remote(client); - - g.add_v("test_has_with_p_steps") - .property("age", 26) - .to_list() - .unwrap(); - let vertices = g - .add_v("test_has_with_p_steps") - .property("age", 20) - .to_list() - .unwrap(); - - let results = g - .v(()) - .has(("test_has_with_p_steps", "age", P::within(vec![19, 20]))) - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(vertices[0].id(), results[0].id()); - - let results = g - .v(()) - .has_label("test_has_with_p_steps") - .values("age") - .is(20) - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(&20, results[0].get::().unwrap()); - - let results = g - .v(()) - .has_label("test_has_with_p_steps") - .values("age") - .is(P::within(vec![19, 20])) - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(&20, results[0].get::().unwrap()); -} - -#[test] -fn test_has_with_text_p_step_v2() { - let client = graph_serializer(IoProtocol::GraphSONV2); - - drop_vertices(&client, "test_has_with_text_p_step").unwrap(); - - let g = traversal().with_remote(client); - - g.add_v("test_has_with_text_p_step") - .property("name", "Jon") - .to_list() - .unwrap(); - - let vertices = g - .add_v("test_has_with_text_p_step") - .property("name", "Alice") - .to_list() - .unwrap(); - - let results = g - .v(()) - .has(("test_has_with_text_p_step", "name", TextP::containing("A"))) - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(vertices[0].id(), results[0].id()); - - let results = g - .v(()) - .has_label("test_has_with_text_p_step") - .values("name") - .is("Alice") - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!("Alice", results[0].get::().unwrap()); - - let results = g - .v(()) - .has_label("test_has_with_text_p_step") - .values("name") - .is(TextP::containing("Al")) - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!("Alice", results[0].get::().unwrap()); - - g.add_v("test_has_with_text_p_step") - .property("name", "Alice2") - .to_list() - .unwrap(); - - let results = g - .v(()) - .has(("test_has_with_text_p_step", "name", TextP::containing("A"))) - .to_list() - .unwrap(); - - assert_eq!(2, results.len()); -} - -#[test] -fn where_step_test_v2() { - let client = graph_serializer(IoProtocol::GraphSONV2); - - drop_vertices(&client, "where_step_test").unwrap(); - - let g = traversal().with_remote(client); - - let v = g - .add_v("where_step_test") - .property("age", 26) - .to_list() - .unwrap(); - - let results = g - .v(()) - .has_label("where_step_test") - .where_(__.values("age").is(26)) - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(v[0].id(), results[0].id()); -} - -#[test] -fn not_step_test_v2() { - let client = graph_serializer(IoProtocol::GraphSONV2); - - drop_vertices(&client, "not_step_test").unwrap(); - - let g = traversal().with_remote(client); - - g.add_v("not_step_test") - .property("age", 26) - .to_list() - .unwrap(); - - let results = g - .v(()) - .has_label("not_step_test") - .not(__.values("age").is(26)) - .to_list() - .unwrap(); - - assert_eq!(0, results.len()); -} - -#[test] -fn order_step_test_v2() { - let client = graph_serializer(IoProtocol::GraphSONV2); - - drop_vertices(&client, "order_step_test").unwrap(); - - let g = traversal().with_remote(client); - - g.add_v("order_step_test") - .property("name", "b") - .to_list() - .unwrap(); - - g.add_v("order_step_test") - .property("name", "a") - .to_list() - .unwrap(); - - let results = g - .v(()) - .has_label("order_step_test") - .values("name") - .order(()) - .to_list() - .unwrap(); - - assert_eq!(2, results.len()); - - assert_eq!("a", results[0].get::().unwrap()); - - let results = g - .v(()) - .has_label("order_step_test") - .values("name") - .order(()) - .by(Order::Desc) - .to_list() - .unwrap(); - - assert_eq!(2, results.len()); - - assert_eq!("b", results[0].get::().unwrap()); -} - -#[test] -fn match_step_test_v2() { - let client = graph_serializer(IoProtocol::GraphSONV2); - - drop_vertices(&client, "match_step_test").unwrap(); - - drop_edges(&client, "match_step_test_edge").unwrap(); - - let g = traversal().with_remote(client); - - let v1 = g - .add_v("match_step_test") - .property("name", "a") - .to_list() - .unwrap(); - - let v2 = g - .add_v("match_step_test") - .property("name", "b") - .to_list() - .unwrap(); - - let v3 = g - .add_v("match_step_test") - .property("name", "c") - .to_list() - .unwrap(); - - g.add_e("match_step_test_edge") - .from(&v1[0]) - .to(&v2[0]) - .to_list() - .unwrap(); - - g.add_e("match_step_test_edge") - .from(&v2[0]) - .to(&v3[0]) - .to_list() - .unwrap(); - - let results = g - .v(()) - .has_label("match_step_test") - .match_(vec![ - __.as_("a") - .has(("name", "a")) - .out("match_step_test_edge") - .as_("b"), - __.as_("b").out("match_step_test_edge").as_("c"), - ]) - .select(vec!["a", "c"]) - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - let first = &results[0].get::().unwrap(); - - assert_eq!(&v1[0], first["a"].get::().unwrap()); - assert_eq!(&v3[0], first["c"].get::().unwrap()); -} - -#[test] -fn drop_step_test_v2() { - let client = graph_serializer(IoProtocol::GraphSONV2); - - drop_vertices(&client, "drop_step_test").unwrap(); - - let g = traversal().with_remote(client); - - g.add_v("drop_step_test") - .property("name", "a") - .next() - .unwrap(); - - g.add_v("drop_step_test") - .property("name", "b") - .next() - .unwrap(); - - let results = g.v(()).has_label("drop_step_test").count().next().unwrap(); - - assert_eq!(Some(2), results); - - g.v(()) - .has_label("drop_step_test") - .drop() - .to_list() - .unwrap(); - - let results = g.v(()).has_label("drop_step_test").has_next().unwrap(); - - assert_eq!(false, results); -} - -#[test] -fn or_step_test_v2() { - let client = graph_serializer(IoProtocol::GraphSONV2); - - drop_vertices(&client, "or_step_test").unwrap(); - - let g = traversal().with_remote(client); - - g.add_v("or_step_test") - .property("foo", "bar") - .property("bar", "foo") - .next() - .unwrap(); - - g.add_v("or_step_test") - .property("foo", "nobar") - .property("bar", "nofoo") - .next() - .unwrap(); - - let result = g - .v(()) - .has_label("or_step_test") - .has(("foo", "bar")) - .or(()) - .has(("bar", "foo")) - .to_list() - .unwrap(); - assert_eq!(result.len(), 1); - - let result = g - .v(()) - .has_label("or_step_test") - .has(("foo", "bar")) - .or(()) - .has(("bar", "nofoo")) - .to_list() - .unwrap(); - assert_eq!(result.len(), 2); -} - -#[test] -fn iter_terminator_test_v2() { - let client = graph_serializer(IoProtocol::GraphSONV2); - - drop_vertices(&client, "iter_terminator_test").unwrap(); - - let g = traversal().with_remote(client); - - g.add_v("iter_terminator_test") - .property("name", "a") - .next() - .unwrap(); - - g.add_v("iter_terminator_test") - .property("name", "b") - .next() - .unwrap(); - - let results: Vec = g - .v(()) - .has_label("iter_terminator_test") - .iter() - .unwrap() - .filter_map(Result::ok) - .collect(); - - assert_eq!(2, results.len()) -} - -#[test] -fn test_select_pop_v2() { - let client = graph_serializer(IoProtocol::GraphSONV2); - - drop_vertices(&client, "test_select_pop").unwrap(); - drop_vertices(&client, "test_select_pop_child").unwrap(); - - let g = traversal().with_remote(client); - - let v1 = g - .add_v("test_select_pop") - .property("name", "a") - .to_list() - .unwrap(); - - let v2 = g - .add_v("test_select_pop") - .property("name", "b") - .to_list() - .unwrap(); - - let e1 = g - .add_v("test_select_pop_child") - .property("name", "a") - .to_list() - .unwrap(); - - let e2 = g - .add_v("test_select_pop_child") - .property("name", "b") - .to_list() - .unwrap(); - - g.add_e("child").from(&v1[0]).to(&e1[0]).to_list().unwrap(); - - g.add_e("child").from(&v2[0]).to(&e2[0]).to_list().unwrap(); - - let results = g - .v(()) - .has_label("test_select_pop") - .has(("name", "a")) - .out("child") - .as_("v") - .v(()) - .has_label("test_select_pop") - .has(("name", "b")) - .out("child") - .as_("v") - .select((Pop::All, "v")) - .unfold() - .to_list() - .unwrap(); - assert_eq!(results.len(), 2); - - let results = g - .v(()) - .has_label("test_select_pop") - .has(("name", "a")) - .out("child") - .as_("v") - .v(()) - .has_label("test_select_pop") - .has(("name", "b")) - .out("child") - .as_("v") - .select((Pop::Last, "v")) - .unfold() - .to_list() - .unwrap(); - assert_eq!(results.len(), 1); - - let results = g - .v(()) - .has_label("test_select_pop") - .has(("name", "a")) - .out("child") - .as_("v") - .v(()) - .has_label("test_select_pop") - .has(("name", "b")) - .out("child") - .as_("v") - .select((Pop::First, "v")) - .unfold() - .to_list() - .unwrap(); - assert_eq!(results.len(), 1); -} - -#[test] -fn test_repeat_until_loops_loops_v2() { - let client = graph_serializer(IoProtocol::GraphSONV2); - - drop_vertices(&client, "test_repeat_until_loops").unwrap(); - drop_vertices(&client, "test_repeat_until_loops_child").unwrap(); - - let g = traversal().with_remote(client); - - let v1 = g - .add_v("test_repeat_until_loops") - .property("name", "a") - .to_list() - .unwrap(); - - let e1 = g - .add_v("test_repeat_until_loops_child") - .property("name", "b") - .to_list() - .unwrap(); - - let e2 = g - .add_v("test_repeat_until_loops_child") - .property("name", "c") - .to_list() - .unwrap(); - - g.add_e("child").from(&v1[0]).to(&e1[0]).to_list().unwrap(); - g.add_e("child").from(&e1[0]).to(&e2[0]).to_list().unwrap(); - - let results = g - .v(v1[0].id()) - .repeat(__.out("child")) - .until(__.loops(()).is(2)) - .to_list() - .unwrap(); - - assert_eq!(results.len(), 1); - assert_eq!(results[0], e2[0]); -} - -#[test] -fn test_simple_path_v2() { - let client = graph_serializer(IoProtocol::GraphSONV2); - - drop_vertices(&client, "test_simple_path").unwrap(); - drop_vertices(&client, "test_simple_path_child").unwrap(); - - let g = traversal().with_remote(client); - - let v1 = g - .add_v("test_simple_path") - .property("name", "a") - .to_list() - .unwrap(); - - let e1 = g - .add_v("test_simple_path_child") - .property("name", "b") - .to_list() - .unwrap(); - - let e2 = g - .add_v("test_simple_path_child") - .property("name", "c") - .to_list() - .unwrap(); - - g.add_e("child").from(&v1[0]).to(&e1[0]).to_list().unwrap(); - g.add_e("child").from(&e1[0]).to(&e2[0]).to_list().unwrap(); - g.add_e("child").from(&e2[0]).to(&v1[0]).to_list().unwrap(); - - let results = g - .v(v1[0].id()) - .repeat(__.out("child").simple_path()) - .until(__.loops(()).is(2)) - .to_list() - .unwrap(); - - assert_eq!(results.len(), 1); - assert_eq!(results[0], e2[0]); -} - -#[test] -fn test_sample_v2() { - let client = graph_serializer(IoProtocol::GraphSONV2); - - drop_vertices(&client, "test_sample").unwrap(); - drop_vertices(&client, "test_sample_child").unwrap(); - - let g = traversal().with_remote(client); - - let v1 = g - .add_v("test_sample") - .property("name", "a") - .to_list() - .unwrap(); - - let e1 = g - .add_v("test_sample_child") - .property("name", "b") - .to_list() - .unwrap(); - - let e2 = g - .add_v("test_sample_child") - .property("name", "b") - .to_list() - .unwrap(); - - g.add_e("child").from(&v1[0]).to(&e1[0]).to_list().unwrap(); - g.add_e("child").from(&v1[0]).to(&e2[0]).to_list().unwrap(); - let results = g.v(v1[0].id()).out("child").sample(1).to_list().unwrap(); - assert_eq!(results.len(), 1); -} - -#[test] -fn test_local_v2() { - let client = graph_serializer(IoProtocol::GraphSONV2); - - drop_vertices(&client, "test_local").unwrap(); - drop_vertices(&client, "test_local_child").unwrap(); - drop_vertices(&client, "test_local_child_child").unwrap(); - - let g = traversal().with_remote(client); - - let v1 = g - .add_v("test_local") - .property("name", "a") - .to_list() - .unwrap(); - - let e1 = g - .add_v("test_local_child") - .property("name", "b") - .to_list() - .unwrap(); - - let e2 = g - .add_v("test_local_child") - .property("name", "b") - .to_list() - .unwrap(); - - let e3 = g - .add_v("test_local_child_child") - .property("name", "c") - .to_list() - .unwrap(); - - let e4 = g - .add_v("test_local_child_child") - .property("name", "d") - .to_list() - .unwrap(); - - let e5 = g - .add_v("test_local_child_child") - .property("name", "e") - .to_list() - .unwrap(); - - g.add_e("child").from(&v1[0]).to(&e1[0]).to_list().unwrap(); - g.add_e("child").from(&v1[0]).to(&e2[0]).to_list().unwrap(); - - g.add_e("child_child") - .from(&e1[0]) - .to(&e3[0]) - .to_list() - .unwrap(); - g.add_e("child_child") - .from(&e1[0]) - .to(&e4[0]) - .to_list() - .unwrap(); - - g.add_e("child_child") - .from(&e2[0]) - .to(&e5[0]) - .to_list() - .unwrap(); - - let results = g - .v(v1[0].id()) - .out("child") - .local(__.out("child_child").sample(1)) //Local used here to only get one vertices from each child - .to_list() - .unwrap(); - - assert_eq!(results.len(), 2); -} - -#[test] -fn test_property_cardinality_v2() { - let client = graph_serializer(IoProtocol::GraphSONV2); - - drop_vertices(&client, "test_property_cardinality").unwrap(); - - let g = traversal().with_remote(client); - - let v1 = g - .add_v("test_property_cardinality") - .property("name", "a") - .to_list() - .unwrap(); - - assert!(v1.len() > 0); - - g.v(v1[0].id()) - .property_with_cardinality(Cardinality::List, "name", "b") - .next() - .unwrap(); - let new_v = g.v(v1[0].id()).property_map(()).next().unwrap().unwrap(); - assert_eq!(2, new_v["name"].get::().unwrap().len()); - - g.v(v1[0].id()) - .property_with_cardinality(Cardinality::Single, "name", "b") - .next() - .unwrap(); - let new_v = g.v(v1[0].id()).property_map(()).next().unwrap().unwrap(); - assert_eq!(1, new_v["name"].get::().unwrap().len()); -} From e2cbd2ce90e2611cf34cbfc86098f812c14ee7aa Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Fri, 8 Nov 2024 19:46:40 -0600 Subject: [PATCH 39/56] Cleaned up loose ends --- gremlin-client/src/io/graph_binary_v1.rs | 51 ++----------------- gremlin-client/src/io/mod.rs | 11 ---- .../tests/graph_binary_read_write_cycle.rs | 35 ------------- 3 files changed, 3 insertions(+), 94 deletions(-) diff --git a/gremlin-client/src/io/graph_binary_v1.rs b/gremlin-client/src/io/graph_binary_v1.rs index 45c07209..cc48704e 100644 --- a/gremlin-client/src/io/graph_binary_v1.rs +++ b/gremlin-client/src/io/graph_binary_v1.rs @@ -51,15 +51,10 @@ const POP: u8 = 0x1C; // const LAMBDA: u8 = 0x1D; const P: u8 = 0x1E; const SCOPE: u8 = 0x1F; -//TODO fill in others - -//... const T: u8 = 0x20; const TRAVERSER: u8 = 0x21; -//... const BOOLEAN: u8 = 0x27; const TEXTP: u8 = 0x28; -//... const MERGE: u8 = 0x2E; const UNSPECIFIED_NULL_OBEJECT: u8 = 0xFE; @@ -127,8 +122,7 @@ impl GraphBinaryV1Deser for ResponseMessage { .try_into() .expect("Status code should fit in i16"); //Status message is nullable - let status_message = String::from_be_bytes_nullable(bytes)? - .expect("TODO what to do with null status message"); + let status_message = String::from_be_bytes_nullable(bytes)?.unwrap_or_default(); let status_attributes = GraphBinaryV1Deser::from_be_bytes(bytes)?; let result_meta: HashMap = GraphBinaryV1Deser::from_be_bytes(bytes)?; @@ -571,9 +565,8 @@ pub trait GraphBinaryV1Deser: Sized { Some(VALUE_FLAG) => Self::from_be_bytes(bytes).map(Option::Some), Some(VALUE_NULL_FLAG) => Ok(None), other => { - let remainder: Vec = bytes.cloned().collect(); return Err(GremlinError::Cast(format!( - "Unexpected byte for nullable check: {other:?}. Remainder: {remainder:?}" + "Unexpected byte for nullable check: {other:?}" ))); } } @@ -708,8 +701,7 @@ impl GraphBinaryV1Deser for GValue { } } other => { - let remainder: Vec = bytes.cloned().collect(); - unimplemented!("TODO {other}. Remainder: {remainder:?}"); + unimplemented!("Unimplemented deserialization byte {other}"); } } } @@ -1049,43 +1041,6 @@ impl GraphBinaryV1Ser for &Uuid { } } -impl GraphBinaryV1Ser for &Vertex { - fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { - //Format: {id}{label}{properties} - - //{id} is a fully qualified typed value composed of {type_code}{type_info}{value_flag}{value}. - self.id().to_be_bytes(buf)?; - - //{label} is a String value - self.label().to_be_bytes(buf)?; - - //{properties} is a fully qualified typed value composed of {type_code}{type_info}{value_flag}{value} which contains properties. - self.properties.len(); - todo!() - } -} - -impl GraphBinaryV1Ser for &VertexProperty { - fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { - //Format: {id}{label}{value}{parent}{properties} - - //{id} is a fully qualified typed value composed of {type_code}{type_info}{value_flag}{value}. - self.id().to_be_bytes(buf)?; - - //{label} is a String value. - self.label().to_be_bytes(buf)?; - - //{value} is a fully qualified typed value composed of {type_code}{type_info}{value_flag}{value}. - //??????? - - //{parent} is a fully qualified typed value composed of {type_code}{type_info}{value_flag}{value} which contains the parent Vertex. Note that as TinkerPop currently send "references" only, this value will always be null.} - - //{properties} is a fully qualified typed value composed of {type_code}{type_info}{value_flag}{value} which contains properties. - - todo!() - } -} - impl GraphBinaryV1Ser for &GID { fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { match self { diff --git a/gremlin-client/src/io/mod.rs b/gremlin-client/src/io/mod.rs index 4b59f61c..926a8afd 100644 --- a/gremlin-client/src/io/mod.rs +++ b/gremlin-client/src/io/mod.rs @@ -40,17 +40,6 @@ pub struct MiddleResponseResult { } impl IoProtocol { - pub fn read(&self, value: &Value) -> GremlinResult> { - if let Value::Null = value { - return Ok(None); - } - match self { - IoProtocol::GraphSONV2 => serializer_v2::deserializer_v2(value).map(Some), - IoProtocol::GraphSONV3 => serializer_v3::deserializer_v3(value).map(Some), - IoProtocol::GraphBinaryV1 => todo!(), - } - } - pub fn read_response(&self, response: Vec) -> GremlinResult { match self { IoProtocol::GraphSONV2 => { diff --git a/gremlin-client/tests/graph_binary_read_write_cycle.rs b/gremlin-client/tests/graph_binary_read_write_cycle.rs index fbdf6ce2..4e782f77 100644 --- a/gremlin-client/tests/graph_binary_read_write_cycle.rs +++ b/gremlin-client/tests/graph_binary_read_write_cycle.rs @@ -37,38 +37,3 @@ fn simple_value_rw_cycle>(#[case] payload: T) { Some(payload) ) } - -// #[test] -// fn edge_rw_cycle() { -// todo!() -// } - -// #[test] -// fn path_rw_cycle() { -// todo!() -// } - -// #[test] -// fn property_rw_cycle() { -// todo!() -// } - -// #[test] -// fn vertex_rw_cycle() { -// todo!() -// } - -// #[test] -// fn vertex_property_rw_cycle() { -// todo!() -// } - -// #[test] -// fn scope_rw_cycle() { -// todo!() -// } - -// #[test] -// fn traverser_rw_cycle() { -// todo!() -// } From fb3bf91b2500f61fa61c7828eab94fd4a4e5449e Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Tue, 12 Nov 2024 14:22:21 -0600 Subject: [PATCH 40/56] Parameterized integration_traversal_async --- .../tests/integration_traversal_async.rs | 20 +++-- .../tests/integration_traversal_async_v2.rs | 77 ------------------- 2 files changed, 15 insertions(+), 82 deletions(-) delete mode 100644 gremlin-client/tests/integration_traversal_async_v2.rs diff --git a/gremlin-client/tests/integration_traversal_async.rs b/gremlin-client/tests/integration_traversal_async.rs index d0a99b0b..fffc0ca1 100644 --- a/gremlin-client/tests/integration_traversal_async.rs +++ b/gremlin-client/tests/integration_traversal_async.rs @@ -2,9 +2,14 @@ mod common; #[cfg(feature = "async_gremlin")] mod aio { - use gremlin_client::process::traversal::traversal; + use rstest::*; + use rstest_reuse::{self, *}; - use super::common::aio::{connect, create_vertex_with_label, drop_vertices}; + use serial_test::serial; + + use gremlin_client::{aio::GremlinClient, process::traversal::traversal}; + + use super::common::aio::{connect_serializer, create_vertex_with_label, drop_vertices}; #[cfg(feature = "async-std-runtime")] use async_std::prelude::*; @@ -12,12 +17,17 @@ mod aio { #[cfg(feature = "tokio-runtime")] use tokio_stream::StreamExt; - use gremlin_client::Vertex; + use gremlin_client::{Vertex, IoProtocol}; + #[rstest] + #[case::graphson_v2(connect_serializer(IoProtocol::GraphSONV2))] + #[case::graphson_v3(connect_serializer(IoProtocol::GraphSONV3))] + #[case::graph_binary_v1(connect_serializer(IoProtocol::GraphBinaryV1))] + #[awt] #[cfg_attr(feature = "async-std-runtime", async_std::test)] #[cfg_attr(feature = "tokio-runtime", tokio::test)] - async fn test_simple_vertex_traversal_with_multiple_id() { - let client = connect().await; + #[serial(test_simple_vertex_traversal_with_multiple_id)] + async fn test_simple_vertex_traversal_with_multiple_id(#[future] #[case] client: GremlinClient) { drop_vertices(&client, "test_simple_vertex_traversal_async") .await .unwrap(); diff --git a/gremlin-client/tests/integration_traversal_async_v2.rs b/gremlin-client/tests/integration_traversal_async_v2.rs deleted file mode 100644 index c813d5ee..00000000 --- a/gremlin-client/tests/integration_traversal_async_v2.rs +++ /dev/null @@ -1,77 +0,0 @@ -#[allow(dead_code)] -mod common; - -#[cfg(feature = "async_gremlin")] -mod aio { - use gremlin_client::process::traversal::traversal; - - use super::common::aio::{connect_serializer, create_vertex_with_label, drop_vertices}; - - #[cfg(feature = "async-std-runtime")] - use async_std::prelude::*; - - #[cfg(feature = "tokio-runtime")] - use tokio_stream::StreamExt; - - use gremlin_client::{IoProtocol, Vertex}; - - #[cfg_attr(feature = "async-std-runtime", async_std::test)] - #[cfg_attr(feature = "tokio-runtime", tokio::test)] - async fn test_simple_vertex_traversal_with_multiple_id_v2() { - let client = connect_serializer(IoProtocol::GraphSONV2).await; - drop_vertices(&client, "test_simple_vertex_traversal_async") - .await - .unwrap(); - - let vertex = - create_vertex_with_label(&client, "test_simple_vertex_traversal_async", "Traversal") - .await; - let vertex2 = - create_vertex_with_label(&client, "test_simple_vertex_traversal_async", "Traversal") - .await; - - let g = traversal().with_remote_async(client); - - let results = g - .v(vec![vertex.id(), vertex2.id()]) - .to_list() - .await - .unwrap(); - - assert_eq!(2, results.len()); - - assert_eq!(vertex.id(), results[0].id()); - assert_eq!(vertex2.id(), results[1].id()); - - let has_next = g - .v(()) - .has_label("test_simple_vertex_traversal_async") - .has_next() - .await - .expect("It should return"); - - assert_eq!(true, has_next); - - let next = g - .v(()) - .has_label("test_simple_vertex_traversal_async") - .next() - .await - .expect("It should execute one traversal") - .expect("It should return one element"); - - assert_eq!("test_simple_vertex_traversal_async", next.label()); - - let vertices = g - .v(()) - .has_label("test_simple_vertex_traversal_async") - .iter() - .await - .expect("It should get the iterator") - .collect::, _>>() - .await - .expect("It should collect elements"); - - assert_eq!(2, vertices.len()); - } -} From 1bd393d3bd751605dca6b9253a9d45183b5276cf Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Tue, 12 Nov 2024 14:25:09 -0600 Subject: [PATCH 41/56] Moved merge_capable_serializers to merge_test mod block so its under the related cfg feature --- gremlin-client/tests/integration_traversal.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/gremlin-client/tests/integration_traversal.rs b/gremlin-client/tests/integration_traversal.rs index f3bf4fe4..d3bb7604 100644 --- a/gremlin-client/tests/integration_traversal.rs +++ b/gremlin-client/tests/integration_traversal.rs @@ -24,14 +24,6 @@ use common::io::{ graph_serializer, }; -//GraphSONV2 doesn't appear to support merge steps, so ommit it from -//being one of the serializers tested for those tests -#[template] -#[rstest] -#[case::graphson_v3(graph_serializer(IoProtocol::GraphSONV3))] -#[case::graph_binary_v1(graph_serializer(IoProtocol::GraphBinaryV1))] -fn merge_capable_serializers(#[case] client: GremlinClient) {} - #[template] #[rstest] #[case::graphson_v2(graph_serializer(IoProtocol::GraphSONV2))] @@ -49,6 +41,14 @@ mod merge_tests { }; use std::collections::HashMap; + //GraphSONV2 doesn't appear to support merge steps, so omit it from + //being one of the serializers tested for those tests + #[template] + #[rstest] + #[case::graphson_v3(graph_serializer(IoProtocol::GraphSONV3))] + #[case::graph_binary_v1(graph_serializer(IoProtocol::GraphBinaryV1))] + fn merge_capable_serializers(#[case] client: GremlinClient) {} + #[apply(merge_capable_serializers)] #[serial(test_merge_v_no_options)] fn test_merge_v_no_options(client: GremlinClient) { From 0bd9f9a06e083933cb3fbcf241e577d2787ae784 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Tue, 12 Nov 2024 23:32:41 -0600 Subject: [PATCH 42/56] Consolidated integration_client tests --- gremlin-client/src/conversion.rs | 87 +++- gremlin-client/src/io/graph_binary_v1.rs | 87 +++- gremlin-client/src/io/mod.rs | 2 +- gremlin-client/tests/common.rs | 14 +- gremlin-client/tests/integration_client.rs | 238 +++++---- gremlin-client/tests/integration_client_v2.rs | 466 ------------------ 6 files changed, 306 insertions(+), 588 deletions(-) delete mode 100644 gremlin-client/tests/integration_client_v2.rs diff --git a/gremlin-client/src/conversion.rs b/gremlin-client/src/conversion.rs index 7d2f3fdc..03333ff3 100644 --- a/gremlin-client/src/conversion.rs +++ b/gremlin-client/src/conversion.rs @@ -129,8 +129,6 @@ impl_from_gvalue!(bool, GValue::Bool); impl_from_gvalue!(uuid::Uuid, GValue::Uuid); impl_from_gvalue!(Metric, GValue::Metric); impl_from_gvalue!(TraversalMetrics, GValue::TraversalMetrics); -impl_from_gvalue!(TraversalExplanation, GValue::TraversalExplanation); -impl_from_gvalue!(IntermediateRepr, GValue::IntermediateRepr); impl_from_gvalue!(chrono::DateTime, GValue::Date); impl_from_gvalue!(Traverser, GValue::Traverser); @@ -139,9 +137,90 @@ impl FromGValue for Null { match v { GValue::Null => Ok(crate::structure::Null {}), _ => Err(GremlinError::Cast(format!( - "Cannot convert {:?} to {}", + "Cannot convert {:?} to Null", v, - stringify!($t) + ))), + } + } +} + +impl FromGValue for TraversalExplanation { + fn from_gvalue(v: GValue) -> GremlinResult { + match v { + GValue::TraversalExplanation(traversal_explaination) => Ok(traversal_explaination), + //GraphBinary sends TraversalExplainations as just a map instead of as a distinct type, + //so handle converting it here + GValue::Map(mut map) => { + let original = map.remove("original").ok_or(GremlinError::Cast(format!( + "Missing expected \"original\" TraversalExplaination property" + )))?; + let intermediate = + map.remove("intermediate") + .ok_or(GremlinError::Cast(format!( + "Missing expected \"intermediate\" TraversalExplaination property" + )))?; + let final_ = map.remove("final").ok_or(GremlinError::Cast(format!( + "Missing expected \"final\" TraversalExplaination property" + )))?; + + let original = ::from_gvalue(original)?; + let intermediate = ::from_gvalue(intermediate)?; + let final_ = ::from_gvalue(final_)?; + + let original: GremlinResult> = original + .into_iter() + .map(|val| ::from_gvalue(val)) + .collect(); + let final_: GremlinResult> = final_ + .into_iter() + .map(|val| ::from_gvalue(val)) + .collect(); + let intermediate: GremlinResult> = intermediate + .into_iter() + .map(|val| IntermediateRepr::from_gvalue(val)) + .collect(); + + Ok(TraversalExplanation::new(original?, final_?, intermediate?)) + } + _ => Err(GremlinError::Cast(format!( + "Cannot convert {:?} to TraversalExplanation", + v + ))), + } + } +} + +impl FromGValue for IntermediateRepr { + fn from_gvalue(v: GValue) -> GremlinResult { + match v { + GValue::IntermediateRepr(ir) => Ok(ir), + //GraphBinary sends TraversalExplainations as just a map instead of as a distinct type, + //so handle converting it here + GValue::Map(mut map) => { + let traversal = map.remove("traversal").ok_or(GremlinError::Cast(format!( + "Missing expected \"traversal\" IntermediateRepr property" + )))?; + let category = map.remove("category").ok_or(GremlinError::Cast(format!( + "Missing expected \"category\" IntermediateRepr property" + )))?; + let strategy = map.remove("strategy").ok_or(GremlinError::Cast(format!( + "Missing expected \"strategy\" IntermediateRepr property" + )))?; + + let traversal: GremlinResult> = + ::from_gvalue(traversal)? + .into_iter() + .map(|val| ::from_gvalue(val)) + .collect(); + Ok(IntermediateRepr::new( + traversal?, + ::from_gvalue(strategy)?, + ::from_gvalue(category)?, + )) + } + _ => Err(GremlinError::Cast(format!( + "Cannot convert {:?} to IntermediateRepr", + v ))), } } diff --git a/gremlin-client/src/io/graph_binary_v1.rs b/gremlin-client/src/io/graph_binary_v1.rs index cc48704e..1182fc87 100644 --- a/gremlin-client/src/io/graph_binary_v1.rs +++ b/gremlin-client/src/io/graph_binary_v1.rs @@ -9,8 +9,8 @@ use crate::{ message::{ReponseStatus, Response, ResponseResult}, process::traversal::{Instruction, Order, Scope}, structure::{Column, Direction, Merge, Pop, TextP, Traverser, T}, - Cardinality, Edge, GKey, GValue, GremlinError, GremlinResult, Path, ToGValue, Vertex, - VertexProperty, GID, + Cardinality, Edge, GKey, GValue, GremlinError, GremlinResult, Metric, Path, ToGValue, + TraversalMetrics, Vertex, VertexProperty, GID, }; use super::IoProtocol; @@ -55,6 +55,8 @@ const T: u8 = 0x20; const TRAVERSER: u8 = 0x21; const BOOLEAN: u8 = 0x27; const TEXTP: u8 = 0x28; +const MERTRICS: u8 = 0x2C; +const TRAVERSAL_MERTRICS: u8 = 0x2D; const MERGE: u8 = 0x2E; const UNSPECIFIED_NULL_OBEJECT: u8 = 0xFE; @@ -691,6 +693,18 @@ impl GraphBinaryV1Deser for GValue { .map(|val| GValue::Traverser(val)) .unwrap_or(GValue::Null)) } + BOOLEAN => Ok(match bool::from_be_bytes_nullable(bytes)? { + Some(value) => GValue::Bool(value), + None => GValue::Null, + }), + MERTRICS => Ok(match Metric::from_be_bytes_nullable(bytes)? { + Some(value) => GValue::Metric(value), + None => GValue::Null, + }), + TRAVERSAL_MERTRICS => Ok(match TraversalMetrics::from_be_bytes_nullable(bytes)? { + Some(value) => GValue::TraversalMetrics(value), + None => GValue::Null, + }), UNSPECIFIED_NULL_OBEJECT => { //Need to confirm the null-ness with the next byte being a 1 match bytes.next().cloned() { @@ -707,6 +721,75 @@ impl GraphBinaryV1Deser for GValue { } } +impl GraphBinaryV1Deser for TraversalMetrics { + fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { + //Format: {duration}{metrics} + + //{duration} is a Long describing the duration in nanoseconds + let duration: i64 = GraphBinaryV1Deser::from_be_bytes(bytes)?; + + //{metrics} is a List composed by Metrics items + let metrics: Vec = GraphBinaryV1Deser::from_be_bytes(bytes)?; + + let metrics: Result, GremlinError> = metrics + .into_iter() + .map(|value| Metric::from_gvalue(value)) + .collect(); + + //It doesn't appear documented but assuming the duration unit inherited from GraphSON is ms, so convert here + let duration_ms = duration as f64 / 1_000.0; + Ok(TraversalMetrics::new(duration_ms, metrics?)) + } +} + +impl GraphBinaryV1Deser for Metric { + fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { + //Format: {id}{name}{duration}{counts}{annotations}{nested_metrics} + + //{id} is a String representing the identifier + let id = String::from_be_bytes(bytes)?; + //{name} is a String representing the name + let name = String::from_be_bytes(bytes)?; + + //{duration} is a Long describing the duration in nanoseconds + let duration: i64 = GraphBinaryV1Deser::from_be_bytes(bytes)?; + + //{counts} is a Map composed by String keys and Long values + let mut counts: HashMap = GraphBinaryV1Deser::from_be_bytes(bytes)?; + + //{annotations} is a Map composed by String keys and a value of any type + let mut annotations: HashMap = GraphBinaryV1Deser::from_be_bytes(bytes)?; + + //{nested_metrics} is a List composed by Metrics items + let nested = GraphBinaryV1Deser::from_be_bytes(bytes)?; + + let traverser_count = + i64::from_gvalue(counts.remove(&GKey::from("traverserCount")).ok_or( + GremlinError::Cast(format!("Missing expected traverserCount property")), + )?)?; + + let element_count = i64::from_gvalue(counts.remove(&GKey::from("elementCount")).ok_or( + GremlinError::Cast(format!("Missing expected elementCount property")), + )?)?; + let percent_dur = f64::from_gvalue(annotations.remove(&GKey::from("percentDur")).ok_or( + GremlinError::Cast(format!("Missing expected percentDur property")), + )?)?; + + //It doesn't appear documented but assuming the duration unit inherited from GraphSON is ms, so convert here + let duration_ms = duration as f64 / 1_000.0; + + Ok(Metric::new( + id, + name, + duration_ms, + element_count, + traverser_count, + percent_dur, + nested, + )) + } +} + impl GraphBinaryV1Deser for T { fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { let literal = GValue::from_be_bytes(bytes)?; diff --git a/gremlin-client/src/io/mod.rs b/gremlin-client/src/io/mod.rs index 926a8afd..846c46d9 100644 --- a/gremlin-client/src/io/mod.rs +++ b/gremlin-client/src/io/mod.rs @@ -19,7 +19,7 @@ use uuid::Uuid; use crate::{io::graph_binary_v1::GraphBinaryV1Ser, GKey, GremlinError, GremlinResult, Message}; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub enum IoProtocol { GraphSONV2, GraphSONV3, diff --git a/gremlin-client/tests/common.rs b/gremlin-client/tests/common.rs index 099a2c75..122a908a 100644 --- a/gremlin-client/tests/common.rs +++ b/gremlin-client/tests/common.rs @@ -1,4 +1,5 @@ use gremlin_client::{structure::T, Map}; +use rstest_reuse::template; pub fn assert_map_property(element_map: &Map, expected_key: &str, expected_value: &str) { let actual_prop_value: &String = element_map @@ -15,6 +16,13 @@ pub fn assert_map_property(element_map: &Map, expected_key: &str, expected_value assert_eq!(expected_value, actual_prop_value); } +#[template] +#[rstest] +#[case::graphson_v2(IoProtocol::GraphSONV2)] +#[case::graphson_v3(IoProtocol::GraphSONV3)] +#[case::graph_binary_v1(IoProtocol::GraphBinaryV1)] +fn serializers(#[case] protocol: IoProtocol) {} + #[allow(dead_code)] pub mod io { use gremlin_client::{ @@ -57,12 +65,6 @@ pub mod io { connect_serializer(serializer).expect("It should connect") } - pub fn graph() -> GremlinClient { - let client = expect_client(); - - client - } - pub fn graph_serializer(serializer: IoProtocol) -> GremlinClient { let client = expect_client_serializer(serializer); diff --git a/gremlin-client/tests/integration_client.rs b/gremlin-client/tests/integration_client.rs index 8232f4c0..db2577a0 100644 --- a/gremlin-client/tests/integration_client.rs +++ b/gremlin-client/tests/integration_client.rs @@ -6,32 +6,31 @@ use std::iter::FromIterator; use chrono::{offset::TimeZone, DateTime, Utc}; use gremlin_client::{ ConnectionOptions, GremlinClient, GremlinError, List, TlsOptions, ToGValue, - TraversalExplanation, TraversalMetrics, VertexProperty, + TraversalExplanation, TraversalMetrics, VertexProperty, GID, }; -use gremlin_client::{Edge, GValue, Map, Vertex}; +use gremlin_client::{Edge, GValue, IoProtocol, Map, Vertex}; -use common::io::{create_edge, create_vertex, expect_client, graph}; +use common::io::{create_edge, create_vertex, graph_serializer}; +use rstest::*; +use rstest_reuse::apply; +use serial_test::serial; -#[test] -fn test_client_connection_ok() { - expect_client(); -} - -#[test] -fn test_empty_query() { +#[apply(common::serializers)] +fn test_empty_query(protocol: IoProtocol) { + let client = graph_serializer(protocol); assert_eq!( 0, - graph() + client .execute("g.V().hasLabel('NotFound')", &[]) .expect("It should execute a traversal") .count() ) } -#[test] -fn test_session_empty_query() { - let mut graph = graph(); - let mut sessioned_graph = graph +#[apply(common::serializers)] +#[serial(test_session_empty_query)] +fn test_session_empty_query(protocol: IoProtocol) { + let mut sessioned_graph = graph_serializer(protocol) .create_session("test-session".to_string()) .expect("It should create a session."); assert_eq!( @@ -46,8 +45,8 @@ fn test_session_empty_query() { .expect("It should close the session."); } -#[test] -fn test_ok_credentials() { +#[apply(common::serializers)] +fn test_ok_credentials(#[case] protocol: IoProtocol) { let client = GremlinClient::connect( ConnectionOptions::builder() .host("localhost") @@ -57,6 +56,8 @@ fn test_ok_credentials() { .tls_options(TlsOptions { accept_invalid_certs: true, }) + .serializer(protocol.clone()) + .deserializer(protocol) .build(), ) .expect("Cannot connect"); @@ -65,8 +66,8 @@ fn test_ok_credentials() { assert!(result.is_ok(), "{:?}", result); } -#[test] -fn test_ko_credentials() { +#[apply(common::serializers)] +fn test_ko_credentials(#[case] protocol: IoProtocol) { let client = GremlinClient::connect( ConnectionOptions::builder() .host("localhost") @@ -76,6 +77,8 @@ fn test_ko_credentials() { .tls_options(TlsOptions { accept_invalid_certs: true, }) + .serializer(protocol.clone()) + .deserializer(protocol) .build(), ) .expect("Cannot connect"); @@ -84,9 +87,9 @@ fn test_ko_credentials() { assert!(result.is_err(), "{:?}", result); } -#[test] -fn test_wrong_query() { - let error = graph() +#[apply(common::serializers)] +fn test_wrong_query(protocol: IoProtocol) { + let error = graph_serializer(protocol) .execute("g.V", &[]) .expect_err("it should return an error"); @@ -99,9 +102,9 @@ fn test_wrong_query() { } } -#[test] -fn test_wrong_alias() { - let error = graph() +#[apply(common::serializers)] +fn test_wrong_alias(protocol: IoProtocol) { + let error = graph_serializer(protocol) .alias("foo") .execute("g.V()", &[]) .expect_err("it should return an error"); @@ -115,11 +118,9 @@ fn test_wrong_alias() { } } -#[test] - -fn test_vertex_query() { - let graph = graph(); - let vertices = graph +#[apply(common::serializers)] +fn test_vertex_query(protocol: IoProtocol) { + let vertices = graph_serializer(protocol) .execute( "g.V().hasLabel('person').has('name',name)", &[("name", &"marko")], @@ -132,10 +133,10 @@ fn test_vertex_query() { assert_eq!("person", vertices[0].label()); } -#[test] -fn test_edge_query() { - let graph = graph(); - let edges = graph + +#[apply(common::serializers)] +fn test_edge_query(protocol: IoProtocol) { + let edges = graph_serializer(protocol) .execute("g.E().hasLabel('knows').limit(1)", &[]) .expect("it should execute a query") .filter_map(Result::ok) @@ -146,14 +147,14 @@ fn test_edge_query() { assert_eq!("knows", edges[0].label()); } -#[test] -fn test_vertex_creation() { - let graph = graph(); - let mark = create_vertex(&graph, "mark"); +#[apply(common::serializers)] +fn test_vertex_creation(protocol: IoProtocol) { + let client = graph_serializer(protocol); + let mark = create_vertex(&client, "mark"); assert_eq!("person", mark.label()); - let value_map = graph + let value_map = client .execute("g.V(identity).valueMap()", &[("identity", mark.id())]) .expect("should fetch valueMap with properties") .filter_map(Result::ok) @@ -169,10 +170,9 @@ fn test_vertex_creation() { ); } -#[test] -fn test_complex_vertex_creation_with_option_none_properties() { - let graph = graph(); - let properties = graph +#[apply(common::serializers)] +fn test_complex_vertex_creation_with_option_none_properties(protocol: IoProtocol) { + let properties = graph_serializer(protocol) .execute(r#"g.addV('person').valueMap()"#, &[]) .expect("it should execute addV") .filter_map(Result::ok) @@ -216,9 +216,12 @@ fn test_complex_vertex_creation_with_option_none_properties() { .is_none()); } -#[test] -fn test_complex_vertex_creation_with_option_some_properties() { - let graph = graph(); +#[apply(common::serializers)] +fn test_complex_vertex_creation_with_option_some_properties(protocol: IoProtocol) { + //GraphSON V2 doesn't have maps, so skip it + if protocol == IoProtocol::GraphSONV2 { + return; + } let q = r#" g.addV('person') .property('name',name) @@ -242,7 +245,7 @@ fn test_complex_vertex_creation_with_option_some_properties() { ("uuid", &uuid), ("date", &now), ]; - let properties = graph + let properties = graph_serializer(protocol) .execute(q, params) .expect("it should execute addV") .filter_map(Result::ok) @@ -313,10 +316,8 @@ fn test_complex_vertex_creation_with_option_some_properties() { ); } -#[test] -fn test_complex_vertex_creation_with_properties() { - let graph = graph(); - +#[apply(common::serializers)] +fn test_complex_vertex_creation_with_properties(protocol: IoProtocol) { let q = r#" g.addV('person') .property('id',UUID.randomUUID()) @@ -339,7 +340,7 @@ fn test_complex_vertex_creation_with_properties() { ("dateTime", &chrono::Utc.timestamp(1551825863, 0)), ("date", &(1551825863 as i64)), ]; - let results = graph + let results = graph_serializer(protocol) .execute(q, params) .expect("it should execute addV") .filter_map(Result::ok) @@ -421,20 +422,18 @@ fn test_complex_vertex_creation_with_properties() { ); } -#[test] -fn test_inserting_date_with_milisecond_precision() { +#[apply(common::serializers)] +fn test_inserting_date_with_milisecond_precision(protocol: IoProtocol) { use chrono::offset::TimeZone; use chrono::DateTime; use chrono::Utc; - let graph = graph(); - let q = r#"g.addV('person').property('dateTime',dateTime).propertyMap()"#; let expected = chrono::Utc.timestamp(1551825863, 0); let params: &[(&str, &dyn ToGValue)] = &[("dateTime", &expected)]; - let results = graph + let results = graph_serializer(protocol) .execute(q, params) .expect("it should execute addV") .filter_map(Result::ok) @@ -456,10 +455,13 @@ fn test_inserting_date_with_milisecond_precision() { ); } -#[test] -fn test_list_cardinality() { - let graph = graph(); - +#[apply(common::serializers)] +fn test_list_cardinality(protocol: IoProtocol) { + //GraphSON V2 doesn't have lists, so skip it + if protocol == IoProtocol::GraphSONV2 { + return; + } + let client = graph_serializer(protocol); //split into 2 queries due to the bindings limit let q1 = r#" @@ -545,7 +547,7 @@ fn test_list_cardinality() { ("bool_4", &true), ]; - let results1 = graph + let results1 = client .execute(q1, params1) .expect("it should execute addV") .filter_map(Result::ok) @@ -566,7 +568,7 @@ fn test_list_cardinality() { let f32_list = properties1["float1"].clone().take::>().unwrap(); assert_eq!(f32_list, vec![1.1, 1.1, 2.3, 3.4]); - let results2 = graph + let results2 = client .execute(q2, params2) .expect("it should execute addV") .filter_map(Result::ok) @@ -595,10 +597,13 @@ fn test_list_cardinality() { assert_eq!(boolean_list, vec![false, true, false, true]); } -#[test] -fn test_set_cardinality() { - let graph = graph(); - +#[apply(common::serializers)] +fn test_set_cardinality(protocol: IoProtocol) { + //GraphSON V2 doesn't have sets, so skip it + if protocol == IoProtocol::GraphSONV2 { + return; + } + let client = graph_serializer(protocol); //split into 2 queries due to the bindings limit let q1 = r#" @@ -667,7 +672,7 @@ fn test_set_cardinality() { ("bool_4", &true), ]; - let results1 = graph + let results1 = client .execute(q1, params1) .expect("it should execute addV") .filter_map(Result::ok) @@ -705,7 +710,7 @@ fn test_set_cardinality() { .unwrap(); assert_eq!(i64_set, HashSet::from_iter(vec![4, 5, 6].iter().cloned())); - let results2 = graph + let results2 = client .execute(q2, params2) .expect("it should execute addV") .filter_map(Result::ok) @@ -743,20 +748,20 @@ fn test_set_cardinality() { ); } -#[test] -fn test_edge_creation() { - let graph = graph(); - let mark = create_vertex(&graph, "mark"); - let frank = create_vertex(&graph, "frank"); +#[apply(common::serializers)] +fn test_edge_creation(protocol: IoProtocol) { + let client = graph_serializer(protocol); + let mark = create_vertex(&client, "mark"); + let frank = create_vertex(&client, "frank"); - let edge = create_edge(&graph, &mark, &frank, "knows"); + let edge = create_edge(&client, &mark, &frank, "knows"); assert_eq!("knows", edge.label()); assert_eq!(&mark, edge.out_v()); assert_eq!(&frank, edge.in_v()); - let edges = graph + let edges = client .execute("g.V(identity).outE()", &[("identity", mark.id())]) .expect("should fetch edge") .filter_map(Result::ok) @@ -774,11 +779,9 @@ fn test_edge_creation() { assert_eq!(&frank, edge.in_v()); } -#[test] -fn test_profile() { - let graph = graph(); - - let metrics = graph +#[apply(common::serializers)] +fn test_profile(protocol: IoProtocol) { + let metrics = graph_serializer(protocol) .execute("g.V().limit(1).profile()", &[]) .expect("should return a profile") .filter_map(Result::ok) @@ -806,11 +809,9 @@ fn test_profile() { ); } -#[test] -fn test_explain() { - let graph = graph(); - - let metrics = graph +#[apply(common::serializers)] +fn test_explain(protocol: IoProtocol) { + let metrics = graph_serializer(protocol) .execute("g.V().limit(1).explain()", &[]) .expect("should return a profile") .filter_map(Result::ok) @@ -840,16 +841,15 @@ fn test_explain() { ); } -#[test] - -fn test_group_count_vertex() { - let graph = graph(); - let mark = create_vertex(&graph, "mark"); - let frank = create_vertex(&graph, "frank"); +#[apply(common::serializers)] +fn test_group_count_vertex(protocol: IoProtocol) { + let client = graph_serializer(protocol.clone()); + let mark = create_vertex(&client, "mark"); + let frank = create_vertex(&client, "frank"); - create_edge(&graph, &mark, &frank, "knows"); + create_edge(&client, &mark, &frank, "knows"); - let map = graph + let map = client .execute( "g.V(identity).out().groupCount()", &[("identity", mark.id())], @@ -866,21 +866,31 @@ fn test_group_count_vertex() { assert_eq!(1, first.len()); - let count = first.get(&frank); + let count = if protocol == IoProtocol::GraphSONV2 { + //GraphSONV2 just sends a simplified map, + //so we need to look up by the edge's id as a simplified type + //instead of as a GID + first.get(match frank.id() { + GID::String(s) => s.to_string(), + GID::Int32(i) => i.to_string(), + GID::Int64(i) => i.to_string(), + }) + } else { + first.get(&frank) + }; assert_eq!(Some(&GValue::Int64(1)), count); } -#[test] - -fn test_group_count_edge() { - let graph = graph(); - let mark = create_vertex(&graph, "mark"); - let frank = create_vertex(&graph, "frank"); +#[apply(common::serializers)] +fn test_group_count_edge(protocol: IoProtocol) { + let client = graph_serializer(protocol.clone()); + let mark = create_vertex(&client, "mark"); + let frank = create_vertex(&client, "frank"); - let edge = create_edge(&graph, &mark, &frank, "knows"); + let edge = create_edge(&client, &mark, &frank, "knows"); - let map = graph + let map = client .execute( "g.V(identity).outE().groupCount()", &[("identity", mark.id())], @@ -897,15 +907,25 @@ fn test_group_count_edge() { assert_eq!(1, first.len()); - let count = first.get(&edge); + let count = if protocol == IoProtocol::GraphSONV2 { + //GraphSONV2 just sends a simplified map, + //so we need to look up by the edge's id as a simplified type + //instead of as a GID + first.get(match edge.id() { + GID::String(s) => s.to_string(), + GID::Int32(i) => i.to_string(), + GID::Int64(i) => i.to_string(), + }) + } else { + first.get(&edge) + }; assert_eq!(Some(&GValue::Int64(1)), count); } -#[test] +#[apply(common::serializers)] #[cfg(feature = "derive")] -fn test_vertex_mapping() { - let graph = graph(); +fn test_vertex_mapping(protocol: IoProtocol) { use gremlin_client::derive::FromGValue; use std::convert::TryFrom; @@ -930,7 +950,7 @@ fn test_vertex_mapping() { ("dateTime", &chrono::Utc.timestamp(1551825863, 0)), ("date", &(1551825863 as i64)), ]; - let mark = graph + let mark = client .execute(q, params) .expect("should create a vertex") .filter_map(Result::ok) @@ -948,7 +968,7 @@ fn test_vertex_mapping() { assert_eq!("person", mark[0].label()); - let value_map = graph + let value_map = client .execute("g.V(identity).valueMap()", &[("identity", mark[0].id())]) .expect("should fetch valueMap with properties") .filter_map(Result::ok) diff --git a/gremlin-client/tests/integration_client_v2.rs b/gremlin-client/tests/integration_client_v2.rs deleted file mode 100644 index bdc3f966..00000000 --- a/gremlin-client/tests/integration_client_v2.rs +++ /dev/null @@ -1,466 +0,0 @@ -mod common; - -use gremlin_client::{ - ConnectionOptions, GremlinClient, GremlinError, IoProtocol, List, TlsOptions, ToGValue, - TraversalExplanation, TraversalMetrics, VertexProperty, -}; -use gremlin_client::{Edge, GKey, GValue, Map, Vertex, GID}; - -use common::io::{create_edge, create_vertex, expect_client_serializer, graph_serializer}; - -#[test] -fn test_client_connection_ok_v2() { - expect_client_serializer(IoProtocol::GraphSONV2); -} - -#[test] -fn test_empty_query_v2() { - assert_eq!( - 0, - graph_serializer(IoProtocol::GraphSONV2) - .execute("g.V().hasLabel('NotFound')", &[]) - .expect("It should execute a traversal") - .count() - ) -} - -#[test] -fn test_ok_credentials_v2() { - let client = GremlinClient::connect( - ConnectionOptions::builder() - .host("localhost") - .port(8183) - .credentials("stephen", "password") - .ssl(true) - .tls_options(TlsOptions { - accept_invalid_certs: true, - }) - .serializer(IoProtocol::GraphSONV2) - .deserializer(IoProtocol::GraphSONV2) - .build(), - ) - .expect("Cannot connect"); - - let result = client.execute("g.V().limit(1)", &[]); - assert!(result.is_ok(), "{:?}", result); -} - -#[test] -fn test_ko_credentials_v2() { - let client = GremlinClient::connect( - ConnectionOptions::builder() - .host("localhost") - .port(8183) - .credentials("stephen", "pwd") - .ssl(true) - .tls_options(TlsOptions { - accept_invalid_certs: true, - }) - .serializer(IoProtocol::GraphSONV2) - .build(), - ) - .expect("Cannot connect"); - - let result = client.execute("g.V().limit(1)", &[]); - assert!(result.is_err(), "{:?}", result); -} - -#[test] -fn test_wrong_query_v2() { - let error = graph_serializer(IoProtocol::GraphSONV2) - .execute("g.V", &[]) - .expect_err("it should return an error"); - - match error { - GremlinError::Request((code, message)) => { - assert_eq!(597, code); - assert_eq!("No such property: V for class: org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource",message) - } - _ => panic!("wrong error type"), - } -} - -#[test] -fn test_wrong_alias_v2() { - let error = graph_serializer(IoProtocol::GraphSONV2) - .alias("foo") - .execute("g.V()", &[]) - .expect_err("it should return an error"); - - match error { - GremlinError::Request((code, message)) => { - assert_eq!(499, code); - assert_eq!("Could not alias [g] to [foo] as [foo] not in the Graph or TraversalSource global bindings",message) - } - _ => panic!("wrong error type"), - } -} - -#[test] - -fn test_vertex_query_v2() { - let graph = graph_serializer(IoProtocol::GraphSONV2); - let vertices = graph - .execute( - "g.V().hasLabel('person').has('name',name)", - &[("name", &"marko")], - ) - .expect("it should execute a query") - .filter_map(Result::ok) - .map(|f| f.take::()) - .collect::, _>>() - .expect("It should be ok"); - - assert_eq!("person", vertices[0].label()); -} -#[test] -fn test_edge_query_v2() { - let graph = graph_serializer(IoProtocol::GraphSONV2); - let edges = graph - .execute("g.E().hasLabel('knows').limit(1)", &[]) - .expect("it should execute a query") - .filter_map(Result::ok) - .map(|f| f.take::()) - .collect::, _>>() - .expect("It should be ok"); - - assert_eq!("knows", edges[0].label()); -} - -#[test] -fn test_vertex_creation_v2() { - let graph = graph_serializer(IoProtocol::GraphSONV2); - let mark = create_vertex(&graph, "mark"); - - assert_eq!("person", mark.label()); - - let value_map = graph - .execute("g.V(identity).valueMap()", &[("identity", mark.id())]) - .expect("should fetch valueMap with properties") - .filter_map(Result::ok) - .map(|f| f.take::()) - .collect::, _>>() - .expect("It should be ok"); - - assert_eq!(1, value_map.len()); - - assert_eq!( - Some(&GValue::List(vec![String::from("mark").into()].into())), - value_map[0].get("name") - ); -} - -#[test] -fn test_inserting_date_with_milisecond_precision() { - use chrono::offset::TimeZone; - use chrono::DateTime; - use chrono::Utc; - - let graph = graph_serializer(IoProtocol::GraphSONV2); - - let q = r#"g.addV('person').property('dateTime',dateTime).propertyMap()"#; - - let expected = chrono::Utc.timestamp(1551825863, 0); - let params: &[(&str, &dyn ToGValue)] = &[("dateTime", &expected)]; - - let results = graph - .execute(q, params) - .expect("it should execute addV") - .filter_map(Result::ok) - .map(|f| f.take::()) - .collect::, _>>() - .expect("It should be ok"); - - let properties = &results[0]; - assert_eq!(1, properties.len()); - - assert_eq!( - &expected, - properties["dateTime"].get::().unwrap()[0] - .get::() - .unwrap() - .get::>() - .unwrap() - ); -} - -#[test] -fn test_complex_vertex_creation_with_properties_v2() { - use chrono::offset::TimeZone; - - let graph = graph_serializer(IoProtocol::GraphSONV2); - - let q = r#" - g.addV('person') - .property('id',UUID.randomUUID()) - .property('name',name) - .property('age',age) - .property('time',time) - .property('score',score) - .property('uuid',uuid) - .property('date',new Date(date)) - .property('dateTime',dateTime) - .propertyMap()"#; - - let uuid = uuid::Uuid::new_v4(); - let params: &[(&str, &dyn ToGValue)] = &[ - ("age", &22), - ("time", &(22 as i64)), - ("name", &"mark"), - ("score", &3.2), - ("uuid", &uuid), - ("dateTime", &chrono::Utc.timestamp(1551825863, 0)), - ("date", &(1551825863 as i64)), - ]; - let results = graph - .execute(q, params) - .expect("it should execute addV") - .filter_map(Result::ok) - .map(|f| f.take::()) - .collect::, _>>() - .expect("It should be ok"); - - let properties = &results[0]; - - assert_eq!(8, properties.len()); - - assert_eq!( - &22, - properties["age"].get::().unwrap()[0] - .get::() - .unwrap() - .get::() - .unwrap() - ); - - assert_eq!( - &22, - properties["time"].get::().unwrap()[0] - .get::() - .unwrap() - .get::() - .unwrap() - ); - - assert_eq!( - &chrono::Utc.timestamp_millis(1551825863), - properties["date"].get::().unwrap()[0] - .get::() - .unwrap() - .get::>() - .unwrap() - ); - - assert!(properties["id"].get::().unwrap()[0] - .get::() - .unwrap() - .get::() - .is_ok()); - - assert_eq!( - &uuid, - properties["uuid"].get::().unwrap()[0] - .get::() - .unwrap() - .get::() - .unwrap() - ); - - assert_eq!( - &String::from("mark"), - properties["name"].get::().unwrap()[0] - .get::() - .unwrap() - .get::() - .unwrap() - ); - - assert_eq!( - &3.2, - properties["score"].get::().unwrap()[0] - .get::() - .unwrap() - .get::() - .unwrap() - ); - - assert_eq!( - &chrono::Utc.timestamp(1551825863, 0), - properties["dateTime"].get::().unwrap()[0] - .get::() - .unwrap() - .get::>() - .unwrap() - ); -} - -#[test] -fn test_edge_creation_v2() { - let graph = graph_serializer(IoProtocol::GraphSONV2); - let mark = create_vertex(&graph, "mark"); - let frank = create_vertex(&graph, "frank"); - - let edge = create_edge(&graph, &mark, &frank, "knows"); - - assert_eq!("knows", edge.label()); - - assert_eq!(&mark, edge.out_v()); - assert_eq!(&frank, edge.in_v()); - - let edges = graph - .execute("g.V(identity).outE()", &[("identity", mark.id())]) - .expect("should fetch edge") - .filter_map(Result::ok) - .map(|f| f.take::()) - .collect::, _>>() - .expect("It should be ok"); - - assert_eq!(1, edges.len()); - - let edge = &edges[0]; - - assert_eq!("knows", edge.label()); - - assert_eq!(&mark, edge.out_v()); - assert_eq!(&frank, edge.in_v()); -} - -#[test] -fn test_profile_v2() { - let graph = graph_serializer(IoProtocol::GraphSONV2); - - let metrics = graph - .execute("g.V().limit(1).profile()", &[]) - .expect("should return a profile") - .filter_map(Result::ok) - .map(|f| f.take::()) - .collect::, _>>() - .expect("It should be ok"); - - assert_eq!(1, metrics.len()); - - let t = &metrics[0]; - - assert_eq!(true, t.duration() > &0.0); - - let steps = t.metrics(); - - assert_ne!(0, steps.len()); - - assert_eq!( - 100.0, - steps - .iter() - .map(|s| s.perc_duration()) - .fold(0.0, |acc, x| acc + x) - .round() - ); -} - -#[test] -fn test_explain_v2() { - let graph = graph_serializer(IoProtocol::GraphSONV2); - - let metrics = graph - .execute("g.V().limit(1).explain()", &[]) - .expect("should return a profile") - .filter_map(Result::ok) - .map(|f| f.take::()) - .collect::, _>>() - .expect("It should be ok"); - - assert_eq!(1, metrics.len()); - - let t = &metrics[0]; - - assert_eq!( - &vec![ - String::from("GraphStep(vertex,[])"), - String::from("RangeGlobalStep(0,1)") - ], - t.original() - ); - - assert_eq!( - &vec![ - String::from("TinkerGraphStep(vertex,[])"), - String::from("RangeGlobalStep(0,1)"), - String::from("ReferenceElementStep") - ], - t.final_t() - ); -} - -#[test] - -fn test_group_count_vertex_v2() { - let graph = graph_serializer(IoProtocol::GraphSONV2); - let mark = create_vertex(&graph, "mark"); - let frank = create_vertex(&graph, "frank"); - - println!("FRANK: {:#?}", frank); - - create_edge(&graph, &mark, &frank, "knows"); - - let map = graph - .execute( - "g.V(identity).out().groupCount()", - &[("identity", mark.id())], - ) - .expect("should fetch a groupCount") - .filter_map(Result::ok) - .map(|f| f.take::()) - .collect::, _>>() - .expect("It should be ok"); - - println!("MAP IS: {:#?}", map); - - assert_eq!(1, map.len()); - - let first = &map[0]; - - assert_eq!(1, first.len()); - - let count = first.get(GKey::String(match frank.id() { - GID::String(s) => s.to_string(), - GID::Int32(i) => i.to_string(), - GID::Int64(i) => i.to_string(), - })); - - assert_eq!(Some(&GValue::Int64(1)), count); -} - -#[test] - -fn test_group_count_edge_v2() { - let graph = graph_serializer(IoProtocol::GraphSONV2); - let mark = create_vertex(&graph, "mark"); - let frank = create_vertex(&graph, "frank"); - - let edge = create_edge(&graph, &mark, &frank, "knows"); - - let map = graph - .execute( - "g.V(identity).outE().groupCount()", - &[("identity", mark.id())], - ) - .expect("should fetch a groupCount") - .filter_map(Result::ok) - .map(|f| f.take::()) - .collect::, _>>() - .expect("It should be ok"); - - assert_eq!(1, map.len()); - - let first = &map[0]; - - assert_eq!(1, first.len()); - - let count = first.get(GKey::String(match edge.id() { - GID::String(s) => s.to_string(), - GID::Int32(i) => i.to_string(), - GID::Int64(i) => i.to_string(), - })); - - assert_eq!(Some(&GValue::Int64(1)), count); -} From ae34c0005a67776bbc5415e39ea7e94611c55447 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Tue, 12 Nov 2024 23:34:32 -0600 Subject: [PATCH 43/56] Centralized integration_traversal_async --- gremlin-client/tests/integration_traversal_async.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/gremlin-client/tests/integration_traversal_async.rs b/gremlin-client/tests/integration_traversal_async.rs index fffc0ca1..8db61b48 100644 --- a/gremlin-client/tests/integration_traversal_async.rs +++ b/gremlin-client/tests/integration_traversal_async.rs @@ -6,7 +6,7 @@ mod aio { use rstest_reuse::{self, *}; use serial_test::serial; - + use gremlin_client::{aio::GremlinClient, process::traversal::traversal}; use super::common::aio::{connect_serializer, create_vertex_with_label, drop_vertices}; @@ -17,7 +17,7 @@ mod aio { #[cfg(feature = "tokio-runtime")] use tokio_stream::StreamExt; - use gremlin_client::{Vertex, IoProtocol}; + use gremlin_client::{IoProtocol, Vertex}; #[rstest] #[case::graphson_v2(connect_serializer(IoProtocol::GraphSONV2))] @@ -27,7 +27,11 @@ mod aio { #[cfg_attr(feature = "async-std-runtime", async_std::test)] #[cfg_attr(feature = "tokio-runtime", tokio::test)] #[serial(test_simple_vertex_traversal_with_multiple_id)] - async fn test_simple_vertex_traversal_with_multiple_id(#[future] #[case] client: GremlinClient) { + async fn test_simple_vertex_traversal_with_multiple_id( + #[future] + #[case] + client: GremlinClient, + ) { drop_vertices(&client, "test_simple_vertex_traversal_async") .await .unwrap(); From aacd28486a5ae92bf60a6f7852f18fd6bbb35a19 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Tue, 12 Nov 2024 23:49:53 -0600 Subject: [PATCH 44/56] Consolidated to serializers templated in common test module --- gremlin-client/tests/integration_traversal.rs | 392 +++++++++++------- 1 file changed, 244 insertions(+), 148 deletions(-) diff --git a/gremlin-client/tests/integration_traversal.rs b/gremlin-client/tests/integration_traversal.rs index d3bb7604..28d980b8 100644 --- a/gremlin-client/tests/integration_traversal.rs +++ b/gremlin-client/tests/integration_traversal.rs @@ -9,7 +9,7 @@ use gremlin_client::structure::{ }; use gremlin_client::{ - utils, BorrowFromGValue, GKey, GValue, GremlinClient, GremlinError, IoProtocol, + utils, BorrowFromGValue, GKey, GValue, GremlinError, IoProtocol, }; mod common; @@ -24,13 +24,6 @@ use common::io::{ graph_serializer, }; -#[template] -#[rstest] -#[case::graphson_v2(graph_serializer(IoProtocol::GraphSONV2))] -#[case::graphson_v3(graph_serializer(IoProtocol::GraphSONV3))] -#[case::graph_binary_v1(graph_serializer(IoProtocol::GraphBinaryV1))] -fn serializers(#[case] client: GremlinClient) {} - #[cfg(feature = "merge_tests")] mod merge_tests { use super::*; @@ -41,17 +34,15 @@ mod merge_tests { }; use std::collections::HashMap; - //GraphSONV2 doesn't appear to support merge steps, so omit it from - //being one of the serializers tested for those tests - #[template] - #[rstest] - #[case::graphson_v3(graph_serializer(IoProtocol::GraphSONV3))] - #[case::graph_binary_v1(graph_serializer(IoProtocol::GraphBinaryV1))] - fn merge_capable_serializers(#[case] client: GremlinClient) {} - - #[apply(merge_capable_serializers)] + #[apply(common::serializers)] #[serial(test_merge_v_no_options)] - fn test_merge_v_no_options(client: GremlinClient) { + fn test_merge_v_no_options(protocol: IoProtocol) { + if protocol == IoProtocol::GraphSONV2 { + //GraphSONV2 doesn't support the non-string key of the merge step, + //so skip it in testing + return; + } + let client = graph_serializer(protocol); let test_vertex_label = "test_merge_v_no_options"; drop_vertices(&client, test_vertex_label) .expect("Failed to drop vertices in case of rerun"); @@ -84,9 +75,15 @@ mod merge_tests { assert_map_property(&vertex_properties, "propertyKey", "propertyValue"); } - #[apply(merge_capable_serializers)] + #[apply(common::serializers)] #[serial(test_merge_v_options)] - fn test_merge_v_options(client: GremlinClient) { + fn test_merge_v_options(protocol: IoProtocol) { + if protocol == IoProtocol::GraphSONV2 { + //GraphSONV2 doesn't support the non-string key of the merge step, + //so skip it in testing + return; + } + let client = graph_serializer(protocol); let expected_label = "test_merge_v_options"; drop_vertices(&client, expected_label).expect("Failed to drop vertices"); let g = traversal().with_remote(client); @@ -135,9 +132,15 @@ mod merge_tests { assert_map_property(&on_match_vertex_map, prop_key, expected_on_match_prop_value); } - #[apply(merge_capable_serializers)] + #[apply(common::serializers)] #[serial(test_merge_v_start_step)] - fn test_merge_v_start_step(client: GremlinClient) { + fn test_merge_v_start_step(protocol: IoProtocol) { + if protocol == IoProtocol::GraphSONV2 { + //GraphSONV2 doesn't support the non-string key of the merge step, + //so skip it in testing + return; + } + let client = graph_serializer(protocol); let expected_label = "test_merge_v_start_step"; drop_vertices(&client, &expected_label).expect("Failed to drop vertiecs"); let g = traversal().with_remote(client); @@ -152,9 +155,15 @@ mod merge_tests { assert_eq!(expected_label, actual_vertex.label()) } - #[apply(merge_capable_serializers)] + #[apply(common::serializers)] #[serial(test_merge_v_anonymous_traversal)] - fn test_merge_v_anonymous_traversal(client: GremlinClient) { + fn test_merge_v_anonymous_traversal(protocol: IoProtocol) { + if protocol == IoProtocol::GraphSONV2 { + //GraphSONV2 doesn't support the non-string key of the merge step, + //so skip it in testing + return; + } + let client = graph_serializer(protocol); let expected_label = "test_merge_v_anonymous_traversal"; drop_vertices(&client, &expected_label).expect("Failed to drop vertiecs"); let g = traversal().with_remote(client); @@ -170,9 +179,15 @@ mod merge_tests { assert_eq!(expected_label, actual_vertex.label()) } - #[apply(merge_capable_serializers)] + #[apply(common::serializers)] #[serial(test_merge_e_start_step)] - fn test_merge_e_start_step(client: GremlinClient) { + fn test_merge_e_start_step(protocol: IoProtocol) { + if protocol == IoProtocol::GraphSONV2 { + //GraphSONV2 doesn't support the non-string key of the merge step, + //so skip it in testing + return; + } + let client = graph_serializer(protocol); 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"; @@ -239,9 +254,15 @@ mod merge_tests { assert_eq!(outgoing_vertex_id, &vertex_b.id().to_gvalue()); } - #[apply(merge_capable_serializers)] + #[apply(common::serializers)] #[serial(test_merge_e_no_options)] - fn test_merge_e_no_options(client: GremlinClient) { + fn test_merge_e_no_options(protocol: IoProtocol) { + if protocol == IoProtocol::GraphSONV2 { + //GraphSONV2 doesn't support the non-string key of the merge step, + //so skip it in testing + return; + } + let client = graph_serializer(protocol); 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"; @@ -310,9 +331,15 @@ mod merge_tests { assert_eq!(outgoing_vertex_id, &vertex_b.id().to_gvalue()); } - #[apply(merge_capable_serializers)] + #[apply(common::serializers)] #[serial(test_merge_e_options)] - fn test_merge_e_options(client: GremlinClient) { + fn test_merge_e_options(protocol: IoProtocol) { + if protocol == IoProtocol::GraphSONV2 { + //GraphSONV2 doesn't support the non-string key of the merge step, + //so skip it in testing + return; + } + let client = graph_serializer(protocol); 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"; @@ -380,9 +407,15 @@ mod merge_tests { ); } - #[apply(merge_capable_serializers)] + #[apply(common::serializers)] #[serial(test_merge_e_anonymous_traversal)] - fn test_merge_e_anonymous_traversal(client: GremlinClient) { + fn test_merge_e_anonymous_traversal(protocol: IoProtocol) { + if protocol == IoProtocol::GraphSONV2 { + //GraphSONV2 doesn't support the non-string key of the merge step, + //so skip it in testing + return; + } + let client = graph_serializer(protocol); 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"); @@ -437,9 +470,15 @@ mod merge_tests { assert_eq!(outgoing_vertex_id, &vertex_b.id().to_gvalue()); } - #[apply(merge_capable_serializers)] + #[apply(common::serializers)] #[serial(test_merge_v_into_merge_e)] - fn test_merge_v_into_merge_e(client: GremlinClient) { + fn test_merge_v_into_merge_e(protocol: IoProtocol) { + if protocol == IoProtocol::GraphSONV2 { + //GraphSONV2 doesn't support the non-string key of the merge step, + //so skip it in testing + return; + } + let client = graph_serializer(protocol); //Based on the reference doc's combo example let expected_vertex_label = "test_merge_v_into_merge_e_vertex"; @@ -505,9 +544,10 @@ mod merge_tests { } } -#[apply(serializers)] +#[apply(common::serializers)] #[serial(test_simple_vertex_traversal)] -fn test_simple_vertex_traversal(client: GremlinClient) { +fn test_simple_vertex_traversal(protocol: IoProtocol) { + let client = graph_serializer(protocol); let g = traversal().with_remote(client); let results = g.v(()).to_list().unwrap(); @@ -515,9 +555,10 @@ fn test_simple_vertex_traversal(client: GremlinClient) { assert!(results.len() > 0); } -#[apply(serializers)] +#[apply(common::serializers)] #[serial(test_inject)] -fn test_inject(client: GremlinClient) { +fn test_inject(protocol: IoProtocol) { + let client = graph_serializer(protocol); let g = traversal().with_remote(client); let expected_value = "foo"; let response: String = g @@ -530,9 +571,10 @@ fn test_inject(client: GremlinClient) { assert_eq!(expected_value, response); } -#[apply(serializers)] +#[apply(common::serializers)] #[serial(test_simple_vertex_traversal_with_id)] -fn test_simple_vertex_traversal_with_id(client: GremlinClient) { +fn test_simple_vertex_traversal_with_id(protocol: IoProtocol) { + let client = graph_serializer(protocol); let vertex = create_vertex(&client, "Traversal"); let g = traversal().with_remote(client); @@ -544,9 +586,10 @@ fn test_simple_vertex_traversal_with_id(client: GremlinClient) { assert_eq!(vertex.id(), results[0].id()); } -#[apply(serializers)] +#[apply(common::serializers)] #[serial(test_simple_vertex_traversal_with_multiple_id)] -fn test_simple_vertex_traversal_with_multiple_id(client: GremlinClient) { +fn test_simple_vertex_traversal_with_multiple_id(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "test_simple_vertex_traversal").unwrap(); let vertex = create_vertex_with_label(&client, "test_simple_vertex_traversal", "Traversal"); @@ -562,9 +605,10 @@ fn test_simple_vertex_traversal_with_multiple_id(client: GremlinClient) { assert_eq!(vertex2.id(), results[1].id()); } -#[apply(serializers)] +#[apply(common::serializers)] #[serial(test_simple_vertex_traversal_with_label)] -fn test_simple_vertex_traversal_with_label(client: GremlinClient) { +fn test_simple_vertex_traversal_with_label(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "test_simple_vertex_traversal_with_label").unwrap(); let vertex = create_vertex_with_label( @@ -586,9 +630,10 @@ fn test_simple_vertex_traversal_with_label(client: GremlinClient) { assert_eq!(vertex.id(), results[0].id()); } -#[apply(serializers)] +#[apply(common::serializers)] #[serial(test_simple_vertex_traversal_with_label_and_has)] -fn test_simple_vertex_traversal_with_label_and_has(client: GremlinClient) { +fn test_simple_vertex_traversal_with_label_and_has(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "test_simple_vertex_traversal_with_label_and_has").unwrap(); let vertex = create_vertex_with_label( @@ -653,9 +698,10 @@ fn test_simple_vertex_traversal_with_label_and_has(client: GremlinClient) { assert_eq!(vertex.id(), results[0].id()); } -#[apply(serializers)] +#[apply(common::serializers)] #[serial(test_simple_edge_traversal)] -fn test_simple_edge_traversal(client: GremlinClient) { +fn test_simple_edge_traversal(protocol: IoProtocol) { + let client = graph_serializer(protocol); let g = traversal().with_remote(client); let results = g.e(()).to_list().unwrap(); @@ -663,9 +709,10 @@ fn test_simple_edge_traversal(client: GremlinClient) { assert!(results.len() > 0); } -#[apply(serializers)] +#[apply(common::serializers)] #[serial(test_simple_edge_traversal_id)] -fn test_simple_edge_traversal_id(client: GremlinClient) { +fn test_simple_edge_traversal_id(protocol: IoProtocol) { + let client = graph_serializer(protocol); let v = create_vertex(&client, "Traversal"); let v1 = create_vertex(&client, "Traversal"); @@ -680,9 +727,10 @@ fn test_simple_edge_traversal_id(client: GremlinClient) { assert_eq!(e.id(), results[0].id()); } -#[apply(serializers)] +#[apply(common::serializers)] #[serial(test_simple_edge_traversal_with_label)] -fn test_simple_edge_traversal_with_label(client: GremlinClient) { +fn test_simple_edge_traversal_with_label(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_edges(&client, "test_simple_edge_traversal_with_label").unwrap(); let v = create_vertex(&client, "Traversal"); @@ -703,9 +751,10 @@ fn test_simple_edge_traversal_with_label(client: GremlinClient) { assert_eq!(e.id(), results[0].id()); } -#[apply(serializers)] +#[apply(common::serializers)] #[serial(test_traversal)] -fn test_traversal(client: GremlinClient) { +fn test_traversal(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_edges(&client, "test_vertex_out_traversal").unwrap(); let v = create_vertex(&client, "Traversal"); @@ -845,9 +894,10 @@ fn test_traversal(client: GremlinClient) { assert_eq!(0, results.len()); } -#[apply(serializers)] +#[apply(common::serializers)] #[serial(test_add_v)] -fn test_add_v(client: GremlinClient) { +fn test_add_v(protocol: IoProtocol) { + let client = graph_serializer(protocol); let g = traversal().with_remote(client); let results = g.add_v("person").to_list().unwrap(); @@ -864,9 +914,10 @@ fn test_add_v(client: GremlinClient) { assert_eq!("vertex", results[0].label()); } -#[apply(serializers)] +#[apply(common::serializers)] #[serial(test_add_v_with_properties)] -fn test_add_v_with_properties(client: GremlinClient) { +fn test_add_v_with_properties(protocol: IoProtocol) { + let client = graph_serializer(protocol); let g = traversal().with_remote(client.clone()); let results = g @@ -909,9 +960,10 @@ fn test_add_v_with_properties(client: GremlinClient) { ); } -#[apply(serializers)] +#[apply(common::serializers)] #[serial(test_add_v_with_property_many)] -fn test_add_v_with_property_many(client: GremlinClient) { +fn test_add_v_with_property_many(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "test_add_v_with_property_many").unwrap(); let g = traversal().with_remote(client.clone()); @@ -958,9 +1010,10 @@ fn test_add_v_with_property_many(client: GremlinClient) { ); } -#[apply(serializers)] +#[apply(common::serializers)] #[serial(test_has_many)] -fn test_has_many(client: GremlinClient) { +fn test_has_many(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "test_has_many").unwrap(); let g = traversal().with_remote(client.clone()); @@ -990,9 +1043,10 @@ fn test_has_many(client: GremlinClient) { assert_eq!(results.len(), 1); } -#[apply(serializers)] +#[apply(common::serializers)] #[serial(test_add_e)] -fn test_add_e(client: GremlinClient) { +fn test_add_e(protocol: IoProtocol) { + let client = graph_serializer(protocol); let g = traversal().with_remote(client.clone()); let v = g @@ -1053,9 +1107,10 @@ fn test_add_e(client: GremlinClient) { assert_eq!("knows", edges[0].label()); } -#[apply(serializers)] +#[apply(common::serializers)] #[serial(test_label_step)] -fn test_label_step(client: GremlinClient) { +fn test_label_step(protocol: IoProtocol) { + let client = graph_serializer(protocol); let vertex = create_vertex(&client, "Traversal"); let g = traversal().with_remote(client); @@ -1067,9 +1122,10 @@ fn test_label_step(client: GremlinClient) { assert_eq!("person", results[0]); } -#[apply(serializers)] +#[apply(common::serializers)] #[serial(test_properties_step)] -fn test_properties_step(client: GremlinClient) { +fn test_properties_step(protocol: IoProtocol) { + let client = graph_serializer(protocol); let vertex = create_vertex(&client, "Traversal"); let g = traversal().with_remote(client); @@ -1091,9 +1147,10 @@ fn test_properties_step(client: GremlinClient) { assert_eq!(0, results.len()); } -#[apply(serializers)] +#[apply(common::serializers)] #[serial(test_property_map)] -fn test_property_map(client: GremlinClient) { +fn test_property_map(protocol: IoProtocol) { + let client = graph_serializer(protocol); let vertex = create_vertex(&client, "Traversal"); let g = traversal().with_remote(client); @@ -1137,9 +1194,10 @@ fn test_property_map(client: GremlinClient) { assert_eq!(0, properties.len()); } -#[apply(serializers)] +#[apply(common::serializers)] #[serial(test_values)] -fn test_values(client: GremlinClient) { +fn test_values(protocol: IoProtocol) { + let client = graph_serializer(protocol); let vertex = create_vertex(&client, "Traversal"); let g = traversal().with_remote(client); @@ -1165,9 +1223,10 @@ fn test_values(client: GremlinClient) { assert_eq!(0, results.len()); } -#[apply(serializers)] +#[apply(common::serializers)] #[serial(test_value_map)] -fn test_value_map(client: GremlinClient) { +fn test_value_map(protocol: IoProtocol) { + let client = graph_serializer(protocol); let g = traversal().with_remote(client); let vertices = g @@ -1225,9 +1284,10 @@ fn test_value_map(client: GremlinClient) { assert_eq!(true, results[0].get("name").is_some()); } -#[apply(serializers)] +#[apply(common::serializers)] #[serial(test_unwrap_map)] -fn test_unwrap_map(client: GremlinClient) { +fn test_unwrap_map(protocol: IoProtocol) { + let client = graph_serializer(protocol); let g = traversal().with_remote(client); let vertices = g @@ -1249,9 +1309,10 @@ fn test_unwrap_map(client: GremlinClient) { assert_eq!(label, Some(vertex.label())); } -#[apply(serializers)] +#[apply(common::serializers)] #[serial(test_element_map)] -fn test_element_map(client: GremlinClient) { +fn test_element_map(protocol: IoProtocol) { + let client = graph_serializer(protocol); let g = traversal().with_remote(client); let vertices = g @@ -1314,9 +1375,10 @@ fn test_element_map(client: GremlinClient) { assert_eq!(true, results[0].get("name").is_some()); } -#[apply(serializers)] +#[apply(common::serializers)] #[serial(test_count)] -fn test_count(client: GremlinClient) { +fn test_count(protocol: IoProtocol) { + let client = graph_serializer(protocol); let vertex = create_vertex_with_label(&client, "test_count", "Count"); let g = traversal().with_remote(client); @@ -1330,9 +1392,10 @@ fn test_count(client: GremlinClient) { assert_eq!(&1, value); } -#[apply(serializers)] +#[apply(common::serializers)] #[serial(test_group_count_step)] -fn test_group_count_step(client: GremlinClient) { +fn test_group_count_step(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "test_group_count").unwrap(); let vertex = create_vertex_with_label(&client, "test_group_count", "Count"); @@ -1403,9 +1466,10 @@ fn test_group_count_step(client: GremlinClient) { assert_eq!(&1, value["test_group_count"].get::().unwrap()); } -#[apply(serializers)] +#[apply(common::serializers)] #[serial(test_group_by_step)] -fn test_group_by_step(client: GremlinClient) { +fn test_group_by_step(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "test_group_by_step").unwrap(); create_vertex_with_label(&client, "test_group_by_step", "Count"); @@ -1458,9 +1522,10 @@ fn test_group_by_step(client: GremlinClient) { assert_eq!(&1, value["test_group_by_step"].get::().unwrap()); } -#[apply(serializers)] +#[apply(common::serializers)] #[serial(test_select_step)] -fn test_select_step(client: GremlinClient) { +fn test_select_step(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "test_select_step").unwrap(); create_vertex_with_label(&client, "test_select_step", "Count"); @@ -1483,9 +1548,10 @@ fn test_select_step(client: GremlinClient) { assert_eq!(&1, value.get::().unwrap()); } -#[apply(serializers)] +#[apply(common::serializers)] #[serial(test_fold_step)] -fn test_fold_step(client: GremlinClient) { +fn test_fold_step(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "test_fold_step").unwrap(); create_vertex_with_label(&client, "test_fold_step", "Count"); @@ -1507,9 +1573,10 @@ fn test_fold_step(client: GremlinClient) { assert_eq!("Count", value[0].get::().unwrap()); } -#[apply(serializers)] +#[apply(common::serializers)] #[serial(test_unfold_step)] -fn test_unfold_step(client: GremlinClient) { +fn test_unfold_step(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "test_unfold_step").unwrap(); let vertex = create_vertex_with_label(&client, "test_unfold_step", "Count"); @@ -1537,9 +1604,10 @@ fn test_unfold_step(client: GremlinClient) { ); } -#[apply(serializers)] +#[apply(common::serializers)] #[serial(test_path_step)] -fn test_path_step(client: GremlinClient) { +fn test_path_step(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "test_path_step").unwrap(); let v = create_vertex_with_label(&client, "test_path_step", "Count"); @@ -1560,9 +1628,10 @@ fn test_path_step(client: GremlinClient) { assert_eq!(v.id(), value.objects()[0].get::().unwrap().id()); } -#[apply(serializers)] +#[apply(common::serializers)] #[serial(test_limit_step)] -fn test_limit_step(client: GremlinClient) { +fn test_limit_step(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "test_limit_step").unwrap(); create_vertex_with_label(&client, "test_limit_step", "Count"); @@ -1580,9 +1649,10 @@ fn test_limit_step(client: GremlinClient) { assert_eq!(1, results.len()); } -#[apply(serializers)] +#[apply(common::serializers)] #[serial(test_dedup_step)] -fn test_dedup_step(client: GremlinClient) { +fn test_dedup_step(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "test_limit_step").unwrap(); create_vertex_with_label(&client, "test_limit_step", "Count"); @@ -1601,9 +1671,10 @@ fn test_dedup_step(client: GremlinClient) { assert_eq!(1, results.len()); } -#[apply(serializers)] +#[apply(common::serializers)] #[serial(test_numerical_steps)] -fn test_numerical_steps(client: GremlinClient) { +fn test_numerical_steps(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "test_numerical_steps").unwrap(); let g = traversal().with_remote(client); @@ -1672,9 +1743,10 @@ fn test_numerical_steps(client: GremlinClient) { assert_eq!(&20, results[0].get::().unwrap()); } -#[apply(serializers)] +#[apply(common::serializers)] #[serial(test_has_with_p_steps)] -fn test_has_with_p_steps(client: GremlinClient) { +fn test_has_with_p_steps(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "test_has_with_p_steps").unwrap(); let g = traversal().with_remote(client); @@ -1747,9 +1819,10 @@ fn test_has_with_p_steps(client: GremlinClient) { assert_eq!(&20, results[0].get::().unwrap()); } -#[apply(serializers)] +#[apply(common::serializers)] #[serial(test_has_with_text_p_step)] -fn test_has_with_text_p_step(client: GremlinClient) { +fn test_has_with_text_p_step(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "test_has_with_text_p_step").unwrap(); let g = traversal().with_remote(client); @@ -1813,9 +1886,10 @@ fn test_has_with_text_p_step(client: GremlinClient) { assert_eq!(2, results.len()); } -#[apply(serializers)] +#[apply(common::serializers)] #[serial(where_step_test)] -fn where_step_test(client: GremlinClient) { +fn where_step_test(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "where_step_test").unwrap(); let g = traversal().with_remote(client); @@ -1838,9 +1912,10 @@ fn where_step_test(client: GremlinClient) { assert_eq!(v[0].id(), results[0].id()); } -#[apply(serializers)] +#[apply(common::serializers)] #[serial(not_step_test)] -fn not_step_test(client: GremlinClient) { +fn not_step_test(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "not_step_test").unwrap(); let g = traversal().with_remote(client); @@ -1860,9 +1935,10 @@ fn not_step_test(client: GremlinClient) { assert_eq!(0, results.len()); } -#[apply(serializers)] +#[apply(common::serializers)] #[serial(order_step_test)] -fn order_step_test(client: GremlinClient) { +fn order_step_test(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "order_step_test").unwrap(); let g = traversal().with_remote(client); @@ -1903,9 +1979,10 @@ fn order_step_test(client: GremlinClient) { assert_eq!("b", results[0].get::().unwrap()); } -#[apply(serializers)] +#[apply(common::serializers)] #[serial(match_step_test)] -fn match_step_test(client: GremlinClient) { +fn match_step_test(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "match_step_test").unwrap(); drop_edges(&client, "match_step_test_edge").unwrap(); @@ -1964,9 +2041,10 @@ fn match_step_test(client: GremlinClient) { assert_eq!(&v3[0], first["c"].get::().unwrap()); } -#[apply(serializers)] +#[apply(common::serializers)] #[serial(drop_step_test)] -fn drop_step_test(client: GremlinClient) { +fn drop_step_test(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "drop_step_test").unwrap(); let g = traversal().with_remote(client); @@ -1996,9 +2074,10 @@ fn drop_step_test(client: GremlinClient) { assert_eq!(false, results); } -#[apply(serializers)] +#[apply(common::serializers)] #[serial(or_step_test)] -fn or_step_test(client: GremlinClient) { +fn or_step_test(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "or_step_test").unwrap(); let g = traversal().with_remote(client); @@ -2036,9 +2115,10 @@ fn or_step_test(client: GremlinClient) { assert_eq!(result.len(), 2); } -#[apply(serializers)] +#[apply(common::serializers)] #[serial(iter_terminator_test)] -fn iter_terminator_test(client: GremlinClient) { +fn iter_terminator_test(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "iter_terminator_test").unwrap(); let g = traversal().with_remote(client); @@ -2064,9 +2144,10 @@ fn iter_terminator_test(client: GremlinClient) { assert_eq!(2, results.len()) } -#[apply(serializers)] +#[apply(common::serializers)] #[serial(test_select_pop)] -fn test_select_pop(client: GremlinClient) { +fn test_select_pop(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "test_select_pop").unwrap(); drop_vertices(&client, "test_select_pop_child").unwrap(); @@ -2152,9 +2233,10 @@ fn test_select_pop(client: GremlinClient) { assert_eq!(results.len(), 1); } -#[apply(serializers)] +#[apply(common::serializers)] #[serial(test_repeat_until_loops_loops)] -fn test_repeat_until_loops_loops(client: GremlinClient) { +fn test_repeat_until_loops_loops(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "test_repeat_until_loops").unwrap(); drop_vertices(&client, "test_repeat_until_loops_child").unwrap(); @@ -2192,9 +2274,10 @@ fn test_repeat_until_loops_loops(client: GremlinClient) { assert_eq!(results[0], e2[0]); } -#[apply(serializers)] +#[apply(common::serializers)] #[serial(test_simple_path)] -fn test_simple_vertex_property(client: GremlinClient) { +fn test_simple_vertex_property(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "test_simple_vertex_property").unwrap(); let g = traversal().with_remote(client); @@ -2211,9 +2294,10 @@ fn test_simple_vertex_property(client: GremlinClient) { assert_eq!(actual_property, "a"); } -#[apply(serializers)] +#[apply(common::serializers)] #[serial(test_simple_path)] -fn test_simple_path(client: GremlinClient) { +fn test_simple_path(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "test_simple_path").unwrap(); drop_vertices(&client, "test_simple_path_child").unwrap(); @@ -2252,9 +2336,10 @@ fn test_simple_path(client: GremlinClient) { assert_eq!(results[0], e2[0]); } -#[apply(serializers)] +#[apply(common::serializers)] #[serial(test_sample)] -fn test_sample(client: GremlinClient) { +fn test_sample(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "test_sample").unwrap(); drop_vertices(&client, "test_sample_child").unwrap(); @@ -2284,9 +2369,10 @@ fn test_sample(client: GremlinClient) { assert_eq!(results.len(), 1); } -#[apply(serializers)] +#[apply(common::serializers)] #[serial(test_local)] -fn test_local(client: GremlinClient) { +fn test_local(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "test_local").unwrap(); drop_vertices(&client, "test_local_child").unwrap(); drop_vertices(&client, "test_local_child_child").unwrap(); @@ -2359,9 +2445,10 @@ fn test_local(client: GremlinClient) { assert_eq!(results.len(), 2); } -#[apply(serializers)] +#[apply(common::serializers)] #[serial(test_side_effect)] -fn test_side_effect(client: GremlinClient) { +fn test_side_effect(protocol: IoProtocol) { + let client = graph_serializer(protocol); let test_vertex_label = "test_side_effect"; let expected_side_effect_key = "prop_key"; let expected_side_effect_value = "prop_val"; @@ -2388,9 +2475,10 @@ fn test_side_effect(client: GremlinClient) { ); } -#[apply(serializers)] +#[apply(common::serializers)] #[serial(test_anonymous_traversal_properties_drop)] -fn test_anonymous_traversal_properties_drop(client: GremlinClient) { +fn test_anonymous_traversal_properties_drop(protocol: IoProtocol) { + let client = graph_serializer(protocol); let test_vertex_label = "test_anonymous_traversal_properties_drop"; let pre_drop_prop_key = "pre_drop_prop_key"; let expected_prop_value = "prop_val"; @@ -2446,9 +2534,10 @@ fn test_anonymous_traversal_properties_drop(client: GremlinClient) { ); } -#[apply(serializers)] +#[apply(common::serializers)] #[serial(test_by_columns)] -fn test_by_columns(client: GremlinClient) { +fn test_by_columns(protocol: IoProtocol) { + let client = graph_serializer(protocol); 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"; @@ -2502,9 +2591,10 @@ fn test_by_columns(client: GremlinClient) { ); } -#[apply(serializers)] +#[apply(common::serializers)] #[serial(test_property_cardinality)] -fn test_property_cardinality(client: GremlinClient) { +fn test_property_cardinality(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "test_property_cardinality").unwrap(); let g = traversal().with_remote(client); @@ -2532,9 +2622,10 @@ fn test_property_cardinality(client: GremlinClient) { assert_eq!(1, new_v["name"].get::().unwrap().len()); } -#[apply(serializers)] +#[apply(common::serializers)] #[serial(test_choose)] -fn test_choose(client: GremlinClient) { +fn test_choose(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "test_choose").unwrap(); let g = traversal().with_remote(client); @@ -2573,9 +2664,10 @@ fn test_choose(client: GremlinClient) { assert_eq!(success_vertices.is_some(), true); } -#[apply(serializers)] +#[apply(common::serializers)] #[serial(test_choose_by_literal_options)] -fn test_choose_by_literal_options(client: GremlinClient) { +fn test_choose_by_literal_options(protocol: IoProtocol) { + let client = graph_serializer(protocol); let g = traversal().with_remote(client); let choosen_literal_a = g @@ -2601,9 +2693,10 @@ fn test_choose_by_literal_options(client: GremlinClient) { assert_eq!(choosen_literal_b, Some("option-b".into())); } -#[apply(serializers)] +#[apply(common::serializers)] #[serial()] -fn test_coalesce(client: GremlinClient) { +fn test_coalesce(protocol: IoProtocol) { + let client = graph_serializer(protocol); use gremlin_client::GValue; drop_vertices(&client, "test_coalesce").unwrap(); @@ -2636,9 +2729,10 @@ fn test_coalesce(client: GremlinClient) { assert!(values.contains(&String::from("b"))); } -#[apply(serializers)] +#[apply(common::serializers)] #[serial(test_coalesce_unfold)] -fn test_coalesce_unfold(client: GremlinClient) { +fn test_coalesce_unfold(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "test_coalesce_unfold").unwrap(); let g = traversal().with_remote(client); @@ -2692,9 +2786,10 @@ fn test_coalesce_unfold(client: GremlinClient) { ); } -#[apply(serializers)] +#[apply(common::serializers)] #[serial(test_none_step)] -fn test_none_step(client: GremlinClient) { +fn test_none_step(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "test_none_step").unwrap(); let g = traversal().with_remote(client); @@ -2750,10 +2845,11 @@ where .transpose() } -#[apply(serializers)] +#[apply(common::serializers)] #[serial(test_traversal_vertex_mapping)] #[cfg(feature = "derive")] -fn test_traversal_vertex_mapping(client: GremlinClient) { +fn test_traversal_vertex_mapping(protocol: IoProtocol) { + let client = graph_serializer(protocol); use chrono::{DateTime, TimeZone, Utc}; use gremlin_client::derive::FromGMap; use std::convert::TryFrom; From f54ea77a2cf535157fc9fd98fa411b3be3a9e95a Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Tue, 12 Nov 2024 23:50:46 -0600 Subject: [PATCH 45/56] 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 28d980b8..54ff2555 100644 --- a/gremlin-client/tests/integration_traversal.rs +++ b/gremlin-client/tests/integration_traversal.rs @@ -8,9 +8,7 @@ use gremlin_client::structure::{ Cardinality, Column, List, Map, Pop, TextP, Vertex, VertexProperty, P, T, }; -use gremlin_client::{ - utils, BorrowFromGValue, GKey, GValue, GremlinError, IoProtocol, -}; +use gremlin_client::{utils, BorrowFromGValue, GKey, GValue, GremlinError, IoProtocol}; mod common; From 85a58f1671a7c8566112d6b1c619a37ee1a02729 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Wed, 13 Nov 2024 11:19:06 -0600 Subject: [PATCH 46/56] Use centralized serializer template --- .../tests/integration_traversal_async.rs | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/gremlin-client/tests/integration_traversal_async.rs b/gremlin-client/tests/integration_traversal_async.rs index 8db61b48..84213119 100644 --- a/gremlin-client/tests/integration_traversal_async.rs +++ b/gremlin-client/tests/integration_traversal_async.rs @@ -7,7 +7,9 @@ mod aio { use serial_test::serial; - use gremlin_client::{aio::GremlinClient, process::traversal::traversal}; + use gremlin_client::process::traversal::traversal; + + use crate::common; use super::common::aio::{connect_serializer, create_vertex_with_label, drop_vertices}; @@ -19,19 +21,12 @@ mod aio { use gremlin_client::{IoProtocol, Vertex}; - #[rstest] - #[case::graphson_v2(connect_serializer(IoProtocol::GraphSONV2))] - #[case::graphson_v3(connect_serializer(IoProtocol::GraphSONV3))] - #[case::graph_binary_v1(connect_serializer(IoProtocol::GraphBinaryV1))] - #[awt] + #[apply(common::serializers)] #[cfg_attr(feature = "async-std-runtime", async_std::test)] #[cfg_attr(feature = "tokio-runtime", tokio::test)] #[serial(test_simple_vertex_traversal_with_multiple_id)] - async fn test_simple_vertex_traversal_with_multiple_id( - #[future] - #[case] - client: GremlinClient, - ) { + async fn test_simple_vertex_traversal_with_multiple_id(protocol: IoProtocol) { + let client = connect_serializer(protocol).await; drop_vertices(&client, "test_simple_vertex_traversal_async") .await .unwrap(); From a1bbe791b0aafce9634857d8151a12568f9f4767 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Wed, 13 Nov 2024 11:28:19 -0600 Subject: [PATCH 47/56] Created parameterized integration_client_async --- .../tests/integration_client_async.rs | 72 +++++--- .../tests/integration_client_async_v2.rs | 174 ------------------ 2 files changed, 42 insertions(+), 204 deletions(-) delete mode 100644 gremlin-client/tests/integration_client_async_v2.rs diff --git a/gremlin-client/tests/integration_client_async.rs b/gremlin-client/tests/integration_client_async.rs index c936cc68..90dedf46 100644 --- a/gremlin-client/tests/integration_client_async.rs +++ b/gremlin-client/tests/integration_client_async.rs @@ -5,24 +5,24 @@ mod common; mod aio { use gremlin_client::{aio::GremlinClient, ConnectionOptions, GremlinError, TlsOptions}; - use gremlin_client::{Edge, GValue, Map, Vertex}; + use gremlin_client::{Edge, GValue, IoProtocol, Map, Vertex}; + + use rstest::*; + use rstest_reuse::{self, *}; - use super::common::aio::{connect, create_edge, create_vertex, drop_vertices}; + use crate::common; + + use super::common::aio::{connect, create_edge, create_vertex, drop_vertices, connect_serializer}; #[cfg(feature = "async-std-runtime")] use async_std::prelude::*; #[cfg(feature = "tokio-runtime")] use tokio_stream::StreamExt; + #[apply(common::serializers)] #[cfg_attr(feature = "async-std-runtime", async_std::test)] #[cfg_attr(feature = "tokio-runtime", tokio::test)] - async fn test_client_connection_ok() { - connect().await; - } - - #[cfg_attr(feature = "async-std-runtime", async_std::test)] - #[cfg_attr(feature = "tokio-runtime", tokio::test)] - async fn test_ok_credentials() { + async fn test_ok_credentials(protocol: IoProtocol) { let client = GremlinClient::connect( ConnectionOptions::builder() .host("localhost") @@ -32,6 +32,8 @@ mod aio { .tls_options(TlsOptions { accept_invalid_certs: true, }) + .serializer(protocol.clone()) + .deserializer(protocol) .build(), ) .await @@ -41,10 +43,11 @@ mod aio { assert!(result.is_ok(), "{:?}", result); } + #[apply(common::serializers)] #[cfg(feature = "async-std-runtime")] #[cfg_attr(feature = "async-std-runtime", async_std::test)] - async fn test_empty_query() { - let graph = connect().await; + async fn test_empty_query(protocol: IoProtocol) { + let graph = connect_serializer(protocol).await; assert_eq!( 0, @@ -57,10 +60,11 @@ mod aio { ) } + #[apply(common::serializers)] #[cfg(feature = "async-std-runtime")] #[cfg_attr(feature = "async-std-runtime", async_std::test)] - async fn test_session_empty_query() { - let mut graph = connect().await; + async fn test_session_empty_query(protocol: IoProtocol) { + let mut graph = connect_serializer(protocol).await; let mut sessioned_graph = graph .create_session("test-session".to_string()) .await @@ -82,10 +86,11 @@ mod aio { .expect("It should close the session."); } + #[apply(common::serializers)] #[cfg(feature = "async-std-runtime")] #[cfg_attr(feature = "async-std-runtime", async_std::test)] - async fn test_keep_alive_query() { - let graph = connect().await; + async fn test_keep_alive_query(protocol: IoProtocol) { + let graph = connect_serializer(protocol).await; assert_eq!( 0, @@ -110,10 +115,11 @@ mod aio { ) } + #[apply(common::serializers)] #[cfg(feature = "async-std-runtime")] #[cfg_attr(feature = "async-std-runtime", async_std::test)] - async fn test_partial_content() { - let graph = connect().await; + async fn test_partial_content(protocol: IoProtocol) { + let graph = connect_serializer(protocol).await; drop_vertices(&graph, "Partial") .await @@ -137,10 +143,11 @@ mod aio { ); } + #[apply(common::serializers)] #[cfg_attr(feature = "async-std-runtime", async_std::test)] #[cfg_attr(feature = "tokio-runtime", tokio::test)] - async fn test_wrong_query() { - let error = connect() + async fn test_wrong_query(protocol: IoProtocol) { + let error = connect_serializer(protocol) .await .execute("g.V", &[]) .await @@ -155,10 +162,11 @@ mod aio { } } + #[apply(common::serializers)] #[cfg_attr(feature = "async-std-runtime", async_std::test)] #[cfg_attr(feature = "tokio-runtime", tokio::test)] - async fn test_wrong_alias() { - let error = connect() + async fn test_wrong_alias(protocol: IoProtocol) { + let error = connect_serializer(protocol) .await .alias("foo") .execute("g.V()", &[]) @@ -174,11 +182,11 @@ mod aio { } } + #[apply(common::serializers)] #[cfg_attr(feature = "async-std-runtime", async_std::test)] #[cfg_attr(feature = "tokio-runtime", tokio::test)] - - async fn test_vertex_query() { - let graph = connect().await; + async fn test_vertex_query(protocol: IoProtocol) { + let graph = connect_serializer(protocol).await; let vertices = graph .execute( "g.V().hasLabel('person').has('name',name)", @@ -194,10 +202,12 @@ mod aio { assert_eq!("person", vertices[0].label()); } + + #[apply(common::serializers)] #[cfg_attr(feature = "async-std-runtime", async_std::test)] #[cfg_attr(feature = "tokio-runtime", tokio::test)] - async fn test_edge_query() { - let graph = connect().await; + async fn test_edge_query(protocol: IoProtocol) { + let graph = connect_serializer(protocol).await; let edges = graph .execute("g.E().hasLabel('knows').limit(1)", &[]) .await @@ -211,10 +221,11 @@ mod aio { assert_eq!("knows", edges[0].label()); } + #[apply(common::serializers)] #[cfg_attr(feature = "async-std-runtime", async_std::test)] #[cfg_attr(feature = "tokio-runtime", tokio::test)] - async fn test_vertex_creation() { - let graph = connect().await; + async fn test_vertex_creation(protocol: IoProtocol) { + let graph = connect_serializer(protocol).await; let mark = create_vertex(&graph, "mark").await; assert_eq!("person", mark.label()); @@ -237,10 +248,11 @@ mod aio { ); } + #[apply(common::serializers)] #[cfg_attr(feature = "async-std-runtime", async_std::test)] #[cfg_attr(feature = "tokio-runtime", tokio::test)] - async fn test_edge_creation() { - let graph = connect().await; + async fn test_edge_creation(protocol: IoProtocol) { + let graph = connect_serializer(protocol).await; let mark = create_vertex(&graph, "mark").await; let frank = create_vertex(&graph, "frank").await; diff --git a/gremlin-client/tests/integration_client_async_v2.rs b/gremlin-client/tests/integration_client_async_v2.rs deleted file mode 100644 index f98d9249..00000000 --- a/gremlin-client/tests/integration_client_async_v2.rs +++ /dev/null @@ -1,174 +0,0 @@ -#[allow(dead_code)] -mod common; - -#[cfg(feature = "async_gremlin")] -mod aio { - - use gremlin_client::GremlinError; - use gremlin_client::{Edge, GValue, IoProtocol, Map, Vertex}; - - use super::common::aio::{connect_serializer, create_edge, create_vertex}; - #[cfg(feature = "async-std-runtime")] - use async_std::prelude::*; - - #[cfg(feature = "tokio-runtime")] - use tokio_stream::StreamExt; - - #[cfg_attr(feature = "async-std-runtime", async_std::test)] - #[cfg_attr(feature = "tokio-runtime", tokio::test)] - async fn test_client_connection_ok_v2() { - connect_serializer(IoProtocol::GraphSONV2).await; - } - - #[cfg(feature = "async-std-runtime")] - #[cfg_attr(feature = "async-std-runtime", async_std::test)] - async fn test_empty_query_v2() { - let graph = connect_serializer(IoProtocol::GraphSONV2).await; - - assert_eq!( - 0, - graph - .execute("g.V().hasLabel('NotFound')", &[]) - .await - .expect("It should execute a traversal") - .count() - .await - ) - } - - #[cfg_attr(feature = "async-std-runtime", async_std::test)] - #[cfg_attr(feature = "tokio-runtime", tokio::test)] - async fn test_wrong_query_v2() { - let error = connect_serializer(IoProtocol::GraphSONV2) - .await - .execute("g.V", &[]) - .await - .expect_err("it should return an error"); - - match error { - GremlinError::Request((code, message)) => { - assert_eq!(597, code); - assert_eq!("No such property: V for class: org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource",message) - } - _ => panic!("wrong error type"), - } - } - - #[cfg_attr(feature = "async-std-runtime", async_std::test)] - #[cfg_attr(feature = "tokio-runtime", tokio::test)] - async fn test_wrong_alias_v2() { - let error = connect_serializer(IoProtocol::GraphSONV2) - .await - .alias("foo") - .execute("g.V()", &[]) - .await - .expect_err("it should return an error"); - - match error { - GremlinError::Request((code, message)) => { - assert_eq!(499, code); - assert_eq!("Could not alias [g] to [foo] as [foo] not in the Graph or TraversalSource global bindings",message) - } - _ => panic!("wrong error type"), - } - } - - #[cfg_attr(feature = "async-std-runtime", async_std::test)] - #[cfg_attr(feature = "tokio-runtime", tokio::test)] - - async fn test_vertex_query_v2() { - let graph = connect_serializer(IoProtocol::GraphSONV2).await; - - println!("About to execute query."); - let vertices = graph - .execute( - "g.V().hasLabel('person').has('name',name)", - &[("name", &"marko")], - ) - .await - .expect("it should execute a query") - .filter_map(Result::ok) - .map(|f| f.take::()) - .collect::, _>>() - .await - .expect("It should be ok"); - - assert_eq!("person", vertices[0].label()); - } - #[cfg_attr(feature = "async-std-runtime", async_std::test)] - #[cfg_attr(feature = "tokio-runtime", tokio::test)] - async fn test_edge_query_v2() { - let graph = connect_serializer(IoProtocol::GraphSONV2).await; - let edges = graph - .execute("g.E().hasLabel('knows').limit(1)", &[]) - .await - .expect("it should execute a query") - .filter_map(Result::ok) - .map(|f| f.take::()) - .collect::, _>>() - .await - .expect("It should be ok"); - - assert_eq!("knows", edges[0].label()); - } - - #[cfg_attr(feature = "async-std-runtime", async_std::test)] - #[cfg_attr(feature = "tokio-runtime", tokio::test)] - async fn test_vertex_creation_v2() { - let graph = connect_serializer(IoProtocol::GraphSONV2).await; - let mark = create_vertex(&graph, "mark").await; - - assert_eq!("person", mark.label()); - - let value_map = graph - .execute("g.V(identity).valueMap()", &[("identity", mark.id())]) - .await - .expect("should fetch valueMap with properties") - .filter_map(Result::ok) - .map(|f| f.take::()) - .collect::, _>>() - .await - .expect("It should be ok"); - - assert_eq!(1, value_map.len()); - - assert_eq!( - Some(&GValue::List(vec![String::from("mark").into()].into())), - value_map[0].get("name") - ); - } - - #[cfg_attr(feature = "async-std-runtime", async_std::test)] - #[cfg_attr(feature = "tokio-runtime", tokio::test)] - async fn test_edge_creation_v2() { - let graph = connect_serializer(IoProtocol::GraphSONV2).await; - let mark = create_vertex(&graph, "mark").await; - let frank = create_vertex(&graph, "frank").await; - - let edge = create_edge(&graph, &mark, &frank, "knows").await; - - assert_eq!("knows", edge.label()); - - assert_eq!(&mark, edge.out_v()); - assert_eq!(&frank, edge.in_v()); - - let edges = graph - .execute("g.V(identity).outE()", &[("identity", mark.id())]) - .await - .expect("should fetch edge") - .filter_map(Result::ok) - .map(|f| f.take::()) - .collect::, _>>() - .await - .expect("It should be ok"); - - assert_eq!(1, edges.len()); - - let edge = &edges[0]; - - assert_eq!("knows", edge.label()); - - assert_eq!(&mark, edge.out_v()); - assert_eq!(&frank, edge.in_v()); - } -} From b4d9015b6aa07cd462f24905667d23295947e827 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Wed, 13 Nov 2024 11:28:35 -0600 Subject: [PATCH 48/56] Formatting --- gremlin-client/tests/integration_client_async.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/gremlin-client/tests/integration_client_async.rs b/gremlin-client/tests/integration_client_async.rs index 90dedf46..2a2e7942 100644 --- a/gremlin-client/tests/integration_client_async.rs +++ b/gremlin-client/tests/integration_client_async.rs @@ -6,13 +6,15 @@ mod aio { use gremlin_client::{aio::GremlinClient, ConnectionOptions, GremlinError, TlsOptions}; use gremlin_client::{Edge, GValue, IoProtocol, Map, Vertex}; - + use rstest::*; use rstest_reuse::{self, *}; use crate::common; - use super::common::aio::{connect, create_edge, create_vertex, drop_vertices, connect_serializer}; + use super::common::aio::{ + connect, connect_serializer, create_edge, create_vertex, drop_vertices, + }; #[cfg(feature = "async-std-runtime")] use async_std::prelude::*; From 5a4931bc1d0b6cd99f8639bacdbe0fbb298f04cb Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Wed, 13 Nov 2024 11:47:15 -0600 Subject: [PATCH 49/56] Import cleanup --- gremlin-client/src/client.rs | 1 - gremlin-client/src/io/mod.rs | 4 +--- gremlin-client/tests/graph_binary_read_write_cycle.rs | 8 +++----- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/gremlin-client/src/client.rs b/gremlin-client/src/client.rs index b3e49e48..98535de3 100644 --- a/gremlin-client/src/client.rs +++ b/gremlin-client/src/client.rs @@ -1,4 +1,3 @@ -use crate::io::IoProtocol; use crate::message::Response; use crate::pool::GremlinConnectionManager; use crate::process::traversal::Bytecode; diff --git a/gremlin-client/src/io/mod.rs b/gremlin-client/src/io/mod.rs index 846c46d9..1a8ae4a9 100644 --- a/gremlin-client/src/io/mod.rs +++ b/gremlin-client/src/io/mod.rs @@ -9,15 +9,13 @@ use crate::message::{ReponseStatus, RequestIdV2, Response, ResponseResult}; use crate::process::traversal::{Order, Scope}; use crate::structure::{Cardinality, Direction, GValue, Merge, T}; use graph_binary_v1::GraphBinaryV1Deser; -use serde::{Deserialize as SerdeDeserialize, Deserializer}; use serde_derive::Deserialize; use serde_json::{json, Map, Value}; use std::collections::HashMap; -use std::convert::TryInto; use std::string::ToString; use uuid::Uuid; -use crate::{io::graph_binary_v1::GraphBinaryV1Ser, GKey, GremlinError, GremlinResult, Message}; +use crate::{io::graph_binary_v1::GraphBinaryV1Ser, GremlinError, GremlinResult, Message}; #[derive(Debug, Clone, PartialEq)] pub enum IoProtocol { diff --git a/gremlin-client/tests/graph_binary_read_write_cycle.rs b/gremlin-client/tests/graph_binary_read_write_cycle.rs index 4e782f77..667e45e9 100644 --- a/gremlin-client/tests/graph_binary_read_write_cycle.rs +++ b/gremlin-client/tests/graph_binary_read_write_cycle.rs @@ -1,12 +1,10 @@ use std::array::IntoIter; -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; -use chrono::{DateTime, TimeZone, Utc}; +use chrono::{TimeZone, Utc}; use common::io::graph_serializer; use gremlin_client::{ - process::traversal::{ - traversal, Bytecode, GraphTraversal, GraphTraversalSource, Scope, SyncTerminator, - }, + process::traversal::{traversal, GraphTraversalSource, SyncTerminator}, GValue, IoProtocol, }; use rstest::rstest; From 97ca6c161129495f6e053c2634f7984cd6f4bd5e Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Wed, 13 Nov 2024 12:14:08 -0600 Subject: [PATCH 50/56] Added serial annotation for test_partial_content to prevent test state intermingling and retry loops for merge tests that observe state between transactions to prevent flakey failures --- .../tests/integration_client_async.rs | 3 + gremlin-client/tests/integration_traversal.rs | 63 ++++++++++++------- 2 files changed, 45 insertions(+), 21 deletions(-) diff --git a/gremlin-client/tests/integration_client_async.rs b/gremlin-client/tests/integration_client_async.rs index 2a2e7942..4f74263f 100644 --- a/gremlin-client/tests/integration_client_async.rs +++ b/gremlin-client/tests/integration_client_async.rs @@ -9,6 +9,7 @@ mod aio { use rstest::*; use rstest_reuse::{self, *}; + use serial_test::serial; use crate::common; @@ -63,6 +64,7 @@ mod aio { } #[apply(common::serializers)] + #[serial(test_session_empty_query)] #[cfg(feature = "async-std-runtime")] #[cfg_attr(feature = "async-std-runtime", async_std::test)] async fn test_session_empty_query(protocol: IoProtocol) { @@ -118,6 +120,7 @@ mod aio { } #[apply(common::serializers)] + #[serial(test_partial_content)] #[cfg(feature = "async-std-runtime")] #[cfg_attr(feature = "async-std-runtime", async_std::test)] async fn test_partial_content(protocol: IoProtocol) { diff --git a/gremlin-client/tests/integration_traversal.rs b/gremlin-client/tests/integration_traversal.rs index 54ff2555..4ab4cfda 100644 --- a/gremlin-client/tests/integration_traversal.rs +++ b/gremlin-client/tests/integration_traversal.rs @@ -373,19 +373,30 @@ mod merge_tests { 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 mut attempt = 0; + loop { + attempt += 1; + let response = 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(); + if response.is_ok() { + break response; + } else if attempt > 10 { + std::panic!("mergeE vertices not observed before attempt exhaustion") + } + std::thread::sleep(std::time::Duration::from_millis(10)); + } + .expect("Should get a response") + .expect("Should return a edge properties") }; let on_create_edge_properties = do_merge_edge(g.clone()); @@ -436,14 +447,24 @@ mod merge_tests { 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 mut attempt = 0; + let anonymous_merge_e_properties = loop { + attempt += 1; + let response = g + .inject(1) + .unfold() + .coalesce::([__.merge_e(assignment_map.clone())]) + .element_map(()) + .next(); + if response.is_ok() { + break response; + } else if attempt > 10 { + std::panic!("mergeE vertices not observed before attempt exhaustion") + } + std::thread::sleep(std::time::Duration::from_millis(10)); + } + .expect("Should get a response") + .expect("Should return a edge properties"); let incoming_vertex: &Map = anonymous_merge_e_properties .get(Direction::In) From 2d2b13af94020cb697b00a050d5bc85b2d310877 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Wed, 13 Nov 2024 15:50:44 -0600 Subject: [PATCH 51/56] Added derive feature flag to GH Action test --- .github/workflows/test.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index daece672..92decde8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -52,29 +52,29 @@ jobs: uses: actions-rs/cargo@v1 with: command: test - args: --manifest-path gremlin-client/Cargo.toml --features=tokio-runtime + args: --manifest-path gremlin-client/Cargo.toml --features=tokio-runtime,derive - 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 + args: --manifest-path gremlin-client/Cargo.toml --features=async-std-runtime,derive # 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 + args: --manifest-path gremlin-client/Cargo.toml --features=merge_tests,derive - 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,merge_tests + args: --manifest-path gremlin-client/Cargo.toml --features=tokio-runtime,merge_tests,derive - 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,merge_tests + args: --manifest-path gremlin-client/Cargo.toml --features=async-std-runtime,merge_tests,derive From 081ad4e3c6541b3ca48c6eb8513a4f29c34a9058 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Wed, 13 Nov 2024 15:51:55 -0600 Subject: [PATCH 52/56] Implemented map get & try_get backwards compatability logic to not break derive macros --- gremlin-client/src/structure/map.rs | 27 ++++- gremlin-client/src/structure/t.rs | 16 +++ gremlin-client/tests/common.rs | 8 +- gremlin-client/tests/integration_client.rs | 1 + gremlin-client/tests/integration_traversal.rs | 109 ++++++------------ 5 files changed, 79 insertions(+), 82 deletions(-) diff --git a/gremlin-client/src/structure/map.rs b/gremlin-client/src/structure/map.rs index b4c90537..def9523a 100644 --- a/gremlin-client/src/structure/map.rs +++ b/gremlin-client/src/structure/map.rs @@ -86,7 +86,28 @@ impl Map { where T: Into, { - self.0.get(&key.into()) + let key = key.into(); + self.0 + .get(&key) + .or_else(|| self.backwards_compatability_get(key)) + } + + fn backwards_compatability_get(&self, key: GKey) -> Option<&GValue> { + //The GraphBinary protocol may be returning properties + //as the distinct "T" type (T::Id) whereas older protocols + //would have just sent them as GKey::String("id"), etc + //This function is intended as a fallback for backwards compatability that if a caller + //requests a get or try_get for "id" that we also then attempt it T::Id or one of its siblings + //This also maintains the representation needed for the derive crate to find + //the types since the derive crate can't discriminate between a vertex property called "id" + //or a T::Id since they're both just called "id" + match key { + GKey::String(string) => match TryInto::::try_into(string.as_str()) { + Ok(fallback_key) => self.0.get(&fallback_key.into()), + Err(_) => None, + }, + _ => None, + } } ///Returns try_get and conversion @@ -95,8 +116,10 @@ impl Map { K: Into, V: std::convert::TryFrom, { + let key = key.into(); self.0 - .get(&key.into()) + .get(&key) + .or_else(|| self.backwards_compatability_get(key)) .cloned() .or_else(|| Some(GValue::Null)) .map(V::try_from) diff --git a/gremlin-client/src/structure/t.rs b/gremlin-client/src/structure/t.rs index 2a62492c..39900c1b 100644 --- a/gremlin-client/src/structure/t.rs +++ b/gremlin-client/src/structure/t.rs @@ -1,3 +1,5 @@ +use std::convert::TryFrom; + #[derive(Debug, PartialEq, Clone, Eq, Hash)] pub enum T { Id, @@ -5,3 +7,17 @@ pub enum T { Label, Value, } + +impl TryFrom<&str> for T { + type Error = String; + + fn try_from(value: &str) -> Result { + match value { + "id" => Ok(T::Id), + "key" => Ok(T::Key), + "label" => Ok(T::Label), + "value" => Ok(T::Value), + other => Err(format!("Unknown T literal {other:?}")), + } + } +} diff --git a/gremlin-client/tests/common.rs b/gremlin-client/tests/common.rs index 122a908a..6c46801e 100644 --- a/gremlin-client/tests/common.rs +++ b/gremlin-client/tests/common.rs @@ -1,15 +1,11 @@ +use std::convert::TryInto; + use gremlin_client::{structure::T, Map}; use rstest_reuse::template; pub fn assert_map_property(element_map: &Map, expected_key: &str, expected_value: &str) { let actual_prop_value: &String = element_map .get(expected_key) - .or(match expected_key { - "id" => element_map.get(T::Id), - "key" => element_map.get(T::Key), - "label" => element_map.get(T::Label), - _ => None, - }) .unwrap_or_else(|| panic!("Didn't have expected key {}", expected_key)) .get() .expect("Should be String"); diff --git a/gremlin-client/tests/integration_client.rs b/gremlin-client/tests/integration_client.rs index db2577a0..ef2ffde8 100644 --- a/gremlin-client/tests/integration_client.rs +++ b/gremlin-client/tests/integration_client.rs @@ -928,6 +928,7 @@ fn test_group_count_edge(protocol: IoProtocol) { fn test_vertex_mapping(protocol: IoProtocol) { use gremlin_client::derive::FromGValue; use std::convert::TryFrom; + let client = graph_serializer(protocol.clone()); let q = r#" g.addV('person') diff --git a/gremlin-client/tests/integration_traversal.rs b/gremlin-client/tests/integration_traversal.rs index 4ab4cfda..010649c4 100644 --- a/gremlin-client/tests/integration_traversal.rs +++ b/gremlin-client/tests/integration_traversal.rs @@ -236,7 +236,6 @@ mod merge_tests { let incoming_vertex_id = incoming_vertex .get("id") - .or(incoming_vertex.get(T::Id)) .expect("Should have returned vertex id"); assert_eq!(incoming_vertex_id, &vertex_a.id().to_gvalue()); @@ -247,7 +246,6 @@ mod merge_tests { .unwrap(); let outgoing_vertex_id = outgoing_vertex .get("id") - .or(outgoing_vertex.get(T::Id)) .expect("Should have returned vertex id"); assert_eq!(outgoing_vertex_id, &vertex_b.id().to_gvalue()); } @@ -313,7 +311,6 @@ mod merge_tests { .unwrap(); let incoming_vertex_id = incoming_vertex .get("id") - .or(incoming_vertex.get(T::Id)) .expect("Should have returned vertex id"); assert_eq!(incoming_vertex_id, &vertex_a.id().to_gvalue()); @@ -324,7 +321,6 @@ mod merge_tests { .unwrap(); let outgoing_vertex_id = outgoing_vertex .get("id") - .or(outgoing_vertex.get(T::Id)) .expect("Should have returned vertex id"); assert_eq!(outgoing_vertex_id, &vertex_b.id().to_gvalue()); } @@ -373,30 +369,19 @@ mod merge_tests { injection_map.insert("match_params".into(), on_match_map.into()); let do_merge_edge = |g: GraphTraversalSource| -> Map { - let mut attempt = 0; - loop { - attempt += 1; - let response = 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(); - if response.is_ok() { - break response; - } else if attempt > 10 { - std::panic!("mergeE vertices not observed before attempt exhaustion") - } - std::thread::sleep(std::time::Duration::from_millis(10)); - } - .expect("Should get a response") - .expect("Should return a edge properties") + 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()); @@ -425,8 +410,8 @@ mod merge_tests { return; } let client = graph_serializer(protocol); - let expected_vertex_label = "test_merge_e_options_vertex"; - let expected_edge_label = "test_merge_e_options_edge"; + let expected_vertex_label = "test_merge_e_anonymous_traversal_vertex"; + let expected_edge_label = "test_merge_e_anonymous_traversal_edge"; drop_vertices(&client, &expected_vertex_label).expect("Failed to drop vertiecs"); let g = traversal().with_remote(client); @@ -447,24 +432,14 @@ mod merge_tests { assignment_map.insert(Direction::Out.into(), vertex_b.id().into()); assignment_map.insert(T::Label.into(), expected_edge_label.into()); - let mut attempt = 0; - let anonymous_merge_e_properties = loop { - attempt += 1; - let response = g - .inject(1) - .unfold() - .coalesce::([__.merge_e(assignment_map.clone())]) - .element_map(()) - .next(); - if response.is_ok() { - break response; - } else if attempt > 10 { - std::panic!("mergeE vertices not observed before attempt exhaustion") - } - std::thread::sleep(std::time::Duration::from_millis(10)); - } - .expect("Should get a response") - .expect("Should return a edge properties"); + let anonymous_merge_e_properties = g + .inject(1) + .unfold() + .coalesce::([__.merge_e(assignment_map.clone())]) + .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) @@ -473,7 +448,6 @@ mod merge_tests { .unwrap(); let incoming_vertex_id = incoming_vertex .get("id") - .or(incoming_vertex.get(T::Id)) .expect("Should have returned vertex id"); assert_eq!(incoming_vertex_id, &vertex_a.id().to_gvalue()); @@ -484,7 +458,6 @@ mod merge_tests { .unwrap(); let outgoing_vertex_id = outgoing_vertex .get("id") - .or(outgoing_vertex.get(T::Id)) .expect("Should have returned vertex id"); assert_eq!(outgoing_vertex_id, &vertex_b.id().to_gvalue()); } @@ -544,7 +517,6 @@ mod merge_tests { .unwrap(); let brandy_vertex_id = brandy_vertex .get("id") - .or(brandy_vertex.get(T::Id)) .expect("Should have returned vertex id"); assert_eq!(*brandy_vertex_id, GValue::Int64(expected_brandy_id)); @@ -555,7 +527,6 @@ mod merge_tests { .unwrap(); let toby_vertex_id = toby_vertex .get("id") - .or(toby_vertex.get(T::Id)) .expect("Should have returned vertex id"); assert_eq!(*toby_vertex_id, GValue::Int64(expected_toby_id)); @@ -1295,11 +1266,8 @@ fn test_value_map(protocol: IoProtocol) { Some("test".to_owned()).as_ref(), get_map(&value, "name").unwrap() ); - assert!(results[0].get("id").or(results[0].get(T::Id)).is_some()); - assert!(results[0] - .get("label") - .or(results[0].get(T::Label)) - .is_some()); + assert!(results[0].get("id").is_some()); + assert!(results[0].get("label").is_some()); assert_eq!(true, results[0].get("name").is_some()); } @@ -1368,11 +1336,8 @@ fn test_element_map(protocol: IoProtocol) { ); assert_eq!(Some(vertex.label()), get_map_label(&value).unwrap()); assert_eq!(2, results[0].len()); - assert!(results[0].get("id").or(results[0].get(T::Id)).is_some()); - assert!(results[0] - .get("label") - .or(results[0].get(T::Label)) - .is_some()); + assert!(results[0].get("id").is_some()); + assert!(results[0].get("label").is_some()); let results = g.v(vertex.id()).element_map(()).to_list().unwrap(); let value = &results[0]; @@ -1386,11 +1351,8 @@ fn test_element_map(protocol: IoProtocol) { Some("test".to_owned()).as_ref(), get_map(&value, "name").unwrap() ); - assert!(results[0].get("id").or(results[0].get(T::Id)).is_some()); - assert!(results[0] - .get("label") - .or(results[0].get(T::Label)) - .is_some()); + assert!(results[0].get("id").is_some()); + assert!(results[0].get("label").is_some()); assert_eq!(true, results[0].get("name").is_some()); } @@ -2520,10 +2482,7 @@ fn test_anonymous_traversal_properties_drop(protocol: IoProtocol) { //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") - .or(element_map.get(T::Id)) - .expect("Should have id property"); + 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"); }; @@ -2871,6 +2830,7 @@ fn test_traversal_vertex_mapping(protocol: IoProtocol) { let client = graph_serializer(protocol); use chrono::{DateTime, TimeZone, Utc}; use gremlin_client::derive::FromGMap; + use gremlin_client::process::traversal::{Bytecode, TraversalBuilder}; use std::convert::TryFrom; drop_vertices(&client, "test_vertex_mapping").unwrap(); @@ -2894,6 +2854,7 @@ fn test_traversal_vertex_mapping(protocol: IoProtocol) { #[derive(Debug, PartialEq, FromGMap)] struct Person { + label: String, name: String, age: i32, time: i64, @@ -2901,11 +2862,11 @@ fn test_traversal_vertex_mapping(protocol: IoProtocol) { uuid: uuid::Uuid, optional: Option, } - let person = Person::try_from(mark.unwrap().unwrap()); - assert_eq!(person.is_ok(), true); + let person = Person::try_from(mark.unwrap().unwrap()).expect("Should get person"); assert_eq!( Person { + label: String::from("person"), name: String::from("Mark"), age: 22, time: 22, @@ -2913,6 +2874,6 @@ fn test_traversal_vertex_mapping(protocol: IoProtocol) { uuid: uuid, optional: None }, - person.unwrap() + person ); } From 5fb1adb781944b9975d0225abe63bdd0cddcc70b Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Wed, 13 Nov 2024 21:06:25 -0600 Subject: [PATCH 53/56] Parameterized custom vertex id tests, added support for JanusGraph's custom JanusGraphRelationIdentifier --- docker-compose/docker-compose.yaml | 2 + docker-compose/janusgraph_config.yaml | 54 +++++++ gremlin-client/src/io/graph_binary_v1.rs | 165 +++++++++++++++------- gremlin-client/tests/common.rs | 15 +- gremlin-client/tests/custom_vertex_ids.rs | 88 +++++++++++- 5 files changed, 262 insertions(+), 62 deletions(-) create mode 100644 docker-compose/janusgraph_config.yaml diff --git a/docker-compose/docker-compose.yaml b/docker-compose/docker-compose.yaml index 38817ac5..7bcd21e5 100644 --- a/docker-compose/docker-compose.yaml +++ b/docker-compose/docker-compose.yaml @@ -26,6 +26,8 @@ services: - JANUS_PROPS_TEMPLATE=inmemory ports: - "8184:8182" + volumes: + - ./janusgraph_config.yaml:/opt/janusgraph/conf/janusgraph-server.yaml healthcheck: test: ["CMD", "bin/gremlin.sh", "-e", "scripts/remote-connect.groovy"] interval: 10s diff --git a/docker-compose/janusgraph_config.yaml b/docker-compose/janusgraph_config.yaml new file mode 100644 index 00000000..811803c1 --- /dev/null +++ b/docker-compose/janusgraph_config.yaml @@ -0,0 +1,54 @@ +# Copyright 2019 JanusGraph Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +host: 0.0.0.0 +port: 8182 +evaluationTimeout: 30000 +channelizer: org.apache.tinkerpop.gremlin.server.channel.WebSocketChannelizer +graphManager: org.janusgraph.graphdb.management.JanusGraphManager +graphs: { + graph: conf/janusgraph-inmemory.properties +} +scriptEngines: { + gremlin-groovy: { + plugins: { org.janusgraph.graphdb.tinkerpop.plugin.JanusGraphGremlinPlugin: {}, + org.apache.tinkerpop.gremlin.server.jsr223.GremlinServerGremlinPlugin: {}, + org.apache.tinkerpop.gremlin.tinkergraph.jsr223.TinkerGraphGremlinPlugin: {}, + org.apache.tinkerpop.gremlin.jsr223.ImportGremlinPlugin: {classImports: [java.lang.Math], methodImports: [java.lang.Math#*]}, + org.apache.tinkerpop.gremlin.jsr223.ScriptFileGremlinPlugin: {files: [scripts/empty-sample.groovy]}}}} +serializers: + - { className: org.apache.tinkerpop.gremlin.util.ser.GraphBinaryMessageSerializerV1, config: { ioRegistries: [org.janusgraph.graphdb.tinkerpop.JanusGraphIoRegistry] }} + - { className: org.apache.tinkerpop.gremlin.util.ser.GraphBinaryMessageSerializerV1, config: { serializeResultToString: true }} + - { className: org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV3, config: { ioRegistries: [org.janusgraph.graphdb.tinkerpop.JanusGraphIoRegistry] }} + # Older serialization versions for backwards compatibility: + - { className: org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV2, config: { ioRegistries: [org.janusgraph.graphdb.tinkerpop.JanusGraphIoRegistry] }} + - { className: org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV1, config: { ioRegistries: [org.janusgraph.graphdb.tinkerpop.JanusGraphIoRegistryV1d0] }} + - { className: org.apache.tinkerpop.gremlin.util.ser.GraphSONUntypedMessageSerializerV1, config: { ioRegistries: [org.janusgraph.graphdb.tinkerpop.JanusGraphIoRegistryV1d0] }} +processors: + - { className: org.apache.tinkerpop.gremlin.server.op.session.SessionOpProcessor, config: { sessionTimeout: 28800000 }} + - { className: org.apache.tinkerpop.gremlin.server.op.traversal.TraversalOpProcessor, config: { cacheExpirationTime: 600000, cacheMaxSize: 1000 }} +metrics: { + consoleReporter: {enabled: true, interval: 180000}, + csvReporter: {enabled: true, interval: 180000, fileName: /tmp/gremlin-server-metrics.csv}, + jmxReporter: {enabled: true}, + slf4jReporter: {enabled: true, interval: 180000}, + graphiteReporter: {enabled: false, interval: 180000}} +maxInitialLineLength: 4096 +maxHeaderSize: 8192 +maxChunkSize: 8192 +maxContentLength: 65536 +maxAccumulationBufferComponents: 1024 +resultIterationBatchSize: 64 +writeBufferLowWaterMark: 32768 +writeBufferHighWaterMark: 65536 \ No newline at end of file diff --git a/gremlin-client/src/io/graph_binary_v1.rs b/gremlin-client/src/io/graph_binary_v1.rs index 1182fc87..30d72167 100644 --- a/gremlin-client/src/io/graph_binary_v1.rs +++ b/gremlin-client/src/io/graph_binary_v1.rs @@ -59,6 +59,7 @@ const MERTRICS: u8 = 0x2C; const TRAVERSAL_MERTRICS: u8 = 0x2D; const MERGE: u8 = 0x2E; const UNSPECIFIED_NULL_OBEJECT: u8 = 0xFE; +const CUSTOM: u8 = 0x00; pub(crate) struct RequestMessage<'a, 'b> { pub(crate) request_id: Uuid, @@ -714,10 +715,113 @@ impl GraphBinaryV1Deser for GValue { ))), } } + CUSTOM => { + let custom_name = String::from_be_bytes(bytes)?; + match custom_name.as_str() { + "janusgraph.RelationIdentifier" => { + let deserialized: Option = + GraphBinaryV1Deser::from_be_bytes(bytes)?; + Ok(deserialized + .map(|value| { + //We don't have a GValue for JG types, and moreover supporting future custom types we may not want to + //so for now just mapping it to a GValue::String + let mut converted = format!( + "{}-{}-{}", + value.relation_id, value.out_vertex_id, value.type_id + ); + if let Some(in_vertex_id) = value.in_vertex_id { + converted.push('-'); + converted.push_str(&in_vertex_id); + } + GValue::String(converted) + }) + .unwrap_or(GValue::Null)) + } + other => unimplemented!("Unimplemented handling of custom type {other}"), + } + } other => { - unimplemented!("Unimplemented deserialization byte {other}"); + let remainder: Vec = bytes.cloned().collect(); + unimplemented!("Unimplemented deserialization byte {other}. {remainder:?}"); + } + } + } +} + +#[derive(Debug)] +struct JanusGraphRelationIdentifier { + out_vertex_id: String, + type_id: i64, + relation_id: i64, + in_vertex_id: Option, +} + +impl GraphBinaryV1Deser for Option { + fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { + //Confirm the marker bytes that should be next + //0x1001 + let marker_bytes: i32 = GraphBinaryV1Deser::from_be_bytes(bytes)?; + if marker_bytes != 0x1001 { + return Err(GremlinError::Cast(format!( + "Unexpected marker bytes for JanusGraphRelationIdentifier" + ))); + } + + match bytes.next() { + Some(0x00) => { + //nothing to do + } + Some(0x01) => return Ok(None), + _ => return Err(GremlinError::Cast(format!("Invalid null value byte"))), + } + + fn read_string<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { + let mut string = String::new(); + //JG custom string serialization uses the high portion of a byte to indicate the terminus of the string + //so we'll need to mask out the value from the low half of the byte and then check the high portion + //for termination + loop { + let Some(byte) = bytes.next() else { + return Err(GremlinError::Cast(format!( + "Exhausted bytes before terminal string marker" + ))); + }; + string.push((byte & 0x7F) as char); + + if byte & 0x80 > 0 { + break; + } } + Ok(string) } + + fn read_vertex_id<'a, S: Iterator>( + bytes: &mut S, + ) -> GremlinResult> { + //There should be a type marker of either Long(0) or String(1) + //reuse bool here, mapping false to a Long and true to String + if bool::from_be_bytes(bytes)? { + Ok(Some(read_string(bytes)?)) + } else { + let value = ::from_be_bytes(bytes)?; + if value == 0 { + Ok(None) + } else { + Ok(Some(value.to_string())) + } + } + } + + let out_vertex_id = read_vertex_id(bytes)?.expect("Out vertex id should never be null"); + let type_id: i64 = GraphBinaryV1Deser::from_be_bytes(bytes)?; + let relation_id: i64 = GraphBinaryV1Deser::from_be_bytes(bytes)?; + let in_vertex_id = read_vertex_id(bytes)?; + Ok(Some(JanusGraphRelationIdentifier { + out_vertex_id, + type_id, + relation_id, + in_vertex_id, + })) } } @@ -900,8 +1004,7 @@ impl GraphBinaryV1Deser for VertexProperty { consume_expected_null_reference_bytes(bytes, "Parent vertex")?; //{properties} is a fully qualified typed value composed of {type_code}{type_info}{value_flag}{value} which contains properties. - //Note that as TinkerPop currently send "references" only, this value will always be null. - consume_expected_null_reference_bytes(bytes, "Properties")?; + let _ = GValue::from_be_bytes(bytes)?; //we don't have a place for this? Ok(VertexProperty::new(id, label, value)) } } @@ -914,58 +1017,18 @@ impl GraphBinaryV1Deser for Vertex { //{label} is a String value. let label: String = GraphBinaryV1Deser::from_be_bytes(bytes)?; //{properties} is a fully qualified typed value composed of {type_code}{type_info}{value_flag}{value} which contains properties. - //Note that as TinkerPop currently send "references" only, this value will always be null. //properties: HashMap>, let properties: GValue = GraphBinaryV1Deser::from_be_bytes(bytes)?; match properties { - //Should always be null GValue::Null => Ok(Vertex::new(id.try_into()?, label, HashMap::new())), - // GValue::Map(map) => { - // let properties = map - // .into_iter() - // .map(|(k, v)| { - // let key = match k { - // GKey::T(t) => match t { - // T::Id => "id".to_owned(), - // T::Key => "key".to_owned(), - // T::Label => "label".to_owned(), - // T::Value => "value".to_owned(), - // }, - // GKey::String(s) => s, - // GKey::Int64(i) => i.to_string(), - // GKey::Int32(i) => i.to_string(), - // _ => { - // return Err(GremlinError::Cast(format!( - // "Unsupported vertex property key type" - // ))) - // } - // }; - - // fn unfurl_gvalue_to_vertex_property( - // v: GValue, - // ) -> Result, GremlinError> { - // match v { - // GValue::VertexProperty(vertex_property) => { - // Ok(vec![vertex_property]) - // } - // GValue::List(list) => Ok(list - // .into_iter() - // .map(|value| unfurl_gvalue_to_vertex_property(value)) - // .collect::>, GremlinError>>()? - // .into_iter() - // .flat_map(|vec| vec.into_iter()) - // .collect()), - // _ => Err(GremlinError::Cast(format!( - // "Unsupported vertex property value type" - // ))), - // } - // } - - // Ok((key, unfurl_gvalue_to_vertex_property(v)?)) - // }) - // .collect::>, GremlinError>>()?; - // Ok(Vertex::new(id.try_into()?, label, properties)) - // } + GValue::List(list) => { + let mut properties = HashMap::new(); + for element in list.into_iter() { + let vp: VertexProperty = element.take()?; + properties.insert(vp.label().clone(), vec![vp]); + } + Ok(Vertex::new(id.try_into()?, label, properties)) + } other => Err(GremlinError::Cast(format!( "Unsupported vertex property type: {other:?}" ))), diff --git a/gremlin-client/tests/common.rs b/gremlin-client/tests/common.rs index 6c46801e..dd7e76b3 100644 --- a/gremlin-client/tests/common.rs +++ b/gremlin-client/tests/common.rs @@ -29,8 +29,15 @@ pub mod io { GremlinClient::connect(("localhost", 8182)) } - fn connect_janusgraph_client() -> GremlinResult { - GremlinClient::connect(("localhost", 8184)) + fn connect_janusgraph_client(serializer: IoProtocol) -> GremlinResult { + GremlinClient::connect( + ConnectionOptions::builder() + .host("localhost") + .port(8184) + .serializer(serializer.clone()) + .deserializer(serializer) + .build(), + ) } pub fn connect_serializer(serializer: IoProtocol) -> GremlinResult { @@ -53,8 +60,8 @@ pub mod io { connect().expect("It should connect") } - pub fn expect_janusgraph_client() -> GremlinClient { - connect_janusgraph_client().expect("It should connect") + pub fn expect_janusgraph_client(serializer: IoProtocol) -> GremlinClient { + connect_janusgraph_client(serializer).expect("It should connect") } pub fn expect_client_serializer(serializer: IoProtocol) -> GremlinClient { diff --git a/gremlin-client/tests/custom_vertex_ids.rs b/gremlin-client/tests/custom_vertex_ids.rs index 97b74f96..802cbda4 100644 --- a/gremlin-client/tests/custom_vertex_ids.rs +++ b/gremlin-client/tests/custom_vertex_ids.rs @@ -4,17 +4,90 @@ use common::io::{drop_vertices, expect_janusgraph_client}; use gremlin_client::{ process::traversal::{traversal, __}, structure::T, - GKey, GValue, + GKey, GValue, IoProtocol, }; +use rstest::*; +use rstest_reuse::apply; +use serial_test::serial; + 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 client = expect_janusgraph_client(); +#[apply(common::serializers)] +#[serial(test_mapping_custom_vertex_id)] +#[cfg(feature = "derive")] +fn test_mapping_custom_vertex_id(protocol: IoProtocol) { + if protocol == IoProtocol::GraphSONV2 { + //GraphSONV2 doesn't support the non-string key of the merge step, + //so skip it in testing + return; + } + let client = expect_janusgraph_client(protocol); + use chrono::{DateTime, TimeZone, Utc}; + use gremlin_client::derive::FromGMap; + use gremlin_client::process::traversal::{Bytecode, TraversalBuilder}; + use std::convert::TryFrom; + + drop_vertices(&client, "test_mapping_custom_vertex_id").unwrap(); + + let g = traversal().with_remote(client); + + let uuid = uuid::Uuid::new_v4(); + let mark = g + .add_v("test_mapping_custom_vertex_id") + .property(T::Id, "test_mapping_custom_id") + .property("name", "Mark") + .property("age", 22) + .property("time", 22 as i64) + .property("score", 3.2) + .property("uuid", uuid.clone()) + .property("datetime", chrono::Utc.timestamp(1551825863, 0)) + .property("date", 1551825863 as i64) + .value_map(true) + .by(TraversalBuilder::new(Bytecode::new()).unfold()) + .next(); + assert_eq!(mark.is_ok(), true); + + #[derive(Debug, PartialEq, FromGMap)] + struct Person { + id: String, + label: String, + name: String, + age: i32, + time: i64, + datetime: DateTime, + uuid: uuid::Uuid, + optional: Option, + } + let person = Person::try_from(mark.unwrap().unwrap()).expect("Should get person"); + + assert_eq!( + Person { + id: String::from("test_mapping_custom_id"), + label: String::from("test_mapping_custom_vertex_id"), + name: String::from("Mark"), + age: 22, + time: 22, + datetime: chrono::Utc.timestamp(1551825863, 0), + uuid: uuid, + optional: None + }, + person + ); +} + +#[apply(common::serializers)] +#[serial(test_merge_v_custom_id)] +fn test_merge_v_custom_id(protocol: IoProtocol) { + if protocol == IoProtocol::GraphSONV2 { + //GraphSONV2 doesn't support the non-string key of the merge step, + //so skip it in testing + return; + } + let client = expect_janusgraph_client(protocol); let expected_label = "test_merge_v_custom_id"; drop_vertices(&client, expected_label).expect("Failed to drop vertices"); let g = traversal().with_remote(client); @@ -75,9 +148,10 @@ fn test_merge_v_custom_id() { assert_eq!(expected_property, actual_property); } -#[test] -fn test_add_v_custom_id() { - let client = expect_janusgraph_client(); +#[apply(common::serializers)] +#[serial(test_merge_v_custom_id)] +fn test_add_v_custom_id(protocol: IoProtocol) { + let client = expect_janusgraph_client(protocol); let expected_id = "test_add_v_custom_id"; let test_vertex_label = "test_add_v_custom_id"; drop_vertices(&client, test_vertex_label).expect("Failed to drop vertices"); From 03cb90a3a81aa8ce3e9e23f522e9193647430bac Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Thu, 14 Nov 2024 16:27:53 -0600 Subject: [PATCH 54/56] Populate edge properties in GraphSONV2/V3 & GraphBinary. Also implemented deserialization of Property in GraphBinary --- gremlin-client/src/io/graph_binary_v1.rs | 46 +++++++++-- gremlin-client/src/io/serializer_v2.rs | 67 ++++++++++------ gremlin-client/src/io/serializer_v3.rs | 59 +++++++++----- gremlin-client/src/structure/macros.rs | 2 +- gremlin-client/tests/custom_vertex_ids.rs | 95 +++++++++++++++++------ 5 files changed, 196 insertions(+), 73 deletions(-) diff --git a/gremlin-client/src/io/graph_binary_v1.rs b/gremlin-client/src/io/graph_binary_v1.rs index 30d72167..db7cdd9c 100644 --- a/gremlin-client/src/io/graph_binary_v1.rs +++ b/gremlin-client/src/io/graph_binary_v1.rs @@ -9,7 +9,7 @@ use crate::{ message::{ReponseStatus, Response, ResponseResult}, process::traversal::{Instruction, Order, Scope}, structure::{Column, Direction, Merge, Pop, TextP, Traverser, T}, - Cardinality, Edge, GKey, GValue, GremlinError, GremlinResult, Metric, Path, ToGValue, + Cardinality, Edge, GKey, GValue, GremlinError, GremlinResult, Metric, Path, Property, ToGValue, TraversalMetrics, Vertex, VertexProperty, GID, }; @@ -34,7 +34,7 @@ const SET: u8 = 0x0B; const UUID: u8 = 0x0C; const EDGE: u8 = 0x0D; const PATH: u8 = 0x0E; -// const PROPERTY: u8 = 0x0F; +const PROPERTY: u8 = 0x0F; // const TINKERGRAPH: u8 = 0x10; const VERTEX: u8 = 0x11; const VERTEX_PROPERTY: u8 = 0x12; @@ -671,6 +671,10 @@ impl GraphBinaryV1Deser for GValue { Some(value) => GValue::Path(value), None => GValue::Null, }), + PROPERTY => Ok(match Property::from_be_bytes_nullable(bytes)? { + Some(value) => GValue::Property(value), + None => GValue::Null, + }), VERTEX => Ok(match Vertex::from_be_bytes_nullable(bytes)? { Some(value) => GValue::Vertex(value), None => GValue::Null, @@ -963,8 +967,23 @@ impl GraphBinaryV1Deser for Edge { //{parent} is a fully qualified typed value composed of {type_code}{type_info}{value_flag}{value} which contains the parent Vertex. Note that as TinkerPop currently send "references" only, this value will always be null. consume_expected_null_reference_bytes(bytes, "Parent")?; - //{properties} is a fully qualified typed value composed of {type_code}{type_info}{value_flag}{value} which contains the properties for the edge. Note that as TinkerPop currently send "references" only this value will always be null. - consume_expected_null_reference_bytes(bytes, "Properties")?; + //{properties} is a fully qualified typed value composed of {type_code}{type_info}{value_flag}{value} which contains the properties for the edge. + let properties = match GValue::from_be_bytes(bytes)? { + GValue::Null => HashMap::new(), + GValue::List(raw_properties) => raw_properties + .into_iter() + .map(|property| { + property + .take::() + .map(|converted| (converted.label().clone(), converted)) + }) + .collect::>()?, + _ => { + return Err(GremlinError::Cast(format!( + "Edge properties should either be Null or List" + ))) + } + }; Ok(Edge::new( id.try_into()?, label, @@ -972,11 +991,28 @@ impl GraphBinaryV1Deser for Edge { in_v_label, out_v_id.try_into()?, out_v_label, - HashMap::new(), + properties, )) } } +impl GraphBinaryV1Deser for Property { + fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { + //Format: {key}{value}{parent} + + //{key} is a String value + let key: String = GraphBinaryV1Deser::from_be_bytes(bytes)?; + + //{value} is a fully qualified typed value composed of {type_code}{type_info}{value_flag}{value} + let value = GValue::from_be_bytes(bytes)?; + + //{parent} is a fully qualified typed value composed of {type_code}{type_info}{value_flag}{value} which is either an Edge or VertexProperty. + //Note that as TinkerPop currently sends "references" only this value will always be null. + consume_expected_null_reference_bytes(bytes, "Parent")?; + Ok(Property::new(key, value)) + } +} + impl GraphBinaryV1Deser for Path { fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { let labels = GValue::from_be_bytes(bytes)?; diff --git a/gremlin-client/src/io/serializer_v2.rs b/gremlin-client/src/io/serializer_v2.rs index a67b2fae..1042f68c 100644 --- a/gremlin-client/src/io/serializer_v2.rs +++ b/gremlin-client/src/io/serializer_v2.rs @@ -177,6 +177,26 @@ where let out_v_id = deserialize_id(reader, &val["outV"])?; let out_v_label = get_value!(&val["outVLabel"], Value::String)?.clone(); + let json_properties = &val["properties"]; + let properties = if json_properties.is_object() { + get_value!(&json_properties, Value::Object)? + .into_iter() + .map(|(key, value)| { + deserializer_v2(value) + .map(|deserialized| { + //if the given value is a property, unfurl it + match deserialized { + GValue::Property(property) => property.value().clone(), + other => other, + } + }) + .map(|property| (key.clone(), Property::new(key, property))) + }) + .collect::>()? + } else { + HashMap::new() + }; + Ok(Edge::new( id, label, @@ -184,7 +204,7 @@ where in_v_label, out_v_id, out_v_label, - HashMap::new(), + properties, ) .into()) } @@ -431,7 +451,7 @@ mod tests { use super::deserializer_v2; use serde_json::json; - use crate::{edge, vertex}; + use crate::{edge, vertex, Edge}; use crate::structure::{GValue, Map, Path, Property, Token, Vertex, VertexProperty, GID}; use chrono::offset::TimeZone; @@ -593,29 +613,32 @@ mod tests { #[test] fn test_edge() { - let value = json!({"@type":"g:Edge","@value":{"id":{"@type":"g:Int32","@value":13},"label":"develops","inVLabel":"software","outVLabel":"person","inV":{"@type":"g:Int32","@value":10},"outV":{"@type":"g:Int32","@value":1},"properties":{"since":{"@type":"g:Property","@value":{"key":"since","value":{"@type":"g:Int32","@value":2009}}}}}}); + let value = json!({"@type":"g:Edge","@value":{"id":{"@type":"g:Int32","@value":13},"label":"develops","inVLabel":"software","outVLabel":"person","inV":{"@type":"g:Int32","@value":10},"outV":{"@type":"g:Int32","@value":1},"properties":{"since":{"@type":"g:Int32","@value":2009}}}}); let result = deserializer_v2(&value).expect("Failed to deserialize an Edge"); + let actual: Edge = result.take().expect("Should have deserialized"); + + let expected = edge!({ + id => 13, + label=> "develops", + inV => { + id => 10, + label => "software" + }, + outV => { + id => 1, + label => "person" + }, + properties => { + "since" => 2009i32 + } + }); - assert_eq!( - result, - edge!({ - id => 13, - label=> "develops", - inV => { - id => 10, - label => "software" - }, - outV => { - id => 1, - label => "person" - }, - properties => { - - } - }) - .into() - ); + assert_eq!(actual, expected); + //Now make sure the properties deserialized correctly + let expected_properties: Vec<(String, Property)> = expected.into_iter().collect(); + let actual_properites: Vec<(String, Property)> = actual.into_iter().collect(); + assert_eq!(actual_properites, expected_properties); } #[test] diff --git a/gremlin-client/src/io/serializer_v3.rs b/gremlin-client/src/io/serializer_v3.rs index ec96fc7b..ea4c6134 100644 --- a/gremlin-client/src/io/serializer_v3.rs +++ b/gremlin-client/src/io/serializer_v3.rs @@ -170,6 +170,20 @@ where let out_v_id = deserialize_id(reader, &val["outV"])?; let out_v_label = get_value!(&val["outVLabel"], Value::String)?.clone(); + let json_properties = &val["properties"]; + let properties = if json_properties.is_object() { + get_value!(&json_properties, Value::Object)? + .values() + .map(|value| { + deserialize_property(reader, &value["@value"]) + .and_then(|property| property.take::()) + .map(|property| (property.label().clone(), property)) + }) + .collect::>()? + } else { + HashMap::new() + }; + Ok(Edge::new( id, label, @@ -177,7 +191,7 @@ where in_v_label, out_v_id, out_v_label, - HashMap::new(), + properties, ) .into()) } @@ -439,7 +453,7 @@ mod tests { use super::deserializer_v3; use serde_json::json; - use crate::{edge, vertex}; + use crate::{edge, vertex, Edge}; use crate::structure::{ GValue, Map, Metric, Path, Property, Token, TraversalMetrics, Vertex, VertexProperty, GID, @@ -626,26 +640,29 @@ mod tests { let value = json!({"@type":"g:Edge","@value":{"id":{"@type":"g:Int32","@value":13},"label":"develops","inVLabel":"software","outVLabel":"person","inV":{"@type":"g:Int32","@value":10},"outV":{"@type":"g:Int32","@value":1},"properties":{"since":{"@type":"g:Property","@value":{"key":"since","value":{"@type":"g:Int32","@value":2009}}}}}}); let result = deserializer_v3(&value).expect("Failed to deserialize an Edge"); + let actual: Edge = result.take().expect("Should have deserialized"); + + let expected = edge!({ + id => 13, + label=> "develops", + inV => { + id => 10, + label => "software" + }, + outV => { + id => 1, + label => "person" + }, + properties => { + "since" => 2009i32 + } + }); - assert_eq!( - result, - edge!({ - id => 13, - label=> "develops", - inV => { - id => 10, - label => "software" - }, - outV => { - id => 1, - label => "person" - }, - properties => { - - } - }) - .into() - ); + assert_eq!(actual, expected); + //Now make sure the properties deserialized correctly + let expected_properties: Vec<(String, Property)> = expected.into_iter().collect(); + let actual_properites: Vec<(String, Property)> = actual.into_iter().collect(); + assert_eq!(actual_properites, expected_properties); } #[test] diff --git a/gremlin-client/src/structure/macros.rs b/gremlin-client/src/structure/macros.rs index 5b675765..43b557af 100644 --- a/gremlin-client/src/structure/macros.rs +++ b/gremlin-client/src/structure/macros.rs @@ -31,7 +31,7 @@ macro_rules! edge { #[allow(unused_mut)] let mut properties = ::std::collections::HashMap::::new(); $( - let p = Property::new($key.into(),$value.into()); + let p = Property::new($key,$value); properties.insert($key.into(),p); )* $crate::Edge::new($id.into(), $label, $inVId.into(),$inVLabel,$outVId.into(),$outVLabel,properties) diff --git a/gremlin-client/tests/custom_vertex_ids.rs b/gremlin-client/tests/custom_vertex_ids.rs index 802cbda4..4f378530 100644 --- a/gremlin-client/tests/custom_vertex_ids.rs +++ b/gremlin-client/tests/custom_vertex_ids.rs @@ -1,15 +1,16 @@ use std::collections::HashMap; -use common::io::{drop_vertices, expect_janusgraph_client}; +use common::io::expect_janusgraph_client; use gremlin_client::{ process::traversal::{traversal, __}, + structure::Edge, structure::T, - GKey, GValue, IoProtocol, + GKey, GValue, IoProtocol, Property, }; use rstest::*; use rstest_reuse::apply; -use serial_test::serial; +use uuid::Uuid; mod common; @@ -17,7 +18,6 @@ mod common; //https://docs.janusgraph.org/advanced-topics/custom-vertex-id/ #[apply(common::serializers)] -#[serial(test_mapping_custom_vertex_id)] #[cfg(feature = "derive")] fn test_mapping_custom_vertex_id(protocol: IoProtocol) { if protocol == IoProtocol::GraphSONV2 { @@ -31,14 +31,13 @@ fn test_mapping_custom_vertex_id(protocol: IoProtocol) { use gremlin_client::process::traversal::{Bytecode, TraversalBuilder}; use std::convert::TryFrom; - drop_vertices(&client, "test_mapping_custom_vertex_id").unwrap(); - let g = traversal().with_remote(client); let uuid = uuid::Uuid::new_v4(); + let mark_id = create_novel_vertex_id(); let mark = g .add_v("test_mapping_custom_vertex_id") - .property(T::Id, "test_mapping_custom_id") + .property(T::Id, mark_id.as_str()) .property("name", "Mark") .property("age", 22) .property("time", 22 as i64) @@ -66,7 +65,7 @@ fn test_mapping_custom_vertex_id(protocol: IoProtocol) { assert_eq!( Person { - id: String::from("test_mapping_custom_id"), + id: mark_id, label: String::from("test_mapping_custom_vertex_id"), name: String::from("Mark"), age: 22, @@ -80,20 +79,64 @@ fn test_mapping_custom_vertex_id(protocol: IoProtocol) { } #[apply(common::serializers)] -#[serial(test_merge_v_custom_id)] +fn test_jg_add_e(protocol: IoProtocol) { + //It seems JanusGraph differs from the standard Tinkerpop Gremlin Server in how it returns edges + //JG returns edge properties in the same call if the edge is the terminal type + let client = expect_janusgraph_client(protocol); + let g = traversal().with_remote(client.clone()); + let v1 = g + .add_v("test_jg_add_e") + .property(T::Id, create_novel_vertex_id()) + .property("name", "foo") + .next() + .expect("Should to get response") + .expect("Should have gotten a vertex"); + + let v2 = g + .add_v("test_jg_add_e") + .property(T::Id, create_novel_vertex_id()) + .next() + .expect("Should get response") + .expect("Should have gotten a vertex"); + + let created_edge = g + .v(v1.id().clone()) + .out_e("knows") + .where_(__.in_v().id().is(v2.id().clone())) + .fold() + .coalesce::([ + __.unfold(), + __.add_e("knows") + .from(__.v(v1.id().clone())) + .to(__.v(v2.id().clone())), + ]) + .property("someKey", "someValue") + .next() + .expect("Should get response") + .expect("Should get edge"); + let actual_property = created_edge + .property("someKey") + .expect("Should have had property"); + let actual_property = actual_property + .get::() + .expect("Should have had property with expected type"); + assert_eq!(actual_property, "someValue"); +} + +#[apply(common::serializers)] fn test_merge_v_custom_id(protocol: IoProtocol) { if protocol == IoProtocol::GraphSONV2 { //GraphSONV2 doesn't support the non-string key of the merge step, //so skip it in testing return; } + let client = expect_janusgraph_client(protocol); - 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"; + let expected_id = create_novel_vertex_id(); + let expected_label = "test_merge_v_custom_id"; let mut start_step_map: HashMap = HashMap::new(); - start_step_map.insert(T::Id.into(), expected_id.into()); + start_step_map.insert(T::Id.into(), expected_id.clone().into()); start_step_map.insert(T::Label.into(), expected_label.into()); let actual_vertex = g .merge_v(start_step_map) @@ -101,19 +144,19 @@ fn test_merge_v_custom_id(protocol: IoProtocol) { .expect("Should get a response") .expect("Should return a vertex"); match actual_vertex.id() { - gremlin_client::GID::String(actual) => assert_eq!(expected_id, actual), + 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_id = create_novel_vertex_id(); 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::Id.into(), expected_id.clone().into()); lookup_map.insert(T::Label.into(), "myvertexlabel".into()); let mut property_map: HashMap = HashMap::new(); property_map.insert("propertyKey".into(), expected_property.into()); @@ -136,7 +179,7 @@ fn test_merge_v_custom_id(protocol: IoProtocol) { .expect("Should have returned a vertex"); match actual_vertex.id() { - gremlin_client::GID::String(actual) => assert_eq!(expected_id, actual), + gremlin_client::GID::String(actual) => assert_eq!(&expected_id, actual), other => panic!("Didn't get expected id type {:?}", other), } @@ -149,21 +192,25 @@ fn test_merge_v_custom_id(protocol: IoProtocol) { } #[apply(common::serializers)] -#[serial(test_merge_v_custom_id)] fn test_add_v_custom_id(protocol: IoProtocol) { let client = expect_janusgraph_client(protocol); - let expected_id = "test_add_v_custom_id"; - let test_vertex_label = "test_add_v_custom_id"; - drop_vertices(&client, test_vertex_label).expect("Failed to drop vertices"); + let expected_id = create_novel_vertex_id(); let g = traversal().with_remote(client); let actual_vertex = g - .add_v(test_vertex_label) - .property(T::Id, expected_id) + .add_v("test_add_v_custom_id") + .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), + gremlin_client::GID::String(actual) => assert_eq!(&expected_id, actual), other => panic!("Didn't get expected id type {:?}", other), } } + +fn create_novel_vertex_id() -> String { + //JanusGraph by default treats "-" as a reserved character + //we can override it, but to stay closer to the default nature of JG just map the character to "_" + //in our generated vertex ids + Uuid::new_v4().to_string().replace("-", "_") +} From 49799e267bffc085ad07e8b07538ef718607b0a9 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Thu, 14 Nov 2024 16:42:04 -0600 Subject: [PATCH 55/56] Removed opaque assert_eq losing Err message --- gremlin-client/tests/custom_vertex_ids.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gremlin-client/tests/custom_vertex_ids.rs b/gremlin-client/tests/custom_vertex_ids.rs index 4f378530..92112a0c 100644 --- a/gremlin-client/tests/custom_vertex_ids.rs +++ b/gremlin-client/tests/custom_vertex_ids.rs @@ -47,8 +47,8 @@ fn test_mapping_custom_vertex_id(protocol: IoProtocol) { .property("date", 1551825863 as i64) .value_map(true) .by(TraversalBuilder::new(Bytecode::new()).unfold()) - .next(); - assert_eq!(mark.is_ok(), true); + .next() + .expect("Should get a response"); #[derive(Debug, PartialEq, FromGMap)] struct Person { From 766f045045b5070ecefb1fb5af6cb4aa11393857 Mon Sep 17 00:00:00 2001 From: Allan Clements Date: Thu, 14 Nov 2024 16:52:41 -0600 Subject: [PATCH 56/56] Use universal JG test serial annotation to prevent flakey test failure --- gremlin-client/tests/custom_vertex_ids.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/gremlin-client/tests/custom_vertex_ids.rs b/gremlin-client/tests/custom_vertex_ids.rs index 92112a0c..1df9028f 100644 --- a/gremlin-client/tests/custom_vertex_ids.rs +++ b/gremlin-client/tests/custom_vertex_ids.rs @@ -10,6 +10,7 @@ use gremlin_client::{ use rstest::*; use rstest_reuse::apply; +use serial_test::serial; use uuid::Uuid; mod common; @@ -18,6 +19,7 @@ mod common; //https://docs.janusgraph.org/advanced-topics/custom-vertex-id/ #[apply(common::serializers)] +#[serial(jg_throttle)] #[cfg(feature = "derive")] fn test_mapping_custom_vertex_id(protocol: IoProtocol) { if protocol == IoProtocol::GraphSONV2 { @@ -61,7 +63,7 @@ fn test_mapping_custom_vertex_id(protocol: IoProtocol) { uuid: uuid::Uuid, optional: Option, } - let person = Person::try_from(mark.unwrap().unwrap()).expect("Should get person"); + let person = Person::try_from(mark.unwrap()).expect("Should get person"); assert_eq!( Person { @@ -79,6 +81,7 @@ fn test_mapping_custom_vertex_id(protocol: IoProtocol) { } #[apply(common::serializers)] +#[serial(jg_throttle)] fn test_jg_add_e(protocol: IoProtocol) { //It seems JanusGraph differs from the standard Tinkerpop Gremlin Server in how it returns edges //JG returns edge properties in the same call if the edge is the terminal type @@ -124,6 +127,7 @@ fn test_jg_add_e(protocol: IoProtocol) { } #[apply(common::serializers)] +#[serial(jg_throttle)] fn test_merge_v_custom_id(protocol: IoProtocol) { if protocol == IoProtocol::GraphSONV2 { //GraphSONV2 doesn't support the non-string key of the merge step, @@ -192,6 +196,7 @@ fn test_merge_v_custom_id(protocol: IoProtocol) { } #[apply(common::serializers)] +#[serial(jg_throttle)] fn test_add_v_custom_id(protocol: IoProtocol) { let client = expect_janusgraph_client(protocol); let expected_id = create_novel_vertex_id();