Giter Club home page Giter Club logo

neo4k's Introduction

alt text

tests stability-alpha

Inspired by Kotlin Exposed, this library aims to provide a type-safe cypher DSL for interacting with Neo4j.

Features

  • User defined graph schemas
  • Cypher CRUD operations
  • Allows you to construct complex queries
  • Returned data is automatically deserialized into the implied type

Example

val findBestRatedMovies = query {
    val (movie, review) = match(::Movie `←-o` ::Reviewed `←-o` ::User)
    many(movie.title, avg(review.rating), count(review))
}.with { (title, averageRating, numberOfReviews) ->
    where(numberOfReviews greaterThan 100)
    orderByDesc(averageRating)
    limit(25)
    many(title, averageRating)
}.build()
graph.findBestRatedMovies().forEach { println(it) /* it: Pair<String, Double> */ }

Setup

You will need a Neo4j database setup. For instructions on installing Neo4j desktop see here.

Note: Some features will only be available for the enterprise edition.

Gradle

Step 1. Add the JitPack repository to your build file

allprojects {
    repositories {
        ...
        maven { url 'https://jitpack.io' }
    }
}

Step 2. Add the dependency

dependencies {
    implementation 'com.github.mnbjhu:neo4k:0.0.2-alpha'
}

Gradle Kotlin DSL

Step 1. Add the JitPack repository to your build file

allprojects {
    repositories {
        ...
        maven("https://jitpack.io")
    }
}

Step 2. Add the dependency

dependencies {
    implementation("com.github.mnbjhu:neo4k:0.0.2-alpha")
}

Connect to Neo4j

To connect to your database, you create an instance of 'Graph':

val graph = Graph(
    name = "neo4j",
    username = "Username",
    password = "Password123!",
    host = "localhost"
)

Build a schema

To take advantage of Kotlin's type safety, neo4k requires you to build a schema.

You can define nodes by extending either the UnitNode or the Node class and by defining its attributes as values in the primary constructor. These values should all be instances of ReturnValue:

Nodes

UnitNode

class Movie(
    val title: StringReturn,
    val releaseYear: LongReturn
): UnitNode()

Node<T>

A UnitNode is just a node which can't be returned directly from a query.

data class MovieData(val title: String, val releaseYear: Long)

class Movie( 
    val title: StringReturn,
    val releaseYear: LongReturn 
): Node<MovieData>(){ 
    override fun ReturnScope.decode() = MovieData( 
        ::title.result(), 
        ::releaseYear.result()
    )
}

Currently supported types are:

Name Class Type Descriptor Return Type
Long LongReturn ::LongReturn kotlin.Long
Boolean BooleanReturn ::BooleanReturn kotlin.Boolean
String StringReturn ::StringReturn kotlin.String
Double DoubleReturn ::DoubleReturn kotlin.Double
Nullable NullableReturn<T, U: ReturnValue<T>> nullable{ /* Inner */ } T?
Arrays ArrayReturn<T, U: ReturnValue<T>> array{ /* Inner */ } List<T>
Structs StructReturn<T, U: ReturnValue<T>> ::MyStruct (see StructReturn) T

Relationships

You can create relationships by extending the DirectionalRelationship or NonDirectionalRelationship classes or their Unit equivalent.

Directional Relationships

Directional relationships take two type arguments: the 'from' type and the 'to' type.

class ActedIn(val role: StringReturn): UnitDirectionalRelationship<Actor, Movie>()

Non-Directional Relationships

On the other hand non-directional only require one type argument

class FriendsWith: UnitNonDirectionalRelationship<User>()

Construct and execute queries

Paths

Once you have built a schema it can be used to query your graph. You can describe your nodes and relationships using their constructor reference:

With no constraints: ::Movie,::ActedIn

With constrains:

import uk.gibby.neo4k.core.invoke // THIS WILL NOT IMPORT AUTOMATICALLY

::Movie{ it[title] = "Pulp Fiction" }

You can then use the arrow functions: `o-→`, `←-o` `-o-` ('o' was added for autocomplete) to describe paths:

::Actor `o-→` ::ActedIn `o-→` ::Movie{ it[title] = "Pulp Fiction" }

Path are currently supported upto a length of ten (not including ranged relation matches).

Queries

QueryScope

Queries are written within a lambda with the QueryScope class as the receiver. This provides you with a set of functions which correspond to the clauses from neo4j. The currently supported clauses are:

  • CREATE
  • DELETE
  • LIMIT
  • MATCH
  • ORDER BY
  • SET
  • SKIP
  • UNWIND
  • WHERE
  • WITH (See below)

Queries can either be executed directly by calling the 'query' function:

graph.query { // this: QueryScope
    val movie = create(::Movie{ it[title] = "Star Wars: Episode V - The Empire Strikes Back"; it[releaseYear] = 1980 })
    create(::Actor{ it[name] = "Mark Hamill" } `o-→` ::ActedIn{ it[role] = "Luke Skywalker" } `o-→` movie)
    create(::Actor{ it[name] = "Carrie Fisher" } `o-→` ::ActedIn{ it[role] = "Princess Leia" } `o-→` movie)
    create(::Actor{ it[name] = "Harrison Ford" } `o-→` ::ActedIn{ it[role] = "Han Solo" } `o-→` movie)
}

or by first constructing a query and then executing it on the graph:

val findActorsInMovie = query(::StringReturn) { searchString -> 
    val (actor, _, movie) = match(::Actor `o-→` ::ActedIn `o-→` ::Movie)
    where(movie.name contains searchString)
    actor.name
}.build()
val actorNames = graph.findActorsInMovie("Star Wars") 

The latter creates the query string and the relevant serializers when 'build' is called: making the query much more efficient. In this example we have a parameter of 'searchString'. Parameterizing queries (rather than constructing them at execution time) makes it easier for neo4j to cache the execution plan, which also makes it faster 🔥.

val findBestRatedMovies = query {
    val (movie, review) = match(::Movie `←-o` ::Reviewed `←-o` ::User)
    many(movie.title, avg(review.rating), count(review))
}.with { (title, averageRating, numberOfReviews) ->
    where(numberOfReviews greaterThan 100)
    orderByDesc(averageRating)
    limit(25)
    many(title, averageRating)
}.build()
graph.findBestRatedMovies() // List<Pair<String, Double>>

You can chain queries together using the 'with' function which passes on the return of the previous query.

neo4k's People

Contributors

mnbjhu avatar

Stargazers

 avatar Gabriel avatar Norbert Sándor avatar  avatar

Watchers

 avatar

neo4k's Issues

Support for null in functions

Currently functions only support the NotNull type.
Most functions in neo4j have the property f(null) = null and are null otherwise.
For this reason I think it makes sense to change most of the functions to have two signatures (probably implemented as an object with two 'invoke' functions)
These would be:
(A) -> B
(A?) -> B?
An example would be adding two longs, the function signatures would be:
operator fun LongReturn.plus(other: LongReturn): LongReturn // Current
operator fun Nullable<Long, LongReturn>.plus(other: LongReturn): Nullable<Long, LongReturn>
operator fun LongReturn.plus(other: Nullable<Long, LongReturn>): Nullable<Long, LongReturn>
operator fun Nullable<Long, LongReturn>.plus(other: Nullable<Long, LongReturn>): Nullable<Long, LongReturn>

I think this would both let the user take advantage of null safety when not using null types as well has not slowing people down by haivng to handle null types

Support for labels

I have some ideas for what this should look like. My initial thoughts are that labels should be interfaces and guarantee certain fields are present on a node.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.