GH-2726 - Consider nested properties in fluent query.

This commit is contained in:
Gerrit Meier
2023-06-14 16:19:20 +02:00
parent 6a2aa539dc
commit 914b128f87
8 changed files with 118 additions and 8 deletions

View File

@@ -120,7 +120,7 @@ final class FetchableFluentQueryByExample<S, R> extends FluentQuerySupport<R> im
public FetchableFluentQuery<R> project(Collection<String> properties) {
return new FetchableFluentQueryByExample<>(this.example, this.resultType, this.mappingContext, this.findOperation,
this.countOperation, this.existsOperation, this.sort, this.limit, mergeProperties(properties));
this.countOperation, this.existsOperation, this.sort, this.limit, mergeProperties(extractAllPaths(properties)));
}
@Override

View File

@@ -125,7 +125,7 @@ final class FetchableFluentQueryByPredicate<S, R> extends FluentQuerySupport<R>
public FetchableFluentQuery<R> project(Collection<String> properties) {
return new FetchableFluentQueryByPredicate<>(this.predicate, this.mappingContext, this.metaData, this.resultType, this.findOperation,
this.countOperation, this.existsOperation, this.sort, this.limit, mergeProperties(properties));
this.countOperation, this.existsOperation, this.sort, this.limit, mergeProperties(extractAllPaths(properties)));
}
@Override

View File

@@ -15,6 +15,7 @@
*/
package org.springframework.data.neo4j.repository.query;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
@@ -97,12 +98,12 @@ abstract class FluentQuerySupport<R> {
Collections.reverse(rawResult);
}
IntFunction<? extends ScrollPosition> ding = null;
IntFunction<? extends ScrollPosition> positionFunction = null;
if (scrollPosition instanceof OffsetScrollPosition) {
ding = OffsetScrollPosition.positionFunction(skip);
positionFunction = OffsetScrollPosition.positionFunction(skip);
} else {
ding = v -> {
positionFunction = v -> {
var accessor = entity.getPropertyAccessor(rawResult.get(v));
var keys = new LinkedHashMap<String, Object>();
sort.forEach(o -> {
@@ -114,7 +115,22 @@ abstract class FluentQuerySupport<R> {
return ScrollPosition.forward(keys);
};
}
return Window.from(getSubList(rawResult, limit, scrollDirection), ding, hasMoreElements(rawResult, limit));
return Window.from(getSubList(rawResult, limit, scrollDirection), positionFunction, hasMoreElements(rawResult, limit));
}
final Collection<String> extractAllPaths(Collection<String> projectingProperties) {
if (projectingProperties.isEmpty()) {
return new HashSet<>();
}
Set<String> allPaths = new HashSet<>();
for (String property : projectingProperties) {
if (property.contains(".")) {
allPaths.addAll(Arrays.stream(property.split("\\.")).toList());
}
allPaths.add(property);
}
return allPaths;
}
private static boolean hasMoreElements(List<?> result, @Nullable Integer limit) {

View File

@@ -121,7 +121,7 @@ final class ReactiveFluentQueryByExample<S, R> extends FluentQuerySupport<R> imp
public ReactiveFluentQuery<R> project(Collection<String> properties) {
return new ReactiveFluentQueryByExample<>(this.example, this.resultType, this.mappingContext, this.findOperation,
this.countOperation, this.existsOperation, this.sort, this.limit, mergeProperties(properties));
this.countOperation, this.existsOperation, this.sort, this.limit, mergeProperties(extractAllPaths(properties)));
}
@Override

View File

@@ -124,7 +124,7 @@ import com.querydsl.core.types.Predicate;
public ReactiveFluentQuery<R> project(Collection<String> properties) {
return new ReactiveFluentQueryByPredicate<>(this.predicate, this.mappingContext, this.metaData, resultType, this.findOperation,
this.countOperation, this.existsOperation, this.sort, this.limit, mergeProperties(properties));
this.countOperation, this.existsOperation, this.sort, this.limit, mergeProperties(extractAllPaths(properties)));
}
@Override

View File

@@ -2777,6 +2777,21 @@ class RepositoryIT {
true, 1L, TEST_PERSON1_BORN_ON, "something", Arrays.asList("a", "b"), NEO4J_HQ, createdAt.toInstant());
person2 = new PersonWithAllConstructor(id2, TEST_PERSON2_NAME, TEST_PERSON2_FIRST_NAME, TEST_PERSON_SAMEVALUE,
false, 2L, TEST_PERSON2_BORN_ON, null, Collections.emptyList(), SFO, null);
transaction.run("""
CREATE (lhr:Airport {code: 'LHR', name: 'London Heathrow'})
CREATE (lax:Airport {code: 'LAX', name: 'Los Angeles'})
CREATE (cdg:Airport {code: 'CDG', name: 'Paris Charles de Gaulle'})
CREATE (f1:Flight {name: 'FL 001'})
CREATE (f2:Flight {name: 'FL 002'})
CREATE (f3:Flight {name: 'FL 003'})
CREATE (f1) -[:DEPARTS] ->(lhr)
CREATE (f1) -[:ARRIVES] ->(lax)
CREATE (f2) -[:DEPARTS] ->(lhr)
CREATE (f2) -[:ARRIVES] ->(cdg)
CREATE (f3) -[:DEPARTS] ->(lax)
CREATE (f3) -[:ARRIVES] ->(lhr)
""");
}
@Test
@@ -2847,6 +2862,24 @@ class RepositoryIT {
});
}
@Test
void findAllByExampleFluentProjectingRelationships(@Autowired FlightRepository repository) {
Example<Flight> example = Example.of(new Flight("FL 001", null, null),
ExampleMatcher.matchingAll().withIgnoreNullValues());
List<Flight> flights = repository.findBy(example,
q -> q.project("name", "departure.name").all());
assertThat(flights)
.hasSize(1)
.first().satisfies(p -> {
assertThat(p.getName()).isEqualTo("FL 001");
assertThat(p.getArrival()).isNull();
assertThat(p.getDeparture()).isNotNull();
assertThat(p.getDeparture().getName()).isEqualTo("London Heathrow");
assertThat(p.getDeparture().getCode()).isNull();
});
}
@Test // GH-2343
void findAllByExampleFluentAs(@Autowired PersonRepository repository) {

View File

@@ -71,6 +71,7 @@ import org.springframework.data.neo4j.core.ReactiveUserSelectionProvider;
import org.springframework.data.neo4j.core.UserSelection;
import org.springframework.data.neo4j.core.transaction.Neo4jBookmarkManager;
import org.springframework.data.neo4j.core.transaction.ReactiveNeo4jTransactionManager;
import org.springframework.data.neo4j.integration.reactive.repositories.ReactiveFlightRepository;
import org.springframework.data.neo4j.integration.reactive.repositories.ReactivePersonRepository;
import org.springframework.data.neo4j.integration.reactive.repositories.ReactiveThingRepository;
import org.springframework.data.neo4j.integration.shared.common.AltHobby;
@@ -86,6 +87,7 @@ import org.springframework.data.neo4j.integration.shared.common.DeepRelationship
import org.springframework.data.neo4j.integration.shared.common.DtoPersonProjection;
import org.springframework.data.neo4j.integration.shared.common.EntitiesWithDynamicLabels;
import org.springframework.data.neo4j.integration.shared.common.EntityWithConvertedId;
import org.springframework.data.neo4j.integration.shared.common.Flight;
import org.springframework.data.neo4j.integration.shared.common.Hobby;
import org.springframework.data.neo4j.integration.shared.common.ImmutablePerson;
import org.springframework.data.neo4j.integration.shared.common.LikesHobbyRelationship;
@@ -190,6 +192,21 @@ class ReactiveRepositoryIT {
person2 = new PersonWithAllConstructor(id2, TEST_PERSON2_NAME, TEST_PERSON2_FIRST_NAME, TEST_PERSON_SAMEVALUE,
false, 2L, TEST_PERSON2_BORN_ON, null, Collections.emptyList(), SFO, null);
transaction.run("""
CREATE (lhr:Airport {code: 'LHR', name: 'London Heathrow'})
CREATE (lax:Airport {code: 'LAX', name: 'Los Angeles'})
CREATE (cdg:Airport {code: 'CDG', name: 'Paris Charles de Gaulle'})
CREATE (f1:Flight {name: 'FL 001'})
CREATE (f2:Flight {name: 'FL 002'})
CREATE (f3:Flight {name: 'FL 003'})
CREATE (f1) -[:DEPARTS] ->(lhr)
CREATE (f1) -[:ARRIVES] ->(lax)
CREATE (f2) -[:DEPARTS] ->(lhr)
CREATE (f2) -[:ARRIVES] ->(cdg)
CREATE (f3) -[:DEPARTS] ->(lax)
CREATE (f3) -[:ARRIVES] ->(lhr)
""");
}
@Test
@@ -337,6 +354,25 @@ class ReactiveRepositoryIT {
}).verifyComplete();
}
@Test
void findAllByExampleFluentProjectingRelationships(@Autowired ReactiveFlightRepository repository) {
Example<Flight> example = Example.of(new Flight("FL 001", null, null),
ExampleMatcher.matchingAll().withIgnoreNullValues());
repository.findBy(example, q -> q.project("name", "departure.name").all())
.as(StepVerifier::create)
.expectNextMatches(p -> {
assertThat(p.getName()).isEqualTo("FL 001");
assertThat(p.getArrival()).isNull();
assertThat(p.getDeparture()).isNotNull();
assertThat(p.getDeparture().getName()).isEqualTo("London Heathrow");
assertThat(p.getDeparture().getCode()).isNull();
return true;
}).verifyComplete();
}
@Test // GH-2343
void findAllByExampleFluentAs(@Autowired ReactivePersonRepository repository) {

View File

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