renesca / renesca   0.3.2

Apache License 2.0 GitHub

Scala library for the Neo4j REST API

Scala versions: 2.11 2.10

renesca

Build Status Coverage Status

Query and modify subgraphs from a Neo4j REST database with Scala.

Also have a look at renesca-magic, which generates code for typesafe graph database schemas based on renesca.

Academic Article

There is also an academic article about renesca and renesca-magic: An Open-Source Object-Graph-Mapping Framework for Neo4j and Scala: Renesca. Feel free to use these libraries in any of your projects. If you use them in an academic setting, we appreciate if you cite this article.

Dietze, F., Karoff, J., Calero Valdez, A. , Ziefle, M., Greven, C., & Schroeder, U. (2016, August). An Open-Source Object-Graph-Mapping Framework for Neo4j and Scala: Renesca. In International Conference on Availability, Reliability, and Security (pp. 204-218). Springer International Publishing.

Concepts

Work with graphs instead of lists

Earlier we tried to interact with graph databases as if they were relational databases. This meant working with lists of query results. As Neo4J gives us full blown graphs as query results, we now take the whole thing and provide a graph to interact with. In the rare cases where the query result is not a graph, it can also be interpreted as a table.

Track changes, persist later

When modifying, creating and deleting nodes and connecting them with relationships it would be very expensive to submit a REST request for each change. In renesca we track changes and apply all of them at once when persisting the whole graph. This takes fewer REST requests and leaves room for optimization.

No lazy loading

There is no further database traversing from a subgraph because there is no need to do so. When working with subgraphs retrieved from a query, you know which data you need in the future and can fetch that with the query before traversing. This approach saves a lot of unnecessary requests.

Feature summary

  • Interpret query results as graphs or tables
  • Modify result graphs and persist the changes back to the database
  • Do everything with transactions

Installation

To use renesca in your sbt project, add this dependency to your build.sbt:

libraryDependencies += "com.github.renesca" %% "renesca" % "0.3.2-9"

Feedback

Please don't hesitate to create issues about anything. Ideas, questions, bugs, feature requests, criticism, missing documentation, confusing examples, ... . Are you stuck with renesca or renesca-magic for some time? Is there something in this README that is unclear? Anything else? This means something does not work as intended or the API is not intuitive. Contact us and let's fix this together.

Usage Example

This example is also available as a sbt project: renesca/renesca-example

package renesca.example

import renesca.graph.{Node, Relation}
import renesca.parameter._
import renesca.parameter.implicits._
import renesca.{DbService, RestService, Transaction, Query}
import spray.http.BasicHttpCredentials

object Main extends App {
  // set up database connection
  val credentials = BasicHttpCredentials("neo4j", "neo4j")
  // RestService contains an ActorSystem to handle HTTP communication via spray-client
  val restService = new RestService("http://localhost:7474", Some(credentials))

  // query interface for submitting single requests
  val db = new DbService
  // dependency injection
  db.restService = restService


  // only proceed if database is available and empty
  val wholeGraph = db.queryWholeGraph
  if(wholeGraph.nonEmpty) {
    restService.actorSystem.shutdown()
    sys.error("Database is not empty.")
  }


  // create example graph:  snake -eats-> dog
  db.query("CREATE (:ANIMAL {name:'snake'})-[:EATS]->(:ANIMAL {name:'dog'})")

  val tx = db.newTransaction

  // query a subgraph from the database
  implicit val graph = tx.queryGraph("MATCH (n:ANIMAL)-[r]->() RETURN n,r")

  // access the graph like scala collections
  val snake = graph.nodes.find(_.properties("name").
    asInstanceOf[StringPropertyValue] == "snake").get

  // useful methods to access the graph (requires implicit val graph in scope)
  // e.g.: neighbours,  successors,  predecessors,  inDegree,  outDegree,  degree, ...
  val name = snake.neighbours.head.properties("name").
    asInstanceOf[StringPropertyValue].value
  println("Name of one snake neighbour: " + name) // prints "dog"

  // changes to the graph are tracked
  snake.labels += "REPTILE"
  snake.properties("hungry") = true

  // creating a local Node (a Node the database does not know about yet)
  val hippo = Node.create

  // changes to locally created Nodes are also tracked
  hippo.labels += "ANIMAL"
  hippo.properties("name") = "hippo"

  // add the created node to the Node Set
  graph.nodes += hippo

  // create a new local relation from a locally created Node to an existing Node
  graph.relations += Relation.create(snake, "EATS", hippo)

  // persist all tracked changes to the database and commit the transaction
  tx.commit.persistChanges(graph)

  // different transaction syntax
  db.transaction { tx =>
    val hippo = tx.queryGraph(
      Query( """MATCH (n:ANIMAL {name: {name}}) return n""",
        Map("name" -> "hippo")) // Cypher query parameters
    ).nodes.head
    hippo.properties("nose") = true
    tx.persistChanges(hippo)
  }

  db.transaction { tx =>
    // delete hippo
    tx.query( """MATCH (n:ANIMAL {name: "hippo"}) OPTIONAL MATCH (n)-[r]-() DELETE n,r""")

    // roll back deletion
    tx.rollback()
  }

  // interpret query result as a table
  val animals = db.queryTable("""MATCH (n:ANIMAL) OPTIONAL MATCH (n)-[r:EATS]->()
    RETURN n.name as name, COUNT(r) as eatcount""")

  println("\n" + animals.columns.mkString("\t")) // prints "name eatcount"
  for(row <- animals.rows) {
    print(row.cells(0).asInstanceOf[StringPropertyValue].value)
    print("\t")
    println(row.cells(1).asInstanceOf[LongPropertyValue].value)
  }
  println()
  // loop prints:
  //  dog	  0
  //  snake	2
  //  hippo	0

  val hungriest = animals.rows.maxBy(_.apply("eatcount").
    asInstanceOf[LongPropertyValue].value).
    apply("name").asInstanceOf[StringPropertyValue].value
  println("hungriest: " + hungriest) // prints "snake"


  // clear database
  db.query("MATCH (n) OPTIONAL MATCH (n)-[r]-() DELETE n,r")

  // shut down actor system
  restService.actorSystem.shutdown()
}

License

renesca is free software released under the Apache License, Version 2.0