1

Built-in queries to Spring Data Neo4j (SDN) return objects populated with depth 1 by default. This means that "children" (related nodes) of an object returned by a query are populated. That's good - there are actual objects on the end of references from objects returned by these queries.

Custom queries are depth 0 by default. This is a hassle.

In this answer, it is described how to get springboot neo4j to populate a related element to the target of a custom query - to achieve an extra one level of depth of results from the query.

I am having trouble with this method when the related elements are in a list:

@NodeEntity
public class BoardPosition {

    @Relationship(type="PARENT", direction = Relationship.INCOMING)
    public List<BoardPosition> children;

I have a query returning a target BoardPosition and I need it's children to be populated.

@Query("MATCH (target:BoardPosition) <-[c:PARENT]- (child:BoardPosition) 
       WHERE target.play={Play} 
       RETURN target, c, child")
BoardPosition findActiveByPlay(@Param("Play") String play);

The problem is that the query appears to return one separate result for each child, and those results aren't being used to populate the array of children in the target.

Instead of Spring Neo collating the children into the array on the target, I get "only 1 result expected" error - as if the query is returning multiple results each with one child, rather than one result with the children in it.

org.springframework.dao.IncorrectResultSizeDataAccessException: Incorrect result size: expected at most 1

How can I have a custom query to populate that target's children list?

(Note that the built-in findByPlay(play) does what I want - the built-in queries have a depth of 1 rather than 0, and it returns a target with populated children - but of course I need to make the query a bit more sophisticated than just "by Play"... that's why I need to solve this)


Versions:

org.springframework.data:spring-data-neo4j:5.1.3.RELEASE

neo4j 3.5.0

GreenAsJade
  • 14,459
  • 11
  • 63
  • 98
  • Which version are you using. – Michael Hunger May 03 '19 at 19:20
  • Can you return the paths? – Michael Hunger May 03 '19 at 19:20
  • What does it mean to "return a path"? I've [read](https://neo4j.com/docs/cypher-manual/current/clauses/return) about how to return nodes, relationships and properties, don't see "paths"? – GreenAsJade May 04 '19 at 01:48
  • You could write your own interceptors. – Sumi Straessle May 06 '19 at 20:37
  • @SumiStraessle That sounds almost like "you could re-implement SDN" ! Or is it more tractable to do this than I imagine? – GreenAsJade May 07 '19 at 01:19
  • 1
    Your problem seems a bit different than what I ran into, but they still have to do with *projections (return value evaluation) of same-typed nodes returned by a custom query*. So, please take a look at our [discussion](https://github.com/neo4j/neo4j-ogm/issues/496#issuecomment-500823271) with michael-simons on an Neo4J OGM bug. It may have been effecting your use-case as well. So if you're still seeing the issue, it may be worth giving a try to that alpha (or now RC) build of Neo4J OGM. – OzgurH Jul 19 '19 at 12:03

2 Answers2

2

=== Edit ======

Your problem arises because you have self-relationship (relationship between nodes of the same label)

This is how Spring treat your query for single node:

org.springframework.data.neo4j.repository.query.GraphQueryExecution

@Override
public Object execute(Query query, Class<?> type) {
    Iterable<?> result;
    ....
    Object ret = iterator.next();
    if (iterator.hasNext()) {
        throw new IncorrectResultSizeDataAccessException("Incorrect result size: expected at most 1", 1);
    }
    return ret;
}

Spring passes your node class type Class<?> type to neo4j-ogm and have your data read back.

You know, neo4j server will returns multiple rows for your query, one for each matching path:

A <- PARENT - B
A <- PARENT - C
A <- PARENT - D

If your nodes are of different labels, i.e. of different class type then the ogm only return single node correspond to your query return type, no problem.

But your nodes are of the same labels, i.e. same class type => Neo4j OGM cannot distinguish which is the returned node -> All nodes A, B, C, D returned -> Exception

Regard this issue, I think you should file a bug report now.

For workaround, you can can change the query to return only the distinct target.your_identity_property (identity_property is 'primary key' of the node, which uniquely identify your node)

Then have your application call load with the that identity property:

public interface BoardRepository extends CrudRepository<BoardPos, Long> {

    @Query("MATCH (target:B) <-[c:PARENT]- (child:B) WHERE target.play={Play} RETURN DISTINCT target.your_identity_property")
    Long findActiveByPlay(@Param("Play") String play);

    BoardPos findByYourIdentityProperty(xxxx);
} 

=== OLD ======

Spring docs says that (highlighted by me):

Custom queries do not support a custom depth. Additionally, @Query does not support mapping a path to domain entities, as such, a path should not be returned from a Cypher query. Instead, return nodes and relationships to have them mapped to domain entities.

So clearly your use-case (populate children nodes by custom query) is supported. Spring framework already maps the results into a single node. (Indeed, my setup on local turnouts that the operation is working properly)

So your exception may be caused by several issues:

  1. You have more than one target:BoardPosition with target.play={play}. So the exception refers to more than one target:BoardPosition instead of one BoardPosition with multiple child result

  2. You have incorrect entity mapping. Do you have your mapping field annotated with @Relationship with correct direction attribute? You might post your entity here.

Here is my local setup:

@NodeEntity(label = "C")
@Data
public class Child {

    @Id
    @GeneratedValue
    private long id;
    private String name;

    @Relationship(type = "PARENT", direction = "INCOMING")
    private List<Parent> parents;
}

public interface ChildRepository extends CrudRepository<Child, Long> {

    @Query("MATCH (target:C) <-[p:PARENT]- (child:P) " 
        + "WHERE target.name={name} " 
        + "RETURN target, p, child")
    Child findByName(@Param("name") String name);
}

(:C) <-[:PARENT] - (:P)
Mạnh Quyết Nguyễn
  • 17,677
  • 1
  • 23
  • 51
  • 1
    Oh my goodness, my arrow might be the wrong way around. Just to quickly acknowledge and thank you for your response. Looks promising, will test as soon as I can. – GreenAsJade May 08 '19 at 09:30
  • Good luck @GreenAsJade :) – Mạnh Quyết Nguyễn May 08 '19 at 10:49
  • Sadly, the arrow is not the wrong way around - now that I look again carefully, the relationship "PARENT" means "this node is my parent", and is incoming on the target, which is the parent. That segment of the entity code is posted. I wonder whether my problem is different to yours in as much as my parent and child are of the same type, wheras yours are of differing types. Is this making it hard for neo to work out what is going on in my case? – GreenAsJade May 10 '19 at 06:51
  • Could you share a minimal data? – Mạnh Quyết Nguyễn May 10 '19 at 07:09
  • I need to make an MCVE. Might take a little time. In the meanwhile, is it possible the problem is the same entity type on each end of the relationship is messing up the matching? Thanks again – GreenAsJade May 10 '19 at 07:10
  • 1
    Oh I got your problem, let me tackle it now – Mạnh Quyết Nguyễn May 10 '19 at 07:26
  • Interesting. As per the question, I'm on 3.5.0! Does it mean "it was a regression at 3.1.8, earlier versions are OK"? – GreenAsJade May 10 '19 at 08:44
  • 1
    Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/193135/discussion-between-mnh-quyt-nguyn-and-greenasjade). – Mạnh Quyết Nguyễn May 10 '19 at 08:59
1

Consider the alternative query

MATCH (target:BoardPosition {play:{Play}})
RETURN target, [ (target)<-[c:PARENT]-(child:BoardPosition) | [c, child] ]

which is using list comprehension to return not only the target but also its relations and related nodes of label BoardPosition within one result row. This ensures that the result will be a single row (as long as your attribute play is unique).

I didn't try it with your example but in my application this approach is working fine. Neo4j OGM hydrates the objects as expected. It is important to include the related nodes as well as the relations pointing to the nodes.

If you enable neo4j OGM logs, you can see that the build-in queries with depth 1 use the same approach.

Steffen Harbich
  • 2,639
  • 2
  • 37
  • 71
  • 1
    For the sake of completeness: `logging.level.org.neo4j.ogm.drivers.bolt.request.BoltRequest=DEBUG` to enable cypher logging – Steffen Harbich May 09 '19 at 12:47
  • I'll need to read up on neo list comprehensions. Amazingly to me, although this returns only one record in neo browser (confirming only one match on the play), it _still_ generates `org.springframework.dao.IncorrectResultSizeDataAccessException: Incorrect result size: expected at most 1`. – GreenAsJade May 10 '19 at 07:06
  • Basically, it should work. Maybe you could try to do a SSCCE and upload it here? – Steffen Harbich May 10 '19 at 08:47