Skip to content

Commit e812631

Browse files
committed
GH-2243 - Add support for distinct derived queries.
Closes #2243
1 parent 06a0a33 commit e812631

File tree

5 files changed

+67
-21
lines changed

5 files changed

+67
-21
lines changed

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ final class CypherQueryCreator extends AbstractQueryCreator<QueryFragmentsAndPar
8585
private final NodeDescription<?> nodeDescription;
8686

8787
private final Neo4jQueryType queryType;
88+
private final boolean isDistinct;
8889

8990
private final Iterator<Neo4jQueryMethod.Neo4jParameter> formalParameters;
9091
private final Queue<Parameter> lastParameter = new LinkedList<>();
@@ -127,6 +128,7 @@ final class CypherQueryCreator extends AbstractQueryCreator<QueryFragmentsAndPar
127128
this.nodeDescription = this.mappingContext.getRequiredNodeDescription(this.domainType);
128129

129130
this.queryType = queryType;
131+
this.isDistinct = tree.isDistinct();
130132

131133
this.formalParameters = actualParameters.getParameters().iterator();
132134
this.maxResults = tree.isLimiting() ? tree.getMaxResults() : null;
@@ -293,7 +295,7 @@ private QueryFragmentsAndParameters.QueryFragments createQueryFragments(@Nullabl
293295
} else if (queryType == Neo4jQueryType.EXISTS) {
294296
queryFragments.setReturnExpression(Functions.count(Constants.NAME_OF_ROOT_NODE).gt(Cypher.literalOf(0)), true);
295297
} else {
296-
queryFragments.setReturnBasedOn(nodeDescription, includedProperties);
298+
queryFragments.setReturnBasedOn(nodeDescription, includedProperties, isDistinct);
297299
queryFragments.setOrderBy(Stream
298300
.concat(sortItems.stream(),
299301
pagingParameter.getSort().and(sort).stream().map(CypherAdapterUtils.sortAdapterFor(nodeDescription)))

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

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -280,8 +280,8 @@ public void setSkip(Long skip) {
280280
this.skip = skip;
281281
}
282282

283-
public void setReturnBasedOn(NodeDescription<?> nodeDescription, List<String> includedProperties) {
284-
this.returnTuple = new ReturnTuple(nodeDescription, includedProperties);
283+
public void setReturnBasedOn(NodeDescription<?> nodeDescription, List<String> includedProperties, boolean isDistinct) {
284+
this.returnTuple = new ReturnTuple(nodeDescription, includedProperties, isDistinct);
285285
}
286286

287287
public ReturnTuple getReturnTuple() {
@@ -300,17 +300,6 @@ public void setRenderConstantsAsParameters(boolean renderConstantsAsParameters)
300300
this.renderConstantsAsParameters = renderConstantsAsParameters;
301301
}
302302

303-
private Expression[] getReturnExpressions() {
304-
return returnExpressions.size() > 0
305-
? returnExpressions.toArray(new Expression[]{})
306-
: CypherGenerator.INSTANCE.createReturnStatementForMatch(getReturnTuple().nodeDescription,
307-
this::includeField);
308-
}
309-
310-
private SortItem[] getOrderBy() {
311-
return orderBy != null ? orderBy : new SortItem[]{};
312-
}
313-
314303
public Statement generateGenericStatement() {
315304
String rootNodeIds = "rootNodeIds";
316305
String relationshipIds = "relationshipIds";
@@ -350,26 +339,48 @@ public Statement toStatement() {
350339
}
351340
}
352341

353-
Statement statement = match
354-
.where(condition)
355-
.returning(getReturnExpressions())
356-
.orderBy(getOrderBy())
357-
.skip(skip)
358-
.limit(limit).build();
342+
StatementBuilder.OngoingReadingWithWhere matchWithWhere = match.where(condition);
343+
344+
StatementBuilder.OngoingReadingAndReturn returnPart = isDistinctReturn()
345+
? matchWithWhere.returningDistinct(getReturnExpressions())
346+
: matchWithWhere.returning(getReturnExpressions());
347+
348+
Statement statement = returnPart
349+
.orderBy(getOrderBy())
350+
.skip(skip)
351+
.limit(limit).build();
352+
359353
statement.setRenderConstantsAsParameters(renderConstantsAsParameters);
360354
return statement;
361355
}
362356

357+
private Expression[] getReturnExpressions() {
358+
return returnExpressions.size() > 0
359+
? returnExpressions.toArray(new Expression[]{})
360+
: CypherGenerator.INSTANCE.createReturnStatementForMatch(getReturnTuple().nodeDescription,
361+
this::includeField);
362+
}
363+
364+
private boolean isDistinctReturn() {
365+
return returnExpressions.isEmpty() && getReturnTuple().isDistinct;
366+
}
367+
368+
private SortItem[] getOrderBy() {
369+
return orderBy != null ? orderBy : new SortItem[]{};
370+
}
371+
363372
/**
364373
* Describes which fields of an entity needs to get returned.
365374
*/
366375
final static class ReturnTuple {
367376
final NodeDescription<?> nodeDescription;
368377
final Set<String> includedProperties;
378+
final boolean isDistinct;
369379

370-
private ReturnTuple(NodeDescription<?> nodeDescription, List<String> includedProperties) {
380+
private ReturnTuple(NodeDescription<?> nodeDescription, List<String> includedProperties, boolean isDistinct) {
371381
this.nodeDescription = nodeDescription;
372382
this.includedProperties = includedProperties == null ? Collections.emptySet() : new HashSet<>(includedProperties);
383+
this.isDistinct = isDistinct;
373384
}
374385
}
375386
}

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3806,6 +3806,16 @@ void findByPropertyOnRelatedEntityOfRelatedSameEntity(@Autowired RelationshipRep
38063806
assertThat(repository.findByPetsFriendsName("Jerry")).isNull();
38073807
}
38083808

3809+
@Test // GH-2243
3810+
void findDistinctByRelatedEntity(@Autowired RelationshipRepository repository) {
3811+
doWithSession(session ->
3812+
session.run("CREATE (n:PersonWithRelationship{name:'Freddie'})-[:Has]->(:Hobby{name: 'Music'})"
3813+
+ "CREATE (n)-[:Has]->(:Hobby{name: 'Music'})").consume());
3814+
3815+
assertThat(repository.findDistinctByHobbiesName("Music")).isNotNull();
3816+
3817+
}
3818+
38093819
@Test
38103820
void findByPropertyOnRelationshipWithProperties(@Autowired PersonWithRelationshipWithPropertiesRepository repository) {
38113821
doWithSession(session ->
@@ -4002,6 +4012,8 @@ interface RelationshipRepository extends Neo4jRepository<PersonWithRelationship,
40024012
+ "}\n"
40034013
+ "RETURN n, collect(r), collect(p)")
40044014
PersonWithRelationship createWithCustomQuery(PersonWithRelationship p);
4015+
4016+
PersonWithRelationship.PersonWithHobby findDistinctByHobbiesName(String hobbyName);
40054017
}
40064018

40074019
interface SimilarThingRepository extends Neo4jRepository<SimilarThing, Long> {}

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1303,6 +1303,17 @@ void findByPropertyOnRelatedEntityOfRelatedSameEntity(@Autowired ReactiveRelatio
13031303
StepVerifier.create(repository.findByPetsFriendsName("Jerry")).verifyComplete();
13041304
}
13051305

1306+
@Test // GH-2243
1307+
void findDistinctByRelatedEntity(@Autowired ReactiveRelationshipRepository repository) {
1308+
doWithSession(session ->
1309+
session.run("CREATE (n:PersonWithRelationship{name:'Freddie'})-[:Has]->(:Hobby{name: 'Music'})"
1310+
+ "CREATE (n)-[:Has]->(:Hobby{name: 'Music'})").consume());
1311+
1312+
StepVerifier.create(repository.findDistinctByHobbiesName("Music"))
1313+
.assertNext(person -> assertThat(person).isNotNull())
1314+
.verifyComplete();
1315+
}
1316+
13061317
@Test
13071318
void findByPropertyOnRelationshipWithProperties(
13081319
@Autowired ReactivePersonWithRelationshipWithPropertiesRepository repository) {
@@ -2513,6 +2524,8 @@ interface ReactiveRelationshipRepository extends ReactiveNeo4jRepository<PersonW
25132524
Mono<PersonWithRelationship> findByPetsFriendsName(String petName);
25142525

25152526
Flux<PersonWithRelationship> findByName(String name, Sort sort);
2527+
2528+
Mono<PersonWithRelationship.PersonWithHobby> findDistinctByHobbiesName(String hobbyName);
25162529
}
25172530

25182531
interface ReactiveSimilarThingRepository extends ReactiveCrudRepository<SimilarThing, Long> {}

src/test/java/org/springframework/data/neo4j/integration/shared/common/PersonWithRelationship.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,4 +77,12 @@ public List<Pet> getPets() {
7777
public void setPets(List<Pet> pets) {
7878
this.pets = pets;
7979
}
80+
81+
/**
82+
* Simple person with hobbies relationship to enforce non-cyclic querying.
83+
*/
84+
public interface PersonWithHobby {
85+
String getName();
86+
Hobby getHobbies();
87+
}
8088
}

0 commit comments

Comments
 (0)