spring data neo4j 6 #Query #Param can't access 2nd level children - neo4j

I've a single object in the #Param to be used in #Query whereas the 1st level fields can be accessed without issues, but not the 2nd level. Unable to access fields from Language object
Consider:
{
"id": 0,
"description": "some text",
"language": {
"name": "English"
}
}
#RelationshipProperties
#Data
public class KnowsEntity {
#Id
#GeneratedValue()
private Long id;
#Property
private String description;
#TargetNode
private Language language;
}
#Query("MATCH (y:PERSON {nid: $from})-[r:KNOWS]->(e:LANGUAGE {name: $rel.__language__.__name__ }) \n" +
"WHERE ID(r) = $rel. id SET r.description = $rel.__description__ \n" +
"return y, collect(r), collect(e)")
UpdatedLanguageProjection updateRel(#Param("from") String from, #Param("rel") KnowsEntity relation);
form the above query i could access the first level field $rel.__description__ and not the field inside first level object $rel.__language__.__name__ (2nd level)
can someone please correct / suggest me an option to get this?

I got this worked with the following SpEL way
https://docs.spring.io/spring-data/neo4j/docs/6.1.1/reference/html/#custom-queries.spel
eg: {name: :#{#rel.language.name}}

Related

SDN5 OGM Neo4j and composite entity

In my Spring Boot, Neo4j, SDN 5 OGM project I have the following composite entity:
#NodeEntity
public class User extends BaseEntity {
private static final String REFERRED_BY = "REFERRED_BY";
#Relationship(type = REFERRED_BY, direction = Relationship.OUTGOING)
private User referrerUser;
}
As you may see, one user may be referred by another.
Now, I'd like to receive a paginated result:
#Query(value = "MATCH (u:User) " +
"OPTIONAL MATCH (u)-[ruru:REFERRED_BY]->(ru:User) " +
"RETURN u, ruru, ru",
countQuery = "MATCH (u:User) " +
"OPTIONAL MATCH (u)-[ruru:REFERRED_BY]->(ru:User) " +
"RETURN count(u)")
Page<User> findUsers(Pageable pageable);
I request 0-50 users. But in the real data - one of them are referred by another:
Pageable pageable = PageRequest.of(0, 50, Sort.by(Sort.Direction.DESC, "u.createdAt"));
Page<User> userPage = userRepository.findUsers(pageable);
I expect not more than 50 items but as the result I receive 51 users in the Page.getContent()...
Is this possible to hint somehow to no include User.referrerUser into the Page.content collection and just leave it as another user entity property (reference)?
What am I doing wrong and hot to fix it?
I solved the issue via #QueryResult

RestAssured assertThat, wildcard for key

I have an object like this,
{
"john": {
"number": "123"
},
"sarah": {
"number": "123"
}
}
It is an object where a persons name is the key for an object, like a map.
In restAssured how can I test for a ValidatableResponse that any number whether it belongs to john or sarah matches a certain value. In this case I know there will only be one person but an "any" matcher seems appepriate here.
I have tried assertThat().body("*.number"), Matchers.is("myValue)" but it does not work and gives an error
Your problem is extracting value from a json with dynamic key (the person name, in this case). There are NO built-in functions in Rest-Assured can help you.
To solve it
you can use JsonPath jayway to extract list of number by using deep-scan feature
then use hasItem assertion of Hamcrest.
import com.jayway.jsonpath.JsonPath;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasItem;
#Test
void name() {
String json = "{\n" +
" \"john\": {\n" +
" \"number\": \"123\"\n" +
" },\n" +
" \"sarah\": {\n" +
" \"number\": \"123\"\n" +
" }\n" +
"}";
List<String> numbers = JsonPath.read(json, "$..number");
assertThat(numbers, hasItem("123"));
}
Note:
Please don't confused 2 JsonPath classes here. One is in Rest-Assured, one is in JsonPath jayway.
I have to say that I hate the json structure like this, it's better if using array to group items.
[
{
"name": "john",
"number": "123"
},
{
"name": "sarah",
"number": "123"
}
]

Returning Neo4j map projection with WebFlux

I have the nodes user and game with some relationships between them.
My REST API should return all relationships between the games and 1 user.
The cypher query i use is:
MATCH (u:User {id: '1234'} ) -[rel]- (game:Game) return game{.*, relationships: collect(DISTINCT rel)}
In my Neo4j Browser, everything works as expected and i see all properties i need.
But the GetMapping retuns everything except the relationship properties.
Neo4j Browser
{
"relationships": [
{
"identity": 54,
"start": 9,
"end": 8,
"type": "OWNED",
"properties": {
"ownedDate": "2021-07-03"
}
},
{
"identity": 45,
"start": 9,
"end": 8,
"type": "PLAYED",
"properties": {
"times": 5
}
}
],
"name": "Blood Rage",
"state": "ACTIVE",
"id": "1c152c91-4044-41f0-9208-0c436d6f6480",
"gameUrl": "https://asmodee.de/blood-rage"
}
GetMapping result (As you can see, the relationships are empty, but i have more empty JsonObjects, when there are more Relationships
{
"game": {
"relationships": [
{},
{}
],
"name": "Blood Rage",
"gameUrl": "https://asmodee.de/blood-rage",
"state": "ACTIVE",
"id": "1c152c91-4044-41f0-9208-0c436d6f6480"
}
}
The GetMapping is:
...
final ReactiveNeo4jClient client;
...
...
...
#GetMapping(value = { "/{id}/games"})
#RolesAllowed({"user", "admin"})
Flux<Map<String, Object>> findGamesByUser(#PathVariable String id){
String query = "MATCH (uuser:User {id: '" + id + "'} ) -[rel]- (game:Game) return game{.*, relationships: collect(DISTINCT rel)}";
return client.query(query).fetch().all();
}
A RelationshipProperty-Example
#RelationshipProperties
#Data
#Builder
public class PlayedGame {
#Id
#GeneratedValue
private Long relationshipId;
#Property
int times = 0;
#TargetNode private GameEntity game;
public int addPlay(){
this.times = this.times + 1;
return this.times;
}
}
What do i have to change in my GetMapping to show the relationship-properties?
Thank you,
Kevin
You need to return the actual nodes and relationships, otherwise you're missing the id-mapping.
There should be examples in the SDN docs.
Best if you have a small reproducible example (e.g. with the default movies graph).
Not sure if there is something off in your SDN setup, in general for such simple queries you should be able to just use a repository and not need to write cypher queries by hand.
The general information given by Michael is correct but there is more in you question:
First of all the meta domain model is completely ignored if you are using the Neo4jClient. It does not automatically map anything back but uses the driver's types.
As a result you will end up with an (current state of this answer) InternalRelationship which does not have any getter-methods.
I assume that you are serializing the result in the application with Jackson. This is the reason why you see objects that represent the relationships but without any content within.
If you want to get things mapped for you, create also the domain objects properly and use (at least) the Neo4jTemplate with your query.
If you model User, Game, and the relationship properties like PlayedGame correctly, a
neo4jTemplate.findAll("MATCH (u:User)<-[rel]-(g:Game) return u, collect(rel), collect(g)", User.class)
will map the results properly. Also if this is all you have, you could also skip the custom query at all and use
neo4jTemplate.findAll(User.class)
or
neo4jTemplate.findById(useId, User.class)

How to query child nodes in parent node entity of neo4j?

For example, I have an entity like this:
#Data
#NodeEntity(label = "Subject")
public class Subject {
#GraphId
private Long id;
private String name;
#Relationship(type = "HAVE_S", direction = Relationship.OUTGOING)
private Set<Subject> children = new HashSet<>();
}
Then I need to query a 'Subject' by graphId;
#Query("MATCH (s:Subject)-[r:HAVE_S*1]->(c:Subject) WHERE ID(s) = {graphId} RETURN s, r, c;")
Subject findById(#Param("graphId") Long graphId);
The result I want just like the following json:
{
"id": 62
"name": "Java"
"children": [
{
"name": "Collection",
"id": 105
},
{
"name": "MultipleThreads",
"id": 0
}
]
}
But when I executed the cypher above through Spring Data, an error comes out and says "Result not of expected size. Expected 1 row but found 3".
I hope someone can help me with this problem, thanks.
As your model suggests A subject have multiple children so when you are fetching it by id it's returning a cartesian product of your subject* children. In this case you need to collect your children(subjects and then return)
#Query("MATCH (s:Subject)-[r:HAVE_S*1]->(c:Subject) WHERE ID(s) = {graphId} RETURN s, collect(c) as childrens;")
or simply you can use findById() of your repository the SDN will create query by itself
You can try like this.
MATCH (k:LabelsTree {name:'465afe3c118589de357745a709c0441f'}) CALL apoc.path.spanningTree(k,{labelFilter:'+LabelsTree', maxLevel:3, optional:true, filterStartNode:true}) yield path return path
Refer to the connection

findAll order by "many to one" column raises exception

I have an entity, Student, defined in Student.groovy as:
#EqualsAndHashCode(includes = ['id'])
class Student {
Long id
String name
String type
University university
static mapping = {
university column : 'UNIVERSITY_ID'
}
}
and a University entity, defined in University.groovy as:
class University {
Long id
String name
static mapping = {
id column : 'id', generator : 'assigned'
}
}
I've been trying to switch from calling
Student.list(sort: ..., order: ...)
to calling:
Student.findAll("from Student s where type = :type ", [type : 'T'], [ sort : 'name' ])
This fails to order correctly by the name field. The previous version, using list worked fine.
I've also tried calling something like
Student.findAll(sort : 'name') { type == "T" }
which worked fine like this, but when trying to sort by the university.name
Student.findAll(sort : 'university.name') { type == 'T" }
it raised an error regarding the university.name field not being found.
Anybody have any idea on how to do this properly?
Thank you.
Use executeQuery instead of findAll - they should function the same, but I've found that executeQuery is for some reason a more direct caller of the HQL, and findAll fails or returns unexpected results in some cases.
So that first query would be
Student.executeQuery(
'select s from Student s where s.type = :type order by s.name',
[type : 'T'])
and ordering by university name would be
Student.executeQuery(
'select s from Student s where s.type = :type order by s.university.name',
[type : 'T'])
I like HQL and tend to use it a lot, but it couples you to Hibernate and relational databases - if you want to switch to a NoSQL database these queries will fail. Criteria queries, "where" queries and finders all use criteria queries internally, and those are converted to native query API calls by the GORM implementation.
The equivalent criteria queries would be
Student.withCriteria {
eq 'type', 'T'
order 'name', 'asc'
}
and
Student.withCriteria {
eq 'type', 'T'
university {
order 'name', 'desc'
}
}
Some unrelated notes:
You shouldn't use id in equals or hashCode calculations; if you have a persistent Student and a new non-persistent instance with the same name, type, and University, they should be considered equal, but since the non-persistent instance's id will be null they'll be considered different.
You don't need to specify the id property - Grails adds it and the version field to the bytecode via an AST transformation during compilation.
There's no need to map the column name of the university property to 'UNIVERSITY_ID' - that's what it would be anyway.
You can omit the redundant column setting in the id mapping.
Here's the Student class with cruft removed:
#EqualsAndHashCode(includes = ['name', 'type', 'university'])
class Student {
String name
String type
University university
}
and University:
class University {
String name
static mapping = {
id generator: 'assigned'
}
}

Resources