A type-safe, compile time DSL for writing Cypher queries in Scala, forked from the excellent scala-cypher-dsl
project, lead by Manish Katoch
.
From the scala-cypher-dsl project:
With Neo4J and Scala, the ORMs satisfy only a small subset of querying needs and majority of the fairly complex cypher query tend to be in the form of strings.Cypher strings (like SQL strings) have inherent issues like no type safety, minimal syntax checking, difficulty in composing etc.
Scala-Cypher-DSL aims to alleviate above by providing following:
- Type-safe constructs using user defined models and ADTs
- Chainable DSL like Cypher.
- Automatic identifiers generation( you don't have to manage identifiers, just work with instances/models).
- Parameterized queries and automatic creation of parameters map.
Note: It does not provide drivers for Neo4J but only concerns with query and query parameters creation
Additional motivations, specifically for this fork:
- Updated support to version 4 of the Cypher language (it has been tested with
4.3.4
) - Some admitted fussiness regarding the package structure, specifically to remove usernames and to correct
cypherDSL
=>cypher.dsl
. Primarily, we dislike the proliferation of usernames in our proprietary and open source code. Sincerely, no offense is meant for Manish. - Added support for the WHERE clause
- The Context implementation was fragile, the DSL format would result in entity names that would change depnding upon minor semantic differences in expression order. We have updated it to remain consistent (and based upon DSL expression names) instead
- Better support for the
return
clause
NOTE - We greatly appreciate the inspiration and design of the original scala-cypher-dsl project and will be watching and incoporating updates and improvements. We haven't filed a PR to push our changes back because many of our changes (and motivations) would be overly disruptive to the source project.
Binary release artifacts are published to the Sonatype OSS Repository Hosting service and synced to Maven Central.
libraryDependencies ++= Seq(
"org.neo4j.driver" % "neo4j-java-driver" % "4.3.4",
"io.github.neo4s" %% "neo4s-cypher-dsl" % "0.5.0",
"io.github.neo4s" %% "neo4s-query-support" % "0.1.2"
)
Consider 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 neo4s.cypher.dsl.syntax._
import neo4s.cypher.dsl.syntax.patterns._ //optional, import for expressing paths.
using DSL for a simple match query generation for an instance of model
// Assumes a Person case class
case class Person(dept: String, name: String, age: Int)
//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
//val res0: String = MATCH (person:Person {dept: $person_dept,name: $person_name,age: $person_age}) RETURN pers
johnDoeQuery.queryMap
// val res1: Map[String,Any] = Map(person_dept -> AX31SD, person_name -> John Doe, person_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 = DSLResult(MATCH (person:Person {name: $person_name}) RETURN person
johnDoeQuery.queryMap
//res1: Map[String,Any] = Map(person_name -> John Doe))
Note: if the property doesn't exist, compilation will fail.
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 (person:Person)
// RETURN person
result.queryMap
//res1: 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 (person:Person {id: {person_id},name: {person_name},age: {person_age}})-[isFriendOf:IS_FRIEND_OF]->(any_person:Person)<--(department:Department {id: {department_id},name: {department_name}})
// RETURN any_person
result.queryMap
//res1: Map[String,Any] = Map(person_id -> AX31SD, person_name -> John Doe, department_name -> Science, person_age -> 50, department_id -> ZSW12R)
as of v0.5.0
Cypher Clauses | DSL Support |
---|---|
MATCH | ✅ |
OPTIONAL MATCH | ✅ |
START | ❌ |
RETURN | ✅ |
WITH | ✅ |
UNWIND | ❌ |
WHERE | ✅ |
ORDER BY | ✅ |
SKIP | ✅ |
LIMIT | ✅ |
CREATE | ✅ |
DELETE | ✅ |
SET | ✅ |
REMOVE | ✅ |
FOREACH | ❌ |
MERGE | ✅ |
CALL […YIELD] | ❌ |
CREATE UNIQUE | ❌ |
UNION | ❌ |
neo4s-cyppher-dsl is maintained by the neo4s organization
Any form of contribution (issue report, PR, etc) is more than welcome.
neo4s-cyppher-dsl is forked from scala-cypher-dsl
This project is made possible by Shapeless. Special thanks to Miles Sabin