Skip to content

Commit cd63e53

Browse files
feature: Allow Cypher LIST<ANY> to be used directly as repository methods returning collections.
Signed-off-by: Michael Simons <[email protected]>
1 parent 67eecab commit cd63e53

File tree

8 files changed

+58
-6
lines changed

8 files changed

+58
-6
lines changed

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.util.function.Function;
2525
import java.util.function.Supplier;
2626
import java.util.stream.Collectors;
27+
import java.util.stream.Stream;
2728

2829
import org.neo4j.driver.Bookmark;
2930
import org.neo4j.driver.Driver;
@@ -472,7 +473,12 @@ public Collection<T> all() {
472473

473474
try (QueryRunner statementRunner = getQueryRunner(this.databaseSelection, this.impersonatedUser)) {
474475
Result result = runnableStatement.runWith(statementRunner);
475-
Collection<T> values = result.stream().map(partialMappingFunction(TypeSystem.getDefault())).filter(Objects::nonNull).collect(Collectors.toList());
476+
Collection<T> values = result.stream().flatMap(r -> {
477+
if (mappingFunction instanceof SingleValueMappingFunction && r.size() == 1 && r.get(0).hasType(TypeSystem.getDefault().LIST())) {
478+
return r.get(0).asList(v -> ((SingleValueMappingFunction<T>) mappingFunction).convertValue(v)).stream();
479+
}
480+
return Stream.of(partialMappingFunction(TypeSystem.getDefault()).apply(r));
481+
}).filter(Objects::nonNull).collect(Collectors.toList());
476482
ResultSummaries.process(result.consume());
477483
return values;
478484
} catch (RuntimeException e) {

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,13 @@ Mono<Tuple2<String, Map<String, Object>>> prepareStatement() {
408408
Flux<T> executeWith(Tuple2<String, Map<String, Object>> t, ReactiveQueryRunner runner) {
409409

410410
return Flux.usingWhen(Flux.from(runner.run(t.getT1(), t.getT2())),
411-
result -> Flux.from(result.records()).mapNotNull(r -> mappingFunction.apply(TypeSystem.getDefault(), r)),
411+
result -> Flux.from(result.records()).flatMap(r -> {
412+
if (mappingFunction instanceof SingleValueMappingFunction && r.size() == 1 && r.get(0).hasType(TypeSystem.getDefault().LIST())) {
413+
return Flux.fromStream(r.get(0).asList(v -> ((SingleValueMappingFunction<T>) mappingFunction).convertValue(v)).stream());
414+
}
415+
var item = mappingFunction.apply(TypeSystem.getDefault(), r);
416+
return item == null ? Flux.empty() : Flux.just(item);
417+
}),
412418
result -> Flux.from(result.consume()).doOnNext(ResultSummaries::process));
413419
}
414420

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1268,7 +1268,7 @@ public List<T> getResults() {
12681268
if (preparedQuery.resultsHaveBeenAggregated()) {
12691269
return all.stream().flatMap(nested -> ((Collection<T>) nested).stream()).distinct().collect(Collectors.toList());
12701270
}
1271-
return all.stream().collect(Collectors.toList());
1271+
return new ArrayList<>(all);
12721272
});
12731273
}
12741274

@@ -1328,8 +1328,8 @@ private Optional<Neo4jClient.RecordFetchSpec<T>> createFetchSpec() {
13281328

13291329
Neo4jClient.MappingSpec<T> newMappingSpec = neo4jClient.query(cypherQuery)
13301330
.bindAll(finalParameters).fetchAs(preparedQuery.getResultType());
1331-
return Optional.of(preparedQuery.getOptionalMappingFunction()
1332-
.map(newMappingSpec::mappedBy).orElse(newMappingSpec));
1331+
return preparedQuery.getOptionalMappingFunction()
1332+
.map(newMappingSpec::mappedBy).or(() -> Optional.of(newMappingSpec));
13331333
}
13341334

13351335
private NodesAndRelationshipsByIdStatementProvider createNodesAndRelationshipsByIdStatementProvider(Neo4jPersistentEntity<?> entityMetaData,

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,11 @@ public T apply(TypeSystem typeSystem, Record record) {
5454
throw new IllegalArgumentException("Records with more than one value cannot be converted without a mapper");
5555
}
5656

57-
Value source = record.get(0);
57+
return convertValue(record.get(0));
58+
}
59+
60+
@Nullable
61+
T convertValue(@Nullable Value source) {
5862
if (targetClass == Void.class || targetClass == void.class) {
5963
return null;
6064
}

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
@@ -265,6 +265,18 @@ RETURN id(n)
265265
RETURN *""");
266266
}
267267

268+
@Test
269+
void noDomainType(@Autowired PersonRepository repository) {
270+
var strings = repository.noDomainType();
271+
assertThat(strings).containsExactly("a", "b", "c");
272+
}
273+
274+
@Test
275+
void noDomainTypeWithListInQueryShouldWork(@Autowired PersonRepository repository) {
276+
var strings = repository.noDomainTypeWithListInQuery();
277+
assertThat(strings).containsExactly("a", "b", "c");
278+
}
279+
268280
@Test
269281
void findAll(@Autowired PersonRepository repository) {
270282

src/test/java/org/springframework/data/neo4j/integration/imperative/repositories/PersonRepository.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,12 @@ Optional<PersonWithAllConstructor> getOptionalPersonViaNamedQuery(@Param("part1"
131131
countQuery = "MATCH (n:PersonWithAllConstructor) WHERE n.name = $aName OR n.name = $anotherName RETURN count(n)")
132132
Page<PersonWithAllConstructor> findPageByCustomQueryWithCount(@Param("aName") String aName, @Param("anotherName") String anotherName, Pageable pageable);
133133

134+
@Query("UNWIND ['a', 'b', 'c'] AS x RETURN x")
135+
List<String> noDomainType();
136+
137+
@Query("RETURN ['a', 'b', 'c']")
138+
List<String> noDomainTypeWithListInQuery();
139+
134140
Long countAllByNameOrName(String aName, String anotherName);
135141

136142
Optional<PersonWithAllConstructor> findOneByNameAndFirstNameAllIgnoreCase(String name, String firstName);

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,18 @@ void findAllByIds(@Autowired ReactivePersonRepository repository) {
250250
.expectNextMatches(personList::contains).verifyComplete();
251251
}
252252

253+
@Test
254+
void noDomainType(@Autowired ReactivePersonRepository repository) {
255+
var strings = repository.noDomainTypeAsFlux();
256+
StepVerifier.create(strings).expectNext("a", "b", "c").verifyComplete();
257+
}
258+
259+
@Test
260+
void noDomainTypeWithListInQueryShouldWork(@Autowired ReactivePersonRepository repository) {
261+
var strings = repository.noDomainTypeWithListInQuery();
262+
StepVerifier.create(strings).expectNext("a", "b", "c").verifyComplete();
263+
}
264+
253265
@Test
254266
void findAllByIdsPublisher(@Autowired ReactivePersonRepository repository) {
255267

src/test/java/org/springframework/data/neo4j/integration/reactive/repositories/ReactivePersonRepository.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,12 @@ public interface ReactivePersonRepository extends ReactiveNeo4jRepository<Person
4848
@Query("MATCH (n:PersonWithAllConstructor{name:'Test'}) return n")
4949
Mono<PersonWithAllConstructor> getOnePersonViaQuery();
5050

51+
@Query("UNWIND ['a', 'b', 'c'] AS x RETURN x")
52+
Flux<String> noDomainTypeAsFlux();
53+
54+
@Query("RETURN ['a', 'b', 'c']")
55+
Flux<String> noDomainTypeWithListInQuery();
56+
5157
Mono<PersonWithAllConstructor> findOneByNameAndFirstName(String name, String firstName);
5258

5359
Mono<PersonWithAllConstructor> findOneByNameAndFirstNameAllIgnoreCase(String name, String firstName);

0 commit comments

Comments
 (0)