Skip to content

Commit 7cca8c1

Browse files
committed
GH-2243 - Add support for distinct derived queries.
Closes #2243
1 parent 4da9b25 commit 7cca8c1

File tree

5 files changed

+70
-19
lines changed

5 files changed

+70
-19
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: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -252,8 +252,8 @@ public void setSkip(Long skip) {
252252
this.skip = skip;
253253
}
254254

255-
public void setReturnBasedOn(NodeDescription<?> nodeDescription, List<String> includedProperties) {
256-
this.returnTuple = new ReturnTuple(nodeDescription, includedProperties);
255+
public void setReturnBasedOn(NodeDescription<?> nodeDescription, List<String> includedProperties, boolean isDistinct) {
256+
this.returnTuple = new ReturnTuple(nodeDescription, includedProperties, isDistinct);
257257
}
258258

259259
public ReturnTuple getReturnTuple() {
@@ -264,17 +264,6 @@ public boolean isScalarValueReturn() {
264264
return scalarValueReturn;
265265
}
266266

267-
private Expression[] getReturnExpressions() {
268-
return returnExpressions.size() > 0
269-
? returnExpressions.toArray(new Expression[]{})
270-
: CypherGenerator.INSTANCE.createReturnStatementForMatch(getReturnTuple().getNodeDescription(),
271-
this::includeField);
272-
}
273-
274-
private SortItem[] getOrderBy() {
275-
return orderBy != null ? orderBy : new SortItem[]{};
276-
}
277-
278267
public Statement generateGenericStatement() {
279268
String rootNodeIds = "rootNodeIds";
280269
String relationshipIds = "relationshipIds";
@@ -314,25 +303,46 @@ public Statement toStatement() {
314303
}
315304
}
316305

317-
return match
318-
.where(condition)
319-
.returning(getReturnExpressions())
306+
StatementBuilder.OngoingReadingWithWhere matchWithWhere = match.where(condition);
307+
308+
StatementBuilder.OngoingReadingAndReturn returnPart = isDistinctReturn()
309+
? matchWithWhere.returningDistinct(getReturnExpressions())
310+
: matchWithWhere.returning(getReturnExpressions());
311+
312+
return returnPart
320313
.orderBy(getOrderBy())
321314
.skip(skip)
322315
.limit(limit).build();
323316
}
324317

318+
private Expression[] getReturnExpressions() {
319+
return returnExpressions.size() > 0
320+
? returnExpressions.toArray(new Expression[]{})
321+
: CypherGenerator.INSTANCE.createReturnStatementForMatch(getReturnTuple().getNodeDescription(),
322+
this::includeField);
323+
}
324+
325+
private boolean isDistinctReturn() {
326+
return returnExpressions.isEmpty() && getReturnTuple().isDistinct();
327+
}
328+
329+
private SortItem[] getOrderBy() {
330+
return orderBy != null ? orderBy : new SortItem[]{};
331+
}
332+
325333
/**
326334
* Describes which fields of an entity needs to get returned.
327335
*/
328336
@API(status = API.Status.INTERNAL, since = "6.0.4")
329-
public final static class ReturnTuple {
337+
private final static class ReturnTuple {
330338
private final NodeDescription<?> nodeDescription;
331339
private final Set<String> includedProperties;
340+
private final boolean isDistinct;
332341

333-
private ReturnTuple(NodeDescription<?> nodeDescription, List<String> includedProperties) {
342+
private ReturnTuple(NodeDescription<?> nodeDescription, List<String> includedProperties, boolean isDistinct) {
334343
this.nodeDescription = nodeDescription;
335344
this.includedProperties = includedProperties == null ? Collections.emptySet() : new HashSet<>(includedProperties);
345+
this.isDistinct = isDistinct;
336346
}
337347

338348
public NodeDescription<?> getNodeDescription() {
@@ -342,6 +352,10 @@ public NodeDescription<?> getNodeDescription() {
342352
public Collection<String> getIncludedProperties() {
343353
return includedProperties;
344354
}
355+
356+
public boolean isDistinct() {
357+
return isDistinct;
358+
}
345359
}
346360
}
347361
}

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3952,6 +3952,17 @@ void findByPropertyOnRelatedEntityOfRelatedSameEntity(@Autowired RelationshipRep
39523952
assertThat(repository.findByPetsFriendsName("Jerry")).isNull();
39533953
}
39543954

3955+
@Test // GH-2243
3956+
void findDistinctByRelatedEntity(@Autowired RelationshipRepository repository) {
3957+
try (Session session = createSession()) {
3958+
session.run("CREATE (n:PersonWithRelationship{name:'Freddie'})-[:Has]->(:Hobby{name: 'Music'})"
3959+
+ "CREATE (n)-[:Has]->(:Hobby{name: 'Music'})");
3960+
}
3961+
3962+
assertThat(repository.findDistinctByHobbiesName("Music")).isNotNull();
3963+
3964+
}
3965+
39553966
@Test
39563967
void findByPropertyOnRelationshipWithProperties(
39573968
@Autowired PersonWithRelationshipWithPropertiesRepository repository) {
@@ -4124,6 +4135,8 @@ interface RelationshipRepository extends Neo4jRepository<PersonWithRelationship,
41244135
PersonWithRelationship findByPetsHobbiesName(String hobbyName);
41254136

41264137
PersonWithRelationship findByPetsFriendsName(String petName);
4138+
4139+
PersonWithRelationship.PersonWithHobby findDistinctByHobbiesName(String hobbyName);
41274140
}
41284141

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

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1389,6 +1389,18 @@ void findByPropertyOnRelatedEntityOfRelatedSameEntity(@Autowired ReactiveRelatio
13891389
StepVerifier.create(repository.findByPetsFriendsName("Jerry")).verifyComplete();
13901390
}
13911391

1392+
@Test // GH-2243
1393+
void findDistinctByRelatedEntity(@Autowired ReactiveRelationshipRepository repository) {
1394+
try (Session session = createSession()) {
1395+
session.run("CREATE (n:PersonWithRelationship{name:'Freddie'})-[:Has]->(:Hobby{name: 'Music'})"
1396+
+ "CREATE (n)-[:Has]->(:Hobby{name: 'Music'})");
1397+
}
1398+
1399+
StepVerifier.create(repository.findDistinctByHobbiesName("Music"))
1400+
.assertNext(person -> assertThat(person).isNotNull())
1401+
.verifyComplete();
1402+
}
1403+
13921404
@Test
13931405
void findByPropertyOnRelationshipWithProperties(
13941406
@Autowired ReactivePersonWithRelationshipWithPropertiesRepository repository) {
@@ -2618,6 +2630,8 @@ interface ReactiveRelationshipRepository extends ReactiveNeo4jRepository<PersonW
26182630
Mono<PersonWithRelationship> findByPetsFriendsName(String petName);
26192631

26202632
Flux<PersonWithRelationship> findByName(String name, Sort sort);
2633+
2634+
Mono<PersonWithRelationship.PersonWithHobby> findDistinctByHobbiesName(String hobbyName);
26212635
}
26222636

26232637
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)