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@2f5298eb
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 DeferredQueryBuilders with other DeferredQueryBuilders or Strings, 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@61206af9
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@7b4241dc
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@40ec1b9f
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@54cf0c24
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@2ce04bfc
val subQuery2Param = "Luis"
// subQuery2Param: String = "Luis"
val subQuery2 = c"user.name = ${subQuery2Param}"
// subQuery2: neotypes.query.DeferredQueryBuilder = neotypes.query.DeferredQueryBuilder@53cafa4a

val query = c"MATCH (user: User) WHERE ${subQuery1} OR ${subQuery2} RETURN user"
// query: neotypes.query.DeferredQueryBuilder = neotypes.query.DeferredQueryBuilder@2fc05b50
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@1032f079
query1.execute
// res13: neotypes.query.ExecuteQuery = ExecuteQuery:
// query:
// UNWIND $p1 AS x CREATE (: Node { data: x } )
// params:
// 	p1: scala.collection.convert.JavaCollectionWrappers$IteratorWrapper@2017d81

// Use the map as the properties of a Node.
val query2 = c"CREATE (: Node ${hmap} )"
// query2: neotypes.query.DeferredQueryBuilder = neotypes.query.DeferredQueryBuilder@4a4a6849
query2.execute
// res14: neotypes.query.ExecuteQuery = ExecuteQuery:
// query:
// CREATE (: Node $p1 )
// params:
// 	p1: {foo=3, bar=Balmung, baz=false}