SDN/RX Cypher tips
Tags: neo4j, sdnrx, spring, kotlin
Published: 2020-05-19 13:45:57

I spent some time working on a project using Neo4j and Spring with Kotlin. I decided to go with WebFlux as it's reactive/all that non-blocking stuff. Now as SDN/RX is fairly new (out of beta since April 2020 I think) there has been some confusion, given that I used OGM for previous stuff I unintentionally tried to combine two approaches and it didn't work as expected. Trying to adjust to new framework took a bit and I realised some features were not only changed but missing completely.

##Embedded objects (like in OGM) problem
Embedded objects at the time of writting are not yet supported as in OGM. This means that nesting object in another one inside of the code will produce relationship rather than the embedded object. For status update follow [this GitHub issue](https://github.com/neo4j/sdn-rx/issues/213)
##One to one implicit relationship
As stated in previous point, if you have complex/custom types for properties in your `class`, those will be resolved as Node to Node relatioship.
Having embedded objects represented as relationships can make custom queries a bit hard if your going with annotation approach. So let's say you have
```kotlin
data class LanguageStuff(
    @Id @GeneratedValue val id: Long,
    val en: String,
    val ba: String
)
data class Stuff(
   @Id @GeneratedValue val id: Long,
   val serial: String,
   //This one will be automatically generated as related node
   val description: LanguageStuff,
   //and this one
   val name: LanguageStuff
)
```
Even though there's no `@Relationship` attribute the _description_ property will be generated as separate Node in Neo4j and therefor, will be fetched automatically each time we use common methods of querying data like letting spring resolve method name and try to generate query or generating Cypher with tools provided by SDN/RX as Cypher DSL which you can read more about [here](https://neo4j.github.io/sdn-rx/current/#cypher-dsl).

##Custom queries using annotations
This all works well when you only need default behaviour. If you need annotated method with query including custom Cypher it gets a bit tricky to spot some things.

I did put `name` property intentionally using the same type as description. This is due to missing detailed note about caching and how objects are resolved by SDN/RX. Given repository:
```kotlin
interface Stuffpository : ReactiveNeo4jRepository<Stuff,Long> {
    @Query("MATCH (s:Stuff) RETURN s")
    fun customCypherStuff()
}
```
You might forget about the fact that Kotlin needs `?` if things can be nullable. And there you go with the exception of `could not be mapped`. If you need those values you would want to fetch `name` and `description` represented with `LanguageStuff` data class. If your lazy enough like me you would try something like `@Query("MATCH p=(s:Stuff)-[*]->(:LanguageStuff) RETURN p")` and of course this doesn't work because it would be too much magic to resolve paths, but it's fun to break stuff and boring to read the docs.

##Cypher "spread" operator and join properties
But if you think a bit harder you might end up with something like:
```cypher
MATCH (s:Stuff)
MATCH (s)-[*]->(desc:LanguageStuff)
MATCH (s)-[*]->(name:LanguageStuff)
RETURN s{.*,description:desc,name:name}
```
inside your `@Query` value. If you're reading this by accident or you are new to this and don't know much about Cypher, might be a good note to point out that `s{.*}` is something like spread operator `.*` where each property/attribute from given `s` will be put to new object `{}`. As `s` only contains `id` and `serial` as real properties and `name` and `description` are related nodes, only first two will be _spread_ from main node. Second two are explicitly set. __Again this doesn't get the result__

##SDN/RX "hidden" naming
Graphs are not tables. There's no "real" one to one relationship so mapping nodes to data class fails. Then we can use arrays which solves problem up to some point
```cypher
MATCH (s:Stuff)
MATCH (s)-[*]->(desc:LanguageStuff)
MATCH (s)-[*]->(name:LanguageStuff)
RETURN s{.*,Stuff_DESCRIPTION_LanguageStuff:[desc],Stuff_NAME_LanguageStuff:[name]}
```
Why the ugly names? SDN/RX by default maps relationships in naming format of `ClassName_CAPITAL LETTERS OF PROPPERTY NAME IF RELATIONSHIP NAME IS NOT SET_PropertyClassName`. So SDN/RX actually maps the relationships and generates special names for them for embedded objects (__for now!__). And therefor, queries need to fetch array named with relationship type where SDN/RX kicks in and resolves where to map what __BUT we face yet another problem__!

##SDN/RX auto generated ID problem
You get things mapped but you will notice that __both `name` and the `description` have the same value__. 
My best guess is that SDN/RX does some caching by type and both properties are the same type, but also, both properties have ID generated in default manner as a Long. This means that ID is not stored as separate/special property in the Neo4j Node but you need to actually resolve it by using `id(node)`. This may be done in the background for automated way of generating queries but when you write a custom one you need to remember to know how does the mapper work since you haven't put in any details like relationship name or such.
I'm unaware am I right about "caching" where it could be that it's using type aware Map but it does ignore rest of the objects of the same type. The main problem is that Id is not being fetched so we end up with:
```cypher
MATCH (s:Stuff)
MATCH (s)-[*]->(desc:LanguageStuff)
MATCH (s)-[*]->(name:LanguageStuff)
RETURN s{.*,Stuff_DESCRIPTION_LanguageStuff:[desc{.en,.ba,__internalNeo4jId__: id(desc)}],Stuff_NAME_LanguageStuff:[name{.en,.ba,__internalNeo4jId__:id(name)}]}
```
`__internalNeo4jId__` is name to use for IDs generated and used by Neo4j to tell SDN/RX to actually map it. This way SDN/RX finally maps correct values to correct places.


As said, I'm unaware how does wrongful mapping of different objects of the same type happens but it does for SDN/RX 1.0.1 version when using generated IDs by default method. I guess using custom generator will prevent this as ID would be stored as separate attributed and then mapped back again automatically when doing object graph mapping.

That's it for now