Redis For Kotlin
Overview
Inspired by Kotlin Exposed, this library aims to provide a type-safe DSL for interacting with Redis to Kotlin.
Redis Graph
- Construct schemas for nodes
- Create and delete nodes
- Create and delete relationships between nodes
- Perform path queries
Setup
Gradle
Step 1. Add the JitPack repository to your build.gradle
allprojects {
repositories {
/* ... */
maven { url "https://jitpack.io" }
}
}
Step 2. Add the dependency
dependencies {
implementation "com.github.mnbjhu:KotlinRedisGraph:$kotlinRedisVersion"
}
Gradle Kotlin DSL
Step 1. Add the JitPack repository to your build.gradle.kts
repositories {
mavenCentral()
maven("https://jitpack.io")
}
Step 2. Add the dependency
dependencies {
implementation("com.github.mnbjhu:KotlinRedisGraph:$kotlinRedisVersion")
}
Basic Usage
Connect To Redis
To start using Redis Graph you need to create a new instance of the RedisGraph class with a graph name, host address and an option port (default is 6379).
import api.RedisGraph
val moviesGraph = RedisGraph(
name = "movies",
host = "raspberrypi.local",
)
Define A Schema
Node types and their relationships are defined by a schema. To create a node type, create a class which:
- Extends RedisClass
- Overrides instanceName as a single constructor parameter
- Sets the typeName in the RedisClass constructor
import api.RedisClass
import api.RedisRelation
class Actor(override val instanceName: String) : RedisClass("Actor"){
val name = string("name")
val actorId = int("actor_id")
val actedIn = relates(ActedIn::class)
}
class Movie(override val instanceName: String) : RedisClass("Movie"){
val title = string("title")
val releaseYear = int("release_year")
val movieId = int("movie_id")
}
class ActedIn(from: Actor, to: Movie, override val instanceName: String):
RedisRelation<Actor, Movie>(from, to, "ACTED_IN"){
val role = string("role")
}
Attributes can be defined on both RedisClass and RedisRelation. While in either scope, you’ll have access to functions for creating instances of Attribute<T>.
Current Supported Types Are:
Type | Function |
---|---|
String | string() |
Long | int() |
Double | double() |
Boolean | boolean() |
Create Nodes
After a node type has been defined as a RedisClass, you can create a single instance like so:
import schemas.Actor
import schemas.Movie
moviesGraph.create(Movie::class) {
title["Star Wars: Episode V - The Empire Strikes Back"]
releaseYear[1980]
movieId[1]
}
Generated Cypher
CREATE (:Movie{title:'Star Wars: Episode V - The Empire Strikes Back', release_year:1980, movie_id:1})
(If an attribute is defined in the type but not set on creation, and exception will be thrown)
You can also create multiple instances by mapping elements from a list.
var index = 1L
val actors = listOf(
"Mark Hamill",
"Harrison Ford",
"Carrie Fisher"
)
moviesGraph.create(Actor::class, actors) {
name[it]
actorId[index++]
}
Generated Cypher
CREATE (:Actor{name:'Mark Hamill', actor_id:1}), (:Actor{name:'Harrison Ford', actor_id:2}), (:Actor{name:'Carrie Fisher', actor_id:3})
Query Scope
Currently all other functionality is performed with the query function which has the general structure of:
moviesGraph.query {
// Create references to varaibles and paths
//Optional
where {
// Filter queries
}
// Optional
delete( /* vararg of nodes/relationships */ )
// Optional
create {
// Create relationships between nodes
}
// Required (Can be placed in the create block)
result( /* vararg of attribute */ ){
// Transform from attribute to some generic class
}
}
Create Relationships
References to nodes can be created using the variableOf function. These refences can be used to filter the data in the where block and relationships between matching nodes can then be made using the create block.
moviesGraph.query {
val actor = variableOf<Actor>("actor")
val movie = variableOf<Movie>("movie")
where { (actor.actorId eq 1) and (movie.movieId eq 1) }
create {
val actedIn = actor.actedIn("r") { role["Luke Skywalker"] } - movie
result(actedIn.role)
}
}
Generated Cypher
MATCH (actor:Actor), (movie:Movie) WHERE (actor.actor_id = 1) AND (movie.movie_id = 1) CREATE (actor)-[r:ACTED_IN {role:'Luke Skywalker'}]->(movie) RETURN r.role
Make Queries
In this example we search for all movies and return the movie ‘title’.
val movies = moviesGraph.query {
val movie = variableOf<Movie>("movie")
result(movie.title)
}
movies `should contain` "Star Wars: Episode V - The Empire Strikes Back"
Generated Cypher
MATCH (movie:Movie) RETURN movie.title
The same however we also return the ‘releaseYear’ and the ‘movieId’. We can also map our return type to a data class to preserve the types.
data class MovieData(val title: String, val releaseYear: Long, val movieId: Long)
val (title, releaseYear, id) = moviesGraph.query {
val movie = variableOf<Movie>("movie")
result(movie.title, movie.releaseYear, movie.movieId){
MovieData(
movie.title(),
movie.releaseYear(),
movie.movieId()
)
}
}.first()
title `should be equal to` "Star Wars: Episode V - The Empire Strikes Back"
releaseYear `should be equal to` 1980
id `should be equal to` 1
Generated Cypher
MATCH (movie:Movie) RETURN movie.title, movie.release_year, movie.movie_id
Here we:
- Search for an actor and a movie where the actor acted in the movie.
- Filter by movieId = 1
- And return the actor name and movie title
val actedIn = moviesGraph.query {
val actor = variableOf<Actor>("actor")
val (movie) = actor.actedIn("relationship")
where { movie.movieId eq 1 }
result(actor.name, movie.title)
}
actedIn.size `should be equal to` 3
val (actorName, movieName) = actedIn.last()
actorName `should be equal to` "Carrie Fisher"
movieName `should be equal to` "Star Wars: Episode V - The Empire Strikes Back"
Generated Cypher
MATCH (actor:Actor)-[movieRelation:ACTED_IN]-(movie:Movie) WHERE movie.movie_id = 1 RETURN actor.name, movie.title
Delete Nodes And Relationships
Any nodes or relationships referenced in the Query block can be deleted calling them in the (vararg) delete function:
val removedRoles = moviesGraph.query {
val actor = variableOf<Actor>("actor")
val (_, relationship) = actor.actedIn("movie")
where { actor.actorId eq 1 }
delete(relationship)
result(relationship.role)
}
removedRoles.size `should be equal to` 1
removedRoles.first() `should be equal to` "Luke Skywalker"
Generated Cypher
MATCH (actor:Actor)-[movieRelation:ACTED_IN]-(movie:Movie) WHERE actor.actor_id = 1 RETURN movieRelation.role