Skip to content

Directional relationships on OPTIONAL MATCH does not map correctly when node is not present #2572

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
aemonalgizlg opened this issue Jul 22, 2022 · 1 comment
Assignees
Labels
in: ogm Object Graph Mapping (Legacy) type: enhancement A general enhancement

Comments

@aemonalgizlg
Copy link

aemonalgizlg commented Jul 22, 2022

In SDN, if you have the following nodes and relationships:

public abstract class MyEntity<T extends MyEntity<T>> {
    @Id
    @Property(name = "id")
    @GeneratedValue(value = MyStrategy.class)
    protected String id;
}

@Getter
@Setter
@NoArgsConstrcutor
@AllArgsConstructor
@Node(value = "Person")
public class Person extends MyEntity {
   @Property(value = "name")
   private String name;
   
   @Property(value = "age")
   private int age;
}
@Getter
@Setter
@NoArgsConstrcutor
@AllArgsConstructor
@Node(value = "Dog")
public class Dog extends MyEntity {
   @Property(value = "name")
   private String name;

   @Relationship(value = "IS_PET", direction = Direction.OUTGOING)
   private Person owner;
}

If a person has no pets directed towards it and you have the following pet repository:

@Repository
public interface DogRepository extends Neo4jRepository<Dog, String> {

   Query(“MATCH(person:Person {id: $id}) “
   + “OPTIONAL MATCH (person)<-[:IS_PET]-(dog:Dog) “
   + “RETURN dog;”)
   List<Dog> getDogsForPerson(String id);

}

SDN will fail the correctly map the Collection of Dog for the getDogsForPerson method due to the dog node coming back as null. This did work correctly in OGM.

If you do the following, this does appear to make it work, though this seems not to be the expected behavior with the root node being returned from Neo4J and it may also cause unintended issues:

@Repository
public interface DogRepository extends Neo4jRepository<Dog, String> {

   Query(“MATCH(person:Person {id: $id}) “
   + “OPTIONAL MATCH (person)<-[:IS_PET]-(dog:Dog) “
   + “RETURN collect(dog);”)
   List<Dog> getDogsForPerson(String id);

}
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Jul 22, 2022
@michael-simons michael-simons self-assigned this Jul 25, 2022
michael-simons added a commit that referenced this issue Jul 25, 2022
… `OPTIONAL` returns.

This fixes #2572 and is a tough call: The query presented there is totally valid but returns not an empty result but a record containing only one `null` element due to the way the Cyper `OPTIONAL` keyword works. We could try parsing the query, but this will be an endless rabbit whole in the future to come.

The approach here is to check now whether a result contains exactly one record with one `NULL` value. If that is the case, no exception is thrown but  `null` returned, which is later filtered on the clients.

That however drops the checking for non-null values there, so that methods don’t throw in those cases but returns empty optionals or empty / smaller lists.
michael-simons added a commit that referenced this issue Jul 25, 2022
… `OPTIONAL` returns.

This fixes #2572 and is a tough call: The query presented there is totally valid but returns not an empty result but a record containing only one `null` element due to the way the Cyper `OPTIONAL` keyword works. We could try parsing the query, but this will be an endless rabbit whole in the future to come.

The approach here is to check now whether a result contains exactly one record with one `NULL` value. If that is the case, no exception is thrown but  `null` returned, which is later filtered on the clients.

That however drops the checking for non-null values there, so that methods don’t throw in those cases but returns empty optionals or empty / smaller lists.
@michael-simons michael-simons added type: enhancement A general enhancement in: ogm Object Graph Mapping (Legacy) and removed status: waiting-for-triage An issue we've not yet triaged labels Jul 25, 2022
@michael-simons michael-simons added this to the 6.2.7 (2021.1.7) milestone Jul 25, 2022
@michael-simons
Copy link
Collaborator

Thanks @aemonalgizlg for reporting this here and taking the effort to strip down the domain model, much appreciated. This will be fixed within the next releases.

Take not however that the owner property of the instances will be null when fetched that way, as your query doesn't contain the person.

Correct way would be:

MATCH(person:GH2572Parent {id: $id}) 
OPTIONAL MATCH (person)<-[r:IS_PET]-(dog:GH2572Child) 
RETURN person, r, dog

With this assertions holding true:

List<GH2572Child> dogsForPerson = dogRepository.getDogsForPerson("GH2572Parent-2");
assertThat(dogsForPerson).hasSize(2);
assertThat(dogsForPerson.get(0).getOwner()).isNotNull();
assertThat(dogsForPerson.get(0).getOwner()).isSameAs(dogsForPerson.get(1).getOwner());

@michael-simons michael-simons mentioned this issue Jan 4, 2023
4 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: ogm Object Graph Mapping (Legacy) type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

3 participants