Skip to content

Commit 8fe7b21

Browse files
GH-2157 - Don't resolve circular possible circular relationships for queries returning count or exists projections.
This fixes #2157 Original pull request: #2158. Co-authored-by: Gerrit Meier <[email protected]>
1 parent fb55363 commit 8fe7b21

File tree

6 files changed

+95
-27
lines changed

6 files changed

+95
-27
lines changed

src/main/java/org/springframework/data/neo4j/core/Neo4jTemplate.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -660,7 +660,7 @@ private Optional<Neo4jClient.RecordFetchSpec<T>> createFetchSpec() {
660660

661661
Map<String, Object> parameters = queryFragmentsAndParameters.getParameters();
662662

663-
if (containsPossibleCircles) {
663+
if (containsPossibleCircles && !queryFragments.isScalarValueReturn()) {
664664
GenericQueryAndParameters genericQueryAndParameters =
665665
createQueryAndParameters(entityMetaData, queryFragments, parameters);
666666

src/main/java/org/springframework/data/neo4j/core/ReactiveNeo4jTemplate.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -454,7 +454,7 @@ private <T> Mono<ExecutableQuery<T>> createExecutableQuery(Class<T> domainType,
454454
returnTuple != null
455455
? returnTuple.getIncludedProperties()
456456
: Collections.emptyList());
457-
if (containsPossibleCircles) {
457+
if (containsPossibleCircles && !queryFragments.isScalarValueReturn()) {
458458
return createQueryAndParameters(entityMetaData, queryFragments, parameters)
459459
.flatMap(finalQueryAndParameters ->
460460
createExecutableQuery(domainType, renderer.render(GenericQueryAndParameters.STATEMENT),
@@ -759,7 +759,7 @@ public <T> Mono<ExecutableQuery<T>> toExecutableQuery(PreparedQuery<T> preparedQ
759759

760760
Map<String, Object> parameters = queryFragmentsAndParameters.getParameters();
761761

762-
if (containsPossibleCircles) {
762+
if (containsPossibleCircles && !queryFragments.isScalarValueReturn()) {
763763
GenericQueryAndParameters genericQueryAndParameters =
764764
createQueryAndParameters(entityMetaData, queryFragments, parameters).block();
765765

@@ -768,7 +768,6 @@ public <T> Mono<ExecutableQuery<T>> toExecutableQuery(PreparedQuery<T> preparedQ
768768
} else {
769769
cypherQuery = renderer.render(queryFragments.toStatement());
770770
}
771-
772771
}
773772

774773
ReactiveNeo4jClient.MappingSpec<T> mappingSpec = this.neo4jClient.query(cypherQuery)

src/main/java/org/springframework/data/neo4j/repository/query/CypherQueryCreator.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -289,9 +289,9 @@ private QueryFragmentsAndParameters.QueryFragments createQueryFragments(@Nullabl
289289
/// end of initial filter query creation
290290

291291
if (queryType == Neo4jQueryType.COUNT) {
292-
queryFragments.addReturnExpression(Functions.count(Cypher.asterisk()));
292+
queryFragments.setReturnExpression(Functions.count(Cypher.asterisk()), true);
293293
} else if (queryType == Neo4jQueryType.EXISTS) {
294-
queryFragments.addReturnExpression(Functions.count(Constants.NAME_OF_ROOT_NODE).gt(Cypher.literalOf(0)));
294+
queryFragments.setReturnExpression(Functions.count(Constants.NAME_OF_ROOT_NODE).gt(Cypher.literalOf(0)), true);
295295
} else {
296296
queryFragments.setReturnBasedOn(nodeDescription, includedProperties);
297297
queryFragments.setOrderBy(Stream

src/main/java/org/springframework/data/neo4j/repository/query/QueryFragmentsAndParameters.java

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ public static QueryFragmentsAndParameters forFindById(Neo4jPersistentEntity<?> e
100100
QueryFragments queryFragments = new QueryFragments();
101101
queryFragments.addMatchOn(cypherGenerator.createRootNode(entityMetaData));
102102
queryFragments.setCondition(condition);
103-
queryFragments.setReturnExpression(returnStatement);
103+
queryFragments.setReturnExpressions(returnStatement);
104104
return new QueryFragmentsAndParameters(entityMetaData, queryFragments, parameters);
105105
}
106106

@@ -112,15 +112,15 @@ public static QueryFragmentsAndParameters forFindByAllId(Neo4jPersistentEntity<?
112112
QueryFragments queryFragments = new QueryFragments();
113113
queryFragments.addMatchOn(cypherGenerator.createRootNode(entityMetaData));
114114
queryFragments.setCondition(condition);
115-
queryFragments.setReturnExpression(returnStatement);
115+
queryFragments.setReturnExpressions(returnStatement);
116116
return new QueryFragmentsAndParameters(entityMetaData, queryFragments, parameters);
117117
}
118118

119119
public static QueryFragmentsAndParameters forFindAll(Neo4jPersistentEntity<?> entityMetaData) {
120120
QueryFragments queryFragments = new QueryFragments();
121121
queryFragments.addMatchOn(cypherGenerator.createRootNode(entityMetaData));
122122
queryFragments.setCondition(Conditions.noCondition());
123-
queryFragments.setReturnExpression(cypherGenerator.createReturnStatementForMatch(entityMetaData));
123+
queryFragments.setReturnExpressions(cypherGenerator.createReturnStatementForMatch(entityMetaData));
124124
return new QueryFragmentsAndParameters(entityMetaData, queryFragments, Collections.emptyMap());
125125
}
126126

@@ -155,7 +155,7 @@ static QueryFragmentsAndParameters forExample(Neo4jMappingContext mappingContext
155155
QueryFragments queryFragments = new QueryFragments();
156156
queryFragments.addMatchOn(cypherGenerator.createRootNode(entityMetaData));
157157
queryFragments.setCondition(condition);
158-
queryFragments.setReturnExpression(returnStatement);
158+
queryFragments.setReturnExpressions(returnStatement);
159159

160160
if (pageable != null) {
161161
Sort pageableSort = pageable.getSort();
@@ -187,6 +187,7 @@ public static final class QueryFragments {
187187
private Number limit;
188188
private Long skip;
189189
private ReturnTuple returnTuple;
190+
private boolean scalarValueReturn = false;
190191

191192
public void addMatchOn(PatternElement match) {
192193
this.matchOn.add(match);
@@ -208,12 +209,13 @@ public Condition getCondition() {
208209
return condition;
209210
}
210211

211-
public void setReturnExpression(Expression[] expression) {
212+
public void setReturnExpressions(Expression[] expression) {
212213
this.returnExpressions = Arrays.asList(expression);
213214
}
214215

215-
public void addReturnExpression(Expression returnExpression) {
216-
this.returnExpressions.add(returnExpression);
216+
public void setReturnExpression(Expression returnExpression, boolean isScalarValue) {
217+
this.returnExpressions = Collections.singletonList(returnExpression);
218+
this.scalarValueReturn = isScalarValue;
217219
}
218220

219221
public void setOrderBy(SortItem[] orderBy) {
@@ -236,6 +238,10 @@ public ReturnTuple getReturnTuple() {
236238
return returnTuple;
237239
}
238240

241+
public boolean isScalarValueReturn() {
242+
return scalarValueReturn;
243+
}
244+
239245
private Expression[] getReturnExpressions() {
240246
return returnExpressions.size() > 0
241247
? returnExpressions.toArray(new Expression[]{})

src/test/java/org/springframework/data/neo4j/integration/imperative/RepositoryIT.java

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -965,18 +965,45 @@ void findEntityWithBidirectionalRelationship(@Autowired BidirectionalStartReposi
965965

966966
@Test
967967
void findEntityWithSelfReferencesInBothDirections(@Autowired PetRepository repository) {
968-
long petId;
969-
try (Session session = createSession()) {
970-
petId = session.run("CREATE (luna:Pet{name:'Luna'})-[:Has]->(daphne:Pet{name:'Daphne'})"
971-
+ "-[:Has]->(luna2:Pet{name:'Luna'})" + "RETURN id(luna) as id").single().get("id").asLong();
972-
}
968+
long petId = createFriendlyPets();
973969
Pet loadedPet = repository.findById(petId).get();
974970

975971
assertThat(loadedPet.getFriends().get(0).getName()).isEqualTo("Daphne");
976-
assertThat(loadedPet.getFriends().get(0).getFriends().get(0).getName()).isEqualTo("Luna");
972+
assertThat(loadedPet.getFriends().get(0).getFriends().get(0).getName()).isEqualTo("Tom");
977973

978974
}
979975

976+
@Test // GH-2157
977+
void countByPropertyWithPossibleCircles(@Autowired PetRepository repository) {
978+
createFriendlyPets();
979+
assertThat(repository.countByName("Luna")).isEqualTo(1L);
980+
}
981+
982+
@Test // GH-2157
983+
void countByPatternPathProperties(@Autowired PetRepository repository) {
984+
createFriendlyPets();
985+
assertThat(repository.countByFriendsNameAndFriendsFriendsName("Daphne", "Tom")).isEqualTo(1L);
986+
}
987+
988+
@Test // GH-2157
989+
void countByCustomQueryShouldWork(@Autowired PetRepository repository) {
990+
createFriendlyPets();
991+
assertThat(repository.countAllByName("Luna")).isEqualTo(4L);
992+
}
993+
994+
@Test // GH-2157
995+
void existsByPropertyWithPossibleCircles(@Autowired PetRepository repository) {
996+
createFriendlyPets();
997+
assertThat(repository.existsByName("Luna")).isTrue();
998+
}
999+
1000+
private long createFriendlyPets() {
1001+
try (Session session = createSession()) {
1002+
return session.run("CREATE (luna:Pet{name:'Luna'})-[:Has]->(daphne:Pet{name:'Daphne'})"
1003+
+ "-[:Has]->(:Pet{name:'Tom'})" + "RETURN id(luna) as id").single().get("id").asLong();
1004+
}
1005+
}
1006+
9801007
@Test
9811008
void findEntityWithBidirectionalRelationshipFromIncomingSide(@Autowired BidirectionalEndRepository repository) {
9821009

@@ -3857,6 +3884,14 @@ interface PetRepository extends Neo4jRepository<Pet, Long> {
38573884

38583885
Pet findByFriendsFriendsName(String friendName);
38593886

3887+
long countByName(String name);
3888+
3889+
@Query(value = "RETURN size($0)", count = true)
3890+
long countAllByName(String name);
3891+
3892+
long countByFriendsNameAndFriendsFriendsName(String friendName, String friendFriendName);
3893+
3894+
boolean existsByName(String name);
38603895
}
38613896

38623897
interface RelationshipRepository extends Neo4jRepository<PersonWithRelationship, Long> {

src/test/java/org/springframework/data/neo4j/integration/reactive/ReactiveRepositoryIT.java

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -998,18 +998,40 @@ void loadEntityWithRelationshipWithAssignedId(@Autowired ReactivePetRepository r
998998
@Test
999999
void findEntityWithSelfReferencesInBothDirections(@Autowired ReactivePetRepository repository) {
10001000

1001-
long petId;
1002-
1003-
try (Session session = createSession()) {
1004-
petId = session.run("CREATE (luna:Pet{name:'Luna'})-[:Has]->(daphne:Pet{name:'Daphne'})"
1005-
+ "-[:Has]->(luna2:Pet{name:'Luna'})" + "RETURN id(luna) as id").single().get("id").asLong();
1006-
}
1001+
long petId = createFriendlyPets();
10071002

10081003
StepVerifier.create(repository.findById(petId)).assertNext(loadedPet -> {
10091004
assertThat(loadedPet.getFriends().get(0).getName()).isEqualTo("Daphne");
1010-
assertThat(loadedPet.getFriends().get(0).getFriends().get(0).getName()).isEqualTo("Luna");
1005+
assertThat(loadedPet.getFriends().get(0).getFriends().get(0).getName()).isEqualTo("Tom");
10111006
}).verifyComplete();
10121007
}
1008+
1009+
@Test // GH-2157
1010+
void countByPropertyWithPossibleCircles(@Autowired ReactivePetRepository repository) {
1011+
createFriendlyPets();
1012+
StepVerifier.create(repository.countByName("Luna")).expectNext(1L).verifyComplete();
1013+
}
1014+
1015+
@Test // GH-2157
1016+
void countByPatternPathProperties(@Autowired ReactivePetRepository repository) {
1017+
createFriendlyPets();
1018+
StepVerifier.create(repository.countByFriendsNameAndFriendsFriendsName("Daphne", "Tom"))
1019+
.expectNextCount(1L)
1020+
.verifyComplete();
1021+
}
1022+
1023+
@Test // GH-2157
1024+
void existsByPropertyWithPossibleCircles(@Autowired ReactivePetRepository repository) {
1025+
createFriendlyPets();
1026+
StepVerifier.create(repository.existsByName("Luna")).expectNext(true).verifyComplete();
1027+
}
1028+
1029+
private long createFriendlyPets() {
1030+
try (Session session = createSession()) {
1031+
return session.run("CREATE (luna:Pet{name:'Luna'})-[:Has]->(daphne:Pet{name:'Daphne'})"
1032+
+ "-[:Has]->(:Pet{name:'Tom'})" + "RETURN id(luna) as id").single().get("id").asLong();
1033+
}
1034+
}
10131035
}
10141036

10151037
@Nested
@@ -2470,7 +2492,13 @@ interface ReactiveHobbyWithRelationshipWithPropertiesRepository
24702492
Flux<AltHobby> loadFromCustomQuery(@Param("personId") Long personId);
24712493
}
24722494

2473-
interface ReactivePetRepository extends ReactiveNeo4jRepository<Pet, Long> {}
2495+
interface ReactivePetRepository extends ReactiveNeo4jRepository<Pet, Long> {
2496+
Mono<Long> countByName(String name);
2497+
2498+
Mono<Boolean> existsByName(String name);
2499+
2500+
Mono<Long> countByFriendsNameAndFriendsFriendsName(String friendName, String friendFriendName);
2501+
}
24742502

24752503
interface ReactiveRelationshipRepository extends ReactiveNeo4jRepository<PersonWithRelationship, Long> {
24762504

0 commit comments

Comments
 (0)