Last active
April 2, 2023 10:11
-
-
Save dacr/3b7dc3aba63afdc4e3eabad32122053c to your computer and use it in GitHub Desktop.
learning arangodb graphs features through java client driver / published by https://github.com/dacr/code-examples-manager #1c820570-fc3d-4e89-b24a-5926e462869d/16c9aad8d1d6b8959a2653317d731f7d59f9c148
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// summary : learning arangodb graphs features through java client driver | |
// keywords : arangodb, graphdb, graph, javadriver, graphs, @testable | |
// publish : gist, corporate | |
// authors : David Crosson | |
// license : Apache NON-AI License Version 2.0 (https://raw.githubusercontent.com/non-ai-licenses/non-ai-licenses/main/NON-AI-APACHE2) | |
// id : 1c820570-fc3d-4e89-b24a-5926e462869d | |
// created-on : 2021-03-05T09:25:00Z | |
// managed-by : https://github.com/dacr/code-examples-manager | |
// execution : scala ammonite script (http://ammonite.io/) - run as follow 'amm scriptname.sc' | |
import $ivy.`com.arangodb:arangodb-java-driver:6.9.1` | |
import $ivy.`com.whisk::docker-testkit-impl-spotify:0.9.9` | |
import $ivy.`org.json4s::json4s-jackson:3.6.11` | |
import $ivy.`org.json4s::json4s-ext:3.6.11` | |
import $ivy.`org.scalatest::scalatest:3.2.6` | |
import $ivy.`org.slf4j:slf4j-simple:1.7.30` | |
import $ivy.`javax.activation:activation:1.1.1` | |
import org.scalatest._ | |
import matchers.should.Matchers | |
import wordspec.AnyWordSpec | |
import OptionValues._ | |
import com.arangodb._ | |
import com.arangodb.entity._ | |
import com.arangodb.model.{AqlQueryOptions, CollectionCreateOptions, DocumentCreateOptions, StreamTransactionOptions, TransactionOptions} | |
import com.arangodb.util.MapBuilder | |
import com.whisk.docker.{DockerContainer, DockerKit, DockerPortMapping, DockerReadyChecker} | |
import org.json4s.{DefaultFormats, FieldSerializer, Formats} | |
import org.json4s.FieldSerializer.{renameFrom, renameTo} | |
import org.json4s.jackson.Serialization.{read, write} | |
import org.json4s.Extraction.decompose | |
import scala.jdk.CollectionConverters._ | |
// ================================================================================================== | |
// Provided DockerTestKit allow to have control over the used scalatest release | |
trait DockerTestKit extends BeforeAndAfterAll with org.scalatest.concurrent.ScalaFutures with DockerKit { | |
self: Suite => | |
import org.scalatest.time.{Span, Seconds, Millis} | |
def dockerInitPatienceInterval = | |
PatienceConfig(scaled(Span(20, Seconds)), scaled(Span(10, Millis))) | |
def dockerPullImagesPatienceInterval = | |
PatienceConfig(scaled(Span(1200, Seconds)), scaled(Span(250, Millis))) | |
override def beforeAll(): Unit = { | |
super.beforeAll(); | |
startAllOrFail() | |
} | |
override def afterAll(): Unit = { | |
stopAllQuietly(); | |
super.afterAll() | |
} | |
} | |
// ================================================================================================== | |
trait DockerArangoDBService extends com.whisk.docker.impl.spotify.DockerKitSpotify { | |
val arangoUsername = "root" | |
val arangoPassword = "changeit" | |
val mappedArangoPort = 9529 | |
lazy val arangoContainer = { | |
val env = Array( | |
s"ARANGO_ROOT_PASSWORD=$arangoPassword", | |
) | |
DockerContainer("arangodb:3.7.3") | |
.withEnv(env: _*) | |
.withPortMapping(8529 -> DockerPortMapping(Some(mappedArangoPort), "127.0.0.1")) | |
.withReadyChecker(DockerReadyChecker.LogLineContains("is ready for business")) | |
} | |
override def dockerContainers: List[DockerContainer] = arangoContainer :: Nil | |
} | |
// ================================================================================================== | |
trait ArangoDatabaseAccessForTest extends DockerArangoDBService { | |
def uuid = java.util.UUID.randomUUID().toString | |
def withArango(testCode: ArangoDB => Any): Unit = { | |
val arango = { | |
new ArangoDB.Builder() | |
.host("127.0.0.1", mappedArangoPort) | |
.user(arangoUsername) | |
.password(arangoPassword) | |
.build() | |
} | |
try { | |
testCode(arango) | |
} finally { | |
arango.shutdown() | |
} | |
} | |
def withArangoAndDatabaseCleanup(testCode: (ArangoDB, String) => Any): Unit = { | |
withArango { arango => | |
val databaseName = "random-" + java.util.UUID.randomUUID().toString | |
try { | |
testCode(arango, databaseName) | |
} finally { | |
val db: ArangoDatabase = arango.db(databaseName) | |
if (db.exists()) db.drop() | |
} | |
} | |
} | |
def withDatabase(testCode: ArangoDatabase => Any): Unit = { | |
withArango { arango => | |
val databaseName = s"test-database-$uuid" | |
val db: ArangoDatabase = arango.db(databaseName) | |
if (db.exists()) db.drop() | |
arango.createDatabase(databaseName) | |
try { | |
testCode(db) | |
} finally { | |
db.drop() | |
} | |
} | |
} | |
def withCollection(testCode: ArangoCollection => Any): Unit = { | |
withDatabase { db => | |
def collectionName = s"test-collection-$uuid".replaceAll("-", "_") | |
val collection = db.collection(collectionName) | |
if (!collection.exists) collection.create() | |
try { | |
testCode(collection) | |
} finally { | |
collection.drop() | |
} | |
} | |
} | |
def withGraph(testCode: ArangoGraph => Any): Unit = { | |
withDatabase { db => | |
def graphName = s"test-graph-$uuid".replaceAll("-", "_") | |
val graph = db.graph(graphName) | |
if (!graph.exists) graph.create(List.empty.asJava) | |
try { | |
testCode(graph) | |
} finally { | |
graph.drop() | |
} | |
} | |
} | |
def addVertex[T](verticesCollectionName: String, vertex: T)(implicit graph: ArangoGraph, format: Formats): VertexEntity = { | |
graph.vertexCollection(verticesCollectionName).insertVertex(write(decompose(vertex))) | |
} | |
def addEdge[T](edgesCollectionName: String, edge: T)(implicit graph: ArangoGraph, format: Formats): EdgeEntity = { | |
graph.edgeCollection(edgesCollectionName).insertEdge(write(decompose(edge))) | |
} | |
} | |
case class Node(key: String) | |
case class Link(key: String, relation: String, from: String, to: String) | |
class ArangoDbLearningTest extends AnyWordSpec with Matchers with ArangoDatabaseAccessForTest with DockerTestKit { | |
override def suiteName: String = "ArangoDbLearningTest" | |
"ArangoDB" can { | |
"deal with graphs" should { | |
// --------------------------------------------------------------------------------------------------------------- | |
"manage graphs" should { | |
"create graph" in withDatabase { db => | |
val graphName = "graph-example" | |
val graph = db.graph(graphName) // handler | |
graph.create(List.empty.asJava) | |
graph.exists() shouldBe true | |
} | |
"drop graph" in withDatabase { db => | |
val graphName = "graph-example" | |
val graph = db.graph(graphName) // handler | |
graph.create(List.empty.asJava) | |
graph.drop() | |
graph.exists() shouldBe false | |
} | |
"create graph with edge definition" in withDatabase { db => | |
val graphName = "graph-example" | |
val edgesCollectionName = "links" | |
val verticesCollectionName = "nodes" | |
val edgeDefinition = new EdgeDefinition().collection(edgesCollectionName).from(verticesCollectionName).to(verticesCollectionName) | |
val graph = db.graph(graphName) | |
graph.create(List(edgeDefinition).asJava) | |
info("a graph is defined through vertices (nodes) and edges (links), ") | |
graph.exists() shouldBe true | |
db.collection(edgesCollectionName).exists() shouldBe true | |
db.collection(verticesCollectionName).exists() shouldBe true | |
} | |
"create graph alternatively with edge definition" in withDatabase { db => | |
val graphName = "graph-example" | |
val edgesCollectionName = "links" | |
val verticesCollectionName = "nodes" | |
val edgeDefinition = new EdgeDefinition().collection(edgesCollectionName).from(verticesCollectionName).to(verticesCollectionName) | |
val graph = db.graph(graphName) | |
db.createGraph(graphName, List(edgeDefinition).asJava) | |
graph.exists() shouldBe true | |
db.collection(edgesCollectionName).exists() shouldBe true | |
db.collection(verticesCollectionName).exists() shouldBe true | |
} | |
} | |
// --------------------------------------------------------------------------------------------------------------- | |
"manage vertices and edges" should { | |
"add vertices and edges using raw strings" in withGraph { graph => | |
val edgesCollName = "links" | |
val verticesCollName = "nodes" | |
val edgeDefinition = new EdgeDefinition().collection(edgesCollName).from(verticesCollName).to(verticesCollName) | |
graph.addEdgeDefinition(edgeDefinition) | |
graph.vertexCollection(verticesCollName).insertVertex("""{"_key":"A"}""") | |
graph.vertexCollection(verticesCollName).insertVertex("""{"_key":"B"}""") | |
graph.db.collection(verticesCollName).count().getCount shouldBe 2 | |
graph.edgeCollection(edgesCollName).insertEdge(s"""{"_from":"$verticesCollName/A","_to":"$verticesCollName/B"}""") | |
graph.db.collection(edgesCollName).count().getCount shouldBe 1 | |
} | |
} | |
// --------------------------------------------------------------------------------------------------------------- | |
"operate graph" should { | |
val nodeFieldsRenamer = FieldSerializer[Node]( | |
renameTo("key", "_key"), | |
renameFrom("_key", "key") | |
) | |
val linkFieldsRenamer = FieldSerializer[Link]( | |
renameTo("from", "_from").orElse(renameTo("to", "_to")).orElse(renameTo("key", "_key")), | |
renameFrom("_from", "from").orElse(renameFrom("_to", "to")).orElse(renameFrom("_key", "key")) | |
) | |
implicit val formats = DefaultFormats.lossless + nodeFieldsRenamer + linkFieldsRenamer | |
def buildSampleGraph(edgesName: String, verticesName: String)(implicit graph: ArangoGraph) = { | |
val edgeDefinition = new EdgeDefinition().collection(edgesName).from(verticesName).to(verticesName) | |
graph.addEdgeDefinition(edgeDefinition) | |
val a = addVertex(verticesName, Node("A")) | |
val b = addVertex(verticesName, Node("B")) | |
val c = addVertex(verticesName, Node("C")) | |
val d = addVertex(verticesName, Node("D")) | |
val e = addVertex(verticesName, Node("E")) | |
val f = addVertex(verticesName, Node("F")) | |
val g = addVertex(verticesName, Node("G")) | |
val h = addVertex(verticesName, Node("H")) | |
addEdge(edgesName, Link("ab", "connected", a.getId, b.getId)) | |
addEdge(edgesName, Link("ac", "connected", a.getId, c.getId)) | |
addEdge(edgesName, Link("ad", "connected", a.getId, d.getId)) | |
addEdge(edgesName, Link("be", "connected", b.getId, e.getId)) | |
addEdge(edgesName, Link("cf", "connected", c.getId, f.getId)) | |
addEdge(edgesName, Link("cg", "connected", c.getId, g.getId)) | |
addEdge(edgesName, Link("gh", "connected", g.getId, h.getId)) | |
addEdge(edgesName, Link("eh", "connected", e.getId, h.getId)) | |
addEdge(edgesName, Link("dh", "connected", d.getId, h.getId)) | |
} | |
/* | |
a | |
/ | \ | |
/ | \ | |
b c d | |
| | \ | | |
e f g | | |
\ / / | |
\ / / | |
\/ / | |
\/ | |
h | |
*/ | |
def queryBaseDocument(query: String, vars: (String, Object)*)(implicit graph: ArangoGraph) = { | |
graph.db.query(query, vars.toMap.asJava, null, classOf[BaseDocument]) | |
} | |
def queryString(query: String, vars: (String, Object)*)(implicit graph: ArangoGraph) = { | |
graph.db.query(query, vars.toMap.asJava, null, classOf[String]) | |
} | |
val edgesName = "links" | |
val verticesName = "nodes" | |
"build simple graph" in withGraph { implicit graph => | |
buildSampleGraph(edgesName, verticesName) | |
graph.db.collection(verticesName).count().getCount shouldBe 8 | |
graph.db.collection(edgesName).count().getCount shouldBe 9 | |
} | |
"get direct children (OUTBOUND)" in withGraph { implicit graph => | |
buildSampleGraph(edgesName, verticesName) | |
val a = graph.db.collection(verticesName).getDocument("A", classOf[BaseDocument]) | |
val q0 = queryBaseDocument( | |
"""FOR v IN 1..1 OUTBOUND @node GRAPH @graph RETURN v""", | |
"graph" -> graph.name, "node" -> a.getId) | |
q0.asListRemaining().asScala.map(_.getKey) should contain allOf("B", "C", "D") | |
} | |
"get direct parents (INBOUND)" in withGraph { implicit graph => | |
buildSampleGraph(edgesName, verticesName) | |
val h = graph.db.collection(verticesName).getDocument("H", classOf[BaseDocument]) | |
val q0 = queryBaseDocument( | |
"""FOR v IN 1..1 INBOUND @node GRAPH @graph RETURN v""", | |
"graph" -> graph.name, "node" -> h.getId) | |
q0.asListRemaining().asScala.map(_.getKey) should contain allOf("E", "G", "D") | |
} | |
"get sub-children" in withGraph { implicit graph => | |
buildSampleGraph(edgesName, verticesName) | |
val a = graph.db.collection(verticesName).getDocument("A", classOf[BaseDocument]) | |
val q0 = queryBaseDocument( | |
"""FOR v IN 2..2 OUTBOUND @node GRAPH @graph RETURN v""", | |
"graph" -> graph.name, "node" -> a.getId) | |
q0.asListRemaining().asScala.map(_.getKey) should contain allOf("E", "F", "G") | |
} | |
"get direct neighbors" in withGraph { implicit graph => | |
buildSampleGraph(edgesName, verticesName) | |
val c = graph.db.collection(verticesName).getDocument("C", classOf[BaseDocument]) | |
val q0 = queryBaseDocument( | |
"""FOR v IN 1..1 ANY @node GRAPH @graph RETURN v""", | |
"graph" -> graph.name, "node" -> c.getId) | |
q0.asListRemaining().asScala.map(_.getKey) should contain allOf("A", "F", "G") | |
} | |
"walk through the graph and get info" in withGraph { implicit graph => | |
buildSampleGraph(edgesName, verticesName) | |
val a = graph.db.collection(verticesName).getDocument("A", classOf[BaseDocument]) | |
val q0 = queryString( | |
"""FOR vertex,edge,path IN 2..2 OUTBOUND @node GRAPH @graph RETURN CONCAT_SEPARATOR(" - ",vertex._key, edge._key, path.edges[*]._key)""", | |
"graph" -> graph.name, "node" -> a.getId) | |
info("all paths to all children at depth 2 (path.edges contains all path details), edge is the last edge to the reached vertex") | |
val res = q0.asListRemaining().asScala | |
res.size shouldBe 4 | |
res should contain allOf("""E - be - ["ab","be"]""", """F - cf - ["ac","cf"]""", """G - cg - ["ac","cg"]""", """H - dh - ["ad","dh"]""") | |
} | |
"get shortest path between 2 nodes" in withGraph { implicit graph => | |
buildSampleGraph(edgesName, verticesName) | |
val a = graph.db.collection(verticesName).getDocument("A", classOf[BaseDocument]) | |
val h = graph.db.collection(verticesName).getDocument("H", classOf[BaseDocument]) | |
val q0 = queryString( | |
"""FOR v IN ANY SHORTEST_PATH @nodeFrom TO @nodeTo GRAPH @graph RETURN v._key""", | |
"graph" -> graph.name, "nodeFrom" -> a.getId, "nodeTo" -> h.getId | |
) | |
q0.asListRemaining() should contain inOrder("A", "D", "H") | |
} | |
} | |
} | |
} | |
} | |
org.scalatest.tools.Runner.main(Array("-oDF", "-s", classOf[ArangoDbLearningTest].getName)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment