A DSL for building Neo4j Cypher queries in Scala. This library is in use in production but does not yet cover the full spec of the Cypher query language.
To use Scalypher in an SBT project, you can use the dependsOn method in build.sbt:
val ScalypherVersion = "0.0.9"
lazy val root: Project = Project("root", file(".")).dependsOn(
ProjectRef(uri("git://github.com/Originate/scalypher.git#v" + ScalypherVersion), "scalypher")
)Scalypher is designed to look similar to Cypher queries:
val startNode = AnyNode()
val cypher = startNode --> AnyNode() where (
startNode.property("name") <> "matt"
) returns startNode
cypher.toQuery
// returns: 'MATCH (a1)-->() WHERE a1.name <> "matt" RETURN a1'The main thing to note is that, if you would like to specify a node or relationship in a WHERE or
RETURN expression, you need to retain a reference to it and use that when building your Cypher expressions. This
eliminates the need to manually choose identifiers for your nodes/relationships/paths.
If you'd like to reference the path from your match expression in your where expression, pass a
Path => Where to the where method
val cypher = startNode --> AnyNode() where { path =>
...
} returns startNodeWhen building out your persistence layer, it is likely you will need to know the identifier used in your return expressions. This can be obtained with:
cypher.getReturnColumnsNote that when your query's action is DELETE, there are no return columns, and when it is RETURN *,
you'll get back an identifier for each node, relationship, and one identifier for the path.
Objects passed in your return clauses can be aliased to any name you want. For example
val cypher = startNode --> AnyNode() where (
startNode.property("name") <> "matt"
) returns (startNode as "startNode", startNode.property("name") as "name")
cypher.getReturnColumnes
// returns: Set("startNode", "name")Properties of a node/relationship can be checked against other properties or values
val cypher = startNode where (startNode.property("name") === "matt") returns startNode
cypher.toQuery
// returns: 'MATCH (a1) WHERE a1.name = "matt" RETURN a1'Simple conditions in Scalypher can be created using references to nodes, relationships, and paths combined
with the operators found in Cypher (with the exception of equals, which uses ===).
val node = AnyNode()
node.property("name") === "matt"
node.property("name") <> "matt"
node.property("age") > 12
node.property("age") < 12
node.property("age") >= 12
node.property("age") <= 12
node.property("name") in Seq("matt", "andy")Conditions can be chained together with and and or
val node = AnyNode()
(node.property("name") === "matt") and
(node.property("age") > 12)Predicates are used to assert conditions on a collection of elements. You can create predicate conditions in your query like this
import com.originate.scalypher.where.All
val cypher = startNode -> AnyNode() where { path =>
All nodesIn path where { node =>
node.property("name") <> "matt"
}
} returns startNode
cypher.toQuery
// returns: MATCH a2 = (a1)-->() WHERE ALL (x IN NODES(a2) WHERE x.name <> "matt") RETURN a1Note that you can use the singular version as well Any nodeIn path where { node => ... }
Since Scalypher doesn't cover the complete language, we have added a way to build custom expressions while maintaining the appropriate node/relationship/path identifiers.
import com.originate.scalypher.where.Expression
val cypher = startNode -> endNode where (
Expression("id(?) <> ?", endNode, 10)
) returns startNode
cypher.toQuery
// returns: MATCH (a1)-->(a2) WHERE id(a2) <> 10 RETURN a1Scalypher uses a CypherExpressible typeclass in order to allow extending the DSL to handle any type you
want to pass to it. Here's an example of how you could use org.joda.time.Instant as a value reference
in your project.
import org.joda.time.Instant
import com.originate.scalypher.CypherExpressible
object CypherExpressibles {
implicit object CypherExpressibleInstant extends CypherExpressible[Instant] {
def toQuery(instant: Instant): String =
wrapString(instant.toString)
}
}This can then be used in your code:
import CypherExpressibles._
val cypher = startNode --> AnyNode() where (
startNode.property("createdAt") === Instant.now
) returns startNode
Note that the CypherExpressible trait provides the helper methods safeWrapString and wrapString to inject
string literals into your Cypher query (safeWrapString escapes quotes).
Clone the repo, then:
sbt testContributions are welcome... submit pull requests!