Skip to content

Instantly share code, notes, and snippets.

@dacr
Last active April 2, 2023 10:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dacr/3b7dc3aba63afdc4e3eabad32122053c to your computer and use it in GitHub Desktop.
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
// 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