GH-2726 - Add scroll support to FluentQuery.
This will implement limit() and scroll() methods in all (Reactive)FluentQuery.. classes. Closes #2726
This commit is contained in:
@@ -624,7 +624,7 @@ public enum CypherGenerator {
|
||||
}
|
||||
|
||||
public Collection<Expression> createReturnStatementForMatch(Neo4jPersistentEntity<?> nodeDescription) {
|
||||
return createReturnStatementForMatch(nodeDescription, (pp -> true));
|
||||
return createReturnStatementForMatch(nodeDescription, PropertyFilter.NO_FILTER);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -125,7 +125,7 @@ public final class NestedRelationshipContext {
|
||||
Object value = propertyAccessor.getProperty(inverse);
|
||||
boolean inverseValueIsEmpty = value == null;
|
||||
|
||||
RelationshipDescription relationship = neo4jPersistentEntity.getRelationshipsInHierarchy((pp -> true)).stream()
|
||||
RelationshipDescription relationship = neo4jPersistentEntity.getRelationshipsInHierarchy((PropertyFilter.NO_FILTER)).stream()
|
||||
.filter(r -> r.getFieldName().equals(inverse.getName())).findFirst().orElseThrow(() -> new MappingException(
|
||||
neo4jPersistentEntity.getName() + " does not define a relationship for " + inverse.getFieldName()));
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.apiguardian.api.API;
|
||||
import org.springframework.data.neo4j.core.schema.Property;
|
||||
@@ -39,6 +40,8 @@ public abstract class PropertyFilter {
|
||||
return new NonFilteringPropertyFilter();
|
||||
}
|
||||
|
||||
public static final Predicate<RelaxedPropertyPath> NO_FILTER = (pp) -> true;
|
||||
|
||||
public abstract boolean contains(String dotPath, Class<?> typeToCheck);
|
||||
|
||||
public abstract boolean contains(RelaxedPropertyPath propertyPath);
|
||||
|
||||
@@ -39,6 +39,7 @@ import org.springframework.data.domain.KeysetScrollPosition;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.ScrollPosition.Direction;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.neo4j.core.convert.Neo4jConversionService;
|
||||
import org.springframework.data.neo4j.core.mapping.Constants;
|
||||
import org.springframework.data.neo4j.core.mapping.Neo4jPersistentEntity;
|
||||
import org.springframework.data.neo4j.core.mapping.Neo4jPersistentProperty;
|
||||
@@ -98,7 +99,7 @@ public final class CypherAdapterUtils {
|
||||
};
|
||||
}
|
||||
|
||||
public static Condition combineKeysetIntoCondition(Neo4jPersistentEntity<?> entity, KeysetScrollPosition scrollPosition, Sort sort) {
|
||||
public static Condition combineKeysetIntoCondition(Neo4jPersistentEntity<?> entity, KeysetScrollPosition scrollPosition, Sort sort, Neo4jConversionService conversionService) {
|
||||
|
||||
var incomingKeys = scrollPosition.getKeys();
|
||||
var orderedKeys = new LinkedHashMap<String, Object>();
|
||||
@@ -135,7 +136,7 @@ public final class CypherAdapterUtils {
|
||||
if (v == null || (v instanceof Value value && value.isNull())) {
|
||||
throw new IllegalStateException("Cannot resume from KeysetScrollPosition. Offending key: '%s' is 'null'".formatted(k));
|
||||
}
|
||||
var parameter = Cypher.anonParameter(v);
|
||||
var parameter = Cypher.anonParameter(conversionService.convert(v, Value.class));
|
||||
|
||||
Expression expression;
|
||||
|
||||
|
||||
@@ -239,7 +239,7 @@ final class CypherQueryCreator extends AbstractQueryCreator<QueryFragmentsAndPar
|
||||
|
||||
queryFragments.setLimit(limitModifier.apply(maxResults.intValue()));
|
||||
if (!keysetScrollPosition.isInitial()) {
|
||||
conditionFragment = conditionFragment.and(CypherAdapterUtils.combineKeysetIntoCondition(entity, keysetScrollPosition, theSort));
|
||||
conditionFragment = conditionFragment.and(CypherAdapterUtils.combineKeysetIntoCondition(entity, keysetScrollPosition, theSort, mappingContext.getConversionService()));
|
||||
}
|
||||
|
||||
queryFragments.setRequiresReverseSort(keysetScrollPosition.scrollsBackward());
|
||||
|
||||
@@ -22,6 +22,7 @@ import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.LongSupplier;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.apiguardian.api.API;
|
||||
import org.neo4j.cypherdsl.core.Condition;
|
||||
@@ -35,6 +36,7 @@ import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.neo4j.core.Neo4jOperations;
|
||||
import org.springframework.data.neo4j.core.mapping.CypherGenerator;
|
||||
import org.springframework.data.neo4j.core.mapping.Neo4jPersistentEntity;
|
||||
import org.springframework.data.neo4j.core.mapping.PropertyFilter;
|
||||
import org.springframework.data.neo4j.repository.support.CypherdslConditionExecutor;
|
||||
import org.springframework.data.neo4j.repository.support.Neo4jEntityInformation;
|
||||
import org.springframework.data.support.PageableExecutionUtils;
|
||||
@@ -66,7 +68,7 @@ public final class CypherdslConditionExecutorImpl<T> implements CypherdslConditi
|
||||
|
||||
return this.neo4jOperations.toExecutableQuery(
|
||||
this.metaData.getType(),
|
||||
QueryFragmentsAndParameters.forCondition(this.metaData, condition, null, null)
|
||||
QueryFragmentsAndParameters.forCondition(this.metaData, condition)
|
||||
).getSingleResult();
|
||||
}
|
||||
|
||||
@@ -75,17 +77,18 @@ public final class CypherdslConditionExecutorImpl<T> implements CypherdslConditi
|
||||
|
||||
return this.neo4jOperations.toExecutableQuery(
|
||||
this.metaData.getType(),
|
||||
QueryFragmentsAndParameters.forCondition(this.metaData, condition, null, null)
|
||||
QueryFragmentsAndParameters.forCondition(this.metaData, condition)
|
||||
).getResults();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<T> findAll(Condition condition, Sort sort) {
|
||||
|
||||
Predicate<PropertyFilter.RelaxedPropertyPath> noFilter = PropertyFilter.NO_FILTER;
|
||||
return this.neo4jOperations.toExecutableQuery(
|
||||
metaData.getType(),
|
||||
QueryFragmentsAndParameters.forCondition(
|
||||
this.metaData, condition, null, CypherAdapterUtils.toSortItems(this.metaData, sort)
|
||||
QueryFragmentsAndParameters.forConditionAndSort(
|
||||
this.metaData, condition, sort, null, noFilter
|
||||
)
|
||||
).getResults();
|
||||
}
|
||||
@@ -95,8 +98,8 @@ public final class CypherdslConditionExecutorImpl<T> implements CypherdslConditi
|
||||
|
||||
return this.neo4jOperations.toExecutableQuery(
|
||||
this.metaData.getType(),
|
||||
QueryFragmentsAndParameters.forCondition(
|
||||
this.metaData, condition, null, Arrays.asList(sortItems)
|
||||
QueryFragmentsAndParameters.forConditionAndSortItems(
|
||||
this.metaData, condition, Arrays.asList(sortItems)
|
||||
)
|
||||
).getResults();
|
||||
}
|
||||
@@ -106,16 +109,17 @@ public final class CypherdslConditionExecutorImpl<T> implements CypherdslConditi
|
||||
|
||||
return this.neo4jOperations.toExecutableQuery(
|
||||
this.metaData.getType(),
|
||||
QueryFragmentsAndParameters.forCondition(this.metaData, Conditions.noCondition(), null, Arrays.asList(sortItems))
|
||||
QueryFragmentsAndParameters.forConditionAndSortItems(this.metaData, Conditions.noCondition(), Arrays.asList(sortItems))
|
||||
).getResults();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<T> findAll(Condition condition, Pageable pageable) {
|
||||
|
||||
Predicate<PropertyFilter.RelaxedPropertyPath> noFilter = PropertyFilter.NO_FILTER;
|
||||
List<T> page = this.neo4jOperations.toExecutableQuery(
|
||||
this.metaData.getType(),
|
||||
QueryFragmentsAndParameters.forCondition(this.metaData, condition, pageable, null)
|
||||
QueryFragmentsAndParameters.forConditionAndPageable(this.metaData, condition, pageable, noFilter)
|
||||
).getResults();
|
||||
LongSupplier totalCountSupplier = () -> this.count(condition);
|
||||
return PageableExecutionUtils.getPage(page, pageable, totalCountSupplier);
|
||||
|
||||
@@ -15,23 +15,29 @@
|
||||
*/
|
||||
package org.springframework.data.neo4j.repository.query;
|
||||
|
||||
import org.apiguardian.api.API;
|
||||
import org.neo4j.cypherdsl.core.Condition;
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.domain.KeysetScrollPosition;
|
||||
import org.springframework.data.domain.OffsetScrollPosition;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.ScrollPosition;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.domain.Window;
|
||||
import org.springframework.data.neo4j.core.FluentFindOperation;
|
||||
import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext;
|
||||
import org.springframework.data.neo4j.core.mapping.Neo4jPersistentEntity;
|
||||
import org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery;
|
||||
import org.springframework.data.support.PageableExecutionUtils;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.LongSupplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apiguardian.api.API;
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.neo4j.core.FluentFindOperation;
|
||||
import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext;
|
||||
import org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery;
|
||||
import org.springframework.data.support.PageableExecutionUtils;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Immutable implementation of a {@link FetchableFluentQuery}. All
|
||||
* methods that return a {@link FetchableFluentQuery} return a new instance, the original instance won't be
|
||||
@@ -64,7 +70,7 @@ final class FetchableFluentQueryByExample<S, R> extends FluentQuerySupport<R> im
|
||||
Function<Example<S>, Boolean> existsOperation
|
||||
) {
|
||||
this(example, resultType, mappingContext, findOperation, countOperation, existsOperation, Sort.unsorted(),
|
||||
null);
|
||||
null, null);
|
||||
}
|
||||
|
||||
FetchableFluentQueryByExample(
|
||||
@@ -75,9 +81,10 @@ final class FetchableFluentQueryByExample<S, R> extends FluentQuerySupport<R> im
|
||||
Function<Example<S>, Long> countOperation,
|
||||
Function<Example<S>, Boolean> existsOperation,
|
||||
Sort sort,
|
||||
@Nullable Integer limit,
|
||||
@Nullable Collection<String> properties
|
||||
) {
|
||||
super(resultType, sort, properties);
|
||||
super(resultType, sort, limit, properties);
|
||||
this.mappingContext = mappingContext;
|
||||
this.example = example;
|
||||
this.findOperation = findOperation;
|
||||
@@ -90,7 +97,14 @@ final class FetchableFluentQueryByExample<S, R> extends FluentQuerySupport<R> im
|
||||
public FetchableFluentQuery<R> sortBy(Sort sort) {
|
||||
|
||||
return new FetchableFluentQueryByExample<>(this.example, this.resultType, this.mappingContext, this.findOperation,
|
||||
this.countOperation, this.existsOperation, this.sort.and(sort), this.properties);
|
||||
this.countOperation, this.existsOperation, this.sort.and(sort), this.limit, this.properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("HiddenField")
|
||||
public FetchableFluentQuery<R> limit(int limit) {
|
||||
return new FetchableFluentQueryByExample<>(this.example, this.resultType, this.mappingContext, this.findOperation,
|
||||
this.countOperation, this.existsOperation, this.sort, limit, this.properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -106,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, mergeProperties(properties));
|
||||
this.countOperation, this.existsOperation, this.sort, this.limit, mergeProperties(properties));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -114,7 +128,7 @@ final class FetchableFluentQueryByExample<S, R> extends FluentQuerySupport<R> im
|
||||
|
||||
return findOperation.find(example.getProbeType())
|
||||
.as(resultType)
|
||||
.matching(QueryFragmentsAndParameters.forExample(mappingContext, example, sort,
|
||||
.matching(QueryFragmentsAndParameters.forExampleWithSort(mappingContext, example, sort, limit,
|
||||
createIncludedFieldsPredicate()))
|
||||
.oneValue();
|
||||
}
|
||||
@@ -131,7 +145,7 @@ final class FetchableFluentQueryByExample<S, R> extends FluentQuerySupport<R> im
|
||||
|
||||
return findOperation.find(example.getProbeType())
|
||||
.as(resultType)
|
||||
.matching(QueryFragmentsAndParameters.forExample(mappingContext, example, sort,
|
||||
.matching(QueryFragmentsAndParameters.forExampleWithSort(mappingContext, example, sort, limit,
|
||||
createIncludedFieldsPredicate()))
|
||||
.all();
|
||||
}
|
||||
@@ -141,7 +155,7 @@ final class FetchableFluentQueryByExample<S, R> extends FluentQuerySupport<R> im
|
||||
|
||||
List<R> page = findOperation.find(example.getProbeType())
|
||||
.as(resultType)
|
||||
.matching(QueryFragmentsAndParameters.forExample(mappingContext, example, pageable,
|
||||
.matching(QueryFragmentsAndParameters.forExampleWithPageable(mappingContext, example, pageable,
|
||||
createIncludedFieldsPredicate()))
|
||||
.all();
|
||||
|
||||
@@ -149,6 +163,28 @@ final class FetchableFluentQueryByExample<S, R> extends FluentQuerySupport<R> im
|
||||
return PageableExecutionUtils.getPage(page, pageable, totalCountSupplier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Window<R> scroll(ScrollPosition scrollPosition) {
|
||||
Class<S> domainType = this.example.getProbeType();
|
||||
Neo4jPersistentEntity<?> entity = mappingContext.getPersistentEntity(domainType);
|
||||
|
||||
var skip = scrollPosition.isInitial()
|
||||
? 0
|
||||
: (scrollPosition instanceof OffsetScrollPosition offsetScrollPosition) ? offsetScrollPosition.getOffset()
|
||||
: 0;
|
||||
|
||||
Condition condition = scrollPosition instanceof KeysetScrollPosition keysetScrollPosition
|
||||
? CypherAdapterUtils.combineKeysetIntoCondition(mappingContext.getPersistentEntity(example.getProbeType()), keysetScrollPosition, sort, mappingContext.getConversionService())
|
||||
: null;
|
||||
|
||||
List<R> rawResult = findOperation.find(domainType)
|
||||
.as(resultType)
|
||||
.matching(QueryFragmentsAndParameters.forExampleWithScrollPosition(mappingContext, example, condition, sort, limit == null ? 1 : limit + 1, skip, scrollPosition, createIncludedFieldsPredicate()))
|
||||
.all();
|
||||
|
||||
return scroll(scrollPosition, rawResult, entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<R> stream() {
|
||||
return all().stream();
|
||||
|
||||
@@ -23,10 +23,14 @@ import java.util.stream.Stream;
|
||||
|
||||
import org.apiguardian.api.API;
|
||||
import org.neo4j.cypherdsl.core.Cypher;
|
||||
import org.springframework.data.domain.KeysetScrollPosition;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.ScrollPosition;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.domain.Window;
|
||||
import org.springframework.data.neo4j.core.FluentFindOperation;
|
||||
import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext;
|
||||
import org.springframework.data.neo4j.core.mapping.Neo4jPersistentEntity;
|
||||
import org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery;
|
||||
import org.springframework.data.support.PageableExecutionUtils;
|
||||
@@ -58,29 +62,35 @@ final class FetchableFluentQueryByPredicate<S, R> extends FluentQuerySupport<R>
|
||||
|
||||
private final Function<Predicate, Boolean> existsOperation;
|
||||
|
||||
private final Neo4jMappingContext mappingContext;
|
||||
|
||||
FetchableFluentQueryByPredicate(
|
||||
Predicate predicate,
|
||||
Neo4jMappingContext mappingContext,
|
||||
Neo4jPersistentEntity<S> metaData,
|
||||
Class<R> resultType,
|
||||
FluentFindOperation findOperation,
|
||||
Function<Predicate, Long> countOperation,
|
||||
Function<Predicate, Boolean> existsOperation
|
||||
) {
|
||||
this(predicate, metaData, resultType, findOperation, countOperation, existsOperation, Sort.unsorted(), null);
|
||||
this(predicate, mappingContext, metaData, resultType, findOperation, countOperation, existsOperation, Sort.unsorted(), null, null);
|
||||
}
|
||||
|
||||
FetchableFluentQueryByPredicate(
|
||||
Predicate predicate,
|
||||
Neo4jMappingContext mappingContext,
|
||||
Neo4jPersistentEntity<S> metaData,
|
||||
Class<R> resultType,
|
||||
FluentFindOperation findOperation,
|
||||
Function<Predicate, Long> countOperation,
|
||||
Function<Predicate, Boolean> existsOperation,
|
||||
Sort sort,
|
||||
@Nullable Integer limit,
|
||||
@Nullable Collection<String> properties
|
||||
) {
|
||||
super(resultType, sort, properties);
|
||||
super(resultType, sort, limit, properties);
|
||||
this.predicate = predicate;
|
||||
this.mappingContext = mappingContext;
|
||||
this.metaData = metaData;
|
||||
this.findOperation = findOperation;
|
||||
this.countOperation = countOperation;
|
||||
@@ -91,15 +101,22 @@ final class FetchableFluentQueryByPredicate<S, R> extends FluentQuerySupport<R>
|
||||
@SuppressWarnings("HiddenField")
|
||||
public FetchableFluentQuery<R> sortBy(Sort sort) {
|
||||
|
||||
return new FetchableFluentQueryByPredicate<>(this.predicate, this.metaData, this.resultType, this.findOperation,
|
||||
this.countOperation, this.existsOperation, this.sort.and(sort), this.properties);
|
||||
return new FetchableFluentQueryByPredicate<>(this.predicate, this.mappingContext, this.metaData, this.resultType, this.findOperation,
|
||||
this.countOperation, this.existsOperation, this.sort.and(sort), this.limit, this.properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("HiddenField")
|
||||
public FetchableFluentQuery<R> limit(int limit) {
|
||||
return new FetchableFluentQueryByPredicate<>(this.predicate, this.mappingContext, this.metaData, this.resultType, this.findOperation,
|
||||
this.countOperation, this.existsOperation, this.sort, limit, this.properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("HiddenField")
|
||||
public <NR> FetchableFluentQuery<NR> as(Class<NR> resultType) {
|
||||
|
||||
return new FetchableFluentQueryByPredicate<>(this.predicate, this.metaData, resultType, this.findOperation,
|
||||
return new FetchableFluentQueryByPredicate<>(this.predicate, this.mappingContext, this.metaData, resultType, this.findOperation,
|
||||
this.countOperation, this.existsOperation);
|
||||
}
|
||||
|
||||
@@ -107,8 +124,8 @@ final class FetchableFluentQueryByPredicate<S, R> extends FluentQuerySupport<R>
|
||||
@SuppressWarnings("HiddenField")
|
||||
public FetchableFluentQuery<R> project(Collection<String> properties) {
|
||||
|
||||
return new FetchableFluentQueryByPredicate<>(this.predicate, this.metaData, this.resultType, this.findOperation,
|
||||
this.countOperation, this.existsOperation, sort, mergeProperties(properties));
|
||||
return new FetchableFluentQueryByPredicate<>(this.predicate, this.mappingContext, this.metaData, this.resultType, this.findOperation,
|
||||
this.countOperation, this.existsOperation, this.sort, this.limit, mergeProperties(properties));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -117,10 +134,10 @@ final class FetchableFluentQueryByPredicate<S, R> extends FluentQuerySupport<R>
|
||||
return findOperation.find(metaData.getType())
|
||||
.as(resultType)
|
||||
.matching(
|
||||
QueryFragmentsAndParameters.forCondition(metaData,
|
||||
QueryFragmentsAndParameters.forConditionAndSort(metaData,
|
||||
Cypher.adapt(predicate).asCondition(),
|
||||
null,
|
||||
CypherAdapterUtils.toSortItems(this.metaData, sort),
|
||||
sort,
|
||||
limit,
|
||||
createIncludedFieldsPredicate()))
|
||||
.oneValue();
|
||||
}
|
||||
@@ -138,10 +155,10 @@ final class FetchableFluentQueryByPredicate<S, R> extends FluentQuerySupport<R>
|
||||
return findOperation.find(metaData.getType())
|
||||
.as(resultType)
|
||||
.matching(
|
||||
QueryFragmentsAndParameters.forCondition(metaData,
|
||||
QueryFragmentsAndParameters.forConditionAndSort(metaData,
|
||||
Cypher.adapt(predicate).asCondition(),
|
||||
null,
|
||||
CypherAdapterUtils.toSortItems(this.metaData, sort),
|
||||
sort,
|
||||
limit,
|
||||
createIncludedFieldsPredicate()))
|
||||
.all();
|
||||
}
|
||||
@@ -152,9 +169,9 @@ final class FetchableFluentQueryByPredicate<S, R> extends FluentQuerySupport<R>
|
||||
List<R> page = findOperation.find(metaData.getType())
|
||||
.as(resultType)
|
||||
.matching(
|
||||
QueryFragmentsAndParameters.forCondition(metaData,
|
||||
QueryFragmentsAndParameters.forConditionAndPageable(metaData,
|
||||
Cypher.adapt(predicate).asCondition(),
|
||||
pageable, null,
|
||||
pageable,
|
||||
createIncludedFieldsPredicate()))
|
||||
.all();
|
||||
|
||||
@@ -162,6 +179,26 @@ final class FetchableFluentQueryByPredicate<S, R> extends FluentQuerySupport<R>
|
||||
return PageableExecutionUtils.getPage(page, pageable, totalCountSupplier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Window<R> scroll(ScrollPosition scrollPosition) {
|
||||
|
||||
QueryFragmentsAndParameters queryFragmentsAndParameters = QueryFragmentsAndParameters.forConditionWithScrollPosition(metaData,
|
||||
Cypher.adapt(predicate).asCondition(),
|
||||
(scrollPosition instanceof KeysetScrollPosition keysetScrollPosition
|
||||
? CypherAdapterUtils.combineKeysetIntoCondition(metaData, keysetScrollPosition, sort, mappingContext.getConversionService())
|
||||
: null),
|
||||
scrollPosition, sort,
|
||||
limit == null ? 1 : limit + 1,
|
||||
createIncludedFieldsPredicate());
|
||||
|
||||
List<R> rawResult = findOperation.find(metaData.getType())
|
||||
.as(resultType)
|
||||
.matching(queryFragmentsAndParameters)
|
||||
.all();
|
||||
|
||||
return scroll(scrollPosition, rawResult, metaData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<R> stream() {
|
||||
return all().stream();
|
||||
|
||||
@@ -18,10 +18,19 @@ package org.springframework.data.neo4j.repository.query;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.IntFunction;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.springframework.data.domain.KeysetScrollPosition;
|
||||
import org.springframework.data.domain.OffsetScrollPosition;
|
||||
import org.springframework.data.domain.ScrollPosition;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.domain.Window;
|
||||
import org.springframework.data.neo4j.core.mapping.Constants;
|
||||
import org.springframework.data.neo4j.core.mapping.Neo4jPersistentEntity;
|
||||
import org.springframework.data.neo4j.core.mapping.PropertyFilter;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
@@ -38,16 +47,20 @@ abstract class FluentQuerySupport<R> {
|
||||
|
||||
protected final Sort sort;
|
||||
|
||||
protected final Integer limit;
|
||||
|
||||
@Nullable
|
||||
protected final Set<String> properties;
|
||||
|
||||
FluentQuerySupport(
|
||||
Class<R> resultType,
|
||||
Sort sort,
|
||||
@Nullable Integer limit,
|
||||
@Nullable Collection<String> properties
|
||||
) {
|
||||
this.resultType = resultType;
|
||||
this.sort = sort;
|
||||
this.limit = limit;
|
||||
if (properties != null) {
|
||||
this.properties = new HashSet<>(properties);
|
||||
} else {
|
||||
@@ -57,8 +70,8 @@ abstract class FluentQuerySupport<R> {
|
||||
|
||||
final Predicate<PropertyFilter.RelaxedPropertyPath> createIncludedFieldsPredicate() {
|
||||
|
||||
if (this.properties == null) {
|
||||
return path -> true;
|
||||
if (this.properties == null || this.properties.isEmpty()) {
|
||||
return PropertyFilter.NO_FILTER;
|
||||
}
|
||||
return path -> this.properties.contains(path.toDotPath());
|
||||
}
|
||||
@@ -71,4 +84,49 @@ abstract class FluentQuerySupport<R> {
|
||||
newProperties.addAll(additionalProperties);
|
||||
return Collections.unmodifiableCollection(newProperties);
|
||||
}
|
||||
|
||||
final Window<R> scroll(ScrollPosition scrollPosition, List<R> rawResult, Neo4jPersistentEntity<?> entity) {
|
||||
|
||||
var skip = scrollPosition.isInitial()
|
||||
? 0
|
||||
: (scrollPosition instanceof OffsetScrollPosition offsetScrollPosition) ? offsetScrollPosition.getOffset()
|
||||
: 0;
|
||||
|
||||
var scrollDirection = scrollPosition instanceof KeysetScrollPosition keysetScrollPosition ? keysetScrollPosition.getDirection() : ScrollPosition.Direction.FORWARD;
|
||||
if (scrollDirection == ScrollPosition.Direction.BACKWARD) {
|
||||
Collections.reverse(rawResult);
|
||||
}
|
||||
|
||||
IntFunction<? extends ScrollPosition> ding = null;
|
||||
|
||||
if (scrollPosition instanceof OffsetScrollPosition) {
|
||||
ding = OffsetScrollPosition.positionFunction(skip);
|
||||
} else {
|
||||
ding = v -> {
|
||||
var accessor = entity.getPropertyAccessor(rawResult.get(v));
|
||||
var keys = new LinkedHashMap<String, Object>();
|
||||
sort.forEach(o -> {
|
||||
// Storing the graph property name here
|
||||
var persistentProperty = entity.getRequiredPersistentProperty(o.getProperty());
|
||||
keys.put(persistentProperty.getPropertyName(), accessor.getProperty(persistentProperty));
|
||||
});
|
||||
keys.put(Constants.NAME_OF_ADDITIONAL_SORT, accessor.getProperty(entity.getRequiredIdProperty()));
|
||||
return ScrollPosition.forward(keys);
|
||||
};
|
||||
}
|
||||
return Window.from(getSubList(rawResult, limit, scrollDirection), ding, hasMoreElements(rawResult, limit));
|
||||
}
|
||||
|
||||
private static boolean hasMoreElements(List<?> result, @Nullable Integer limit) {
|
||||
return !result.isEmpty() && result.size() > (limit != null ? limit : 0);
|
||||
}
|
||||
|
||||
private static <T> List<T> getSubList(List<T> result, @Nullable Integer limit, ScrollPosition.Direction scrollDirection) {
|
||||
|
||||
if (limit != null && limit > 0 && result.size() > limit) {
|
||||
return scrollDirection == ScrollPosition.Direction.FORWARD ? result.subList(0, limit) : result.subList(1, limit + 1);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,6 @@ import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.neo4j.driver.Value;
|
||||
import org.neo4j.driver.Values;
|
||||
import org.neo4j.driver.types.MapAccessor;
|
||||
import org.neo4j.driver.types.TypeSystem;
|
||||
@@ -292,15 +291,17 @@ abstract class Neo4jQuerySupport {
|
||||
return Window.from(getSubList(rawResult, limit, scrollDirection), v -> {
|
||||
if (scrollPosition instanceof OffsetScrollPosition offsetScrollPosition) {
|
||||
return offsetScrollPosition.advanceBy(v + limit);
|
||||
} else {
|
||||
} else {
|
||||
var accessor = neo4jPersistentEntity.getPropertyAccessor(rawResult.get(v));
|
||||
var keys = new LinkedHashMap<String, Object>();
|
||||
orderBy.getSort().forEach(o -> {
|
||||
// Storing the graph property name here
|
||||
var persistentProperty = neo4jPersistentEntity.getRequiredPersistentProperty(o.getProperty());
|
||||
keys.put(persistentProperty.getPropertyName(), conversionService.convert(accessor.getProperty(persistentProperty), Value.class));
|
||||
keys.put(persistentProperty.getPropertyName(), accessor.getProperty(persistentProperty));
|
||||
// keys.put(persistentProperty.getPropertyName(), conversionService.convert(accessor.getProperty(persistentProperty), Value.class));
|
||||
});
|
||||
keys.put(Constants.NAME_OF_ADDITIONAL_SORT, conversionService.convert(accessor.getProperty(neo4jPersistentEntity.getRequiredIdProperty()), Value.class));
|
||||
keys.put(Constants.NAME_OF_ADDITIONAL_SORT, accessor.getProperty(neo4jPersistentEntity.getRequiredIdProperty()));
|
||||
// keys.put(Constants.NAME_OF_ADDITIONAL_SORT, conversionService.convert(accessor.getProperty(neo4jPersistentEntity.getRequiredIdProperty()), Value.class));
|
||||
return ScrollPosition.forward(keys);
|
||||
}
|
||||
}, hasMoreElements(rawResult, limit));
|
||||
|
||||
@@ -24,7 +24,10 @@ import org.neo4j.cypherdsl.core.PatternElement;
|
||||
import org.neo4j.cypherdsl.core.RelationshipPattern;
|
||||
import org.neo4j.cypherdsl.core.SortItem;
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.domain.KeysetScrollPosition;
|
||||
import org.springframework.data.domain.OffsetScrollPosition;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.ScrollPosition;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.neo4j.core.mapping.Constants;
|
||||
import org.springframework.data.neo4j.core.mapping.CypherGenerator;
|
||||
@@ -172,32 +175,58 @@ public final class QueryFragmentsAndParameters {
|
||||
return new QueryFragmentsAndParameters(entityMetaData, queryFragments, parameters, null);
|
||||
}
|
||||
|
||||
public static QueryFragmentsAndParameters forPageableAndSort(Neo4jPersistentEntity<?> neo4jPersistentEntity,
|
||||
@Nullable Pageable pageable, @Nullable Sort sort) {
|
||||
|
||||
return getQueryFragmentsAndParameters(neo4jPersistentEntity, pageable, sort, null, null, null, Collections.emptyMap(), null, null, null);
|
||||
}
|
||||
|
||||
/*
|
||||
* Following methods are used by the Simple(Reactive)QueryByExampleExecutor
|
||||
*/
|
||||
static QueryFragmentsAndParameters forExample(Neo4jMappingContext mappingContext, Example<?> example) {
|
||||
return QueryFragmentsAndParameters.forExample(mappingContext, example,
|
||||
(java.util.function.Predicate<PropertyFilter.RelaxedPropertyPath>) null);
|
||||
return forExample(mappingContext, example, null, null, null, null, null, null, null);
|
||||
}
|
||||
|
||||
static QueryFragmentsAndParameters forExample(Neo4jMappingContext mappingContext, Example<?> example, @Nullable java.util.function.Predicate<PropertyFilter.RelaxedPropertyPath> includeField) {
|
||||
return QueryFragmentsAndParameters.forExample(mappingContext, example, null, null, includeField);
|
||||
static QueryFragmentsAndParameters forExampleWithPageable(Neo4jMappingContext mappingContext, Example<?> example, Pageable pageable, @Nullable java.util.function.Predicate<PropertyFilter.RelaxedPropertyPath> includeField) {
|
||||
return forExample(mappingContext, example, null, pageable, null, null, null, null, includeField);
|
||||
}
|
||||
|
||||
static QueryFragmentsAndParameters forExample(Neo4jMappingContext mappingContext, Example<?> example, Sort sort) {
|
||||
return QueryFragmentsAndParameters.forExample(mappingContext, example, sort, null);
|
||||
static QueryFragmentsAndParameters forExampleWithSort(Neo4jMappingContext mappingContext, Example<?> example, Sort sort, @Nullable Integer limit, @Nullable java.util.function.Predicate<PropertyFilter.RelaxedPropertyPath> includeField) {
|
||||
return forExample(mappingContext, example, null, null, sort, limit, null, null, includeField);
|
||||
}
|
||||
|
||||
static QueryFragmentsAndParameters forExample(Neo4jMappingContext mappingContext, Example<?> example, Sort sort, @Nullable java.util.function.Predicate<PropertyFilter.RelaxedPropertyPath> includeField) {
|
||||
return QueryFragmentsAndParameters.forExample(mappingContext, example, null, sort, includeField);
|
||||
static QueryFragmentsAndParameters forExampleWithScrollPosition(Neo4jMappingContext mappingContext, Example<?> example, @Nullable Condition keysetScrollPositionCondition, Sort sort, @Nullable Integer limit, @Nullable Long skip, ScrollPosition scrollPosition, @Nullable java.util.function.Predicate<PropertyFilter.RelaxedPropertyPath> includeField) {
|
||||
return forExample(mappingContext, example, keysetScrollPositionCondition, null, sort, limit, skip, scrollPosition, includeField);
|
||||
}
|
||||
|
||||
static QueryFragmentsAndParameters forExample(Neo4jMappingContext mappingContext, Example<?> example, Pageable pageable) {
|
||||
return QueryFragmentsAndParameters.forExample(mappingContext, example, pageable, null);
|
||||
}
|
||||
private static QueryFragmentsAndParameters forExample(Neo4jMappingContext mappingContext, Example<?> example,
|
||||
@Nullable Condition keysetScrollPositionCondition,
|
||||
@Nullable Pageable pageable,
|
||||
@Nullable Sort sort,
|
||||
@Nullable Integer limit,
|
||||
@Nullable Long skip,
|
||||
@Nullable ScrollPosition scrollPosition,
|
||||
@Nullable java.util.function.Predicate<PropertyFilter.RelaxedPropertyPath> includeField) {
|
||||
|
||||
static QueryFragmentsAndParameters forExample(Neo4jMappingContext mappingContext, Example<?> example, Pageable pageable, @Nullable java.util.function.Predicate<PropertyFilter.RelaxedPropertyPath> includeField) {
|
||||
return QueryFragmentsAndParameters.forExample(mappingContext, example, pageable, null, includeField);
|
||||
Predicate predicate = Predicate.create(mappingContext, example);
|
||||
Map<String, Object> parameters = predicate.getParameters();
|
||||
Set<PropertyPathWrapper> propertyPathWrappers = predicate.getPropertyPathWrappers();
|
||||
Condition condition = predicate.getCondition();
|
||||
Neo4jPersistentEntity<?> persistentEntity = mappingContext.getPersistentEntity(example.getProbeType());
|
||||
if (scrollPosition instanceof KeysetScrollPosition keysetScrollPosition) {
|
||||
|
||||
if (!keysetScrollPosition.isInitial()) {
|
||||
condition = condition.and(keysetScrollPositionCondition);
|
||||
}
|
||||
QueryFragmentsAndParameters queryFragmentsAndParameters = getQueryFragmentsAndParameters(persistentEntity, pageable,
|
||||
sort, null, limit, skip, parameters, condition, includeField, propertyPathWrappers);
|
||||
queryFragmentsAndParameters.getQueryFragments().setRequiresReverseSort(keysetScrollPosition.scrollsBackward());
|
||||
return queryFragmentsAndParameters;
|
||||
}
|
||||
|
||||
return getQueryFragmentsAndParameters(persistentEntity, pageable,
|
||||
sort, null, limit, skip, parameters, condition, includeField, propertyPathWrappers);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -208,78 +237,86 @@ public final class QueryFragmentsAndParameters {
|
||||
* @return Fully populated fragments and parameter
|
||||
*/
|
||||
@API(status = API.Status.EXPERIMENTAL, since = "6.1.7")
|
||||
public static QueryFragmentsAndParameters forCondition(Neo4jPersistentEntity<?> entityMetaData, Condition condition) {
|
||||
return forCondition(entityMetaData, condition, null, null);
|
||||
public static QueryFragmentsAndParameters forCondition(Neo4jPersistentEntity<?> entityMetaData,
|
||||
Condition condition) {
|
||||
|
||||
return forCondition(entityMetaData, condition, null, null, null, null, null, null);
|
||||
}
|
||||
|
||||
static QueryFragmentsAndParameters forCondition(Neo4jPersistentEntity<?> entityMetaData,
|
||||
Condition condition,
|
||||
@Nullable Pageable pageable,
|
||||
@Nullable Collection<SortItem> sortItems
|
||||
) {
|
||||
return forCondition(entityMetaData, condition, pageable, sortItems, null);
|
||||
static QueryFragmentsAndParameters forConditionAndPageable(Neo4jPersistentEntity<?> entityMetaData,
|
||||
Condition condition, Pageable pageable,
|
||||
java.util.function.Predicate<PropertyFilter.RelaxedPropertyPath> includeField) {
|
||||
|
||||
return forCondition(entityMetaData, condition, pageable, null, null, null, null, includeField);
|
||||
}
|
||||
|
||||
static QueryFragmentsAndParameters forCondition(Neo4jPersistentEntity<?> entityMetaData,
|
||||
static QueryFragmentsAndParameters forConditionAndSort(Neo4jPersistentEntity<?> entityMetaData, Condition condition, Sort sort, @Nullable Integer limit,
|
||||
@Nullable java.util.function.Predicate<PropertyFilter.RelaxedPropertyPath> includeField) {
|
||||
|
||||
return forCondition(entityMetaData, condition, null, sort, null, limit, null, includeField);
|
||||
}
|
||||
|
||||
static QueryFragmentsAndParameters forConditionAndSortItems(Neo4jPersistentEntity<?> entityMetaData, Condition condition, @Nullable Collection<SortItem> sortItems) {
|
||||
return forCondition(entityMetaData, condition, null, null, sortItems, null, null, null);
|
||||
}
|
||||
|
||||
static QueryFragmentsAndParameters forConditionWithScrollPosition(Neo4jPersistentEntity<?> entityMetaData,
|
||||
Condition condition,
|
||||
@Nullable Condition keysetCondition,
|
||||
ScrollPosition scrollPosition,
|
||||
@Nullable Sort sort,
|
||||
@Nullable Integer limit,
|
||||
@Nullable java.util.function.Predicate<PropertyFilter.RelaxedPropertyPath> includeField) {
|
||||
|
||||
long skip = 0L;
|
||||
|
||||
if (scrollPosition instanceof OffsetScrollPosition offsetScrollPosition) {
|
||||
skip = offsetScrollPosition.isInitial()
|
||||
? 0
|
||||
: offsetScrollPosition.getOffset();
|
||||
|
||||
return forCondition(entityMetaData, condition, null, sort, null, limit, skip, includeField);
|
||||
}
|
||||
|
||||
if (scrollPosition instanceof KeysetScrollPosition keysetScrollPosition) {
|
||||
if (!scrollPosition.isInitial()) {
|
||||
condition = condition.and(keysetCondition);
|
||||
}
|
||||
QueryFragmentsAndParameters queryFragmentsAndParameters = getQueryFragmentsAndParameters(entityMetaData, null,
|
||||
sort, null, limit, skip, Collections.emptyMap(), condition, includeField, null);
|
||||
queryFragmentsAndParameters.getQueryFragments().setRequiresReverseSort(keysetScrollPosition.scrollsBackward());
|
||||
return queryFragmentsAndParameters;
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("ScrollPosition must be of type OffsetScrollPosition or KeysetScrollPosition. Unexpected type %s found.".formatted(scrollPosition.getClass()));
|
||||
|
||||
}
|
||||
|
||||
// Parameter re-ordering helper
|
||||
private static QueryFragmentsAndParameters forCondition(Neo4jPersistentEntity<?> entityMetaData,
|
||||
Condition condition,
|
||||
@Nullable Pageable pageable,
|
||||
@Nullable Sort sort,
|
||||
@Nullable Collection<SortItem> sortItems,
|
||||
@Nullable Integer limit,
|
||||
@Nullable Long skip,
|
||||
@Nullable java.util.function.Predicate<PropertyFilter.RelaxedPropertyPath> includeField
|
||||
) {
|
||||
|
||||
QueryFragments queryFragments = new QueryFragments();
|
||||
queryFragments.addMatchOn(cypherGenerator.createRootNode(entityMetaData));
|
||||
queryFragments.setCondition(condition);
|
||||
if (includeField == null) {
|
||||
queryFragments.setReturnExpressions(cypherGenerator.createReturnStatementForMatch(entityMetaData));
|
||||
} else {
|
||||
queryFragments.setReturnExpressions(
|
||||
cypherGenerator.createReturnStatementForMatch(entityMetaData, includeField));
|
||||
}
|
||||
queryFragments.setRenderConstantsAsParameters(true);
|
||||
|
||||
if (pageable != null) {
|
||||
adaptPageable(entityMetaData, pageable, queryFragments);
|
||||
} else if (sortItems != null) {
|
||||
queryFragments.setOrderBy(sortItems);
|
||||
}
|
||||
|
||||
return new QueryFragmentsAndParameters(entityMetaData, queryFragments, Collections.emptyMap(), null);
|
||||
}
|
||||
|
||||
private static void adaptPageable(
|
||||
Neo4jPersistentEntity<?> entityMetaData,
|
||||
Pageable pageable,
|
||||
QueryFragments queryFragments
|
||||
) {
|
||||
Sort pageableSort = pageable.getSort();
|
||||
queryFragments.setSkip(pageable.getOffset());
|
||||
queryFragments.setLimit(pageable.getPageSize());
|
||||
queryFragments.setOrderBy(CypherAdapterUtils.toSortItems(entityMetaData, pageableSort));
|
||||
}
|
||||
|
||||
static QueryFragmentsAndParameters forExample(Neo4jMappingContext mappingContext, Example<?> example,
|
||||
@Nullable Pageable pageable, @Nullable Sort sort, java.util.function.Predicate<PropertyFilter.RelaxedPropertyPath> includeField) {
|
||||
|
||||
Predicate predicate = Predicate.create(mappingContext, example);
|
||||
Map<String, Object> parameters = predicate.getParameters();
|
||||
Set<PropertyPathWrapper> propertyPathWrappers = predicate.getPropertyPathWrappers();
|
||||
Condition condition = predicate.getCondition();
|
||||
|
||||
return getQueryFragmentsAndParameters(mappingContext.getPersistentEntity(example.getProbeType()), pageable,
|
||||
sort, parameters, condition, includeField, propertyPathWrappers);
|
||||
}
|
||||
|
||||
public static QueryFragmentsAndParameters forPageableAndSort(Neo4jPersistentEntity<?> neo4jPersistentEntity,
|
||||
@Nullable Pageable pageable, @Nullable Sort sort) {
|
||||
|
||||
return getQueryFragmentsAndParameters(neo4jPersistentEntity, pageable, sort, Collections.emptyMap(), null, null, null);
|
||||
return getQueryFragmentsAndParameters(entityMetaData, pageable, sort, sortItems, limit, skip, Collections.emptyMap(), condition, includeField, null);
|
||||
}
|
||||
|
||||
private static QueryFragmentsAndParameters getQueryFragmentsAndParameters(
|
||||
Neo4jPersistentEntity<?> entityMetaData, @Nullable Pageable pageable, @Nullable Sort sort,
|
||||
@Nullable Map<String, Object> parameters, @Nullable Condition condition, @Nullable
|
||||
java.util.function.Predicate<PropertyFilter.RelaxedPropertyPath> includeField,
|
||||
Neo4jPersistentEntity<?> entityMetaData,
|
||||
@Nullable Pageable pageable,
|
||||
@Nullable Sort sort,
|
||||
@Nullable Collection<SortItem> sortItems,
|
||||
@Nullable Integer limit,
|
||||
@Nullable Long skip,
|
||||
@Nullable Map<String, Object> parameters,
|
||||
@Nullable Condition condition,
|
||||
@Nullable java.util.function.Predicate<PropertyFilter.RelaxedPropertyPath> includeField,
|
||||
@Nullable Set<PropertyPathWrapper> propertyPathWrappers) {
|
||||
|
||||
QueryFragments queryFragments = new QueryFragments();
|
||||
@@ -305,11 +342,34 @@ public final class QueryFragmentsAndParameters {
|
||||
|
||||
if (pageable != null) {
|
||||
adaptPageable(entityMetaData, pageable, queryFragments);
|
||||
} else if (sort != null) {
|
||||
queryFragments.setOrderBy(CypherAdapterUtils.toSortItems(entityMetaData, sort));
|
||||
} else {
|
||||
if (sort != null) {
|
||||
queryFragments.setOrderBy(CypherAdapterUtils.toSortItems(entityMetaData, sort));
|
||||
} else if (sortItems != null) {
|
||||
queryFragments.setOrderBy(sortItems);
|
||||
}
|
||||
if (limit != null) {
|
||||
// we don't need to additionally pass the limit to the constructor
|
||||
// because it will get fetched from the QueryFragments later
|
||||
queryFragments.setLimit(limit);
|
||||
}
|
||||
if (skip != null) {
|
||||
queryFragments.setSkip(skip);
|
||||
}
|
||||
}
|
||||
|
||||
return new QueryFragmentsAndParameters(entityMetaData, queryFragments, parameters, sort);
|
||||
}
|
||||
|
||||
private static void adaptPageable(
|
||||
Neo4jPersistentEntity<?> entityMetaData,
|
||||
Pageable pageable,
|
||||
QueryFragments queryFragments
|
||||
) {
|
||||
Sort pageableSort = pageable.getSort();
|
||||
queryFragments.setSkip(pageable.getOffset());
|
||||
queryFragments.setLimit(pageable.getPageSize());
|
||||
queryFragments.setOrderBy(CypherAdapterUtils.toSortItems(entityMetaData, pageableSort));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.neo4j.core.FluentFindOperation;
|
||||
import org.springframework.data.neo4j.core.Neo4jOperations;
|
||||
import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext;
|
||||
import org.springframework.data.neo4j.core.mapping.Neo4jPersistentEntity;
|
||||
import org.springframework.data.neo4j.repository.support.CypherdslConditionExecutor;
|
||||
import org.springframework.data.neo4j.repository.support.Neo4jEntityInformation;
|
||||
@@ -65,9 +66,15 @@ public final class QuerydslNeo4jPredicateExecutor<T> implements QuerydslPredicat
|
||||
*/
|
||||
private final Neo4jPersistentEntity<T> metaData;
|
||||
|
||||
public QuerydslNeo4jPredicateExecutor(Neo4jEntityInformation<T, Object> entityInformation,
|
||||
Neo4jOperations neo4jOperations) {
|
||||
/**
|
||||
* Mapping context
|
||||
*/
|
||||
private final Neo4jMappingContext mappingContext;
|
||||
|
||||
public QuerydslNeo4jPredicateExecutor(Neo4jMappingContext mappingContext, Neo4jEntityInformation<T, Object> entityInformation,
|
||||
Neo4jOperations neo4jOperations) {
|
||||
|
||||
this.mappingContext = mappingContext;
|
||||
this.delegate = new CypherdslConditionExecutorImpl<>(entityInformation, neo4jOperations);
|
||||
this.neo4jOperations = neo4jOperations;
|
||||
this.metaData = entityInformation.getEntityMetaData();
|
||||
@@ -134,7 +141,7 @@ public final class QuerydslNeo4jPredicateExecutor<T> implements QuerydslPredicat
|
||||
if (this.neo4jOperations instanceof FluentFindOperation ops) {
|
||||
@SuppressWarnings("unchecked") // defaultResultType will be a supertype of S and at this stage, the same.
|
||||
FetchableFluentQuery<S> fluentQuery =
|
||||
(FetchableFluentQuery<S>) new FetchableFluentQueryByPredicate<>(predicate, metaData, metaData.getType(),
|
||||
(FetchableFluentQuery<S>) new FetchableFluentQueryByPredicate<>(predicate, mappingContext, metaData, metaData.getType(),
|
||||
ops, this::count, this::exists);
|
||||
return queryFunction.apply(fluentQuery);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.springframework.data.neo4j.repository.query;
|
||||
import static org.neo4j.cypherdsl.core.Cypher.asterisk;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.apiguardian.api.API;
|
||||
import org.neo4j.cypherdsl.core.Condition;
|
||||
@@ -29,6 +30,7 @@ import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.neo4j.core.ReactiveNeo4jOperations;
|
||||
import org.springframework.data.neo4j.core.mapping.CypherGenerator;
|
||||
import org.springframework.data.neo4j.core.mapping.Neo4jPersistentEntity;
|
||||
import org.springframework.data.neo4j.core.mapping.PropertyFilter;
|
||||
import org.springframework.data.neo4j.repository.support.ReactiveCypherdslConditionExecutor;
|
||||
import org.springframework.data.neo4j.repository.support.Neo4jEntityInformation;
|
||||
|
||||
@@ -63,7 +65,7 @@ public final class ReactiveCypherdslConditionExecutorImpl<T> implements Reactive
|
||||
|
||||
return this.neo4jOperations.toExecutableQuery(
|
||||
this.metaData.getType(),
|
||||
QueryFragmentsAndParameters.forCondition(this.metaData, condition, null, null)
|
||||
QueryFragmentsAndParameters.forCondition(this.metaData, condition)
|
||||
).flatMap(ReactiveNeo4jOperations.ExecutableQuery::getSingleResult);
|
||||
}
|
||||
|
||||
@@ -72,17 +74,18 @@ public final class ReactiveCypherdslConditionExecutorImpl<T> implements Reactive
|
||||
|
||||
return this.neo4jOperations.toExecutableQuery(
|
||||
this.metaData.getType(),
|
||||
QueryFragmentsAndParameters.forCondition(this.metaData, condition, null, null)
|
||||
QueryFragmentsAndParameters.forCondition(this.metaData, condition)
|
||||
).flatMapMany(ReactiveNeo4jOperations.ExecutableQuery::getResults);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<T> findAll(Condition condition, Sort sort) {
|
||||
|
||||
Predicate<PropertyFilter.RelaxedPropertyPath> noFilter = PropertyFilter.NO_FILTER;
|
||||
return this.neo4jOperations.toExecutableQuery(
|
||||
metaData.getType(),
|
||||
QueryFragmentsAndParameters.forCondition(
|
||||
this.metaData, condition, null, CypherAdapterUtils.toSortItems(this.metaData, sort)
|
||||
QueryFragmentsAndParameters.forConditionAndSort(
|
||||
this.metaData, condition, sort, null, noFilter
|
||||
)
|
||||
).flatMapMany(ReactiveNeo4jOperations.ExecutableQuery::getResults);
|
||||
}
|
||||
@@ -92,8 +95,8 @@ public final class ReactiveCypherdslConditionExecutorImpl<T> implements Reactive
|
||||
|
||||
return this.neo4jOperations.toExecutableQuery(
|
||||
this.metaData.getType(),
|
||||
QueryFragmentsAndParameters.forCondition(
|
||||
this.metaData, condition, null, Arrays.asList(sortItems)
|
||||
QueryFragmentsAndParameters.forConditionAndSortItems(
|
||||
this.metaData, condition, Arrays.asList(sortItems)
|
||||
)
|
||||
).flatMapMany(ReactiveNeo4jOperations.ExecutableQuery::getResults);
|
||||
}
|
||||
@@ -103,7 +106,7 @@ public final class ReactiveCypherdslConditionExecutorImpl<T> implements Reactive
|
||||
|
||||
return this.neo4jOperations.toExecutableQuery(
|
||||
this.metaData.getType(),
|
||||
QueryFragmentsAndParameters.forCondition(this.metaData, Conditions.noCondition(), null,
|
||||
QueryFragmentsAndParameters.forConditionAndSortItems(this.metaData, Conditions.noCondition(),
|
||||
Arrays.asList(sortItems))
|
||||
).flatMapMany(ReactiveNeo4jOperations.ExecutableQuery::getResults);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,12 @@
|
||||
*/
|
||||
package org.springframework.data.neo4j.repository.query;
|
||||
|
||||
import org.neo4j.cypherdsl.core.Condition;
|
||||
import org.springframework.data.domain.KeysetScrollPosition;
|
||||
import org.springframework.data.domain.OffsetScrollPosition;
|
||||
import org.springframework.data.domain.ScrollPosition;
|
||||
import org.springframework.data.domain.Window;
|
||||
import org.springframework.data.neo4j.core.mapping.Neo4jPersistentEntity;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@@ -64,7 +70,7 @@ final class ReactiveFluentQueryByExample<S, R> extends FluentQuerySupport<R> imp
|
||||
Function<Example<S>, Mono<Boolean>> existsOperation
|
||||
) {
|
||||
this(example, resultType, mappingContext, findOperation, countOperation, existsOperation, Sort.unsorted(),
|
||||
null);
|
||||
null, null);
|
||||
}
|
||||
|
||||
ReactiveFluentQueryByExample(
|
||||
@@ -75,9 +81,10 @@ final class ReactiveFluentQueryByExample<S, R> extends FluentQuerySupport<R> imp
|
||||
Function<Example<S>, Mono<Long>> countOperation,
|
||||
Function<Example<S>, Mono<Boolean>> existsOperation,
|
||||
Sort sort,
|
||||
@Nullable Integer limit,
|
||||
@Nullable Collection<String> properties
|
||||
) {
|
||||
super(resultType, sort, properties);
|
||||
super(resultType, sort, limit, properties);
|
||||
this.mappingContext = mappingContext;
|
||||
this.example = example;
|
||||
this.findOperation = findOperation;
|
||||
@@ -90,7 +97,15 @@ final class ReactiveFluentQueryByExample<S, R> extends FluentQuerySupport<R> imp
|
||||
public ReactiveFluentQuery<R> sortBy(Sort sort) {
|
||||
|
||||
return new ReactiveFluentQueryByExample<>(this.example, this.resultType, this.mappingContext, this.findOperation,
|
||||
this.countOperation, this.existsOperation, this.sort.and(sort), this.properties);
|
||||
this.countOperation, this.existsOperation, this.sort.and(sort), this.limit, this.properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("HiddenField")
|
||||
public ReactiveFluentQuery<R> limit(int limit) {
|
||||
|
||||
return new ReactiveFluentQueryByExample<>(this.example, this.resultType, this.mappingContext, this.findOperation,
|
||||
this.countOperation, this.existsOperation, this.sort, limit, this.properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -106,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, sort, mergeProperties(properties));
|
||||
this.countOperation, this.existsOperation, this.sort, this.limit, mergeProperties(properties));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -114,7 +129,7 @@ final class ReactiveFluentQueryByExample<S, R> extends FluentQuerySupport<R> imp
|
||||
|
||||
return findOperation.find(example.getProbeType())
|
||||
.as(resultType)
|
||||
.matching(QueryFragmentsAndParameters.forExample(mappingContext, example, sort,
|
||||
.matching(QueryFragmentsAndParameters.forExampleWithSort(mappingContext, example, sort, limit,
|
||||
createIncludedFieldsPredicate()))
|
||||
.one();
|
||||
}
|
||||
@@ -130,7 +145,7 @@ final class ReactiveFluentQueryByExample<S, R> extends FluentQuerySupport<R> imp
|
||||
|
||||
return findOperation.find(example.getProbeType())
|
||||
.as(resultType)
|
||||
.matching(QueryFragmentsAndParameters.forExample(mappingContext, example, sort,
|
||||
.matching(QueryFragmentsAndParameters.forExampleWithSort(mappingContext, example, sort, limit,
|
||||
createIncludedFieldsPredicate()))
|
||||
.all();
|
||||
}
|
||||
@@ -140,7 +155,7 @@ final class ReactiveFluentQueryByExample<S, R> extends FluentQuerySupport<R> imp
|
||||
|
||||
Flux<R> results = findOperation.find(example.getProbeType())
|
||||
.as(resultType)
|
||||
.matching(QueryFragmentsAndParameters.forExample(mappingContext, example, pageable,
|
||||
.matching(QueryFragmentsAndParameters.forExampleWithPageable(mappingContext, example, pageable,
|
||||
createIncludedFieldsPredicate()))
|
||||
.all();
|
||||
return results.collectList().zipWith(countOperation.apply(example)).map(tuple -> {
|
||||
@@ -149,6 +164,28 @@ final class ReactiveFluentQueryByExample<S, R> extends FluentQuerySupport<R> imp
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Window<R>> scroll(ScrollPosition scrollPosition) {
|
||||
Class<S> domainType = this.example.getProbeType();
|
||||
Neo4jPersistentEntity<?> entity = mappingContext.getPersistentEntity(domainType);
|
||||
|
||||
var skip = scrollPosition.isInitial()
|
||||
? 0
|
||||
: (scrollPosition instanceof OffsetScrollPosition offsetScrollPosition) ? offsetScrollPosition.getOffset()
|
||||
: 0;
|
||||
|
||||
Condition condition = scrollPosition instanceof KeysetScrollPosition keysetScrollPosition
|
||||
? CypherAdapterUtils.combineKeysetIntoCondition(mappingContext.getPersistentEntity(example.getProbeType()), keysetScrollPosition, sort, mappingContext.getConversionService())
|
||||
: null;
|
||||
|
||||
return findOperation.find(domainType)
|
||||
.as(resultType)
|
||||
.matching(QueryFragmentsAndParameters.forExampleWithScrollPosition(mappingContext, example, condition, sort, limit == null ? 1 : limit + 1, skip, scrollPosition, createIncludedFieldsPredicate()))
|
||||
.all()
|
||||
.collectList()
|
||||
.map(rawResult -> scroll(scrollPosition, rawResult, entity));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Long> count() {
|
||||
return countOperation.apply(example);
|
||||
|
||||
@@ -15,6 +15,10 @@
|
||||
*/
|
||||
package org.springframework.data.neo4j.repository.query;
|
||||
|
||||
import org.springframework.data.domain.KeysetScrollPosition;
|
||||
import org.springframework.data.domain.ScrollPosition;
|
||||
import org.springframework.data.domain.Window;
|
||||
import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@@ -57,29 +61,35 @@ import com.querydsl.core.types.Predicate;
|
||||
|
||||
private final Function<Predicate, Mono<Boolean>> existsOperation;
|
||||
|
||||
private final Neo4jMappingContext mappingContext;
|
||||
|
||||
ReactiveFluentQueryByPredicate(
|
||||
Predicate predicate,
|
||||
Neo4jMappingContext mappingContext,
|
||||
Neo4jPersistentEntity<S> metaData,
|
||||
Class<R> resultType,
|
||||
ReactiveFluentFindOperation findOperation,
|
||||
Function<Predicate, Mono<Long>> countOperation,
|
||||
Function<Predicate, Mono<Boolean>> existsOperation
|
||||
) {
|
||||
this(predicate, metaData, resultType, findOperation, countOperation, existsOperation, Sort.unsorted(), null);
|
||||
this(predicate, mappingContext, metaData, resultType, findOperation, countOperation, existsOperation, Sort.unsorted(), null, null);
|
||||
}
|
||||
|
||||
ReactiveFluentQueryByPredicate(
|
||||
Predicate predicate,
|
||||
Neo4jMappingContext mappingContext,
|
||||
Neo4jPersistentEntity<S> metaData,
|
||||
Class<R> resultType,
|
||||
ReactiveFluentFindOperation findOperation,
|
||||
Function<Predicate, Mono<Long>> countOperation,
|
||||
Function<Predicate, Mono<Boolean>> existsOperation,
|
||||
Sort sort,
|
||||
@Nullable Integer limit,
|
||||
@Nullable Collection<String> properties
|
||||
) {
|
||||
super(resultType, sort, properties);
|
||||
super(resultType, sort, limit, properties);
|
||||
this.predicate = predicate;
|
||||
this.mappingContext = mappingContext;
|
||||
this.metaData = metaData;
|
||||
this.findOperation = findOperation;
|
||||
this.countOperation = countOperation;
|
||||
@@ -90,15 +100,22 @@ import com.querydsl.core.types.Predicate;
|
||||
@SuppressWarnings("HiddenField")
|
||||
public ReactiveFluentQuery<R> sortBy(Sort sort) {
|
||||
|
||||
return new ReactiveFluentQueryByPredicate<>(this.predicate, this.metaData, this.resultType, this.findOperation,
|
||||
this.countOperation, this.existsOperation, this.sort.and(sort), this.properties);
|
||||
return new ReactiveFluentQueryByPredicate<>(this.predicate, this.mappingContext, this.metaData, this.resultType, this.findOperation,
|
||||
this.countOperation, this.existsOperation, this.sort.and(sort), this.limit, this.properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("HiddenField")
|
||||
public ReactiveFluentQuery<R> limit(int limit) {
|
||||
return new ReactiveFluentQueryByPredicate<>(this.predicate, this.mappingContext, this.metaData, this.resultType, this.findOperation,
|
||||
this.countOperation, this.existsOperation, this.sort, limit, this.properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("HiddenField")
|
||||
public <NR> ReactiveFluentQuery<NR> as(Class<NR> resultType) {
|
||||
|
||||
return new ReactiveFluentQueryByPredicate<>(this.predicate, this.metaData, resultType, this.findOperation,
|
||||
return new ReactiveFluentQueryByPredicate<>(this.predicate, this.mappingContext, this.metaData, resultType, this.findOperation,
|
||||
this.countOperation, this.existsOperation);
|
||||
}
|
||||
|
||||
@@ -106,8 +123,8 @@ import com.querydsl.core.types.Predicate;
|
||||
@SuppressWarnings("HiddenField")
|
||||
public ReactiveFluentQuery<R> project(Collection<String> properties) {
|
||||
|
||||
return new ReactiveFluentQueryByPredicate<>(this.predicate, this.metaData, resultType, this.findOperation,
|
||||
this.countOperation, this.existsOperation, sort, mergeProperties(properties));
|
||||
return new ReactiveFluentQueryByPredicate<>(this.predicate, this.mappingContext, this.metaData, resultType, this.findOperation,
|
||||
this.countOperation, this.existsOperation, this.sort, this.limit, mergeProperties(properties));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -116,10 +133,10 @@ import com.querydsl.core.types.Predicate;
|
||||
return findOperation.find(metaData.getType())
|
||||
.as(resultType)
|
||||
.matching(
|
||||
QueryFragmentsAndParameters.forCondition(metaData,
|
||||
QueryFragmentsAndParameters.forConditionAndSort(metaData,
|
||||
Cypher.adapt(predicate).asCondition(),
|
||||
null,
|
||||
CypherAdapterUtils.toSortItems(this.metaData, sort),
|
||||
sort,
|
||||
limit,
|
||||
createIncludedFieldsPredicate()))
|
||||
.one();
|
||||
}
|
||||
@@ -136,10 +153,10 @@ import com.querydsl.core.types.Predicate;
|
||||
return findOperation.find(metaData.getType())
|
||||
.as(resultType)
|
||||
.matching(
|
||||
QueryFragmentsAndParameters.forCondition(metaData,
|
||||
QueryFragmentsAndParameters.forConditionAndSort(metaData,
|
||||
Cypher.adapt(predicate).asCondition(),
|
||||
null,
|
||||
CypherAdapterUtils.toSortItems(this.metaData, sort),
|
||||
sort,
|
||||
limit,
|
||||
createIncludedFieldsPredicate()))
|
||||
.all();
|
||||
}
|
||||
@@ -150,9 +167,9 @@ import com.querydsl.core.types.Predicate;
|
||||
Flux<R> results = findOperation.find(metaData.getType())
|
||||
.as(resultType)
|
||||
.matching(
|
||||
QueryFragmentsAndParameters.forCondition(metaData,
|
||||
QueryFragmentsAndParameters.forConditionAndPageable(metaData,
|
||||
Cypher.adapt(predicate).asCondition(),
|
||||
pageable, null,
|
||||
pageable,
|
||||
createIncludedFieldsPredicate()))
|
||||
.all();
|
||||
|
||||
@@ -162,6 +179,25 @@ import com.querydsl.core.types.Predicate;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Window<R>> scroll(ScrollPosition scrollPosition) {
|
||||
QueryFragmentsAndParameters queryFragmentsAndParameters = QueryFragmentsAndParameters.forConditionWithScrollPosition(metaData,
|
||||
Cypher.adapt(predicate).asCondition(),
|
||||
(scrollPosition instanceof KeysetScrollPosition keysetScrollPosition
|
||||
? CypherAdapterUtils.combineKeysetIntoCondition(metaData, keysetScrollPosition, sort, mappingContext.getConversionService())
|
||||
: null),
|
||||
scrollPosition, sort,
|
||||
limit == null ? 1 : limit + 1,
|
||||
createIncludedFieldsPredicate());
|
||||
|
||||
return findOperation.find(metaData.getType())
|
||||
.as(resultType)
|
||||
.matching(queryFragmentsAndParameters)
|
||||
.all()
|
||||
.collectList()
|
||||
.map(rawResult -> scroll(scrollPosition, rawResult, metaData));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Long> count() {
|
||||
return countOperation.apply(predicate);
|
||||
|
||||
@@ -17,6 +17,7 @@ package org.springframework.data.neo4j.repository.query;
|
||||
|
||||
import static org.neo4j.cypherdsl.core.Cypher.asterisk;
|
||||
|
||||
import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@@ -63,9 +64,15 @@ public final class ReactiveQuerydslNeo4jPredicateExecutor<T> implements Reactive
|
||||
|
||||
private final Neo4jPersistentEntity<T> metaData;
|
||||
|
||||
public ReactiveQuerydslNeo4jPredicateExecutor(Neo4jEntityInformation<T, Object> entityInformation,
|
||||
/**
|
||||
* Mapping context
|
||||
*/
|
||||
private final Neo4jMappingContext mappingContext;
|
||||
|
||||
public ReactiveQuerydslNeo4jPredicateExecutor(Neo4jMappingContext mappingContext, Neo4jEntityInformation<T, Object> entityInformation,
|
||||
ReactiveNeo4jOperations neo4jOperations) {
|
||||
|
||||
this.mappingContext = mappingContext;
|
||||
this.entityInformation = entityInformation;
|
||||
this.neo4jOperations = neo4jOperations;
|
||||
this.metaData = this.entityInformation.getEntityMetaData();
|
||||
@@ -76,8 +83,7 @@ public final class ReactiveQuerydslNeo4jPredicateExecutor<T> implements Reactive
|
||||
|
||||
return this.neo4jOperations.toExecutableQuery(
|
||||
this.metaData.getType(),
|
||||
QueryFragmentsAndParameters.forCondition(this.metaData, Cypher.adapt(predicate).asCondition(), null,
|
||||
null)
|
||||
QueryFragmentsAndParameters.forCondition(this.metaData, Cypher.adapt(predicate).asCondition())
|
||||
).flatMap(ReactiveNeo4jOperations.ExecutableQuery::getSingleResult);
|
||||
}
|
||||
|
||||
@@ -108,7 +114,7 @@ public final class ReactiveQuerydslNeo4jPredicateExecutor<T> implements Reactive
|
||||
private Flux<T> doFindAll(Condition condition, Collection<SortItem> sortItems) {
|
||||
return this.neo4jOperations.toExecutableQuery(
|
||||
this.metaData.getType(),
|
||||
QueryFragmentsAndParameters.forCondition(this.metaData, condition, null,
|
||||
QueryFragmentsAndParameters.forConditionAndSortItems(this.metaData, condition,
|
||||
sortItems)
|
||||
).flatMapMany(ReactiveNeo4jOperations.ExecutableQuery::getResults);
|
||||
}
|
||||
@@ -132,7 +138,7 @@ public final class ReactiveQuerydslNeo4jPredicateExecutor<T> implements Reactive
|
||||
|
||||
if (this.neo4jOperations instanceof ReactiveFluentFindOperation ops) {
|
||||
@SuppressWarnings("unchecked") // defaultResultType will be a supertype of S and at this stage, the same.
|
||||
ReactiveFluentQuery<S> fluentQuery = (ReactiveFluentQuery<S>) new ReactiveFluentQueryByPredicate<>(predicate, metaData, metaData.getType(),
|
||||
ReactiveFluentQuery<S> fluentQuery = (ReactiveFluentQuery<S>) new ReactiveFluentQueryByPredicate<>(predicate, mappingContext, metaData, metaData.getType(),
|
||||
ops, this::count, this::exists);
|
||||
return queryFunction.apply(fluentQuery);
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import org.springframework.data.neo4j.core.FluentFindOperation;
|
||||
import org.springframework.data.neo4j.core.Neo4jOperations;
|
||||
import org.springframework.data.neo4j.core.mapping.CypherGenerator;
|
||||
import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext;
|
||||
import org.springframework.data.neo4j.core.mapping.PropertyFilter;
|
||||
import org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery;
|
||||
import org.springframework.data.repository.query.QueryByExampleExecutor;
|
||||
import org.springframework.data.support.PageableExecutionUtils;
|
||||
@@ -76,14 +77,14 @@ public final class SimpleQueryByExampleExecutor<T> implements QueryByExampleExec
|
||||
@Override
|
||||
public <S extends T> List<S> findAll(Example<S> example, Sort sort) {
|
||||
return this.neo4jOperations.toExecutableQuery(example.getProbeType(),
|
||||
QueryFragmentsAndParameters.forExample(mappingContext, example, sort)).getResults();
|
||||
QueryFragmentsAndParameters.forExampleWithSort(mappingContext, example, sort, null, PropertyFilter.NO_FILTER)).getResults();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S extends T> Page<S> findAll(Example<S> example, Pageable pageable) {
|
||||
|
||||
List<S> page = this.neo4jOperations.toExecutableQuery(example.getProbeType(),
|
||||
QueryFragmentsAndParameters.forExample(mappingContext, example, pageable)).getResults();
|
||||
QueryFragmentsAndParameters.forExampleWithPageable(mappingContext, example, pageable, PropertyFilter.NO_FILTER)).getResults();
|
||||
|
||||
LongSupplier totalCountSupplier = () -> this.count(example);
|
||||
return PageableExecutionUtils.getPage(page, pageable, totalCountSupplier);
|
||||
|
||||
@@ -25,6 +25,7 @@ import org.springframework.data.neo4j.core.ReactiveFluentFindOperation;
|
||||
import org.springframework.data.neo4j.core.ReactiveNeo4jOperations;
|
||||
import org.springframework.data.neo4j.core.mapping.CypherGenerator;
|
||||
import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext;
|
||||
import org.springframework.data.neo4j.core.mapping.PropertyFilter;
|
||||
import org.springframework.data.repository.query.FluentQuery.ReactiveFluentQuery;
|
||||
import org.springframework.data.repository.query.ReactiveQueryByExampleExecutor;
|
||||
import reactor.core.publisher.Flux;
|
||||
@@ -75,7 +76,7 @@ public final class SimpleReactiveQueryByExampleExecutor<T> implements ReactiveQu
|
||||
@Override
|
||||
public <S extends T> Flux<S> findAll(Example<S> example, Sort sort) {
|
||||
return this.neo4jOperations
|
||||
.toExecutableQuery(example.getProbeType(), QueryFragmentsAndParameters.forExample(mappingContext, example, sort))
|
||||
.toExecutableQuery(example.getProbeType(), QueryFragmentsAndParameters.forExampleWithSort(mappingContext, example, sort, null, PropertyFilter.NO_FILTER))
|
||||
.flatMapMany(ReactiveNeo4jOperations.ExecutableQuery::getResults);
|
||||
}
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ final class Neo4jRepositoryFactory extends RepositoryFactorySupport {
|
||||
|
||||
if (isQueryDslRepository) {
|
||||
|
||||
fragments = fragments.append(createDSLExecutorFragment(metadata, QuerydslNeo4jPredicateExecutor.class));
|
||||
fragments = fragments.append(createDSLPredicateExecutorFragment(metadata, QuerydslNeo4jPredicateExecutor.class));
|
||||
}
|
||||
|
||||
if (CypherdslConditionExecutor.class.isAssignableFrom(metadata.getRepositoryInterface())) {
|
||||
@@ -101,6 +101,14 @@ final class Neo4jRepositoryFactory extends RepositoryFactorySupport {
|
||||
return fragments;
|
||||
}
|
||||
|
||||
private RepositoryFragment<Object> createDSLPredicateExecutorFragment(RepositoryMetadata metadata, Class<?> implementor) {
|
||||
|
||||
Neo4jEntityInformation<?, Object> entityInformation = getEntityInformation(metadata.getDomainType());
|
||||
Object querydslFragment = instantiateClass(implementor, mappingContext, entityInformation, neo4jOperations);
|
||||
|
||||
return RepositoryFragment.implemented(querydslFragment);
|
||||
}
|
||||
|
||||
private RepositoryFragment<Object> createDSLExecutorFragment(RepositoryMetadata metadata, Class<?> implementor) {
|
||||
|
||||
Neo4jEntityInformation<?, Object> entityInformation = getEntityInformation(metadata.getDomainType());
|
||||
|
||||
@@ -92,7 +92,7 @@ final class ReactiveNeo4jRepositoryFactory extends ReactiveRepositoryFactorySupp
|
||||
|
||||
if (isQueryDslRepository) {
|
||||
|
||||
fragments = fragments.append(createDSLExecutorFragment(metadata, ReactiveQuerydslNeo4jPredicateExecutor.class));
|
||||
fragments = fragments.append(createDSLPredicateExecutorFragment(metadata, ReactiveQuerydslNeo4jPredicateExecutor.class));
|
||||
}
|
||||
|
||||
if (ReactiveCypherdslConditionExecutor.class.isAssignableFrom(metadata.getRepositoryInterface())) {
|
||||
@@ -103,6 +103,14 @@ final class ReactiveNeo4jRepositoryFactory extends ReactiveRepositoryFactorySupp
|
||||
return fragments;
|
||||
}
|
||||
|
||||
private RepositoryFragment<Object> createDSLPredicateExecutorFragment(RepositoryMetadata metadata, Class<?> implementor) {
|
||||
|
||||
Neo4jEntityInformation<?, Object> entityInformation = getEntityInformation(metadata.getDomainType());
|
||||
Object querydslFragment = instantiateClass(implementor, mappingContext, entityInformation, neo4jOperations);
|
||||
|
||||
return RepositoryFragment.implemented(querydslFragment);
|
||||
}
|
||||
|
||||
private RepositoryFragment<Object> createDSLExecutorFragment(RepositoryMetadata metadata, Class<?> implementor) {
|
||||
|
||||
Neo4jEntityInformation<?, Object> entityInformation = getEntityInformation(metadata.getDomainType());
|
||||
|
||||
@@ -18,9 +18,11 @@ package org.springframework.data.neo4j.integration.imperative;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.neo4j.driver.Driver;
|
||||
import org.neo4j.driver.Session;
|
||||
@@ -28,9 +30,12 @@ import org.neo4j.driver.Transaction;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.domain.KeysetScrollPosition;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.ScrollPosition;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.domain.Window;
|
||||
import org.springframework.data.neo4j.test.Neo4jImperativeTestConfiguration;
|
||||
import org.springframework.data.neo4j.core.DatabaseSelectionProvider;
|
||||
import org.springframework.data.neo4j.core.transaction.Neo4jBookmarkManager;
|
||||
@@ -126,6 +131,96 @@ class QuerydslNeo4jPredicateExecutorIT {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tag("GH-2726")
|
||||
void scrollByExampleWithNoOffset(@Autowired QueryDSLPersonRepository repository) {
|
||||
Predicate predicate = Expressions.predicate(Ops.EQ, firstNamePath, Expressions.asString("Helge"))
|
||||
.or(Expressions.predicate(Ops.EQ, lastNamePath, Expressions.asString("B.")));
|
||||
|
||||
Window<Person> peopleWindow = repository.findBy(predicate, q -> q.limit(1).scroll(ScrollPosition.offset(0)));
|
||||
|
||||
assertThat(peopleWindow.getContent()).extracting(Person::getFirstName)
|
||||
.containsExactlyInAnyOrder("Helge");
|
||||
|
||||
assertThat(peopleWindow.isLast()).isFalse();
|
||||
assertThat(peopleWindow.hasNext()).isTrue();
|
||||
|
||||
assertThat(peopleWindow.positionAt(peopleWindow.getContent().get(0))).isEqualTo(ScrollPosition.offset(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tag("GH-2726")
|
||||
void scrollByExampleWithOffset(@Autowired QueryDSLPersonRepository repository) {
|
||||
Predicate predicate = Expressions.predicate(Ops.EQ, firstNamePath, Expressions.asString("Helge"))
|
||||
.or(Expressions.predicate(Ops.EQ, lastNamePath, Expressions.asString("B.")));
|
||||
|
||||
Window<Person> peopleWindow = repository.findBy(predicate, q -> q.limit(1).scroll(ScrollPosition.offset(1)));
|
||||
|
||||
assertThat(peopleWindow.getContent()).extracting(Person::getFirstName)
|
||||
.containsExactlyInAnyOrder("Bela");
|
||||
|
||||
assertThat(peopleWindow.isLast()).isTrue();
|
||||
|
||||
assertThat(peopleWindow.positionAt(peopleWindow.getContent().get(0))).isEqualTo(ScrollPosition.offset(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tag("GH-2726")
|
||||
void scrollByExampleWithContinuingOffset(@Autowired QueryDSLPersonRepository repository) {
|
||||
Predicate predicate = Expressions.predicate(Ops.EQ, firstNamePath, Expressions.asString("Helge"))
|
||||
.or(Expressions.predicate(Ops.EQ, lastNamePath, Expressions.asString("B.")));
|
||||
|
||||
Window<Person> peopleWindow = repository.findBy(predicate, q -> q.limit(1).scroll(ScrollPosition.offset(0)));
|
||||
ScrollPosition currentPosition = peopleWindow.positionAt(peopleWindow.getContent().get(0));
|
||||
peopleWindow = repository.findBy(predicate, q -> q.limit(1).scroll(currentPosition));
|
||||
|
||||
assertThat(peopleWindow.getContent()).extracting(Person::getFirstName)
|
||||
.containsExactlyInAnyOrder("Bela");
|
||||
|
||||
assertThat(peopleWindow.isLast()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tag("GH-2726")
|
||||
void scrollByExampleWithKeysetOffset(@Autowired QueryDSLPersonRepository repository) {
|
||||
Predicate predicate = Expressions.predicate(Ops.EQ, firstNamePath, Expressions.asString("Helge"))
|
||||
.or(Expressions.predicate(Ops.EQ, lastNamePath, Expressions.asString("B.")));
|
||||
|
||||
Window<Person> peopleWindow = repository.findBy(predicate, q -> q.sortBy(Sort.by("firstName")).limit(1).scroll(ScrollPosition.keyset()));
|
||||
assertThat(peopleWindow.getContent()).extracting(Person::getFirstName)
|
||||
.containsExactly("Bela");
|
||||
|
||||
ScrollPosition currentPosition = peopleWindow.positionAt(peopleWindow.size() - 1);
|
||||
peopleWindow = repository.findBy(predicate, q -> q.limit(1).scroll(currentPosition));
|
||||
|
||||
assertThat(peopleWindow.getContent()).extracting(Person::getFirstName)
|
||||
.containsExactlyInAnyOrder("Helge");
|
||||
|
||||
assertThat(peopleWindow.isLast()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tag("GH-2726")
|
||||
void scrollByExampleWithKeysetOffsetBackward(@Autowired QueryDSLPersonRepository repository) {
|
||||
Predicate predicate = Expressions.predicate(Ops.EQ, firstNamePath, Expressions.asString("Helge"))
|
||||
.or(Expressions.predicate(Ops.EQ, lastNamePath, Expressions.asString("B.")));
|
||||
|
||||
KeysetScrollPosition startPosition = ScrollPosition.backward(Map.of(
|
||||
"lastName", "Schneider"
|
||||
));
|
||||
Window<Person> peopleWindow = repository.findBy(predicate, q -> q.sortBy(Sort.by("firstName")).limit(1).scroll(startPosition));
|
||||
assertThat(peopleWindow.getContent()).extracting(Person::getFirstName)
|
||||
.containsExactly("Helge");
|
||||
|
||||
var nextPos = ScrollPosition.backward(
|
||||
((KeysetScrollPosition) peopleWindow.positionAt(0)).getKeys());
|
||||
|
||||
peopleWindow = repository.findBy(predicate, q -> q.limit(1).scroll(nextPos));
|
||||
|
||||
assertThat(peopleWindow.getContent()).extracting(Person::getFirstName)
|
||||
.containsExactlyInAnyOrder("Bela");
|
||||
}
|
||||
|
||||
static class DtoPersonProjection {
|
||||
|
||||
private final String firstName;
|
||||
@@ -222,6 +317,18 @@ class QuerydslNeo4jPredicateExecutorIT {
|
||||
assertThat(count).isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test // GH-2726
|
||||
void fluentFindAllWithLimitShouldWork(@Autowired QueryDSLPersonRepository repository) {
|
||||
|
||||
Predicate predicate = Expressions.predicate(Ops.EQ, firstNamePath, Expressions.asString("Helge"))
|
||||
.or(Expressions.predicate(Ops.EQ, lastNamePath, Expressions.asString("B.")));
|
||||
List<Person> people = repository.findBy(predicate,
|
||||
q -> q.limit(1)).all();
|
||||
|
||||
assertThat(people).hasSize(1);
|
||||
assertThat(people).extracting(Person::getFirstName).containsExactly("Helge");
|
||||
}
|
||||
|
||||
@Test
|
||||
void findOneShouldWork(@Autowired QueryDSLPersonRepository repository) {
|
||||
|
||||
|
||||
@@ -84,6 +84,7 @@ import org.springframework.data.domain.Range.Bound;
|
||||
import org.springframework.data.domain.ScrollPosition;
|
||||
import org.springframework.data.domain.Slice;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.domain.Window;
|
||||
import org.springframework.data.geo.Box;
|
||||
import org.springframework.data.geo.Circle;
|
||||
import org.springframework.data.geo.Distance;
|
||||
@@ -2880,6 +2881,23 @@ class RepositoryIT {
|
||||
assertThat(person).isEqualTo(person1);
|
||||
}
|
||||
|
||||
@Test // GH-2726
|
||||
void scrollByExample(@Autowired PersonRepository repository) {
|
||||
|
||||
PersonWithAllConstructor sameValuePerson = new PersonWithAllConstructor(null, null, null, TEST_PERSON_SAMEVALUE, null, null, null, null, null, null, null);
|
||||
|
||||
Example<PersonWithAllConstructor> example = Example.of(sameValuePerson,
|
||||
ExampleMatcher.matchingAll().withIgnoreNullValues());
|
||||
Window<PersonWithAllConstructor> person = repository.findBy(example, q -> q.sortBy(Sort.by("name")).limit(1).scroll(ScrollPosition.offset(0)));
|
||||
|
||||
assertThat(person).isNotNull();
|
||||
assertThat(person.getContent().get(0)).isEqualTo(person1);
|
||||
|
||||
ScrollPosition currentPosition = person.positionAt(person1);
|
||||
person = repository.findBy(example, q -> q.sortBy(Sort.by("name")).limit(1).scroll(currentPosition));
|
||||
assertThat(person.getContent().get(0)).isEqualTo(person2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void findAllByExampleWithDifferentMatchers(@Autowired PersonRepository repository) {
|
||||
|
||||
@@ -2964,6 +2982,16 @@ class RepositoryIT {
|
||||
assertThat(persons).containsExactly(person2);
|
||||
}
|
||||
|
||||
@Test // GH-2726
|
||||
void findAllByExampleWithScrollFluent(@Autowired PersonRepository repository) {
|
||||
|
||||
Example<PersonWithAllConstructor> example = Example.of(personExample(TEST_PERSON_SAMEVALUE));
|
||||
Window<PersonWithAllConstructor> persons = repository.findBy(example,
|
||||
q -> q.sortBy(Sort.by("name")).limit(1).scroll(ScrollPosition.keyset().forward()));
|
||||
|
||||
assertThat(persons.getContent()).containsExactly(person1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void existsByExample(@Autowired PersonRepository repository) {
|
||||
|
||||
|
||||
@@ -23,12 +23,15 @@ import java.util.function.Function;
|
||||
|
||||
import org.assertj.core.data.Index;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.neo4j.driver.Driver;
|
||||
import org.neo4j.driver.Values;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.domain.ExampleMatcher;
|
||||
import org.springframework.data.domain.KeysetScrollPosition;
|
||||
import org.springframework.data.domain.ScrollPosition;
|
||||
import org.springframework.data.neo4j.core.DatabaseSelectionProvider;
|
||||
@@ -107,6 +110,32 @@ class ScrollingIT {
|
||||
.containsExactly("H0", "I0");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tag("GH-2726")
|
||||
void forwardWithFluentQueryByExample(@Autowired ScrollingRepository scrollingRepository) {
|
||||
ScrollingEntity scrollingEntity = new ScrollingEntity();
|
||||
Example<ScrollingEntity> example = Example.of(scrollingEntity, ExampleMatcher.matchingAll().withIgnoreNullValues());
|
||||
|
||||
var window = scrollingRepository.findBy(example, q -> q.sortBy(ScrollingEntity.SORT_BY_C).limit(4).scroll(ScrollPosition.keyset()));
|
||||
assertThat(window.hasNext()).isTrue();
|
||||
assertThat(window)
|
||||
.hasSize(4)
|
||||
.extracting(ScrollingEntity::getA)
|
||||
.containsExactly("A0", "B0", "C0", "D0");
|
||||
|
||||
ScrollPosition newPosition = ScrollPosition.forward(((KeysetScrollPosition) window.positionAt(window.size() - 1)).getKeys());
|
||||
window = scrollingRepository.findBy(example, q -> q.sortBy(ScrollingEntity.SORT_BY_C).limit(4).scroll(newPosition));
|
||||
assertThat(window)
|
||||
.hasSize(4)
|
||||
.extracting(ScrollingEntity::getA)
|
||||
.containsExactly("D0", "E0", "F0", "G0");
|
||||
|
||||
window = scrollingRepository.findTop4By(ScrollingEntity.SORT_BY_C, window.positionAt(window.size() - 1));
|
||||
assertThat(window.isLast()).isTrue();
|
||||
assertThat(window).extracting(ScrollingEntity::getA)
|
||||
.containsExactly("H0", "I0");
|
||||
}
|
||||
|
||||
@Test
|
||||
void forwardWithDuplicatesIteratorIteration(@Autowired ScrollingRepository repository) {
|
||||
|
||||
@@ -162,6 +191,42 @@ class ScrollingIT {
|
||||
.containsExactly("A0", "B0");
|
||||
}
|
||||
|
||||
@Test
|
||||
void backwardWithFluentQueryByExample(@Autowired ScrollingRepository repository) {
|
||||
|
||||
ScrollingEntity scrollingEntity = new ScrollingEntity();
|
||||
Example<ScrollingEntity> example = Example.of(scrollingEntity, ExampleMatcher.matchingAll().withIgnoreNullValues());
|
||||
|
||||
var last = repository.findFirstByA("I0");
|
||||
var keys = Map.of(
|
||||
"c", last.getC(),
|
||||
Constants.NAME_OF_ADDITIONAL_SORT, Values.value(last.getId().toString())
|
||||
);
|
||||
|
||||
var window = repository.findBy(example, q -> q.sortBy(ScrollingEntity.SORT_BY_C).limit(4).scroll(ScrollPosition.backward(keys)));
|
||||
assertThat(window.hasNext()).isTrue();
|
||||
assertThat(window)
|
||||
.hasSize(4)
|
||||
.extracting(ScrollingEntity::getA)
|
||||
.containsExactly("F0", "G0", "H0", "I0");
|
||||
|
||||
var pos = ((KeysetScrollPosition) window.positionAt(0));
|
||||
var nextPos = ScrollPosition.backward(pos.getKeys());
|
||||
window = repository.findBy(example, q -> q.sortBy(ScrollingEntity.SORT_BY_C).limit(4).scroll(nextPos));
|
||||
assertThat(window.hasNext()).isTrue();
|
||||
assertThat(window)
|
||||
.hasSize(4)
|
||||
.extracting(Function.identity())
|
||||
.extracting(ScrollingEntity::getA)
|
||||
.containsExactly("C0", "D0", "D0", "E0");
|
||||
|
||||
var nextNextPos = ScrollPosition.backward(((KeysetScrollPosition) window.positionAt(0)).getKeys());
|
||||
window = repository.findBy(example, q -> q.sortBy(ScrollingEntity.SORT_BY_C).limit(4).scroll(nextNextPos));
|
||||
assertThat(window.isLast()).isTrue();
|
||||
assertThat(window).extracting(ScrollingEntity::getA)
|
||||
.containsExactly("A0", "B0");
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableNeo4jRepositories
|
||||
@EnableTransactionManagement
|
||||
|
||||
@@ -17,6 +17,8 @@ package org.springframework.data.neo4j.integration.reactive;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.springframework.data.domain.ScrollPosition;
|
||||
import org.springframework.data.neo4j.test.Neo4jReactiveTestConfiguration;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
@@ -188,6 +190,70 @@ class ReactiveQuerydslNeo4jPredicateExecutorIT {
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tag("GH-2726")
|
||||
void scrollByExampleWithNoOffset(@Autowired QueryDSLPersonRepository repository) {
|
||||
Predicate predicate = Expressions.predicate(Ops.EQ, firstNamePath, Expressions.asString("Helge"))
|
||||
.or(Expressions.predicate(Ops.EQ, lastNamePath, Expressions.asString("B.")));
|
||||
|
||||
repository.findBy(predicate, q -> q.limit(1).scroll(ScrollPosition.offset(0)))
|
||||
.as(StepVerifier::create)
|
||||
.expectNextMatches(peopleWindow -> {
|
||||
|
||||
assertThat(peopleWindow.getContent()).extracting(Person::getFirstName)
|
||||
.containsExactlyInAnyOrder("Helge");
|
||||
|
||||
assertThat(peopleWindow.isLast()).isFalse();
|
||||
assertThat(peopleWindow.hasNext()).isTrue();
|
||||
|
||||
assertThat(peopleWindow.positionAt(peopleWindow.getContent().get(0))).isEqualTo(ScrollPosition.offset(1));
|
||||
return true;
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tag("GH-2726")
|
||||
void scrollByExampleWithOffset(@Autowired QueryDSLPersonRepository repository) {
|
||||
Predicate predicate = Expressions.predicate(Ops.EQ, firstNamePath, Expressions.asString("Helge"))
|
||||
.or(Expressions.predicate(Ops.EQ, lastNamePath, Expressions.asString("B.")));
|
||||
|
||||
repository.findBy(predicate, q -> q.limit(1).scroll(ScrollPosition.offset(1)))
|
||||
.as(StepVerifier::create)
|
||||
.expectNextMatches(peopleWindow -> {
|
||||
assertThat(peopleWindow.getContent()).extracting(Person::getFirstName)
|
||||
.containsExactlyInAnyOrder("Bela");
|
||||
|
||||
assertThat(peopleWindow.isLast()).isTrue();
|
||||
assertThat(peopleWindow.positionAt(peopleWindow.getContent().get(0))).isEqualTo(ScrollPosition.offset(2));
|
||||
return true;
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tag("GH-2726")
|
||||
void scrollByExampleWithContinuingOffset(@Autowired QueryDSLPersonRepository repository) {
|
||||
Predicate predicate = Expressions.predicate(Ops.EQ, firstNamePath, Expressions.asString("Helge"))
|
||||
.or(Expressions.predicate(Ops.EQ, lastNamePath, Expressions.asString("B.")));
|
||||
|
||||
repository.findBy(predicate, q -> q.limit(1).scroll(ScrollPosition.offset(0)))
|
||||
.as(StepVerifier::create)
|
||||
.expectNextMatches(peopleWindow -> {
|
||||
ScrollPosition currentPosition = peopleWindow.positionAt(peopleWindow.getContent().get(0));
|
||||
repository.findBy(predicate, q -> q.limit(1).scroll(currentPosition))
|
||||
.as(StepVerifier::create)
|
||||
.expectNextMatches(nextPeopleWindow -> {
|
||||
|
||||
assertThat(nextPeopleWindow.getContent()).extracting(Person::getFirstName)
|
||||
.containsExactlyInAnyOrder("Bela");
|
||||
|
||||
assertThat(nextPeopleWindow.isLast()).isTrue();
|
||||
return true;
|
||||
});
|
||||
return true;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Test // GH-2361
|
||||
void fluentExistsShouldWork(@Autowired QueryDSLPersonRepository repository) {
|
||||
|
||||
|
||||
@@ -60,6 +60,7 @@ import org.springframework.core.convert.ConverterNotFoundException;
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.domain.ExampleMatcher;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.ScrollPosition;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.data.neo4j.core.DatabaseSelection;
|
||||
@@ -360,6 +361,30 @@ class ReactiveRepositoryIT {
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // GH-2726
|
||||
void scrollByExample(@Autowired ReactivePersonRepository repository) {
|
||||
|
||||
PersonWithAllConstructor sameValuePerson = new PersonWithAllConstructor(null, null, null, TEST_PERSON_SAMEVALUE, null, null, null, null, null, null, null);
|
||||
|
||||
Example<PersonWithAllConstructor> example = Example.of(sameValuePerson,
|
||||
ExampleMatcher.matchingAll().withIgnoreNullValues());
|
||||
repository.findBy(example, q -> q.sortBy(Sort.by("name")).limit(1).scroll(ScrollPosition.offset(0)))
|
||||
.as(StepVerifier::create)
|
||||
.expectNextMatches(person -> {
|
||||
assertThat(person).isNotNull();
|
||||
assertThat(person.getContent().get(0)).isEqualTo(person1);
|
||||
|
||||
ScrollPosition currentPosition = person.positionAt(person1);
|
||||
repository.findBy(example, q -> q.sortBy(Sort.by("name")).limit(1).scroll(currentPosition))
|
||||
.as(StepVerifier::create)
|
||||
.expectNextMatches(nextPerson -> {
|
||||
assertThat(nextPerson.getContent().get(0)).isEqualTo(person2);
|
||||
return true;
|
||||
});
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void findAllByExampleWithDifferentMatchers(@Autowired ReactivePersonRepository repository) {
|
||||
PersonWithAllConstructor person;
|
||||
|
||||
@@ -24,12 +24,15 @@ import java.util.function.Function;
|
||||
|
||||
import org.assertj.core.data.Index;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.neo4j.driver.Driver;
|
||||
import org.neo4j.driver.Values;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.domain.ExampleMatcher;
|
||||
import org.springframework.data.domain.KeysetScrollPosition;
|
||||
import org.springframework.data.domain.ScrollPosition;
|
||||
import org.springframework.data.domain.Window;
|
||||
@@ -180,6 +183,96 @@ class ReactiveScrollingIT {
|
||||
.containsExactly("A0", "B0");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tag("GH-2726")
|
||||
void forwardWithFluentQueryByExample(@Autowired ReactiveScrollingRepository repository) {
|
||||
ScrollingEntity scrollingEntity = new ScrollingEntity();
|
||||
Example<ScrollingEntity> example = Example.of(scrollingEntity, ExampleMatcher.matchingAll().withIgnoreNullValues());
|
||||
|
||||
var windowContainer = new AtomicReference<Window<ScrollingEntity>>();
|
||||
repository.findBy(example, q -> q.sortBy(ScrollingEntity.SORT_BY_C).limit(4).scroll(ScrollPosition.keyset()))
|
||||
.as(StepVerifier::create)
|
||||
.consumeNextWith(windowContainer::set)
|
||||
.verifyComplete();
|
||||
var window = windowContainer.get();
|
||||
assertThat(window.hasNext()).isTrue();
|
||||
assertThat(window)
|
||||
.hasSize(4)
|
||||
.extracting(ScrollingEntity::getA)
|
||||
.containsExactly("A0", "B0", "C0", "D0");
|
||||
|
||||
ScrollPosition nextScrollPosition = window.positionAt(window.size() - 1);
|
||||
repository.findBy(example, q -> q.sortBy(ScrollingEntity.SORT_BY_C).limit(4).scroll(nextScrollPosition))
|
||||
.as(StepVerifier::create)
|
||||
.consumeNextWith(windowContainer::set)
|
||||
.verifyComplete();
|
||||
window = windowContainer.get();
|
||||
assertThat(window.hasNext()).isTrue();
|
||||
assertThat(window)
|
||||
.hasSize(4)
|
||||
.extracting(ScrollingEntity::getA)
|
||||
.containsExactly("D0", "E0", "F0", "G0");
|
||||
|
||||
ScrollPosition nextNextScrollPosition = window.positionAt(window.size() - 1);
|
||||
repository.findBy(example, q -> q.sortBy(ScrollingEntity.SORT_BY_C).limit(4).scroll(nextNextScrollPosition))
|
||||
.as(StepVerifier::create)
|
||||
.consumeNextWith(windowContainer::set)
|
||||
.verifyComplete();
|
||||
window = windowContainer.get();
|
||||
assertThat(window.isLast()).isTrue();
|
||||
assertThat(window).extracting(ScrollingEntity::getA)
|
||||
.containsExactly("H0", "I0");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tag("GH-2726")
|
||||
void backwardWithFluentQueryByExample(@Autowired ReactiveScrollingRepository repository) {
|
||||
|
||||
Example<ScrollingEntity> example = Example.of(new ScrollingEntity(), ExampleMatcher.matchingAll().withIgnoreNullValues());
|
||||
|
||||
// Recreate the last position
|
||||
var last = repository.findFirstByA("I0").block();
|
||||
var keys = Map.of(
|
||||
"c", Values.value(last.getC()),
|
||||
Constants.NAME_OF_ADDITIONAL_SORT, Values.value(last.getId().toString())
|
||||
);
|
||||
|
||||
var windowContainer = new AtomicReference<Window<ScrollingEntity>>();
|
||||
repository.findBy(example, q -> q.sortBy(ScrollingEntity.SORT_BY_C).limit(4).scroll(ScrollPosition.backward(keys)))
|
||||
.as(StepVerifier::create)
|
||||
.consumeNextWith(windowContainer::set)
|
||||
.verifyComplete();
|
||||
var window = windowContainer.get();
|
||||
assertThat(window.hasNext()).isTrue();
|
||||
assertThat(window)
|
||||
.hasSize(4)
|
||||
.extracting(ScrollingEntity::getA)
|
||||
.containsExactly("F0", "G0", "H0", "I0");
|
||||
|
||||
var nextPos = ScrollPosition.backward(((KeysetScrollPosition) window.positionAt(0)).getKeys());
|
||||
repository.findBy(example, q -> q.sortBy(ScrollingEntity.SORT_BY_C).limit(4).scroll(nextPos))
|
||||
.as(StepVerifier::create)
|
||||
.consumeNextWith(windowContainer::set)
|
||||
.verifyComplete();
|
||||
window = windowContainer.get();
|
||||
assertThat(window.hasNext()).isTrue();
|
||||
assertThat(window)
|
||||
.hasSize(4)
|
||||
.extracting(Function.identity())
|
||||
.extracting(ScrollingEntity::getA)
|
||||
.containsExactly("C0", "D0", "D0", "E0");
|
||||
|
||||
var nextNextPos = ScrollPosition.backward(((KeysetScrollPosition) window.positionAt(0)).getKeys());
|
||||
repository.findBy(example, q -> q.sortBy(ScrollingEntity.SORT_BY_C).limit(4).scroll(nextNextPos))
|
||||
.as(StepVerifier::create)
|
||||
.consumeNextWith(windowContainer::set)
|
||||
.verifyComplete();
|
||||
window = windowContainer.get();
|
||||
assertThat(window.isLast()).isTrue();
|
||||
assertThat(window).extracting(ScrollingEntity::getA)
|
||||
.containsExactly("A0", "B0");
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableNeo4jRepositories
|
||||
@EnableReactiveNeo4jRepositories
|
||||
|
||||
@@ -37,6 +37,7 @@ public class ScrollingEntity {
|
||||
* Sorting by b and a will not be unique for 3 and D0, so this will trigger the additional condition based on the id
|
||||
*/
|
||||
public static final Sort SORT_BY_B_AND_A = Sort.by(Sort.Order.asc("b"), Sort.Order.desc("a"));
|
||||
public static final Sort SORT_BY_C = Sort.by(Sort.Order.asc("c"));
|
||||
|
||||
public static void createTestData(QueryRunner queryRunner) {
|
||||
queryRunner.run("MATCH (n) DETACH DELETE n");
|
||||
|
||||
@@ -44,7 +44,7 @@ class CypherAdapterUtilsTest {
|
||||
|
||||
var condition = CypherAdapterUtils.combineKeysetIntoCondition(entity,
|
||||
ScrollPosition.forward(Map.of("foobar", "D0", "b", 3, "c", LocalDateTime.of(2023, 3, 19, 14, 21, 8, 716))),
|
||||
Sort.by(Sort.Order.asc("b"), Sort.Order.desc("a"), Sort.Order.asc("c"))
|
||||
Sort.by(Sort.Order.asc("b"), Sort.Order.desc("a"), Sort.Order.asc("c")), mappingContext.getConversionService()
|
||||
);
|
||||
|
||||
var expected = """
|
||||
|
||||
Reference in New Issue
Block a user