Spring boot Neo4j - query depth not working correctly - spring-boot

TL;DR: #Depth(value = -1) throws nullpointer and other values above 1 are ignored
In my Spring Boot with Neo4j project I have 3 simple entities with relationships:
#NodeEntity
data class Metric(
#Id #GeneratedValue val id: Long = -1,
val name: String = "",
val description: String = "",
#Relationship(type = "CALCULATES")
val calculates: MutableSet<Calculable> = mutableSetOf()
) {
fun calculates(calculable: Calculus) = calculates.add(calculable)
fun calculate() = calculates.map { c -> c.calculate() }.sum()
}
interface Calculable {
fun calculate(): Double
}
#NodeEntity
data class Calculus(
#Id #GeneratedValue val id: Long = -1,
val name: String = "",
#Relationship(type = "LEFT")
var left: Calculable? = null,
#Relationship(type = "RIGHT")
var right: Calculable? = null,
var operator: Operator? = null
) : Calculable {
override fun calculate(): Double =
operator!!.apply(left!!.calculate(), right!!.calculate())
}
#NodeEntity
data class Value(
#Id #GeneratedValue val id: Long = -1,
val name: String = "",
var value: Double = 0.0
) : Calculable {
override fun calculate(): Double = value
}
enum class Operator : BinaryOperator<Double>, DoubleBinaryOperator {//not relevant}
I create a simple graph like this one:
With the following repositories:
#Repository
interface MetricRepository : Neo4jRepository<Metric, Long>{
#Depth(value = 2)
fun findByName(name: String): Metric?
}
#Repository
interface CalculusRepository : Neo4jRepository<Calculus, Long>{
fun findByName(name: String): Calculus?
}
#Repository
interface ValueRepository : Neo4jRepository<Value, Long>{
fun findByName(name: String): Value?
}
And the following code:
// calculus
val five = valueRepository.save(Value(
name = "5",
value = 5.0
))
val two = valueRepository.save(Value(
name = "2",
value = 2.0
))
val fiveTimesTwo = calculusRepository.save(Calculus(
name = "5 * 2",
operator = Operator.TIMES,
left = five,
right = two
))
println("---")
println(fiveTimesTwo)
val fromRepository = calculusRepository.findByName("5 * 2")!!
println(fromRepository) // sometimes has different id than fiveTimesTwo
println("5 * 2 = ${fromRepository.calculate()}")
println("--- \n")
// metric
val metric = metricRepository.save(Metric(
name = "Metric 1",
description = "Measures a calculus",
calculates = mutableSetOf(fromRepository)
))
metricRepository.save(metric)
println("---")
println(metric)
val metricFromRepository = metricRepository.findByName("Metric 1")!!
println(metricFromRepository) // calculates node is partially empty
println("--- \n")
To retrieve the same graph as shown in the picture above (taken from the actual neo4j dashboard), I do metricRepository.findByName("Metric 1") which has #Depth(value = 2) and then print the saved metric and the retrieved metric:
Metric(id=9, name=Metric 1, description=Measures a calculus, calculates=[Calculus(id=2, name=5 * 2, left=Value(id=18, name=5, value=5.0), right=Value(id=1, name=2, value=2.0), operator=TIMES)])
Metric(id=9, name=Metric 1, description=Measures a calculus, calculates=[Calculus(id=2, name=5 * 2, left=null, right=null, operator=TIMES)])
No matter the value of the depth, I can't get the Metric node with all his children nodes, it retrieves one level deep max and returns null on the leaf nodes.
I've read in the docs that depth=-1 retrieves the fully-resolved node but doing so causes the findByName() method to fail with a null pointer: kotlin.KotlinNullPointerException: null
Here is a list of resources I've consulted and a working GitHub repository with the full code:
GitHub Repo
Spring Data Neo4j Reference Documentation
Neo4j-OGM Docs
Final notes:
The entities all have default parameters because Kotlin then makes an empty constructor, I think the OGM needs it
I've also tried making custom queries but couldn't specify the depth value because there are different relationships and can be at different levels
To use the GitHub repository I linked you must have Neo4j installed, the repo has a stackoverflow-question branch with all the code.
Versions:
Spring boot: 2.3.0.BUILD-SNAPSHOT
spring-boot-starter-data-neo4j: 2.3.0.BUILD-SNAPSHOT
Thank you for helping and all feedback is welcomed!

The problem is not with the query depth but with the model. The Metric entity has a relation with Calculable, but Calculable itself has no relationships defined. Spring Data cannot scan all possible implementations of the Calculable interface for their relationships. If you changed Metrics.calculates type to MutableSet<Calculus>, it would work as expected.
To see Cypher requests send to the server you can add logging.level.org.neo4j.ogm.drivers.bolt=DEBUG to the application.properties
Request before the change:
MATCH (n:`Metric`) WHERE n.`name` = $`name_0` WITH n RETURN n,[ [ (n)->[r_c1:`CALCULATES`]->(x1) | [ r_c1, x1 ] ] ], ID(n) with params {name_0=Metric 1}
Request after the change:
MATCH (n:`Metric`) WHERE n.`name` = $`name_0` WITH n RETURN n,[ [ (n)->[r_c1:`CALCULATES`]->(c1:`Calculus`) | [ r_c1, c1, [ [ (c1)-[r_l2:`LEFT`]-(v2:`Value`) | [ r_l2, v2 ] ], [ (c1)-[r_r2:`RIGHT`]-(v2:`Value`) | [ r_r2, v2 ] ] ] ] ] ], ID(n) with params {name_0=Metric 1}

Related

Kotlin MVVM, How to get the latest value from Entity in ViewModel?

I have created an app where I try to insert a record with the latest order number increased by one.
The main function is triggered from Activity, however, the whole process is in my ViewModel.
Issue no 1, After I insert a new record the order by number is not updated.
Issue no 2, When I insert first record the order by number is null, for that reason I am checking for null and setting the value to 0.
My goal here is to get the latest order_by number from Entity in my ViewModel, increased by 1 and add that new number to my new record using fun addTestData(..).
Entity:
#Entity(tableName = "word_table")
data class Word(
#ColumnInfo(name = "id") val id: Int,
#ColumnInfo(name = "word") val word: String,
#ColumnInfo(name = "order_by") val orderBy: Int
Dao:
#Query("SELECT order_by FROM word_table ORDER BY order_by DESC LIMIT 1")
suspend fun getHighestOrderId(): Int
Repository:
#Suppress("RedundantSuspendModifier")
#WorkerThread
suspend fun getHighestOrderId(): Int {
return wordDao.getHighestOrderId()
}
ViewModel:
private var _highestOrderId = MutableLiveData<Int>()
val highestOrderId: LiveData<Int> = _highestOrderId
fun getHighestOrderId() = viewModelScope.launch {
val highestOrderId = repository.getHighestOrderId()
_highestOrderId.postValue(highestOrderId)
}
fun addTestData(text: String) {
for (i in 0..1500) {
getHighestOrderId()
var highestNo = 0
val highestOrderId = highestOrderId.value
if (highestOrderId == null) {
highestNo = 0
} else {
highestNo = highestOrderId
}
val addNumber = highestNo + 1
val word2 = Word(0, text + "_" + addNumber,addNumber)
insertWord(word2)
}
}
Activity:
wordViewModel.addTestData(text)

Room - RxJava : getByName() query not working

I'm using Room for persistence in my app. I have a Player table, which has a name and matchesPlayed column.
When a match is played by a Player, I would like to increment matchesPlayed if it was played by an existing Player, or create a new Player otherwise. In order to determine this, I created a SQL query getPlayerByName(name: String) which should return us the Player if it exists. When I run the query in the Database Inspector in Android Studio, everything works fine. However, when I run the same query in the app, it fails. Does anybody know what I'm doing wrong?
Player.kt
#Entity(tableName = "players")
data class Player(
#PrimaryKey val id: String,
val name: String,
#ColumnInfo(name = "matches_played") val matchesPlayed: String,
#ColumnInfo(name = "matches_won") val matchesWon: String
)
PlayerDao.kt
#Dao
interface PlayerDao {
#Query("SELECT * FROM $PLAYER_TABLE_NAME")
fun getAll(): Single<List<Player>>
#Query("SELECT * FROM $PLAYER_TABLE_NAME WHERE name = :name LIMIT 1")
fun getPlayerByName(name: String): Maybe<Player>
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(player: Player): Completable
}
PlayerViewModel.kt
#HiltViewModel
class PlayerViewModel #Inject constructor(
private val playerRepository: PlayerRepositoryImpl
) : ViewModel() {
...
private fun findAndInsertPlayer(
playerName: String,
playerWon: Boolean
) {
viewModelScope.launch {
val increment = if (playerWon) 1 else 0
playerRepository.getPlayerByName(playerName)
.subscribe(
{ player ->
// Handle Success
},
{
// Somehow, this block always gets called
}
)
}
)
}

Gradle Spring mvc validation data class recursive Kotlin

I need to create a recursive validation to be able to validate PizzaCreate Object and base attribute in recursive mode using Kotlin. The test should return 400 but it returns 200 ok (Base name size must be greater than 2):
data class Base(#field:Length(min = 2, max = 100) val name:String)
data class PizzaCreate(
val id: Long,
#field:Length(min = 2, max = 100) val name: String,
val description: String,
val price: Int,
#Valid val base: Base
)
#RestController
#RequestMapping("/pizza")
class PizzaController(val pizzaService: PizzaService) {
#PostMapping
fun post(#RequestBody #Valid pizza: PizzaCreate) = pizzaService.addPizza(pizza)
}
#Test
fun `should add pizza `() {
val pizza = easyRandom.nextObject(PizzaCreate::class.java).copy(id = 1, name="aaa", base = Base(""))
val pizzaOut = PizzaOut(id=1,name=pizza.name,description = pizza.description,price = pizza.price)
`when`(pizzaService.addPizza(pizza)).thenReturn(pizzaOut.toMono())
webTestClient.post()
.uri("/pizza")
.bodyValue(pizza)
.exchange()
.expectStatus().isBadRequest
.returnResult<PizzaOut>().responseBody
}
Validation on Base should be #field:Valid val base: Base instead of #Valid val base: Base
field: specifies the annotation is applied on the field not construc
ref:
https://stackoverflow.com/a/35853200
https://stackoverflow.com/a/36521309

Spring - Springboot controller GET requests with default parameters

I have a Springboot controller with two GET endpoints:
#RestController
#RequestMapping("/foo")
class MyController {
fun getFoo(
#RequestParam("x", required = false) x: Int = 0
) = ...
fun getFoo(
#RequestParam("x", required = true) x: Int,
#RequestParam("y", required = true) y: Int
) = ...
The behaviour I want is that when called:
/foo calls the first endpoint with an optional x param.
/foo?x=123 calls the first endpoint with a supplied x param.
/foo?x=123&y=456' calls the second endpoint with the supplied xandy` params.
Currently I get an error:
{
"timestamp": "2020-07-20T13:11:24.732+0000",
"status": 400,
"error": "Bad Request",
"message": "Parameter conditions \"x\" OR \"x, y\" not met for actual request parameters: ",
"path": "/foo"
}
Any ideas how to determine a default endpoint when zero params are specified?
Set a params in #RequestMapping or its variants(#GetMapping, #PostMapping etc).
eg.
#GetMapping(params=arrayOf("!x", "!y"))
fun getFoo()
#GetMapping(params=arrayOf("x", "!y"))
fun getFoo(
#RequestParam("x", required = true) x: Int = 0
#GetMapping(params=arrayOf("x", "y"))
fun getFoo(
#RequestParam("x", required = true) x: Int,
#RequestParam("y", required = true) y: Int
Different params can be applied and identified on the same URI.
You can specify a defaultValue as a String in #RequestParam:
fun getFoo(
#RequestParam(name = "x", required = false, defaultValue = "0") x: Int,
#RequestParam(name = "y", required = false, defaultValue = "1") y: Int
) =
Spring will convert the String to whatever type you really want (Int in your case) using the same method as if you specified x as a parameter (same coercion, errors, etc).
If you want more then one mapping to the same url (lot of times it is a codesmell, but sometime it is necessary, you can do i twith filter params in #GetMapping or #RequestMapping annotations' params element.
#RestController
#RequestMapping("/foo")
class MyController {
#GetMapping(params = ["!y"])
fun getFoo(
#RequestParam("x", required = false) x: Int?
) = ...
#GetMapping(params = ["x", "y"])
fun getFoo(
#RequestParam("x", required = true) x: Int,
#RequestParam("y", required = true) y: Int
) = ...
}

How do fetch the state with custome query? Corda application using Spring boot webserver- error while fetching the result

I have created the IOU in corda applicatiion, the IOU has ID,xml payload in body, partyName. NOW, i want to fetch the state with custome query that is basis on ID. NOTE- i am not using linearID.
Below is my API call- which gives me syntax error on. Can someone please correct me, what is the wrong thing that i am doing.
#GetMapping(value = ["getIous"],produces = [ MediaType.APPLICATION_JSON_VALUE])
private fun getTransactionOne(#RequestParam(value = "payloadId") payloadId: String): ResponseEntity<List<IOUState>> {
val generalCriteria = QueryCriteria.VaultQueryCriteria(Vault.StateStatus.ALL)
val results = builder { IOUState::iouId.equal(payloadId)
val customCriteria = QueryCriteria.VaultCustomQueryCriteria(results)}
val criteria = customCriteria.and(customCriteria)
val res = proxy.vaultQueryBy<IOUState>(criteria)
return ResponseEntity.ok(res)
}
I think the issue is because VaultCustomQueryCriteria is applicable only to StatePersistable objects. So you should use PersistentIOU instead of IOUState. Also, I could see incorrect use of brackets. Here is how your code should look like:
#GetMapping(value = ["getIous"],produces = [ MediaType.APPLICATION_JSON_VALUE])
private fun getTransactionOne(#RequestParam(value = "payloadId") payloadId: String): ResponseEntity<List<IOUState>> {
val generalCriteria = QueryCriteria.VaultQueryCriteria(Vault.StateStatus.ALL)
val results = builder {
val idx = IOUSchemaV1.PersistentIOU::iouId.equal(payloadId);
val customCriteria = QueryCriteria.VaultCustomQueryCriteria(idx)
val criteria = generalCriteria.and(customCriteria)
proxy.vaultQueryBy<IOUState>(criteria);
}
return ResponseEntity.ok(results)
}

Resources