Skip to content

Instantly share code, notes, and snippets.

@dacr
Last active April 2, 2023 10:10
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/8b62abfd89c6fded0778dce858f15196 to your computer and use it in GitHub Desktop.
Save dacr/8b62abfd89c6fded0778dce858f15196 to your computer and use it in GitHub Desktop.
learning arangodb through java client driver / published by https://github.com/dacr/code-examples-manager #0e73daf5-12d6-4eb7-8a8b-60c24420f597/5f59e57dec423cbc30145da9b7c2f1b9420d66a1
// summary : learning arangodb through java client driver
// keywords : arangodb, graphdb, javadriver, @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 : 0e73daf5-12d6-4eb7-8a8b-60c24420f597
// 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 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()
}
}
}
}
class ArangoDbLearningTest extends AnyWordSpec with Matchers with ArangoDatabaseAccessForTest with DockerTestKit {
override def suiteName: String = "ArangoDbLearningTest"
"ArangoDb (synchronous API)" can {
// -----------------------------------------------------------------------------------------------------------------
"manage databases" should {
"create database" in withArangoAndDatabaseCleanup { (arango, dbname) =>
arango.createDatabase(dbname)
}
"check if a database exists" in withArangoAndDatabaseCleanup { (arango, dbname) =>
arango.createDatabase(dbname)
arango.db(dbname).exists() shouldBe true
}
"list visible databases" in withArangoAndDatabaseCleanup { (arango, dbname) =>
arango.createDatabase(dbname)
arango.getDatabases.asScala should contain allOf("_system", dbname)
}
"drop database" in withArangoAndDatabaseCleanup { (arango, dbname) =>
arango.createDatabase(dbname)
arango.db(dbname).drop()
}
}
// -----------------------------------------------------------------------------------------------------------------
"manage document collections" should {
"create collection" in withDatabase { db =>
val collname = s"test-collection-$uuid"
db.createCollection(collname)
info("default collection are for generic documents")
}
"check if a collection exists" in withDatabase { db =>
val collname = s"test-collection-$uuid"
db.createCollection(collname)
db.collection(collname).exists() shouldBe true
}
"drop collection" in withDatabase { db =>
val collname = s"test-collection-$uuid"
db.createCollection(collname)
db.collection(collname).drop()
db.collection(collname).exists() shouldBe false
}
}
// -----------------------------------------------------------------------------------------------------------------
"manage documents within a collection" should {
"create a document from a raw json string" in withCollection { collection =>
val result = collection.insertDocument("""{"name":"joe","age":42}""")
info("arango natively understand string as valid json documents")
result.getKey.size should be > 0
}
"create a document from a raw json string using a user-defined key" in withCollection { collection =>
val result = collection.insertDocument("""{"_key":"a", "name":"joe","age":42}""")
info("arango add a unique key (a string) to all inserted documents, but you can choose your own")
result.getKey shouldBe "a"
info("arango generates a unique id for all document within the database where they belong to by concatening the collection name and the key")
result.getId shouldBe s"${collection.name}/a"
}
"create a document from a generic java instance named BaseDocument" in withCollection { collection =>
info("BaseDocument allow to manage documents dynamically, without an already known rigid structure")
val doc = new BaseDocument() {
setKey("a")
addAttribute("name", "joe")
addAttribute("age", 42)
}
collection.insertDocument(doc)
collection.documentExists("a") shouldBe true
}
"check if a document exists" in withCollection { collection =>
collection.insertDocument("""{"_key":"a","name":"joe","age":42}""")
collection.documentExists("a") shouldBe true
}
"fetch a non-existent document" in withCollection {collection =>
val doc = collection.getDocument("atrucmuche", classOf[BaseDocument])
doc shouldBe null
}
"count number of documents" in withCollection { collection =>
collection.insertDocument("""{"name":"joe","age":42}""")
collection.insertDocument("""{"name":"sarah","age":24}""")
collection.count().getCount shouldBe 2
}
"get a document as raw json string" in withCollection { collection =>
val inputJsonText = """{"_key":"a","name":"joe","age":42}"""
collection.insertDocument(inputJsonText)
val receivedJsonText = collection.getDocument("a", classOf[String])
info("When using String class, arango java driver return raw json as string, as such allowing us to choose easily custom json library")
receivedJsonText should include regex """"age":42"""
}
"get a document as json base document" in withCollection { collection =>
val inputJsonText = """{"_key":"a","name":"joe","age":42}"""
collection.insertDocument(inputJsonText)
val doc = collection.getDocument("a", classOf[BaseDocument])
info("When using BaseDocument class, arango java driver return generic java instance containing meta data and json as a java Map")
doc.getId shouldBe s"${collection.name}/a"
doc.getKey shouldBe "a"
doc.getAttribute("name") shouldBe "joe"
doc.getAttribute("age") shouldBe 42
}
"update a document" in withCollection { collection =>
val inputJsonText = """{"_key":"a","name":"joe","age":42}"""
collection.insertDocument(inputJsonText)
val newInputJsonText = """{"name":"joe","age":43}"""
collection.updateDocument("a", newInputJsonText)
collection.count().getCount shouldBe 1
val doc = collection.getDocument("a", classOf[BaseDocument])
doc.getAttribute("age") shouldBe 43
}
"delete a document" in withCollection { collection =>
collection.insertDocument("""{"_key":"a","name":"joe","age":42}""")
collection.deleteDocument("a")
collection.documentExists("a") shouldBe false
}
"delete a non-existent document" in withCollection {collection =>
val ex = intercept[ArangoDBException] {
collection.deleteDocument("aMissingDocument")
}
ex.getResponseCode shouldBe 404
ex.getErrorNum shouldBe 1202
}
"delete documents" in withCollection { collection =>
collection.insertDocument("""{"_key":"a","name":"joe","age":42}""")
collection.insertDocument("""{"_key":"b","name":"sarah","age":24}""")
collection.count().getCount shouldBe 2
collection.deleteDocuments(List("a", "b").asJava)
collection.count().getCount shouldBe 0
}
"be able to get several documents as raw json string" in withCollection { collection =>
for {i <- 1 to 42} collection.insertDocument(s"""{"_key":"a-$i","name":"joe","age":$i}""")
collection.count().getCount shouldBe 42
val docs = collection.getDocuments(java.util.List.of("a-1", "a-31", "a-42"), classOf[String])
docs.getDocuments.size() shouldBe 3
}
"query one document" in withCollection { collection =>
for {i <- 1 to 42} collection.insertDocument(s"""{"_key":"a-$i","name":"joe","age":$i}""")
collection.db().query(
"RETURN @docid",
new MapBuilder().put("docid", s"${collection.name}/a-42").get(),
new AqlQueryOptions(),
classOf[String]
).asListRemaining().size() shouldBe 1
}
"query all documents" in withCollection { collection =>
for {i <- 1 to 42} collection.insertDocument(s"""{"name":"joe", "age":$i}""")
collection.db().query(
"FOR i IN @@collection RETURN i",
new MapBuilder().put("@collection", collection.name).get(),
new AqlQueryOptions(),
classOf[String]
).asListRemaining().size() shouldBe 42
}
"query for some documents" in withCollection { collection =>
for {i <- 1 to 42} collection.insertDocument(s"""{"name":"joe", "age":$i}""")
collection.db().query(
"FOR i IN @@collection FILTER i.age > 35 RETURN i",
new MapBuilder().put("@collection", collection.name).get(),
new AqlQueryOptions(),
classOf[String]
).asListRemaining().size() shouldBe 7
}
"query all documents using cursor and BaseDocument" in withCollection { collection =>
for {i <- 1 to 42} collection.insertDocument(s"""{"name":"joe", "age":$i}""")
val cursor = collection.db().query(
"FOR i IN @@collection RETURN i",
new MapBuilder().put("@collection", collection.name).get(),
new AqlQueryOptions(),
classOf[BaseDocument]
)
val it = cursor.iterator().asScala
val ages = it.map(_.getProperties.get("age")).collect { case age: Number => age.intValue() }.toList
ages.min shouldBe 1
ages.max shouldBe 42
}
}
// -----------------------------------------------------------------------------------------------------------------
"supports client-side transaction" should {
"commit all changes" in withCollection { collection =>
val collectionName = collection.name()
val db = collection.db()
val tr = db.beginStreamTransaction(new StreamTransactionOptions().writeCollections(collectionName))
val trId = tr.getId
try {
collection.insertDocument(s"""{"message":"Hello"}""", new DocumentCreateOptions().streamTransactionId(trId))
collection.insertDocument(s"""{"message":"world"}""", new DocumentCreateOptions().streamTransactionId(trId))
collection.count().getCount shouldBe 0
db.commitStreamTransaction(trId)
} catch {
case ex: Exception => db.abortStreamTransaction(trId)
}
collection.count().getCount shouldBe 2
}
"rollback any changes if something wrong occurs" in withCollection { collection =>
val collectionName = collection.name()
val db = collection.db()
val tr = db.beginStreamTransaction(new StreamTransactionOptions().writeCollections(collectionName))
val trId = tr.getId
try {
collection.insertDocument(s"""{"message":"Hello"}""", new DocumentCreateOptions().streamTransactionId(trId))
collection.insertDocument(s"""{"message":"world"}""", new DocumentCreateOptions().streamTransactionId(trId))
throw new Exception("something wrong happened")
db.commitStreamTransaction(trId)
} catch {
case ex: Exception => db.abortStreamTransaction(trId)
}
collection.count().getCount shouldBe 0
}
}
// -----------------------------------------------------------------------------------------------------------------
"supports server-side transaction" should {
"be able to commit all changes" in withCollection { collection =>
val db = collection.db()
val collectionName = collection.name()
val actions =
s"""
|function () {
| var db = require("@arangodb").db
| db.$collectionName.save({ msg: "hello" });
| db.$collectionName.save({ msg: "world" });
| // will commit the transaction and return the value "hello"
| return "ok";
|}
|""".stripMargin
db.transaction(
actions, classOf[String], new TransactionOptions().exclusiveCollections(collectionName)
)
collection.count().getCount shouldBe 2
}
"be able to rollback if something goes wrong" in withCollection { collection =>
val db = collection.db()
val collectionName = collection.name()
val actions =
s"""
|function () {
| var db = require("@arangodb").db
| db.$collectionName.save({ msg: "hello" });
| db.$collectionName.save({ msg: "world" });
| throw "BUG";
| return "ok";
|}
|""".stripMargin
try {
db.transaction(
actions, classOf[String], new TransactionOptions().exclusiveCollections(collectionName)
)
} catch {
case ex: Exception =>
ex.getMessage should include regex "BUG"
}
collection.count().getCount shouldBe 0
}
}
// -----------------------------------------------------------------------------------------------------------------
"supports graph natively" should {
}
}
}
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