Skip to content

Commit 3fe9d28

Browse files
authored
graphson: support embedded nodes as properties (#378)
* graphson: support embedded nodes as properties * fix other test (side effect) * fmt
1 parent bd1960e commit 3fe9d28

File tree

8 files changed

+66
-16
lines changed

8 files changed

+66
-16
lines changed

formats/src/main/scala/overflowdb/formats/graphson/GraphSONExporter.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ object GraphSONExporter extends Exporter {
8787
case x: Float => FloatValue(x)
8888
case x: Int => IntValue(x)
8989
case x: Long => LongValue(x)
90+
case x: Node => NodeIdValue(x.id())
9091
}
9192
}
9293

formats/src/main/scala/overflowdb/formats/graphson/GraphSONImporter.scala

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,16 @@ object GraphSONImporter extends Importer {
2525
}
2626

2727
private def addNode(n: Vertex, graph: Graph): Unit = {
28-
graph.addNode(n.id.`@value`, n.label, flattenProperties(n.properties): _*)
28+
graph.addNode(n.id.`@value`, n.label, flattenProperties(n.properties, graph): _*)
2929
}
3030

31-
private def flattenProperties(m: Map[String, Property]): Array[_] = {
31+
private def flattenProperties(m: Map[String, Property], graph: Graph): Array[_] = {
3232
m.view
3333
.mapValues { v =>
3434
v.`@value` match {
35-
case x: ListValue => x.`@value`.map(_.`@value`)
36-
case x => x.`@value`
35+
case ListValue(value, _) => value.map(_.`@value`)
36+
case NodeIdValue(value, _) => graph.node(value)
37+
case x => x.`@value`
3738
}
3839
}
3940
.flatMap { case (k, v) => Seq(k, v) }
@@ -43,6 +44,6 @@ object GraphSONImporter extends Importer {
4344
private def addEdge(e: Edge, graph: Graph): Unit = {
4445
val src = graph.node(e.outV.`@value`)
4546
val tgt = graph.node(e.inV.`@value`)
46-
src.addEdge(e.label, tgt, flattenProperties(e.properties): _*)
47+
src.addEdge(e.label, tgt, flattenProperties(e.properties, graph): _*)
4748
}
4849
}

formats/src/main/scala/overflowdb/formats/graphson/GraphSONProtocol.scala

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,12 @@ object GraphSONProtocol extends DefaultJsonProtocol {
3333
"@value" -> JsNumber(x.`@value`),
3434
"@type" -> JsString(x.`@type`)
3535
)
36-
case _ => serializationError("PropertyValue expected")
36+
case x: NodeIdValue =>
37+
JsObject(
38+
"@value" -> JsNumber(x.`@value`),
39+
"@type" -> JsString(x.`@type`)
40+
)
41+
case x => serializationError(s"unsupported propertyValue: $x")
3742
}
3843
}
3944

@@ -56,6 +61,7 @@ object GraphSONProtocol extends DefaultJsonProtocol {
5661
else if (typ.equals(Type.Int.typ)) IntValue(v.toIntExact)
5762
else if (typ.equals(Type.Float.typ)) FloatValue(v.toFloat)
5863
else if (typ.equals(Type.Double.typ)) DoubleValue(v.toDouble)
64+
else if (typ.equals(Type.NodeId.typ)) NodeIdValue(v.toLongExact)
5965
else deserializationError("Valid number type or list expected")
6066
case _ => deserializationError("PropertyValue expected")
6167
}

formats/src/main/scala/overflowdb/formats/graphson/package.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ package object graphson {
1515
val Float = GraphSONVal(5, "g:Float")
1616
val Double = GraphSONVal(6, "g:Double")
1717
val List = GraphSONVal(7, "g:List")
18+
val NodeId = GraphSONVal(8, "g:VertexId")
1819

1920
def fromRuntimeClass(clazz: Class[_]): Type.Value = {
2021
if (clazz.isAssignableFrom(classOf[Boolean]) || clazz.isAssignableFrom(classOf[java.lang.Boolean]))
@@ -78,6 +79,8 @@ package object graphson {
7879
case class ListValue(override val `@value`: Array[PropertyValue], `@type`: String = Type.List.typ)
7980
extends PropertyValue
8081

82+
case class NodeIdValue(override val `@value`: Long, `@type`: String = Type.NodeId.typ) extends PropertyValue
83+
8184
case class Property(id: LongValue, `@value`: PropertyValue, `@type`: String = "g:Property")
8285

8386
}

formats/src/test/scala/overflowdb/formats/graphson/GraphSONTests.scala

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,30 @@ class GraphSONTests extends AnyWordSpec {
111111
}
112112
}
113113
}
114+
115+
"using 'contained node' property" in {
116+
val graph = SimpleDomain.newGraph()
117+
118+
val node1 = graph.addNode(1, TestNode.LABEL)
119+
graph.addNode(2, TestNode.LABEL, TestNode.CONTAINED_TESTNODE_PROPERTY, node1, TestNode.INT_PROPERTY, 11)
120+
121+
File.usingTemporaryDirectory(getClass.getName) { exportRootDirectory =>
122+
val exportResult = GraphSONExporter.runExport(graph, exportRootDirectory.pathAsString)
123+
exportResult.nodeCount shouldBe 2
124+
val Seq(graphJsonFile) = exportResult.files
125+
126+
// import graphml into new graph, use difftool for round trip of conversion
127+
val reimported = SimpleDomain.newGraph()
128+
GraphSONImporter.runImport(reimported, graphJsonFile)
129+
val diff = DiffTool.compare(graph, reimported)
130+
val diffString = diff.asScala.mkString(lineSeparator)
131+
withClue(
132+
s"original graph contained two list properties, these should also be present in reimported graph $diffString $lineSeparator"
133+
) {
134+
diff.size shouldBe 0
135+
}
136+
}
137+
}
114138
}
115139

116140
}

formats/src/test/scala/overflowdb/formats/neo4jcsv/Neo4jCsvTests.scala

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -131,14 +131,14 @@ class Neo4jCsvTests extends AnyWordSpec {
131131
// assert csv file contents
132132
val nodeHeaderFile = fuzzyFindFile(exportedFiles, TestNode.LABEL, HeaderFileSuffix)
133133
nodeHeaderFile.contentAsString.trim shouldBe
134-
":ID,:LABEL,FunkyListProperty:string[],IntListProperty:int[],IntProperty:int,StringListProperty:string[],StringProperty:string"
134+
":ID,:LABEL,ContainedTestNodeProperty,FunkyListProperty:string[],IntListProperty:int[],IntProperty:int,StringListProperty:string[],StringProperty:string"
135135

136136
val nodeDataFileLines = fuzzyFindFile(exportedFiles, TestNode.LABEL, DataFileSuffix).lines.toSeq
137137
nodeDataFileLines.size shouldBe 3
138-
nodeDataFileLines should contain("2,testNode,,,,,stringProp2")
139-
nodeDataFileLines should contain("3,testNode,,,13,,DEFAULT_STRING_VALUE")
138+
nodeDataFileLines should contain("2,testNode,,,,,,stringProp2")
139+
nodeDataFileLines should contain("3,testNode,,,,13,,DEFAULT_STRING_VALUE")
140140
nodeDataFileLines should contain(
141-
"1,testNode,apoplectic;bucolic,21;31;41,11,stringListProp1a;stringListProp1b,stringProp1"
141+
"1,testNode,,apoplectic;bucolic,21;31;41,11,stringListProp1a;stringListProp1b,stringProp1"
142142
)
143143

144144
val edgeHeaderFile = fuzzyFindFile(exportedFiles, TestEdge.LABEL, HeaderFileSuffix)
@@ -153,11 +153,12 @@ class Neo4jCsvTests extends AnyWordSpec {
153153
"""LOAD CSV FROM 'file:/nodes_testNode_data.csv' AS line
154154
|CREATE (:testNode {
155155
|id: toInteger(line[0]),
156-
|FunkyListProperty: toStringList(split(line[2], ";")),
157-
|IntListProperty: toIntegerList(split(line[3], ";")),
158-
|IntProperty: toInteger(line[4]),
159-
|StringListProperty: toStringList(split(line[5], ";")),
160-
|StringProperty: line[6]
156+
|ContainedTestNodeProperty: line[2],
157+
|FunkyListProperty: toStringList(split(line[3], ";")),
158+
|IntListProperty: toIntegerList(split(line[4], ";")),
159+
|IntProperty: toInteger(line[5]),
160+
|StringListProperty: toStringList(split(line[6], ";")),
161+
|StringProperty: line[7]
161162
|});
162163
|""".stripMargin
163164

testdomains/src/main/java/overflowdb/testdomains/simple/TestNode.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public class TestNode extends NodeRef<TestNodeDb> {
1414
public static final String STRING_LIST_PROPERTY = "StringListProperty";
1515
public static final String INT_LIST_PROPERTY = "IntListProperty";
1616
public static final String FUNKY_LIST_PROPERTY = "FunkyListProperty";
17+
public static final String CONTAINED_TESTNODE_PROPERTY = "ContainedTestNodeProperty";
1718

1819
public TestNode(Graph graph, long id) {
1920
super(graph, id);
@@ -42,6 +43,8 @@ public List<Integer> intListProperty() {
4243

4344
public FunkyList funkyList() { return get().funkyList(); }
4445

46+
public TestNode containedTestNode() { return get().containedTestNode(); }
47+
4548
@Override
4649
public Object propertyDefaultValue(String propertyKey) {
4750
if (STRING_PROPERTY.equals(propertyKey))

testdomains/src/main/java/overflowdb/testdomains/simple/TestNodeDb.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ protected TestNodeDb(NodeRef ref) {
1717
private List<String> _stringListProperty;
1818
private int[] _intListProperty; // to test primitive arrays serialization
1919
private FunkyList _funkyList;
20+
private TestNode _containedTestNode;
2021

2122
public String stringProperty() {
2223
if (_stringProperty != null)
@@ -41,6 +42,10 @@ public FunkyList funkyList() {
4142
return _funkyList;
4243
}
4344

45+
public TestNode containedTestNode() {
46+
return _containedTestNode;
47+
}
48+
4449
@Override
4550
public NodeLayoutInformation layoutInformation() {
4651
return layoutInformation;
@@ -59,6 +64,8 @@ public Object property(String key) {
5964
return _intListProperty;
6065
} else if (key == TestNode.FUNKY_LIST_PROPERTY) {
6166
return funkyList();
67+
} else if (key == TestNode.CONTAINED_TESTNODE_PROPERTY) {
68+
return containedTestNode();
6269
} else {
6370
return propertyDefaultValue(key);
6471
}
@@ -111,6 +118,8 @@ protected void updateSpecificProperty(String key, Object value) {
111118
} else {
112119
this._funkyList = (FunkyList) value;
113120
}
121+
} else if (TestNode.CONTAINED_TESTNODE_PROPERTY.equals(key)) {
122+
this._containedTestNode = (TestNode) value;
114123
} else {
115124
throw new RuntimeException("property with key=" + key + " not (yet) supported by " + this.getClass().getName());
116125
}
@@ -128,14 +137,16 @@ protected void removeSpecificProperty(String key) {
128137
this._intListProperty = null;
129138
} else if (TestNode.FUNKY_LIST_PROPERTY.equals(key)) {
130139
this._funkyList = null;
140+
} else if (TestNode.CONTAINED_TESTNODE_PROPERTY.equals(key)) {
141+
this._containedTestNode = null;
131142
} else {
132143
throw new RuntimeException("property with key=" + key + " not (yet) supported by " + this.getClass().getName());
133144
}
134145
}
135146

136147
public static NodeLayoutInformation layoutInformation = new NodeLayoutInformation(
137148
TestNode.LABEL,
138-
new HashSet<>(Arrays.asList(TestNode.STRING_PROPERTY, TestNode.INT_PROPERTY, TestNode.STRING_LIST_PROPERTY, TestNode.INT_LIST_PROPERTY, TestNode.FUNKY_LIST_PROPERTY)),
149+
new HashSet<>(Arrays.asList(TestNode.STRING_PROPERTY, TestNode.INT_PROPERTY, TestNode.STRING_LIST_PROPERTY, TestNode.INT_LIST_PROPERTY, TestNode.FUNKY_LIST_PROPERTY, TestNode.CONTAINED_TESTNODE_PROPERTY)),
139150
Arrays.asList(TestEdge.layoutInformation),
140151
Arrays.asList(TestEdge.layoutInformation));
141152

0 commit comments

Comments
 (0)