Parameterized Queries
neotypes leverages StringContext for interpolating query parameters.
And uses the ParameterMapper
typeclass to ensure type-safety.
Which can be used like this:
import neotypes.syntax.all._ // Adds the `c` interpolator into the scope. // Adds the `c` interpolator into the scope.
val name = "John"
// name: String = "John"
val query = c"CREATE (a: Test { name: ${name} })"
// query: neotypes.query.DeferredQueryBuilder = neotypes.query.DeferredQueryBuilder@5c1dad76
query.execute
// res1: neotypes.query.ExecuteQuery = ExecuteQuery:
// query:
// CREATE (a: Test { name: $p1 })
// params:
// p1: John
All parameters will be converted toNeo4j supported types (please see Supported types).
The c
interpolator creates a DeferredQueryBuilder
which is an immutable representation of a Cyypher query.
You can concatenate DeferredQueryBuilder
s with other DeferredQueryBuilder
s or String
s, to build complex queries.
import neotypes.syntax.all._ // Adds the `c` interpolator into the scope. // Adds the `c` interpolator into the scope.
val name = "John"
// name: String = "John"
val born = 1980
// born: Int = 1980
val LABEL = "User"
// LABEL: String = "User"
val query =
c"CREATE (a: " +
LABEL +
c"{ name: $name }, " +
c"born: ${born} })"
// query: neotypes.query.DeferredQueryBuilder = neotypes.query.DeferredQueryBuilder@7f2a9f09
query.execute
// res3: neotypes.query.ExecuteQuery = ExecuteQuery:
// query:
// CREATE (a: User { name: $p1 }, born: $p2 })
// params:
// p1: John
// p2: 1980
Alternatively, you can also have plain interpolation by using #$
instead of $
import neotypes.syntax.all._ // Adds the `c` interpolator into the scope. // Adds the `c` interpolator into the scope.
val name = "John"
// name: String = "John"
val LABEL = "User"
// LABEL: String = "User"
val query = c"CREATE (a: #${LABEL} { name: ${name} })"
// query: neotypes.query.DeferredQueryBuilder = neotypes.query.DeferredQueryBuilder@538e8977
query.execute
// res5: neotypes.query.ExecuteQuery = ExecuteQuery:
// query:
// CREATE (a: User { name: $p1 })
// params:
// p1: John
You may also use triple quotes to split the query in multiple lines:
import neotypes.syntax.all._ // Adds the `c` interpolator into the scope. // Adds the `c` interpolator into the scope.
val name = "John"
// name: String = "John"
val born = 1980
// born: Int = 1980
val LABEL = "User"
// LABEL: String = "User"
val query =
c"""CREATE (a: #${LABEL} {
name: ${name},
born: ${born}
})
"""
// query: neotypes.query.DeferredQueryBuilder = neotypes.query.DeferredQueryBuilder@62c9e86d
query.execute
// res7: neotypes.query.ExecuteQuery = ExecuteQuery:
// query:
// CREATE (a: User {
// name: $p1,
// born: $p2
// })
//
// params:
// p1: John
// p2: 1980
A case class can be used directly in the interpolation:
import neotypes.generic.implicits._ // Provides automatic derivation of ParameterMapper for any case class. // Provides automatic derivation of ParameterMapper for any case class.
import neotypes.syntax.all._ // Adds the `c` interpolator into the scope. // Adds the `c` interpolator into the scope.
final case class User(name: String, born: Int)
final case class Cat(tag: String)
final case class HasCat(since: Int)
val user = User("John", 1980)
// user: User = User(name = "John", born = 1980)
val cat = Cat("Waffles")
// cat: Cat = Cat(tag = "Waffles")
val hasCat = HasCat(2010)
// hasCat: HasCat = HasCat(since = 2010)
val query = c"CREATE (u: User { $user })-[r: HAS_CAT { $hasCat }]->(c: Cat { $cat })"
// query: neotypes.query.DeferredQueryBuilder = neotypes.query.DeferredQueryBuilder@2459d1c
query.execute
// res9: neotypes.query.ExecuteQuery = ExecuteQuery:
// query:
// CREATE (u: User { name: $p1, born: $p2 })-[r: HAS_CAT { since: $p3 }]->(c: Cat { tag: $p4 })
// params:
// p1: John
// p2: 1980
// p3: 2010
// p4: Waffles
A DeferredQueryBuilder
can also be interpolated inside another one:
import neotypes.syntax.all._ // Adds the `c` interpolator into the scope. // Adds the `c` interpolator into the scope.
// Two sub-queries.
val subQuery1Param = 1
// subQuery1Param: Int = 1
val subQuery1 = c"user.id = ${subQuery1Param}"
// subQuery1: neotypes.query.DeferredQueryBuilder = neotypes.query.DeferredQueryBuilder@63156079
val subQuery2Param = "Luis"
// subQuery2Param: String = "Luis"
val subQuery2 = c"user.name = ${subQuery2Param}"
// subQuery2: neotypes.query.DeferredQueryBuilder = neotypes.query.DeferredQueryBuilder@2fdafdc6
val query = c"MATCH (user: User) WHERE ${subQuery1} OR ${subQuery2} RETURN user"
// query: neotypes.query.DeferredQueryBuilder = neotypes.query.DeferredQueryBuilder@1e730e36
query.execute
// res11: neotypes.query.ExecuteQuery = ExecuteQuery:
// query:
// MATCH (user: User) WHERE user.id = $q1_p1 OR user.name = $q2_p1 RETURN user
// params:
// q1_p1: 1
// q2_p1: Luis
You may also interpolate heterogeneous Lists
and Maps
(of supported types):
import neotypes.syntax.all._ // Adds the `c` interpolator into the scope. // Adds the `c` interpolator into the scope.
import neotypes.model.query.QueryParam
val hlist = List(
QueryParam(1),
QueryParam("Luis"),
QueryParam(true)
)
// hlist: List[QueryParam] = List(1, "Luis", true)
val hmap = Map(
"foo" -> QueryParam(3),
"bar" -> QueryParam("Balmung"),
"baz" -> QueryParam(false)
)
// hmap: Map[String, QueryParam] = Map(
// "foo" -> 3,
// "bar" -> "Balmung",
// "baz" -> false
// )
// Unwind the list to create many nodes.
val query1 = c"UNWIND ${hlist} AS x CREATE (: Node { data: x } )"
// query1: neotypes.query.DeferredQueryBuilder = neotypes.query.DeferredQueryBuilder@1aa658d5
query1.execute
// res13: neotypes.query.ExecuteQuery = ExecuteQuery:
// query:
// UNWIND $p1 AS x CREATE (: Node { data: x } )
// params:
// p1: scala.collection.convert.JavaCollectionWrappers$IteratorWrapper@182bfb25
// Use the map as the properties of a Node.
val query2 = c"CREATE (: Node ${hmap} )"
// query2: neotypes.query.DeferredQueryBuilder = neotypes.query.DeferredQueryBuilder@44db7d8d
query2.execute
// res14: neotypes.query.ExecuteQuery = ExecuteQuery:
// query:
// CREATE (: Node $p1 )
// params:
// p1: {foo=3, bar=Balmung, baz=false}