GH-2726 - Consider nested properties in fluent query.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
|
||||
@@ -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> {
|
||||
}
|
||||
Reference in New Issue
Block a user