Skip to content

Commit 914b128

Browse files
committed
GH-2726 - Consider nested properties in fluent query.
1 parent 6a2aa53 commit 914b128

File tree

8 files changed

+118
-8
lines changed

8 files changed

+118
-8
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ public <NR> FetchableFluentQuery<NR> as(Class<NR> resultType) {
120120
public FetchableFluentQuery<R> project(Collection<String> properties) {
121121

122122
return new FetchableFluentQueryByExample<>(this.example, this.resultType, this.mappingContext, this.findOperation,
123-
this.countOperation, this.existsOperation, this.sort, this.limit, mergeProperties(properties));
123+
this.countOperation, this.existsOperation, this.sort, this.limit, mergeProperties(extractAllPaths(properties)));
124124
}
125125

126126
@Override

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ public <NR> FetchableFluentQuery<NR> as(Class<NR> resultType) {
125125
public FetchableFluentQuery<R> project(Collection<String> properties) {
126126

127127
return new FetchableFluentQueryByPredicate<>(this.predicate, this.mappingContext, this.metaData, this.resultType, this.findOperation,
128-
this.countOperation, this.existsOperation, this.sort, this.limit, mergeProperties(properties));
128+
this.countOperation, this.existsOperation, this.sort, this.limit, mergeProperties(extractAllPaths(properties)));
129129
}
130130

131131
@Override

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

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package org.springframework.data.neo4j.repository.query;
1717

18+
import java.util.Arrays;
1819
import java.util.Collection;
1920
import java.util.Collections;
2021
import java.util.HashSet;
@@ -97,12 +98,12 @@ final Window<R> scroll(ScrollPosition scrollPosition, List<R> rawResult, Neo4jPe
9798
Collections.reverse(rawResult);
9899
}
99100

100-
IntFunction<? extends ScrollPosition> ding = null;
101+
IntFunction<? extends ScrollPosition> positionFunction = null;
101102

102103
if (scrollPosition instanceof OffsetScrollPosition) {
103-
ding = OffsetScrollPosition.positionFunction(skip);
104+
positionFunction = OffsetScrollPosition.positionFunction(skip);
104105
} else {
105-
ding = v -> {
106+
positionFunction = v -> {
106107
var accessor = entity.getPropertyAccessor(rawResult.get(v));
107108
var keys = new LinkedHashMap<String, Object>();
108109
sort.forEach(o -> {
@@ -114,7 +115,22 @@ final Window<R> scroll(ScrollPosition scrollPosition, List<R> rawResult, Neo4jPe
114115
return ScrollPosition.forward(keys);
115116
};
116117
}
117-
return Window.from(getSubList(rawResult, limit, scrollDirection), ding, hasMoreElements(rawResult, limit));
118+
return Window.from(getSubList(rawResult, limit, scrollDirection), positionFunction, hasMoreElements(rawResult, limit));
119+
}
120+
121+
final Collection<String> extractAllPaths(Collection<String> projectingProperties) {
122+
if (projectingProperties.isEmpty()) {
123+
return new HashSet<>();
124+
}
125+
126+
Set<String> allPaths = new HashSet<>();
127+
for (String property : projectingProperties) {
128+
if (property.contains(".")) {
129+
allPaths.addAll(Arrays.stream(property.split("\\.")).toList());
130+
}
131+
allPaths.add(property);
132+
}
133+
return allPaths;
118134
}
119135

120136
private static boolean hasMoreElements(List<?> result, @Nullable Integer limit) {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ public <NR> ReactiveFluentQuery<NR> as(Class<NR> resultType) {
121121
public ReactiveFluentQuery<R> project(Collection<String> properties) {
122122

123123
return new ReactiveFluentQueryByExample<>(this.example, this.resultType, this.mappingContext, this.findOperation,
124-
this.countOperation, this.existsOperation, this.sort, this.limit, mergeProperties(properties));
124+
this.countOperation, this.existsOperation, this.sort, this.limit, mergeProperties(extractAllPaths(properties)));
125125
}
126126

127127
@Override

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ public <NR> ReactiveFluentQuery<NR> as(Class<NR> resultType) {
124124
public ReactiveFluentQuery<R> project(Collection<String> properties) {
125125

126126
return new ReactiveFluentQueryByPredicate<>(this.predicate, this.mappingContext, this.metaData, resultType, this.findOperation,
127-
this.countOperation, this.existsOperation, this.sort, this.limit, mergeProperties(properties));
127+
this.countOperation, this.existsOperation, this.sort, this.limit, mergeProperties(extractAllPaths(properties)));
128128
}
129129

130130
@Override

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

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2777,6 +2777,21 @@ RETURN id(n)
27772777
true, 1L, TEST_PERSON1_BORN_ON, "something", Arrays.asList("a", "b"), NEO4J_HQ, createdAt.toInstant());
27782778
person2 = new PersonWithAllConstructor(id2, TEST_PERSON2_NAME, TEST_PERSON2_FIRST_NAME, TEST_PERSON_SAMEVALUE,
27792779
false, 2L, TEST_PERSON2_BORN_ON, null, Collections.emptyList(), SFO, null);
2780+
2781+
transaction.run("""
2782+
CREATE (lhr:Airport {code: 'LHR', name: 'London Heathrow'})
2783+
CREATE (lax:Airport {code: 'LAX', name: 'Los Angeles'})
2784+
CREATE (cdg:Airport {code: 'CDG', name: 'Paris Charles de Gaulle'})
2785+
CREATE (f1:Flight {name: 'FL 001'})
2786+
CREATE (f2:Flight {name: 'FL 002'})
2787+
CREATE (f3:Flight {name: 'FL 003'})
2788+
CREATE (f1) -[:DEPARTS] ->(lhr)
2789+
CREATE (f1) -[:ARRIVES] ->(lax)
2790+
CREATE (f2) -[:DEPARTS] ->(lhr)
2791+
CREATE (f2) -[:ARRIVES] ->(cdg)
2792+
CREATE (f3) -[:DEPARTS] ->(lax)
2793+
CREATE (f3) -[:ARRIVES] ->(lhr)
2794+
""");
27802795
}
27812796

27822797
@Test
@@ -2847,6 +2862,24 @@ void findAllByExampleFluentProjecting(@Autowired PersonRepository repository) {
28472862
});
28482863
}
28492864

2865+
@Test
2866+
void findAllByExampleFluentProjectingRelationships(@Autowired FlightRepository repository) {
2867+
Example<Flight> example = Example.of(new Flight("FL 001", null, null),
2868+
ExampleMatcher.matchingAll().withIgnoreNullValues());
2869+
List<Flight> flights = repository.findBy(example,
2870+
q -> q.project("name", "departure.name").all());
2871+
2872+
assertThat(flights)
2873+
.hasSize(1)
2874+
.first().satisfies(p -> {
2875+
assertThat(p.getName()).isEqualTo("FL 001");
2876+
assertThat(p.getArrival()).isNull();
2877+
assertThat(p.getDeparture()).isNotNull();
2878+
assertThat(p.getDeparture().getName()).isEqualTo("London Heathrow");
2879+
assertThat(p.getDeparture().getCode()).isNull();
2880+
});
2881+
}
2882+
28502883
@Test // GH-2343
28512884
void findAllByExampleFluentAs(@Autowired PersonRepository repository) {
28522885

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

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
import org.springframework.data.neo4j.core.UserSelection;
7272
import org.springframework.data.neo4j.core.transaction.Neo4jBookmarkManager;
7373
import org.springframework.data.neo4j.core.transaction.ReactiveNeo4jTransactionManager;
74+
import org.springframework.data.neo4j.integration.reactive.repositories.ReactiveFlightRepository;
7475
import org.springframework.data.neo4j.integration.reactive.repositories.ReactivePersonRepository;
7576
import org.springframework.data.neo4j.integration.reactive.repositories.ReactiveThingRepository;
7677
import org.springframework.data.neo4j.integration.shared.common.AltHobby;
@@ -86,6 +87,7 @@
8687
import org.springframework.data.neo4j.integration.shared.common.DtoPersonProjection;
8788
import org.springframework.data.neo4j.integration.shared.common.EntitiesWithDynamicLabels;
8889
import org.springframework.data.neo4j.integration.shared.common.EntityWithConvertedId;
90+
import org.springframework.data.neo4j.integration.shared.common.Flight;
8991
import org.springframework.data.neo4j.integration.shared.common.Hobby;
9092
import org.springframework.data.neo4j.integration.shared.common.ImmutablePerson;
9193
import org.springframework.data.neo4j.integration.shared.common.LikesHobbyRelationship;
@@ -190,6 +192,21 @@ RETURN id(n)
190192

191193
person2 = new PersonWithAllConstructor(id2, TEST_PERSON2_NAME, TEST_PERSON2_FIRST_NAME, TEST_PERSON_SAMEVALUE,
192194
false, 2L, TEST_PERSON2_BORN_ON, null, Collections.emptyList(), SFO, null);
195+
196+
transaction.run("""
197+
CREATE (lhr:Airport {code: 'LHR', name: 'London Heathrow'})
198+
CREATE (lax:Airport {code: 'LAX', name: 'Los Angeles'})
199+
CREATE (cdg:Airport {code: 'CDG', name: 'Paris Charles de Gaulle'})
200+
CREATE (f1:Flight {name: 'FL 001'})
201+
CREATE (f2:Flight {name: 'FL 002'})
202+
CREATE (f3:Flight {name: 'FL 003'})
203+
CREATE (f1) -[:DEPARTS] ->(lhr)
204+
CREATE (f1) -[:ARRIVES] ->(lax)
205+
CREATE (f2) -[:DEPARTS] ->(lhr)
206+
CREATE (f2) -[:ARRIVES] ->(cdg)
207+
CREATE (f3) -[:DEPARTS] ->(lax)
208+
CREATE (f3) -[:ARRIVES] ->(lhr)
209+
""");
193210
}
194211

195212
@Test
@@ -337,6 +354,25 @@ void findAllByExampleFluentProjecting(@Autowired ReactivePersonRepository reposi
337354
}).verifyComplete();
338355
}
339356

357+
@Test
358+
void findAllByExampleFluentProjectingRelationships(@Autowired ReactiveFlightRepository repository) {
359+
360+
Example<Flight> example = Example.of(new Flight("FL 001", null, null),
361+
ExampleMatcher.matchingAll().withIgnoreNullValues());
362+
363+
repository.findBy(example, q -> q.project("name", "departure.name").all())
364+
.as(StepVerifier::create)
365+
.expectNextMatches(p -> {
366+
assertThat(p.getName()).isEqualTo("FL 001");
367+
assertThat(p.getArrival()).isNull();
368+
assertThat(p.getDeparture()).isNotNull();
369+
assertThat(p.getDeparture().getName()).isEqualTo("London Heathrow");
370+
assertThat(p.getDeparture().getCode()).isNull();
371+
372+
return true;
373+
}).verifyComplete();
374+
}
375+
340376
@Test // GH-2343
341377
void findAllByExampleFluentAs(@Autowired ReactivePersonRepository repository) {
342378

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright 2011-2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.neo4j.integration.reactive.repositories;
17+
18+
import org.springframework.data.neo4j.integration.shared.common.Flight;
19+
import org.springframework.data.neo4j.repository.ReactiveNeo4jRepository;
20+
21+
/**
22+
* @author Gerrit Meier
23+
*/
24+
public interface ReactiveFlightRepository extends ReactiveNeo4jRepository<Flight, Long> {
25+
}

0 commit comments

Comments
 (0)