From 464905cdc8ee20d5395bc8bd7041f8e1c95c4acf Mon Sep 17 00:00:00 2001 From: David Baker Effendi Date: Mon, 15 Feb 2021 13:44:09 +0200 Subject: [PATCH] :sparkles: Can Process DiffGraphs (#68) * Vertex mapper can handle the case where ID isn't given * Initial work on DiffGraph conversion * Created initial diff graph processor * Adjusted delete vertex for builders but ran into circular dependency * Adjusted tests for new deleteVertex signature * Added basic create and delete node * Have tested DiffGraph operations * Fixed ID bug in TigerGraphDriver.kt * Documented new features and bumped version * Added `updateVertexProperty` to IDriver and DiffGraphUtil.kt --- CHANGELOG.md | 11 +- cpgconv/build.gradle | 1 - plume/build.gradle | 2 +- .../kotlin/io/github/plume/oss/Extractor.kt | 2 +- .../plume/oss/domain/mappers/VertexMapper.kt | 44 +++---- .../github/plume/oss/drivers/GremlinDriver.kt | 16 +-- .../io/github/plume/oss/drivers/IDriver.kt | 15 ++- .../github/plume/oss/drivers/Neo4jDriver.kt | 32 +++-- .../plume/oss/drivers/OverflowDbDriver.kt | 19 ++- .../plume/oss/drivers/TigerGraphDriver.kt | 33 ++++-- .../io/github/plume/oss/util/DiffGraphUtil.kt | 54 +++++++++ .../oss/drivers/JanusGraphDriverIntTest.kt | 21 +++- .../plume/oss/drivers/Neo4jDriverIntTest.kt | 19 ++- .../plume/oss/drivers/NeptuneDriverIntTest.kt | 19 ++- .../oss/drivers/OverflowDbDriverIntTest.kt | 19 ++- .../oss/drivers/TigerGraphDriverIntTest.kt | 19 ++- .../oss/drivers/TinkerGraphDriverIntTest.kt | 19 ++- .../plume/oss/util/DiffGraphUtilTest.kt | 110 ++++++++++++++++++ 18 files changed, 365 insertions(+), 90 deletions(-) create mode 100644 plume/src/main/kotlin/io/github/plume/oss/util/DiffGraphUtil.kt create mode 100644 plume/src/test/kotlin/io/github/plume/oss/util/DiffGraphUtilTest.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index b6264655..21a0e41e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,12 +7,21 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] - yyyy-mm-dd ### Added -- `deleteEdge` to `IDriver` ### Changed ### Fixed +## [0.1.8] - 2021-02-15 + +### Added +- `deleteEdge` to `IDriver` +- `updateVertexProperty` to `IDriver` +- `DiffGraphUtil::processDiffGraph` to accept `DiffGraph`s and apply changes to a given `IDriver` + +### Changed +- Modified `deleteVertex` signature to take ID and optional label + ## [0.1.7] - 2021-02-11 ### Changed diff --git a/cpgconv/build.gradle b/cpgconv/build.gradle index 35972bf9..e242fbd7 100644 --- a/cpgconv/build.gradle +++ b/cpgconv/build.gradle @@ -14,7 +14,6 @@ dependencies { } group = "io.github.plume-oss" -version = "0.0.3" sourceSets { main.java.srcDirs = [] diff --git a/plume/build.gradle b/plume/build.gradle index 3feccb39..71fbd766 100644 --- a/plume/build.gradle +++ b/plume/build.gradle @@ -172,7 +172,7 @@ dockerCompose { def artifactDesc = "Plume is a code property graph analysis library with options to extract the CPG from" + " Java bytecode and store the result in various graph databases." def repoUrl = "https://github.com/plume-oss/plume.git" -def artifactVersion = "0.1.7" +def artifactVersion = "0.1.8" def website = "https://plume-oss.github.io/plume-docs/" group = "io.github.plume-oss" diff --git a/plume/src/main/kotlin/io/github/plume/oss/Extractor.kt b/plume/src/main/kotlin/io/github/plume/oss/Extractor.kt index 60a47c7f..2114b5c6 100644 --- a/plume/src/main/kotlin/io/github/plume/oss/Extractor.kt +++ b/plume/src/main/kotlin/io/github/plume/oss/Extractor.kt @@ -464,7 +464,7 @@ class Extractor(val driver: IDriver) { } } logger.debug("Deleting $fileV") - driver.deleteVertex(mapToVertex(fileV) as NewFileBuilder) + driver.deleteVertex(fileV.id(), fileV.label()) } else { logger.debug("Existing class was found and file hashes match, no need to rebuild.") } diff --git a/plume/src/main/kotlin/io/github/plume/oss/domain/mappers/VertexMapper.kt b/plume/src/main/kotlin/io/github/plume/oss/domain/mappers/VertexMapper.kt index 07fd8042..1770b0a7 100644 --- a/plume/src/main/kotlin/io/github/plume/oss/domain/mappers/VertexMapper.kt +++ b/plume/src/main/kotlin/io/github/plume/oss/domain/mappers/VertexMapper.kt @@ -6,6 +6,7 @@ import overflowdb.Node import scala.Option import scala.collection.immutable.`$colon$colon` import scala.collection.immutable.`Nil$` +import scala.jdk.CollectionConverters import java.util.* import kotlin.collections.HashMap @@ -26,6 +27,17 @@ object VertexMapper { return mapToVertex(map) } + /** + * Converts a [NewNode] to its respective [NewNodeBuilder] object. + * + * @param v The [NewNode] to deserialize. + * @return a [NewNodeBuilder] represented by the information in the givennode. + */ + fun mapToVertex(v: NewNode): NewNodeBuilder { + val map = CollectionConverters.MapHasAsJava(v.properties()).asJava() + mapOf("label" to v.label()) + return mapToVertex(map) + } + /** * Converts a [Map] containing vertex properties to its respective [NewNodeBuilder] object. * @@ -34,30 +46,26 @@ object VertexMapper { */ fun mapToVertex(mapToConvert: Map): NewNodeBuilder { val map = HashMap() + // Only ID should be left as Long mapToConvert.keys.forEach { when (val value = mapToConvert[it]) { - is Long -> map[it] = value.toInt() + is Long -> if (it != "id") map[it] = value.toInt() else map[it] = value as Any else -> map[it] = value as Any } } - map.computeIfPresent("id") { _, v -> v.toString().toLong() } return when (map["label"] as String) { ArrayInitializer.Label() -> NewArrayInitializerBuilder() .order(map["ORDER"] as Int) - .id(map["id"] as Long) Binding.Label() -> NewBindingBuilder() .name(map["NAME"] as String) .signature(map["SIGNATURE"] as String) - .id(map["id"] as Long) MetaData.Label() -> NewMetaDataBuilder() .language(map["LANGUAGE"] as String) .version(map["VERSION"] as String) - .id(map["id"] as Long) File.Label() -> NewFileBuilder() .name(map["NAME"] as String) .hash(Option.apply(map["HASH"] as String)) .order(map["ORDER"] as Int) - .id(map["id"] as Long) Method.Label() -> NewMethodBuilder() .astParentFullName(map["AST_PARENT_FULL_NAME"] as String) .astParentType(map["AST_PARENT_TYPE"] as String) @@ -68,7 +76,6 @@ object VertexMapper { .lineNumber(Option.apply(map["LINE_NUMBER"] as Int)) .columnNumber(Option.apply(map["COLUMN_NUMBER"] as Int)) .order(map["ORDER"] as Int) - .id(map["id"] as Long) MethodParameterIn.Label() -> NewMethodParameterInBuilder() .code(map["CODE"] as String) .name(map["NAME"] as String) @@ -77,7 +84,6 @@ object VertexMapper { .lineNumber(Option.apply(map["LINE_NUMBER"] as Int)) .columnNumber(Option.apply(map["COLUMN_NUMBER"] as Int)) .order(map["ORDER"] as Int) - .id(map["id"] as Long) MethodReturn.Label() -> NewMethodReturnBuilder() .code(map["CODE"] as String) .evaluationStrategy(map["EVALUATION_STRATEGY"] as String) @@ -85,16 +91,13 @@ object VertexMapper { .lineNumber(Option.apply(map["LINE_NUMBER"] as Int)) .columnNumber(Option.apply(map["COLUMN_NUMBER"] as Int)) .order(map["ORDER"] as Int) - .id(map["id"] as Long) Modifier.Label() -> NewModifierBuilder() .modifierType(map["MODIFIER_TYPE"] as String) .order(map["ORDER"] as Int) - .id(map["id"] as Long) Type.Label() -> NewTypeBuilder() .name(map["NAME"] as String) .fullName(map["FULL_NAME"] as String) .typeDeclFullName(map["TYPE_DECL_FULL_NAME"] as String) - .id(map["id"] as Long) TypeDecl.Label() -> NewTypeDeclBuilder() .astParentFullName(map["AST_PARENT_FULL_NAME"] as String) .astParentType(map["AST_PARENT_TYPE"] as String) @@ -102,26 +105,21 @@ object VertexMapper { .fullName(map["FULL_NAME"] as String) .order(map["ORDER"] as Int) .isExternal(map["IS_EXTERNAL"] as Boolean) - .id(map["id"] as Long) TypeParameter.Label() -> NewTypeParameterBuilder() .name(map["NAME"] as String) .order(map["ORDER"] as Int) - .id(map["id"] as Long) TypeArgument.Label() -> NewTypeArgumentBuilder() .order(map["ORDER"] as Int) - .id(map["id"] as Long) Member.Label() -> NewMemberBuilder() .typeFullName(map["TYPE_FULL_NAME"] as String) .code(map["CODE"] as String) .name(map["NAME"] as String) .order(map["ORDER"] as Int) - .id(map["id"] as Long) NamespaceBlock.Label() -> NewNamespaceBlockBuilder() .fullName(map["FULL_NAME"] as String) .filename(map["FILENAME"] as String) .name(map["NAME"] as String) .order(map["ORDER"] as Int) - .id(map["id"] as Long) Literal.Label() -> NewLiteralBuilder() .typeFullName(map["TYPE_FULL_NAME"] as String) .code(map["CODE"] as String) @@ -129,7 +127,6 @@ object VertexMapper { .lineNumber(Option.apply(map["LINE_NUMBER"] as Int)) .columnNumber(Option.apply(map["COLUMN_NUMBER"] as Int)) .argumentIndex(map["ARGUMENT_INDEX"] as Int) - .id(map["id"] as Long) Call.Label() -> NewCallBuilder() .typeFullName(map["TYPE_FULL_NAME"] as String) .code(map["CODE"] as String) @@ -148,7 +145,6 @@ object VertexMapper { else -> createScalaList("") } ) - .id(map["id"] as Long) Local.Label() -> NewLocalBuilder() .typeFullName(map["TYPE_FULL_NAME"] as String) .code(map["CODE"] as String) @@ -156,7 +152,6 @@ object VertexMapper { .order(map["ORDER"] as Int) .lineNumber(Option.apply(map["LINE_NUMBER"] as Int)) .columnNumber(Option.apply(map["COLUMN_NUMBER"] as Int)) - .id(map["id"] as Long) Identifier.Label() -> NewIdentifierBuilder() .typeFullName(map["TYPE_FULL_NAME"] as String) .code(map["CODE"] as String) @@ -165,7 +160,6 @@ object VertexMapper { .lineNumber(Option.apply(map["LINE_NUMBER"] as Int)) .columnNumber(Option.apply(map["COLUMN_NUMBER"] as Int)) .argumentIndex(map["ARGUMENT_INDEX"] as Int) - .id(map["id"] as Long) FieldIdentifier.Label() -> NewFieldIdentifierBuilder() .canonicalName(map["CANONICAL_NAME"] as String) .code(map["CODE"] as String) @@ -173,14 +167,12 @@ object VertexMapper { .lineNumber(Option.apply(map["LINE_NUMBER"] as Int)) .columnNumber(Option.apply(map["COLUMN_NUMBER"] as Int)) .argumentIndex(map["ARGUMENT_INDEX"] as Int) - .id(map["id"] as Long) Return.Label() -> NewReturnBuilder() .code(map["CODE"] as String) .order(map["ORDER"] as Int) .lineNumber(Option.apply(map["LINE_NUMBER"] as Int)) .columnNumber(Option.apply(map["COLUMN_NUMBER"] as Int)) .argumentIndex(map["ARGUMENT_INDEX"] as Int) - .id(map["id"] as Long) Block.Label() -> NewBlockBuilder() .typeFullName(map["TYPE_FULL_NAME"] as String) .code(map["CODE"] as String) @@ -188,7 +180,6 @@ object VertexMapper { .lineNumber(Option.apply(map["LINE_NUMBER"] as Int)) .columnNumber(Option.apply(map["COLUMN_NUMBER"] as Int)) .argumentIndex(map["ARGUMENT_INDEX"] as Int) - .id(map["id"] as Long) MethodRef.Label() -> NewMethodRefBuilder() .code(map["CODE"] as String) .order(map["ORDER"] as Int) @@ -197,7 +188,6 @@ object VertexMapper { .lineNumber(Option.apply(map["LINE_NUMBER"] as Int)) .columnNumber(Option.apply(map["COLUMN_NUMBER"] as Int)) .argumentIndex(map["ARGUMENT_INDEX"] as Int) - .id(map["id"] as Long) TypeRef.Label() -> NewTypeRefBuilder() .typeFullName(map["TYPE_FULL_NAME"] as String) .dynamicTypeHintFullName( @@ -212,7 +202,6 @@ object VertexMapper { .lineNumber(Option.apply(map["LINE_NUMBER"] as Int)) .columnNumber(Option.apply(map["COLUMN_NUMBER"] as Int)) .argumentIndex(map["ARGUMENT_INDEX"] as Int) - .id(map["id"] as Long) JumpTarget.Label() -> NewJumpTargetBuilder() .code(map["CODE"] as String) .order(map["ORDER"] as Int) @@ -220,14 +209,12 @@ object VertexMapper { .columnNumber(Option.apply(map["COLUMN_NUMBER"] as Int)) .argumentIndex(map["ARGUMENT_INDEX"] as Int) .name(map["NAME"] as String) - .id(map["id"] as Long) ControlStructure.Label() -> NewControlStructureBuilder() .code(map["CODE"] as String) .order(map["ORDER"] as Int) .lineNumber(Option.apply(map["LINE_NUMBER"] as Int)) .columnNumber(Option.apply(map["COLUMN_NUMBER"] as Int)) .argumentIndex(map["ARGUMENT_INDEX"] as Int) - .id(map["id"] as Long) else -> NewUnknownBuilder() .code(map["CODE"] as String) .order(map["ORDER"] as Int) @@ -235,8 +222,7 @@ object VertexMapper { .typeFullName(map["TYPE_FULL_NAME"] as String) .lineNumber(Option.apply(map["LINE_NUMBER"] as Int)) .columnNumber(Option.apply(map["COLUMN_NUMBER"] as Int)) - .id(map["id"] as Long) - } + }.apply { if (map.containsKey("id")) this.id(map["id"] as Long) } } /** diff --git a/plume/src/main/kotlin/io/github/plume/oss/drivers/GremlinDriver.kt b/plume/src/main/kotlin/io/github/plume/oss/drivers/GremlinDriver.kt index b5654549..43473d7f 100644 --- a/plume/src/main/kotlin/io/github/plume/oss/drivers/GremlinDriver.kt +++ b/plume/src/main/kotlin/io/github/plume/oss/drivers/GremlinDriver.kt @@ -10,10 +10,7 @@ import org.apache.logging.log4j.LogManager import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource import org.apache.tinkerpop.gremlin.process.traversal.step.util.WithOptions -import org.apache.tinkerpop.gremlin.structure.Edge -import org.apache.tinkerpop.gremlin.structure.Graph -import org.apache.tinkerpop.gremlin.structure.T -import org.apache.tinkerpop.gremlin.structure.Vertex +import org.apache.tinkerpop.gremlin.structure.* import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph import overflowdb.Config import scala.jdk.CollectionConverters @@ -202,9 +199,9 @@ abstract class GremlinDriver : IDriver { return gremlinToPlume(neighbourSubgraph.traversal()) } - override fun deleteVertex(v: NewNodeBuilder) { - if (!exists(v)) return - findVertexTraversal(v).drop().iterate() + override fun deleteVertex(id: Long, label: String?) { + if (!g.V(id).hasNext()) return + g.V(id).drop().iterate() } override fun deleteEdge(src: NewNodeBuilder, tgt: NewNodeBuilder, edge: String) { @@ -226,6 +223,11 @@ abstract class GremlinDriver : IDriver { } } + override fun updateVertexProperty(id: Long, label: String?, key: String, value: Any) { + if (!g.V(id).hasNext()) return + g.V(id).property(key, value).iterate() + } + /** * Converts a [GraphTraversalSource] instance to a [overflowdb.Graph] instance. * diff --git a/plume/src/main/kotlin/io/github/plume/oss/drivers/IDriver.kt b/plume/src/main/kotlin/io/github/plume/oss/drivers/IDriver.kt index 02a5176a..e9b58ab6 100644 --- a/plume/src/main/kotlin/io/github/plume/oss/drivers/IDriver.kt +++ b/plume/src/main/kotlin/io/github/plume/oss/drivers/IDriver.kt @@ -91,9 +91,10 @@ interface IDriver : AutoCloseable { /** * Given a vertex, will remove it from the graph if present. * - * @param v The vertex to remove. + * @param id The id to remove. + * @param label The label, if known. */ - fun deleteVertex(v: NewNodeBuilder) + fun deleteVertex(id: Long, label: String? = null) /** * Given the full signature of a method, removes the method and its body from the graph. @@ -112,4 +113,14 @@ interface IDriver : AutoCloseable { */ fun deleteEdge(src: NewNodeBuilder, tgt: NewNodeBuilder, edge: String) + /** + * Updates a vertex's property if the node exists. + * + * @param id The ID of the vertex to update. + * @param label The label of the node, if known. + * @param key The key of the property to update. + * @param value The updated value. + */ + fun updateVertexProperty(id: Long, label: String?, key: String, value: Any) + } \ No newline at end of file diff --git a/plume/src/main/kotlin/io/github/plume/oss/drivers/Neo4jDriver.kt b/plume/src/main/kotlin/io/github/plume/oss/drivers/Neo4jDriver.kt index 21617fe0..32031516 100644 --- a/plume/src/main/kotlin/io/github/plume/oss/drivers/Neo4jDriver.kt +++ b/plume/src/main/kotlin/io/github/plume/oss/drivers/Neo4jDriver.kt @@ -153,14 +153,15 @@ class Neo4jDriver : IDriver { } } - override fun exists(v: NewNodeBuilder): Boolean { - val node = v.build() + override fun exists(v: NewNodeBuilder): Boolean = checkVertexExist(v.id(), v.build().label()) + + private fun checkVertexExist(id: Long, label: String? = null): Boolean { driver.session().use { session -> return session.writeTransaction { tx -> val result = tx.run( """ - MATCH (n:${node.label()}) - WHERE id(n) = ${v.id()} + MATCH (n${if (label != null) ":$label" else ""}) + WHERE id(n) = $id RETURN n """.trimIndent() ) @@ -374,14 +375,14 @@ class Neo4jDriver : IDriver { } } - override fun deleteVertex(v: NewNodeBuilder) { - if (!exists(v)) return + override fun deleteVertex(id: Long, label: String?) { + if (!checkVertexExist(id, label)) return driver.session().use { session -> session.writeTransaction { tx -> tx.run( """ - MATCH (n) - WHERE ID(n) = ${v.id()} + MATCH (n${if (label != null) ":$label" else ""}) + WHERE ID(n) = $id DETACH DELETE n """.trimIndent() ) @@ -421,6 +422,21 @@ class Neo4jDriver : IDriver { } } + override fun updateVertexProperty(id: Long, label: String?, key: String, value: Any) { + if (!checkVertexExist(id, label)) return + driver.session().use { session -> + session.writeTransaction { tx -> + tx.run( + """ + MATCH (n${if (label != null) ":$label" else ""}) + WHERE ID(n) = $id + SET n.$key = ${if (value is String) "\"$value\"" else value} + """.trimIndent() + ) + } + } + } + private fun newOverflowGraph(): Graph = Graph.open( Config.withDefaults(), NodeFactories.allAsJava(), diff --git a/plume/src/main/kotlin/io/github/plume/oss/drivers/OverflowDbDriver.kt b/plume/src/main/kotlin/io/github/plume/oss/drivers/OverflowDbDriver.kt index dcb9c6f5..a8470753 100644 --- a/plume/src/main/kotlin/io/github/plume/oss/drivers/OverflowDbDriver.kt +++ b/plume/src/main/kotlin/io/github/plume/oss/drivers/OverflowDbDriver.kt @@ -67,9 +67,7 @@ class OverflowDbDriver : IDriver { v.id(node.id()) } - override fun exists(v: NewNodeBuilder): Boolean { - return (graph.node(v.id()) != null) - } + override fun exists(v: NewNodeBuilder) = (graph.node(v.id()) != null) override fun exists(src: NewNodeBuilder, tgt: NewNodeBuilder, edge: String): Boolean { val srcNode = graph.node(src.id()) ?: return false @@ -142,12 +140,10 @@ class OverflowDbDriver : IDriver { return g } - override fun getNeighbours(v: NewNodeBuilder): Graph { - return deepCopyGraph(Traversals.getNeighbours(graph, v.id())) - } + override fun getNeighbours(v: NewNodeBuilder): Graph = deepCopyGraph(Traversals.getNeighbours(graph, v.id())) - override fun deleteVertex(v: NewNodeBuilder) { - graph.node(v.id())?.let { graph.remove(it) } + override fun deleteVertex(id: Long, label: String?) { + graph.node(id)?.let { graph.remove(it) } } override fun deleteEdge(src: NewNodeBuilder, tgt: NewNodeBuilder, edge: String) { @@ -156,8 +152,11 @@ class OverflowDbDriver : IDriver { if (e?.inNode()?.id() == tgt.id()) e.remove() } - override fun deleteMethod(fullName: String, signature: String) { - Traversals.deleteMethod(graph, fullName, signature) + override fun deleteMethod(fullName: String, signature: String) = Traversals.deleteMethod(graph, fullName, signature) + + override fun updateVertexProperty(id: Long, label: String?, key: String, value: Any) { + val node = graph.node(id) ?: return + node.setProperty(key, value) } override fun close() { diff --git a/plume/src/main/kotlin/io/github/plume/oss/drivers/TigerGraphDriver.kt b/plume/src/main/kotlin/io/github/plume/oss/drivers/TigerGraphDriver.kt index e927b99b..4dbc2dcf 100644 --- a/plume/src/main/kotlin/io/github/plume/oss/drivers/TigerGraphDriver.kt +++ b/plume/src/main/kotlin/io/github/plume/oss/drivers/TigerGraphDriver.kt @@ -5,6 +5,7 @@ import io.github.plume.oss.domain.exceptions.PlumeTransactionException import io.github.plume.oss.domain.mappers.VertexMapper import io.github.plume.oss.domain.mappers.VertexMapper.checkSchemaConstraints import io.github.plume.oss.util.PlumeKeyProvider +import io.shiftleft.codepropertygraph.generated.NodeTypes.META_DATA import io.shiftleft.codepropertygraph.generated.nodes.NewMetaDataBuilder import io.shiftleft.codepropertygraph.generated.nodes.NewNodeBuilder import org.apache.logging.log4j.LogManager @@ -101,13 +102,15 @@ class TigerGraphDriver : IOverridenIdDriver { post("graph/$GRAPH_NAME", payload) } - override fun exists(v: NewNodeBuilder): Boolean { - val route = when (v) { - is NewMetaDataBuilder -> "graph/$GRAPH_NAME/vertices/META_DATA_VERT" + override fun exists(v: NewNodeBuilder): Boolean = checkVertexExists(v.id(), v.build().label()) + + private fun checkVertexExists(id: Long, label: String?): Boolean { + val route = when (label) { + META_DATA -> "graph/$GRAPH_NAME/vertices/META_DATA_VERT" else -> "graph/$GRAPH_NAME/vertices/CPG_VERT" } return try { - get("$route/${v.id()}") + get("$route/$id") true } catch (e: PlumeTransactionException) { false @@ -156,7 +159,7 @@ class TigerGraphDriver : IOverridenIdDriver { fromPayload.keys.first() to fromPayload.values.first(), toPayload.keys.first() to toPayload.values.first() ) - val payload = mutableMapOf( + val payload = mapOf( "vertices" to vertexPayload, "edges" to createEdgePayload(src, tgt, edge) ) @@ -233,10 +236,10 @@ class TigerGraphDriver : IOverridenIdDriver { return payloadToGraph(result) } - override fun deleteVertex(v: NewNodeBuilder) { - if (!exists(v)) return - val label = if (v is NewMetaDataBuilder) "META_DATA_VERT" else "CPG_VERT" - delete("graph/$GRAPH_NAME/vertices/$label/${v.id()}") + override fun deleteVertex(id: Long, label: String?) { + if (!checkVertexExists(id, label)) return + val lbl = if (label == META_DATA) "META_DATA_VERT" else "CPG_VERT" + delete("graph/$GRAPH_NAME/vertices/$lbl/$id") } override fun deleteEdge(src: NewNodeBuilder, tgt: NewNodeBuilder, edge: String) { @@ -252,6 +255,13 @@ class TigerGraphDriver : IOverridenIdDriver { } } + override fun updateVertexProperty(id: Long, label: String?, key: String, value: Any) { + if (!checkVertexExists(id, label)) return + val lbl = if (label == META_DATA) "META_DATA_VERT" else "CPG_VERT" + val payload = mapOf("vertices" to mapOf(lbl to mapOf(id to mapOf(key to mapOf("value" to value))))) + post("graph/$GRAPH_NAME", payload) + } + override fun getVertexIds(lowerBound: Long, upperBound: Long): Set { val result = (get( endpoint = "query/$GRAPH_NAME/getVertexIds", @@ -294,7 +304,10 @@ class TigerGraphDriver : IOverridenIdDriver { val attributes = o["attributes"] as JSONObject val vertexMap = HashMap() attributes.keySet().filter { attributes[it] != "" } - .map { Pair(if (it == "AST_ORDER") "ORDER" else it, attributes[it]) } + .map { + if (it == "id") Pair(it, attributes[it].toString().toLong()) + else Pair(if (it == "AST_ORDER") "ORDER" else it, attributes[it]) + } .forEach { vertexMap[it.first] = it.second } return VertexMapper.mapToVertex(vertexMap) } diff --git a/plume/src/main/kotlin/io/github/plume/oss/util/DiffGraphUtil.kt b/plume/src/main/kotlin/io/github/plume/oss/util/DiffGraphUtil.kt new file mode 100644 index 00000000..a548e128 --- /dev/null +++ b/plume/src/main/kotlin/io/github/plume/oss/util/DiffGraphUtil.kt @@ -0,0 +1,54 @@ +package io.github.plume.oss.util + +import io.github.plume.oss.domain.mappers.VertexMapper +import io.github.plume.oss.drivers.IDriver +import io.shiftleft.codepropertygraph.generated.nodes.NewNode +import io.shiftleft.passes.DiffGraph +import org.apache.logging.log4j.LogManager +import org.apache.logging.log4j.Logger +import overflowdb.Node + +/** + * Utility class to process [DiffGraph] changes. + */ +object DiffGraphUtil { + + private val logger: Logger = LogManager.getLogger(DiffGraphUtil::class.java) + + /** + * Processes [DiffGraph] operations and applies them to the given [IDriver]. + * + * @param driver The driver to which the changes are to be applied. + * @param df The [DiffGraph] from which to extract the changes from. + */ + fun processDiffGraph(driver: IDriver, df: DiffGraph) { + df.iterator().foreach { change: DiffGraph.Change -> + when (change) { + is io.shiftleft.passes.`DiffGraph$Change$CreateNode` -> + driver.addVertex(VertexMapper.mapToVertex(change.node())) + is io.shiftleft.passes.`DiffGraph$Change$RemoveNode` -> + driver.deleteVertex(change.nodeId()) + is io.shiftleft.passes.`DiffGraph$Change$RemoveEdge` -> { + val edge = change.edge() + driver.deleteEdge( + src = VertexMapper.mapToVertex(edge.outNode() as Node), + tgt = VertexMapper.mapToVertex(edge.inNode() as Node), + edge = edge.label() + ) + } + is io.shiftleft.passes.`DiffGraph$Change$CreateEdge` -> + driver.addEdge( + src = VertexMapper.mapToVertex(change.src() as NewNode), + tgt = VertexMapper.mapToVertex(change.dst() as NewNode), + edge = change.label() + ) + is io.shiftleft.passes.`DiffGraph$Change$SetNodeProperty` -> { + val node = change.node() + driver.updateVertexProperty(node.id(), node.label(), change.key(), change.value()) + } + else -> logger.warn("Unsupported DiffGraph operation ${change.javaClass} encountered.") + } + } + } + +} diff --git a/plume/src/test/kotlin/io/github/plume/oss/drivers/JanusGraphDriverIntTest.kt b/plume/src/test/kotlin/io/github/plume/oss/drivers/JanusGraphDriverIntTest.kt index 7c013c13..d76e0ab0 100644 --- a/plume/src/test/kotlin/io/github/plume/oss/drivers/JanusGraphDriverIntTest.kt +++ b/plume/src/test/kotlin/io/github/plume/oss/drivers/JanusGraphDriverIntTest.kt @@ -31,16 +31,16 @@ import io.github.plume.oss.TestDomainResources.Companion.typeParameterVertex import io.github.plume.oss.TestDomainResources.Companion.typeRefVertex import io.github.plume.oss.TestDomainResources.Companion.unknownVertex import io.github.plume.oss.domain.exceptions.PlumeSchemaViolationException -import io.github.plume.oss.graphio.GraphMLWriter import io.github.plume.oss.util.SootToPlumeUtil import io.shiftleft.codepropertygraph.generated.EdgeTypes.* +import io.shiftleft.codepropertygraph.generated.NodeKeyNames.NAME +import io.shiftleft.codepropertygraph.generated.NodeTypes.FILE import io.shiftleft.codepropertygraph.generated.nodes.* import org.apache.logging.log4j.LogManager import org.junit.jupiter.api.* import org.junit.jupiter.api.Assertions.* import overflowdb.Graph import scala.Option -import java.io.FileWriter import kotlin.properties.Delegates class JanusGraphDriverIntTest { @@ -203,6 +203,17 @@ class JanusGraphDriverIntTest { assertTrue(driver.exists(v1)) assertTrue(driver.exists(v2)) } + + @Test + fun updateVertexTest() { + driver.addVertex(fileVertex) + assertTrue(driver.exists(fileVertex)) + driver.getWholeGraph() + .use { g -> assertTrue(g.nodes(fileVertex.id()).asSequence().any { it.property(NAME) == STRING_1 }) } + driver.updateVertexProperty(fileVertex.id(), FILE, NAME, STRING_2) + driver.getWholeGraph() + .use { g -> assertTrue(g.nodes(fileVertex.id()).asSequence().any { it.property(NAME) == STRING_2 }) } + } } @Nested @@ -549,14 +560,14 @@ class JanusGraphDriverIntTest { @Test fun testVertexDelete() { assertTrue(driver.exists(methodVertex)) - driver.deleteVertex(methodVertex) + driver.deleteVertex(methodVertex.id(), methodVertex.build().label()) assertFalse(driver.exists(methodVertex)) // Try delete vertex which doesn't exist, should not throw error - driver.deleteVertex(methodVertex) + driver.deleteVertex(methodVertex.id(), methodVertex.build().label()) assertFalse(driver.exists(methodVertex)) // Delete metadata assertTrue(driver.exists(metaDataVertex)) - driver.deleteVertex(metaDataVertex) + driver.deleteVertex(metaDataVertex.id(), metaDataVertex.build().label()) assertFalse(driver.exists(metaDataVertex)) } diff --git a/plume/src/test/kotlin/io/github/plume/oss/drivers/Neo4jDriverIntTest.kt b/plume/src/test/kotlin/io/github/plume/oss/drivers/Neo4jDriverIntTest.kt index 543894f6..60ef53d4 100644 --- a/plume/src/test/kotlin/io/github/plume/oss/drivers/Neo4jDriverIntTest.kt +++ b/plume/src/test/kotlin/io/github/plume/oss/drivers/Neo4jDriverIntTest.kt @@ -33,6 +33,8 @@ import io.github.plume.oss.TestDomainResources.Companion.unknownVertex import io.github.plume.oss.domain.exceptions.PlumeSchemaViolationException import io.github.plume.oss.util.SootToPlumeUtil import io.shiftleft.codepropertygraph.generated.EdgeTypes.* +import io.shiftleft.codepropertygraph.generated.NodeKeyNames.NAME +import io.shiftleft.codepropertygraph.generated.NodeTypes.FILE import io.shiftleft.codepropertygraph.generated.nodes.* import org.junit.jupiter.api.* import org.junit.jupiter.api.Assertions.* @@ -205,6 +207,17 @@ class Neo4jDriverIntTest { assertTrue(driver.exists(v1)) assertTrue(driver.exists(v2)) } + + @Test + fun updateVertexTest() { + driver.addVertex(fileVertex) + assertTrue(driver.exists(fileVertex)) + driver.getWholeGraph() + .use { g -> assertTrue(g.nodes(fileVertex.id()).asSequence().any { it.property(NAME) == STRING_1 }) } + driver.updateVertexProperty(fileVertex.id(), FILE, NAME, STRING_2) + driver.getWholeGraph() + .use { g -> assertTrue(g.nodes(fileVertex.id()).asSequence().any { it.property(NAME) == STRING_2 }) } + } } @Nested @@ -551,14 +564,14 @@ class Neo4jDriverIntTest { @Test fun testVertexDelete() { assertTrue(driver.exists(methodVertex)) - driver.deleteVertex(methodVertex) + driver.deleteVertex(methodVertex.id(), methodVertex.build().label()) assertFalse(driver.exists(methodVertex)) // Try delete vertex which doesn't exist, should not throw error - driver.deleteVertex(methodVertex) + driver.deleteVertex(methodVertex.id(), methodVertex.build().label()) assertFalse(driver.exists(methodVertex)) // Delete metadata assertTrue(driver.exists(metaDataVertex)) - driver.deleteVertex(metaDataVertex) + driver.deleteVertex(metaDataVertex.id(), metaDataVertex.build().label()) assertFalse(driver.exists(metaDataVertex)) } diff --git a/plume/src/test/kotlin/io/github/plume/oss/drivers/NeptuneDriverIntTest.kt b/plume/src/test/kotlin/io/github/plume/oss/drivers/NeptuneDriverIntTest.kt index f2da6d3c..1dfafeab 100644 --- a/plume/src/test/kotlin/io/github/plume/oss/drivers/NeptuneDriverIntTest.kt +++ b/plume/src/test/kotlin/io/github/plume/oss/drivers/NeptuneDriverIntTest.kt @@ -33,6 +33,8 @@ import io.github.plume.oss.TestDomainResources.Companion.unknownVertex import io.github.plume.oss.domain.exceptions.PlumeSchemaViolationException import io.github.plume.oss.util.SootToPlumeUtil import io.shiftleft.codepropertygraph.generated.EdgeTypes.* +import io.shiftleft.codepropertygraph.generated.NodeKeyNames.NAME +import io.shiftleft.codepropertygraph.generated.NodeTypes.FILE import io.shiftleft.codepropertygraph.generated.nodes.* import org.junit.jupiter.api.* import org.junit.jupiter.api.Assertions.* @@ -200,6 +202,17 @@ class NeptuneDriverIntTest { assertTrue(driver.exists(v1)) assertTrue(driver.exists(v2)) } + + @Test + fun updateVertexTest() { + driver.addVertex(fileVertex) + assertTrue(driver.exists(fileVertex)) + driver.getWholeGraph() + .use { g -> assertTrue(g.nodes(fileVertex.id()).asSequence().any { it.property(NAME) == STRING_1 }) } + driver.updateVertexProperty(fileVertex.id(), FILE, NAME, STRING_2) + driver.getWholeGraph() + .use { g -> assertTrue(g.nodes(fileVertex.id()).asSequence().any { it.property(NAME) == STRING_2 }) } + } } @Nested @@ -551,14 +564,14 @@ class NeptuneDriverIntTest { @Test fun testVertexDelete() { assertTrue(driver.exists(methodVertex)) - driver.deleteVertex(methodVertex) + driver.deleteVertex(methodVertex.id(), methodVertex.build().label()) assertFalse(driver.exists(methodVertex)) // Try delete vertex which doesn't exist, should not throw error - driver.deleteVertex(methodVertex) + driver.deleteVertex(methodVertex.id(), methodVertex.build().label()) assertFalse(driver.exists(methodVertex)) // Delete metadata assertTrue(driver.exists(metaDataVertex)) - driver.deleteVertex(metaDataVertex) + driver.deleteVertex(metaDataVertex.id(), metaDataVertex.build().label()) assertFalse(driver.exists(metaDataVertex)) } diff --git a/plume/src/test/kotlin/io/github/plume/oss/drivers/OverflowDbDriverIntTest.kt b/plume/src/test/kotlin/io/github/plume/oss/drivers/OverflowDbDriverIntTest.kt index db58bb8d..b39b20c1 100644 --- a/plume/src/test/kotlin/io/github/plume/oss/drivers/OverflowDbDriverIntTest.kt +++ b/plume/src/test/kotlin/io/github/plume/oss/drivers/OverflowDbDriverIntTest.kt @@ -33,6 +33,8 @@ import io.github.plume.oss.TestDomainResources.Companion.unknownVertex import io.github.plume.oss.domain.exceptions.PlumeSchemaViolationException import io.github.plume.oss.util.SootToPlumeUtil import io.shiftleft.codepropertygraph.generated.EdgeTypes.* +import io.shiftleft.codepropertygraph.generated.NodeKeyNames.NAME +import io.shiftleft.codepropertygraph.generated.NodeTypes.FILE import io.shiftleft.codepropertygraph.generated.nodes.* import org.apache.logging.log4j.LogManager import org.junit.jupiter.api.* @@ -212,6 +214,17 @@ class OverflowDbDriverIntTest { assertTrue(driver.exists(v1)) assertTrue(driver.exists(v2)) } + + @Test + fun updateVertexTest() { + driver.addVertex(fileVertex) + assertTrue(driver.exists(fileVertex)) + driver.getWholeGraph() + .use { g -> assertTrue(g.nodes(fileVertex.id()).asSequence().any { it.property(NAME) == STRING_1 }) } + driver.updateVertexProperty(fileVertex.id(), FILE, NAME, STRING_2) + driver.getWholeGraph() + .use { g -> assertTrue(g.nodes(fileVertex.id()).asSequence().any { it.property(NAME) == STRING_2 }) } + } } @Nested @@ -569,14 +582,14 @@ class OverflowDbDriverIntTest { @Test fun testVertexDelete() { assertTrue(driver.exists(methodVertex)) - driver.deleteVertex(methodVertex) + driver.deleteVertex(methodVertex.id(), methodVertex.build().label()) assertFalse(driver.exists(methodVertex)) // Try delete vertex which doesn't exist, should not throw error - driver.deleteVertex(methodVertex) + driver.deleteVertex(methodVertex.id(), methodVertex.build().label()) assertFalse(driver.exists(methodVertex)) // Delete metadata assertTrue(driver.exists(metaDataVertex)) - driver.deleteVertex(metaDataVertex) + driver.deleteVertex(metaDataVertex.id(), metaDataVertex.build().label()) assertFalse(driver.exists(metaDataVertex)) } diff --git a/plume/src/test/kotlin/io/github/plume/oss/drivers/TigerGraphDriverIntTest.kt b/plume/src/test/kotlin/io/github/plume/oss/drivers/TigerGraphDriverIntTest.kt index 31affc8a..ad790e6b 100644 --- a/plume/src/test/kotlin/io/github/plume/oss/drivers/TigerGraphDriverIntTest.kt +++ b/plume/src/test/kotlin/io/github/plume/oss/drivers/TigerGraphDriverIntTest.kt @@ -33,6 +33,8 @@ import io.github.plume.oss.TestDomainResources.Companion.unknownVertex import io.github.plume.oss.domain.exceptions.PlumeSchemaViolationException import io.github.plume.oss.util.SootToPlumeUtil import io.shiftleft.codepropertygraph.generated.EdgeTypes.* +import io.shiftleft.codepropertygraph.generated.NodeKeyNames.NAME +import io.shiftleft.codepropertygraph.generated.NodeTypes.FILE import io.shiftleft.codepropertygraph.generated.nodes.* import org.junit.jupiter.api.* import org.junit.jupiter.api.Assertions.* @@ -201,6 +203,17 @@ class TigerGraphDriverIntTest { assertTrue(driver.exists(v1)) assertTrue(driver.exists(v2)) } + + @Test + fun updateVertexTest() { + driver.addVertex(fileVertex) + assertTrue(driver.exists(fileVertex)) + driver.getWholeGraph() + .use { g -> assertTrue(g.nodes(fileVertex.id()).asSequence().any { it.property(NAME) == STRING_1 }) } + driver.updateVertexProperty(fileVertex.id(), FILE, NAME, STRING_2) + driver.getWholeGraph() + .use { g -> assertTrue(g.nodes(fileVertex.id()).asSequence().any { it.property(NAME) == STRING_2 }) } + } } @Nested @@ -558,14 +571,14 @@ class TigerGraphDriverIntTest { @Test fun testVertexDelete() { assertTrue(driver.exists(methodVertex)) - driver.deleteVertex(methodVertex) + driver.deleteVertex(methodVertex.id(), methodVertex.build().label()) assertFalse(driver.exists(methodVertex)) // Try delete vertex which doesn't exist, should not throw error - driver.deleteVertex(methodVertex) + driver.deleteVertex(methodVertex.id(), methodVertex.build().label()) assertFalse(driver.exists(methodVertex)) // Delete metadata assertTrue(driver.exists(metaDataVertex)) - driver.deleteVertex(metaDataVertex) + driver.deleteVertex(metaDataVertex.id(), metaDataVertex.build().label()) assertFalse(driver.exists(metaDataVertex)) } diff --git a/plume/src/test/kotlin/io/github/plume/oss/drivers/TinkerGraphDriverIntTest.kt b/plume/src/test/kotlin/io/github/plume/oss/drivers/TinkerGraphDriverIntTest.kt index e365b72c..d5004d93 100644 --- a/plume/src/test/kotlin/io/github/plume/oss/drivers/TinkerGraphDriverIntTest.kt +++ b/plume/src/test/kotlin/io/github/plume/oss/drivers/TinkerGraphDriverIntTest.kt @@ -33,6 +33,8 @@ import io.github.plume.oss.TestDomainResources.Companion.unknownVertex import io.github.plume.oss.domain.exceptions.PlumeSchemaViolationException import io.github.plume.oss.util.SootToPlumeUtil import io.shiftleft.codepropertygraph.generated.EdgeTypes.* +import io.shiftleft.codepropertygraph.generated.NodeKeyNames.NAME +import io.shiftleft.codepropertygraph.generated.NodeTypes.FILE import io.shiftleft.codepropertygraph.generated.nodes.* import org.apache.logging.log4j.LogManager import org.junit.jupiter.api.* @@ -213,6 +215,17 @@ class TinkerGraphDriverIntTest { assertTrue(driver.exists(v1)) assertTrue(driver.exists(v2)) } + + @Test + fun updateVertexTest() { + driver.addVertex(fileVertex) + assertTrue(driver.exists(fileVertex)) + driver.getWholeGraph() + .use { g -> assertTrue(g.nodes(fileVertex.id()).asSequence().any { it.property(NAME) == STRING_1 }) } + driver.updateVertexProperty(fileVertex.id(), FILE, NAME, STRING_2) + driver.getWholeGraph() + .use { g -> assertTrue(g.nodes(fileVertex.id()).asSequence().any { it.property(NAME) == STRING_2 }) } + } } @Nested @@ -610,14 +623,14 @@ class TinkerGraphDriverIntTest { @Test fun testVertexDelete() { assertTrue(driver.exists(methodVertex)) - driver.deleteVertex(methodVertex) + driver.deleteVertex(methodVertex.id(), methodVertex.build().label()) assertFalse(driver.exists(methodVertex)) // Try delete vertex which doesn't exist, should not throw error - driver.deleteVertex(methodVertex) + driver.deleteVertex(methodVertex.id(), methodVertex.build().label()) assertFalse(driver.exists(methodVertex)) // Delete metadata assertTrue(driver.exists(metaDataVertex)) - driver.deleteVertex(metaDataVertex) + driver.deleteVertex(metaDataVertex.id(), metaDataVertex.build().label()) assertFalse(driver.exists(metaDataVertex)) } diff --git a/plume/src/test/kotlin/io/github/plume/oss/util/DiffGraphUtilTest.kt b/plume/src/test/kotlin/io/github/plume/oss/util/DiffGraphUtilTest.kt new file mode 100644 index 00000000..3cbc081a --- /dev/null +++ b/plume/src/test/kotlin/io/github/plume/oss/util/DiffGraphUtilTest.kt @@ -0,0 +1,110 @@ +package io.github.plume.oss.util + +import io.github.plume.oss.TestDomainResources +import io.github.plume.oss.TestDomainResources.Companion.STRING_1 +import io.github.plume.oss.TestDomainResources.Companion.STRING_2 +import io.github.plume.oss.TestDomainResources.Companion.fileVertex +import io.github.plume.oss.TestDomainResources.Companion.methodVertex +import io.github.plume.oss.drivers.DriverFactory +import io.github.plume.oss.drivers.GraphDatabase +import io.github.plume.oss.drivers.TinkerGraphDriver +import io.shiftleft.codepropertygraph.generated.EdgeTypes.SOURCE_FILE +import io.shiftleft.codepropertygraph.generated.NodeKeyNames.NAME +import io.shiftleft.codepropertygraph.generated.nodes.File +import io.shiftleft.codepropertygraph.generated.nodes.StoredNode +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import overflowdb.Edge +import overflowdb.Node +import scala.Tuple2 +import scala.collection.immutable.Seq +import scala.jdk.CollectionConverters + +class DiffGraphUtilTest { + + val driver = (DriverFactory(GraphDatabase.TINKER_GRAPH) as TinkerGraphDriver).apply { connect() } + + @AfterEach + fun tearDown() { + TestDomainResources.simpleCpgVertices.forEach { it.id(-1) } + driver.clearGraph() + } + + @Test + fun addNodeTest() { + val builder = io.shiftleft.passes.DiffGraph.newBuilder() + builder.addNode(fileVertex.build()) + + DiffGraphUtil.processDiffGraph(driver, builder.build()) + driver.getWholeGraph().use { g -> + assertTrue(g.nodes().asSequence().any { it.label() == File.Label() && it.property(NAME) == STRING_1 }) + } + } + + @Test + fun removeNodeTest() { + driver.addVertex(fileVertex) + driver.getWholeGraph().use { g -> + assertTrue(g.nodes().asSequence().any { it.label() == File.Label() && it.property(NAME) == STRING_1 }) + } + val builder = io.shiftleft.passes.DiffGraph.newBuilder() + builder.removeNode(fileVertex.id()) + + DiffGraphUtil.processDiffGraph(driver, builder.build()) + driver.getWholeGraph().use { g -> + assertTrue(g.nodes().asSequence().none { it.label() == File.Label() && it.property(NAME) == STRING_1 }) + } + } + + @Test + fun createEdgeTest() { + driver.getWholeGraph().use { g -> assertFalse(g.edges().hasNext()) } + val builder = io.shiftleft.passes.DiffGraph.newBuilder() + + builder.addEdge( + methodVertex.build(), + fileVertex.build(), + SOURCE_FILE, + Seq.from(CollectionConverters.IterableHasAsScala(emptyList>()).asScala()) + ) + DiffGraphUtil.processDiffGraph(driver, builder.build()) + driver.getWholeGraph().use { g -> + assertTrue(g.edges().hasNext()) + val edge = g.edges().next() + assertTrue(edge.outNode().label() == methodVertex.build().label()) + assertTrue(edge.inNode().label() == fileVertex.build().label()) + assertTrue(edge.label() == SOURCE_FILE) + } + } + + @Test + fun removeEdgeTest() { + driver.addEdge(methodVertex, fileVertex, SOURCE_FILE) + val edgeToRemove: Edge + driver.getWholeGraph().use { g -> edgeToRemove = g.edges().next() } + val builder = io.shiftleft.passes.DiffGraph.newBuilder() + builder.removeEdge(edgeToRemove) + + DiffGraphUtil.processDiffGraph(driver, builder.build()) + driver.getWholeGraph().use { g -> assertFalse(g.edges().hasNext()) } + } + + @Test + fun updateNodePropertyTest() { + driver.addVertex(fileVertex) + driver.getWholeGraph() + .use { g -> assertTrue(g.nodes(fileVertex.id()).asSequence().any { it.property(NAME) == STRING_1 }) } + + val builder = io.shiftleft.passes.DiffGraph.newBuilder() + val storedNode: StoredNode + driver.getWholeGraph().use { g -> storedNode = g.node(fileVertex.id()) as StoredNode } + builder.addNodeProperty(storedNode, NAME, STRING_2) + + DiffGraphUtil.processDiffGraph(driver, builder.build()) + driver.getWholeGraph() + .use { g -> assertTrue(g.nodes(fileVertex.id()).asSequence().any { it.property(NAME) == STRING_2 }) } + } + +} \ No newline at end of file