Skip to content

Commit

Permalink
Parameterized custom vertex id tests, added support for JanusGraph's …
Browse files Browse the repository at this point in the history
…custom JanusGraphRelationIdentifier
  • Loading branch information
criminosis committed Nov 14, 2024
1 parent 081ad4e commit 5fb1adb
Show file tree
Hide file tree
Showing 5 changed files with 262 additions and 62 deletions.
2 changes: 2 additions & 0 deletions docker-compose/docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
54 changes: 54 additions & 0 deletions docker-compose/janusgraph_config.yaml
Original file line number Diff line number Diff line change
@@ -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
165 changes: 114 additions & 51 deletions gremlin-client/src/io/graph_binary_v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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<JanusGraphRelationIdentifier> =
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<u8> = 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<String>,
}

impl GraphBinaryV1Deser for Option<JanusGraphRelationIdentifier> {
fn from_be_bytes<'a, S: Iterator<Item = &'a u8>>(bytes: &mut S) -> GremlinResult<Self> {
//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<Item = &'a u8>>(bytes: &mut S) -> GremlinResult<String> {
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<Item = &'a u8>>(
bytes: &mut S,
) -> GremlinResult<Option<String>> {
//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 = <i64 as GraphBinaryV1Deser>::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,
}))
}
}

Expand Down Expand Up @@ -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))
}
}
Expand All @@ -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<String, Vec<VertexProperty>>,
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<Vec<VertexProperty>, 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::<Result<Vec<Vec<VertexProperty>>, 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::<Result<HashMap<String, Vec<VertexProperty>>, 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:?}"
))),
Expand Down
15 changes: 11 additions & 4 deletions gremlin-client/tests/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,15 @@ pub mod io {
GremlinClient::connect(("localhost", 8182))
}

fn connect_janusgraph_client() -> GremlinResult<GremlinClient> {
GremlinClient::connect(("localhost", 8184))
fn connect_janusgraph_client(serializer: IoProtocol) -> GremlinResult<GremlinClient> {
GremlinClient::connect(
ConnectionOptions::builder()
.host("localhost")
.port(8184)
.serializer(serializer.clone())
.deserializer(serializer)
.build(),
)
}

pub fn connect_serializer(serializer: IoProtocol) -> GremlinResult<GremlinClient> {
Expand All @@ -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 {
Expand Down
Loading

0 comments on commit 5fb1adb

Please sign in to comment.