Overview
Requirements
- Scala
2.13
/3.3
- Java
17+
- Neo4j server
4+
- Neo4j driver
5+
Driver creation
neotypes provides the GraphDatabase
factory for creating a Neo4j Driver
,
which represents a connection with the Database.
You can use this Driver
to perform operations (Transactions
) over the Database.
Those Scala classes are nothing more than simple wrappers over their Java counterparts,
but providing a more “Scala-friendly” and functional API.
You can create wrappers for any effect type (F[_]
)
for which you have an implementation of the neotypes.Async
typeclass in scope.
The implementation for scala.concurrent.Future
is built-in
in the core
module (for other types, please read alternative effects).
import neotypes.GraphDatabase
import neotypes.generic.implicits._ // Allows to automatically derive an implicit ResultMapper for case classes. // Allows to automatically derive an implicit ResultMapper for case classes.
import neotypes.mappers.ResultMapper // Allows to decode query results. // Allows to decode query results.
import neotypes.syntax.all._ // Provides the query extension method. // Provides the query extension method.
import org.neo4j.driver.AuthTokens
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
final case class Movie(title: String, released: Int)
val driver =
GraphDatabase.asyncDriver[Future]("bolt://localhost:7687", AuthTokens.basic("neo4j", "****"))
val result: Future[List[Movie]] =
"""MATCH (movie: Movie)
WHERE lower(movie.title) CONTAINS "thing"
RETURN movie
""".query(ResultMapper.productDerive[Movie]).list(driver)
Please remember that, you have to make sure that the Driver
is properly closed
at the end of the application execution,
to make sure all obtained resources (such as network connections) are cleaned up properly.
Note: for other effect types instead of Future
(e.g. IO
),
the creation of the Driver
will be managed by the effect specific implementation of Resource
;
which usually ensures it is properly closed after its use.
Query execution
Once you have a Driver
instance, you can start querying the database.
The import neotypes.syntax.all._
adds an extension method query
to each string literal in its scope,
or you can use the Cypher (c
) string interpolator.
import neotypes.model.query.QueryParam
// Simple query.
"CREATE (p: Person { name: 'John', born: 1980 })"
.execute
.void(driver)
// Query with custom parameters (manual).
"CREATE (p: Person { name: $name, born: $born })"
.execute
.withParams(Map("name" -> QueryParam("John"), "born" -> QueryParam(1980)))
.void(driver)
// Query wih custom parameters (string interpolator).
val name = "John"
val born = 1980
c"CREATE (p: Person { name: ${name}, born: ${born} })"
.execute
.void(driver)
A query can be run in different ways:
execute.void(driver)
- Executes a query and discards its output.execute.resultSummary(driver)
- Executes a query and returns only itsResultSummary
.query(mapper).single(driver)
- Runs a query and returns a single result.query(mapper).list(driver)
- Runs a query and returns aList
of results.query(mapper).set(driver)
- Runs a query and returns aSet
of results.query(mapper).vector(driver)
- Runs a query and returns aVector
of results.query(mapper).map(driver)
- Runs a query and returns aMap
of results (only if the elements are tuples).query(mapper).collectAs(Col, driver)
- Runs a query and returns the supplied type of collection of results.query(mapper).stream(driver)
- Runs a query and returns aStream
of results (for more information, please read streaming).query(mapper).withResultSummary.single(driver)
- Runs a query and returns a single result plus itsResultMapper
(you may call any of the other query options afterwithResultSummary
).
import neotypes.generic.implicits._ // Allows to automatically derive an implicit ResultMapper for case classes. // Allows to automatically derive an implicit ResultMapper for case classes.
import neotypes.mappers.ResultMapper // Allows to decode query results. // Allows to decode query results.
import scala.collection.immutable.ListSet
final case class Movie(title: String, released: Int)
final case class Person(name: String, born: Int)
// Execute.
"CREATE (p: Person { name: 'Charlize Theron', born: 1975 })"
.execute
.void(driver)
// Single.
"MATCH (p: Person { name: 'Charlize Theron' }) RETURN p.name"
.query(ResultMapper.string)
.single(driver)
"MATCH (p: Person { name: '1243' }) RETURN p.born"
.query(ResultMapper.option(ResultMapper.int))
.single(driver)
"MATCH (p: Person { name: 'Charlize Theron' }) RETURN p"
.query(ResultMapper.productDerive[Person])
.single(driver)
// List.
"MATCH (p: Person { name: 'Charlize Theron' })-[]->(m: Movie) RETURN p, m"
.query(ResultMapper.tuple[Person, Movie])
.list(driver)
// Set.
"MATCH (p: Person { name: 'Charlize Theron' })-[]->(m: Movie) RETURN p, m"
.query(ResultMapper.tuple[Person, Movie])
.set(driver)
// Vector.
"MATCH (p: Person { name: 'Charlize Theron' })-[]->(m: Movie) RETURN p, m"
.query(ResultMapper.tuple[Person, Movie])
.vector(driver)
// Map.
"MATCH (p: Person { name: 'Charlize Theron' })-[]->(m: Movie) RETURN p, m"
.query(ResultMapper.tuple[Person, Movie])
.map(driver)
// Any collection.
"MATCH (p: Person { name: 'Charlize Theron' })-[]->(m: Movie) RETURN p, m"
.query(ResultMapper.tuple[Person, Movie])
.collectAs(ListSet, driver)