Last active
April 2, 2023 10:10
-
-
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
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 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