Skip to content

Commit

Permalink
GH-2726 - Add scroll support to FluentQuery.
Browse files Browse the repository at this point in the history
This will implement limit() and scroll()
methods in all (Reactive)FluentQuery.. classes.

Closes #2726
  • Loading branch information
meistermeier committed Jun 12, 2023
1 parent 78afd1c commit 77d94bc
Show file tree
Hide file tree
Showing 28 changed files with 851 additions and 159 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -624,7 +624,7 @@ public Collection<Expression> createReturnStatementForExists(Neo4jPersistentEnti
}

public Collection<Expression> createReturnStatementForMatch(Neo4jPersistentEntity<?> nodeDescription) {
return createReturnStatementForMatch(nodeDescription, (pp -> true));
return createReturnStatementForMatch(nodeDescription, PropertyFilter.NO_FILTER);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ public static NestedRelationshipContext of(Association<Neo4jPersistentProperty>
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()));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
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;
Expand All @@ -39,6 +40,8 @@ public static PropertyFilter acceptAll() {
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
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;
Expand Down Expand Up @@ -98,7 +99,7 @@ public static Function<Sort.Order, SortItem> sortAdapterFor(NodeDescription<?> n
};
}

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>();
Expand Down Expand Up @@ -135,7 +136,7 @@ record PropertyAndOrder(Neo4jPersistentProperty property, Sort.Order order) {
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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ private QueryFragments createQueryFragments(@Nullable Condition condition, Sort

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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
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;
Expand All @@ -35,6 +36,7 @@
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;
Expand Down Expand Up @@ -66,7 +68,7 @@ public Optional<T> findOne(Condition condition) {

return this.neo4jOperations.toExecutableQuery(
this.metaData.getType(),
QueryFragmentsAndParameters.forCondition(this.metaData, condition, null, null)
QueryFragmentsAndParameters.forCondition(this.metaData, condition)
).getSingleResult();
}

Expand All @@ -75,17 +77,18 @@ public Collection<T> findAll(Condition condition) {

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();
}
Expand All @@ -95,8 +98,8 @@ public Collection<T> findAll(Condition condition, SortItem... sortItems) {

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();
}
Expand All @@ -106,16 +109,17 @@ public Collection<T> findAll(SortItem... sortItems) {

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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,29 @@
*/
package org.springframework.data.neo4j.repository.query;

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.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;

/**
* Immutable implementation of a {@link FetchableFluentQuery}. All
* methods that return a {@link FetchableFluentQuery} return a new instance, the original instance won't be
Expand Down Expand Up @@ -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(
Expand All @@ -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;
Expand All @@ -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
Expand All @@ -106,15 +120,15 @@ public <NR> FetchableFluentQuery<NR> as(Class<NR> resultType) {
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
public R oneValue() {

return findOperation.find(example.getProbeType())
.as(resultType)
.matching(QueryFragmentsAndParameters.forExample(mappingContext, example, sort,
.matching(QueryFragmentsAndParameters.forExampleWithSort(mappingContext, example, sort, limit,
createIncludedFieldsPredicate()))
.oneValue();
}
Expand All @@ -131,7 +145,7 @@ public List<R> all() {

return findOperation.find(example.getProbeType())
.as(resultType)
.matching(QueryFragmentsAndParameters.forExample(mappingContext, example, sort,
.matching(QueryFragmentsAndParameters.forExampleWithSort(mappingContext, example, sort, limit,
createIncludedFieldsPredicate()))
.all();
}
Expand All @@ -141,14 +155,36 @@ public Page<R> page(Pageable pageable) {

List<R> page = findOperation.find(example.getProbeType())
.as(resultType)
.matching(QueryFragmentsAndParameters.forExample(mappingContext, example, pageable,
.matching(QueryFragmentsAndParameters.forExampleWithPageable(mappingContext, example, pageable,
createIncludedFieldsPredicate()))
.all();

LongSupplier totalCountSupplier = this::count;
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();
Expand Down

0 comments on commit 77d94bc

Please sign in to comment.