Working with Neo4J and Scala at ThoughtWorks, I had a major qualm regarding the way we were interacting with the graph database using Cypher. Cypher is a declarative language, hence it is tricky to compose it programmatically. This is the reason why most of the ORMs (or micro-ORMs) for Neo4J are effective only for simple use cases. We observed that as the scale and complexity of our business logic started to increase, our code fragmented into two distinct flavors. There were Scala models and business implementations. And then there were string generation and manipulation methods to generate Cypher queries.

String-based queries have inherent issues like no type safety, minimal syntax checking, difficulty in composing directly proportional to complexity, etc.

Scala-cypher-DSL aims to leverage the models created as part of business logic and create Cypher queries intelligently and type-safe manner.

Installation

Binary release artifacts are published to the Sonatype OSS Repository Hosting service and synced to Maven Central.

SBT

"me.manishkatoch" %% "scala-cypher-dsl" % "0.4.6"

Gradle

implementation group: 'me.manishkatoch', name: 'scala-cypher-dsl', version: '0.4.6'

Usage

Consider the following domain models representing people working in a fictitious department and friendly by nature.

//sample domain models case class Person(id: String, name: String, age: Int) case class WorksIn(sinceDays: Int) case class IsFriendOf(since: Int, lastConnectedOn: String) case class Department(id: String, name: String)

To start writing query DSL, import the following

import me.manishkatoch.scala.cypherDSL.spec.syntax.v1._ import me.manishkatoch.scala.cypherDSL.spec.syntax.patterns._ //optional, import for expressing paths.

Using DSL for a simple match query generation for an instance of model

//for a person John Doe val johnDoe = Person("AX31SD", "John Doe", 50) //match and return Neo4J data val johnDoeQuery = cypher.MATCH(johnDoe) .RETURN(johnDoe) .toQuery() johnDoeQuery.query //res0: String = MATCH (a0:Person {id: {a0_id},name: {a0_name},age: {a0_age}}) // RETURN a0 johnDoeQuery.queryMap //res1: scala.collection.immutable.Map[String,Any] = Map(a0_id -> AX31SD, a0_name -> John Doe, a0_age -> 50))

Match Person only by a property (e.g. name)

//for a person John Doe val johnDoe = Person("AX31SD", "John Doe", 50) //match and return Neo4J data val johnDoeQuery = cypher.MATCH(johnDoe('name)) .RETURN(johnDoe) .toQuery() johnDoeQuery.query //res0: String = MATCH (a0:Person {id: {a0_id},name: {a0_name},age: {a0_age}}) // RETURN a0 johnDoeQuery.queryMap //res1: scala.collection.immutable.Map[String,Any] = Map(a0_id -> AX31SD, a0_name -> John Doe, a0_age -> 50))

Note: if the property doesn't exist, compilation will fail. Yay!

Using DSL for matching any instance of model.

//for any person val anyPerson = any[Person] // any instance of node labelled Person val result = cypher.MATCH(anyPerson) .RETURN(anyPerson) .toQuery() result.query //res0: String = MATCH (a0:Person) // RETURN a0 result.queryMap //res1: scala.collection.immutable.Map[String,Any] = Map() query for all the friends of John Doe in Science department val scienceDept = Department("ZSW12R", "Science") val anyPerson = any[Person] val isFriendOf = anyRel[IsFriendOf] //any relation instance of label IsFriendOf val result = cypher.MATCH(johnDoe -| isFriendOf |-> anyPerson <-- scienceDept) .RETURN(anyPerson) .toQuery() result.query //res0: String = MATCH (a0:Person {id: {a0_id},name: {a0_name},age: {a0_age}})-[a1:IS_FRIEND_OF]->(a2:Person)<--(a3:Department {id: {a3_id},name: {a3_name}}) // RETURN a2 result.queryMap //res1: scala.collection.immutable.Map[String,Any] = Map(a0_id -> AX31SD, a0_name -> John Doe, a3_name -> Science, a0_age -> 50, a3_id -> ZSW12R)

For detailed DSL usage and more examples, there is a Wiki.

Support and Contributions

Scala-cypher-dsl aims to be an important library for anyone who wants to write idiomatic scala when interacting with Neo4J or any other cypher query language based platforms. I aim to support 100% of cypher specifications, and any form of contribution (issue report, PR, etc.) is more than welcome!