Skip to content

Commit

Permalink
Populate edge properties in GraphSONV2/V3 & GraphBinary. Also impleme…
Browse files Browse the repository at this point in the history
…nted deserialization of Property in GraphBinary
  • Loading branch information
criminosis committed Nov 14, 2024
1 parent 5fb1adb commit 03cb90a
Show file tree
Hide file tree
Showing 5 changed files with 196 additions and 73 deletions.
46 changes: 41 additions & 5 deletions gremlin-client/src/io/graph_binary_v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};

Expand All @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -963,20 +967,52 @@ 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::<Property>()
.map(|converted| (converted.label().clone(), converted))
})
.collect::<GremlinResult<_>>()?,
_ => {
return Err(GremlinError::Cast(format!(
"Edge properties should either be Null or List"
)))
}
};
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(),
properties,
))
}
}

impl GraphBinaryV1Deser for Property {
fn from_be_bytes<'a, S: Iterator<Item = &'a u8>>(bytes: &mut S) -> GremlinResult<Self> {
//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<Item = &'a u8>>(bytes: &mut S) -> GremlinResult<Self> {
let labels = GValue::from_be_bytes(bytes)?;
Expand Down
67 changes: 45 additions & 22 deletions gremlin-client/src/io/serializer_v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,14 +177,34 @@ 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::<GremlinResult<_>>()?
} else {
HashMap::new()
};

Ok(Edge::new(
id,
label,
in_v_id,
in_v_label,
out_v_id,
out_v_label,
HashMap::new(),
properties,
)
.into())
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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]
Expand Down
59 changes: 38 additions & 21 deletions gremlin-client/src/io/serializer_v3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,14 +170,28 @@ 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::<Property>())
.map(|property| (property.label().clone(), property))
})
.collect::<GremlinResult<_>>()?
} else {
HashMap::new()
};

Ok(Edge::new(
id,
label,
in_v_id,
in_v_label,
out_v_id,
out_v_label,
HashMap::new(),
properties,
)
.into())
}
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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]
Expand Down
2 changes: 1 addition & 1 deletion gremlin-client/src/structure/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ macro_rules! edge {
#[allow(unused_mut)]
let mut properties = ::std::collections::HashMap::<String,$crate::Property>::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)
Expand Down
Loading

0 comments on commit 03cb90a

Please sign in to comment.