Merge branch '1.2.x'
This commit is contained in:
@@ -35,6 +35,7 @@ import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.domain.KeysetScrollPosition;
|
||||
import org.springframework.data.domain.OffsetScrollPosition;
|
||||
import org.springframework.data.domain.ScrollPosition;
|
||||
import org.springframework.data.domain.Sort;
|
||||
@@ -370,24 +371,29 @@ public abstract class QueryByExampleDataFetcher<T> {
|
||||
private final CursorStrategy<ScrollPosition> cursorStrategy;
|
||||
|
||||
@Nullable
|
||||
private final ScrollSubrange defaultSubrange;
|
||||
private final Integer defaultScrollCount;
|
||||
|
||||
@Nullable
|
||||
private final Function<Boolean, ScrollPosition> defaultScrollPosition;
|
||||
|
||||
private final Sort sort;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Builder(QueryByExampleExecutor<T> executor, Class<R> domainType) {
|
||||
this(executor, TypeInformation.of((Class<T>) domainType), domainType, null, null, Sort.unsorted());
|
||||
this(executor, TypeInformation.of((Class<T>) domainType), domainType, null, null, null, Sort.unsorted());
|
||||
}
|
||||
|
||||
Builder(QueryByExampleExecutor<T> executor, TypeInformation<T> domainType, Class<R> resultType,
|
||||
@Nullable CursorStrategy<ScrollPosition> cursorStrategy, @Nullable ScrollSubrange defaultSubrange,
|
||||
@Nullable CursorStrategy<ScrollPosition> cursorStrategy,
|
||||
@Nullable Integer defaultScrollCount, @Nullable Function<Boolean, ScrollPosition> defaultScrollPosition,
|
||||
Sort sort) {
|
||||
|
||||
this.executor = executor;
|
||||
this.domainType = domainType;
|
||||
this.resultType = resultType;
|
||||
this.cursorStrategy = cursorStrategy;
|
||||
this.defaultSubrange = defaultSubrange;
|
||||
this.defaultScrollCount = defaultScrollCount;
|
||||
this.defaultScrollPosition = defaultScrollPosition;
|
||||
this.sort = sort;
|
||||
}
|
||||
|
||||
@@ -402,8 +408,8 @@ public abstract class QueryByExampleDataFetcher<T> {
|
||||
*/
|
||||
public <P> Builder<T, P> projectAs(Class<P> projectionType) {
|
||||
Assert.notNull(projectionType, "Projection type must not be null");
|
||||
return new Builder<>(this.executor, this.domainType,
|
||||
projectionType, this.cursorStrategy, this.defaultSubrange, this.sort);
|
||||
return new Builder<>(this.executor, this.domainType, projectionType,
|
||||
this.cursorStrategy, this.defaultScrollCount, this.defaultScrollPosition, this.sort);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -416,8 +422,26 @@ public abstract class QueryByExampleDataFetcher<T> {
|
||||
* @since 1.2.0
|
||||
*/
|
||||
public Builder<T, R> cursorStrategy(@Nullable CursorStrategy<ScrollPosition> cursorStrategy) {
|
||||
return new Builder<>(this.executor, this.domainType, this.resultType,
|
||||
cursorStrategy, this.defaultScrollCount, this.defaultScrollPosition, this.sort);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure a default scroll count to use, and function to return a default
|
||||
* {@link ScrollPosition} for forward vs backward pagination.
|
||||
* <p>For offset scrolling, use {@link ScrollPosition#offset()} to scroll
|
||||
* from the beginning. Currently, it is not possible to go back from the end.
|
||||
* <p>For keyset scrolling, use {@link ScrollPosition#keyset()} to scroll
|
||||
* from the beginning, or {@link KeysetScrollPosition#reverse()} the same
|
||||
* to go back from the end.
|
||||
* <p>By default a count of 20 and {@link ScrollPosition#offset()} are used.
|
||||
* @since 1.2.5
|
||||
*/
|
||||
public Builder<T, R> defaultScrollSubrange(
|
||||
int defaultCount, Function<Boolean, ScrollPosition> defaultPosition) {
|
||||
|
||||
return new Builder<>(this.executor, this.domainType,
|
||||
this.resultType, cursorStrategy, this.defaultSubrange, this.sort);
|
||||
this.resultType, this.cursorStrategy, defaultCount, defaultPosition, this.sort);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -427,11 +451,16 @@ public abstract class QueryByExampleDataFetcher<T> {
|
||||
* count of 20.
|
||||
* @return a new {@link Builder} instance with all previously configured
|
||||
* options and {@code Sort} applied
|
||||
* @since 1.2.0
|
||||
* @deprecated in favor of {@link #defaultScrollSubrange(int, Function)}
|
||||
*/
|
||||
@SuppressWarnings("OptionalGetWithoutIsPresent")
|
||||
@Deprecated(since = "1.2.5", forRemoval = true)
|
||||
public Builder<T, R> defaultScrollSubrange(@Nullable ScrollSubrange defaultSubrange) {
|
||||
return new Builder<>(this.executor, this.domainType,
|
||||
this.resultType, this.cursorStrategy, defaultSubrange, this.sort);
|
||||
this.resultType, this.cursorStrategy,
|
||||
(defaultSubrange != null ? defaultSubrange.count().getAsInt() : null),
|
||||
(defaultSubrange != null ? forward -> defaultSubrange.position().get() : null),
|
||||
this.sort);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -442,8 +471,8 @@ public abstract class QueryByExampleDataFetcher<T> {
|
||||
*/
|
||||
public Builder<T, R> sortBy(Sort sort) {
|
||||
Assert.notNull(sort, "Sort must not be null");
|
||||
return new Builder<>(this.executor, this.domainType,
|
||||
this.resultType, this.cursorStrategy, this.defaultSubrange, sort);
|
||||
return new Builder<>(this.executor, this.domainType, this.resultType,
|
||||
this.cursorStrategy, this.defaultScrollCount, this.defaultScrollPosition, sort);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -469,7 +498,8 @@ public abstract class QueryByExampleDataFetcher<T> {
|
||||
return new ScrollableEntityFetcher<>(
|
||||
this.executor, this.domainType, this.resultType,
|
||||
(this.cursorStrategy != null ? this.cursorStrategy : RepositoryUtils.defaultCursorStrategy()),
|
||||
(this.defaultSubrange != null ? this.defaultSubrange : RepositoryUtils.defaultScrollSubrange()),
|
||||
(this.defaultScrollCount != null ? this.defaultScrollCount : RepositoryUtils.defaultScrollCount()),
|
||||
(this.defaultScrollPosition != null ? this.defaultScrollPosition : RepositoryUtils.defaultScrollPosition()),
|
||||
this.sort);
|
||||
}
|
||||
|
||||
@@ -516,25 +546,30 @@ public abstract class QueryByExampleDataFetcher<T> {
|
||||
private final CursorStrategy<ScrollPosition> cursorStrategy;
|
||||
|
||||
@Nullable
|
||||
private final ScrollSubrange defaultSubrange;
|
||||
private final Integer defaultScrollCount;
|
||||
|
||||
@Nullable
|
||||
private final Function<Boolean, ScrollPosition> defaultScrollPosition;
|
||||
|
||||
private final Sort sort;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
ReactiveBuilder(ReactiveQueryByExampleExecutor<T> executor, Class<R> domainType) {
|
||||
this(executor, TypeInformation.of((Class<T>) domainType), domainType, null, null, Sort.unsorted());
|
||||
this(executor, TypeInformation.of((Class<T>) domainType), domainType, null, null, null, Sort.unsorted());
|
||||
}
|
||||
|
||||
ReactiveBuilder(
|
||||
ReactiveQueryByExampleExecutor<T> executor, TypeInformation<T> domainType, Class<R> resultType,
|
||||
@Nullable CursorStrategy<ScrollPosition> cursorStrategy, @Nullable ScrollSubrange defaultSubrange,
|
||||
@Nullable CursorStrategy<ScrollPosition> cursorStrategy,
|
||||
@Nullable Integer defaultScrollCount, @Nullable Function<Boolean, ScrollPosition> defaultScrollPosition,
|
||||
Sort sort) {
|
||||
|
||||
this.executor = executor;
|
||||
this.domainType = domainType;
|
||||
this.resultType = resultType;
|
||||
this.cursorStrategy = cursorStrategy;
|
||||
this.defaultSubrange = defaultSubrange;
|
||||
this.defaultScrollCount = defaultScrollCount;
|
||||
this.defaultScrollPosition = defaultScrollPosition;
|
||||
this.sort = sort;
|
||||
}
|
||||
|
||||
@@ -550,7 +585,7 @@ public abstract class QueryByExampleDataFetcher<T> {
|
||||
public <P> ReactiveBuilder<T, P> projectAs(Class<P> projectionType) {
|
||||
Assert.notNull(projectionType, "Projection type must not be null");
|
||||
return new ReactiveBuilder<>(this.executor, this.domainType,
|
||||
projectionType, this.cursorStrategy, this.defaultSubrange, this.sort);
|
||||
projectionType, this.cursorStrategy, this.defaultScrollCount, this.defaultScrollPosition, this.sort);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -563,8 +598,26 @@ public abstract class QueryByExampleDataFetcher<T> {
|
||||
* @since 1.2.0
|
||||
*/
|
||||
public ReactiveBuilder<T, R> cursorStrategy(@Nullable CursorStrategy<ScrollPosition> cursorStrategy) {
|
||||
return new ReactiveBuilder<>(this.executor, this.domainType, this.resultType,
|
||||
cursorStrategy, this.defaultScrollCount, this.defaultScrollPosition, this.sort);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure a default scroll count to use, and function to return a default
|
||||
* {@link ScrollPosition} for forward vs backward pagination.
|
||||
* <p>For offset scrolling, use {@link ScrollPosition#offset()} to scroll
|
||||
* from the beginning. Currently, it is not possible to go back from the end.
|
||||
* <p>For keyset scrolling, use {@link ScrollPosition#keyset()} to scroll
|
||||
* from the beginning, or {@link KeysetScrollPosition#reverse()} the same
|
||||
* to go back from the end.
|
||||
* <p>By default a count of 20 and {@link ScrollPosition#offset()} are used.
|
||||
* @since 1.2.5
|
||||
*/
|
||||
public ReactiveBuilder<T, R> defaultScrollSubrange(
|
||||
int defaultCount, Function<Boolean, ScrollPosition> defaultPosition) {
|
||||
|
||||
return new ReactiveBuilder<>(this.executor, this.domainType,
|
||||
this.resultType, cursorStrategy, this.defaultSubrange, this.sort);
|
||||
this.resultType, this.cursorStrategy, defaultCount, defaultPosition, this.sort);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -574,11 +627,16 @@ public abstract class QueryByExampleDataFetcher<T> {
|
||||
* count of 20.
|
||||
* @return a new {@link Builder} instance with all previously configured
|
||||
* options and {@code Sort} applied
|
||||
* @since 1.2.0
|
||||
* @deprecated in favor of {@link #defaultScrollSubrange(int, Function)}
|
||||
*/
|
||||
@SuppressWarnings("OptionalGetWithoutIsPresent")
|
||||
@Deprecated(since = "1.2.5", forRemoval = true)
|
||||
public ReactiveBuilder<T, R> defaultScrollSubrange(@Nullable ScrollSubrange defaultSubrange) {
|
||||
return new ReactiveBuilder<>(this.executor, this.domainType,
|
||||
this.resultType, this.cursorStrategy, defaultSubrange, this.sort);
|
||||
this.resultType, this.cursorStrategy,
|
||||
(defaultSubrange != null ? defaultSubrange.count().getAsInt() : null),
|
||||
(defaultSubrange != null ? forward -> defaultSubrange.position().get() : null),
|
||||
this.sort);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -589,8 +647,8 @@ public abstract class QueryByExampleDataFetcher<T> {
|
||||
*/
|
||||
public ReactiveBuilder<T, R> sortBy(Sort sort) {
|
||||
Assert.notNull(sort, "Sort must not be null");
|
||||
return new ReactiveBuilder<>(this.executor, this.domainType,
|
||||
this.resultType, this.cursorStrategy, this.defaultSubrange, sort);
|
||||
return new ReactiveBuilder<>(this.executor, this.domainType, this.resultType,
|
||||
this.cursorStrategy, this.defaultScrollCount, this.defaultScrollPosition, sort);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -616,7 +674,8 @@ public abstract class QueryByExampleDataFetcher<T> {
|
||||
return new ReactiveScrollableEntityFetcher<>(
|
||||
this.executor, this.domainType, this.resultType,
|
||||
(this.cursorStrategy != null ? this.cursorStrategy : RepositoryUtils.defaultCursorStrategy()),
|
||||
(this.defaultSubrange != null ? this.defaultSubrange : RepositoryUtils.defaultScrollSubrange()),
|
||||
(this.defaultScrollCount != null ? this.defaultScrollCount : RepositoryUtils.defaultScrollCount()),
|
||||
(this.defaultScrollPosition != null ? this.defaultScrollPosition : RepositoryUtils.defaultScrollPosition()),
|
||||
this.sort);
|
||||
}
|
||||
|
||||
@@ -747,23 +806,27 @@ public abstract class QueryByExampleDataFetcher<T> {
|
||||
|
||||
private final CursorStrategy<ScrollPosition> cursorStrategy;
|
||||
|
||||
private final ScrollSubrange defaultSubrange;
|
||||
private final int defaultCount;
|
||||
|
||||
private final Function<Boolean, ScrollPosition> defaultPosition;
|
||||
|
||||
private final ResolvableType scrollableResultType;
|
||||
|
||||
ScrollableEntityFetcher(
|
||||
QueryByExampleExecutor<T> executor, TypeInformation<T> domainType, Class<R> resultType,
|
||||
CursorStrategy<ScrollPosition> cursorStrategy, ScrollSubrange defaultSubrange, Sort sort) {
|
||||
CursorStrategy<ScrollPosition> cursorStrategy,
|
||||
int defaultCount,
|
||||
Function<Boolean, ScrollPosition> defaultPosition,
|
||||
Sort sort) {
|
||||
|
||||
super(executor, domainType, resultType, sort);
|
||||
|
||||
Assert.notNull(cursorStrategy, "CursorStrategy is required");
|
||||
Assert.notNull(defaultSubrange, "Default ScrollSubrange is required");
|
||||
Assert.isTrue(defaultSubrange.position().isPresent(), "Default ScrollPosition is required");
|
||||
Assert.isTrue(defaultSubrange.count().isPresent(), "Default scroll limit is required");
|
||||
Assert.notNull(defaultPosition, "'defaultPosition' is required");
|
||||
|
||||
this.cursorStrategy = cursorStrategy;
|
||||
this.defaultSubrange = defaultSubrange;
|
||||
this.defaultCount = defaultCount;
|
||||
this.defaultPosition = defaultPosition;
|
||||
this.scrollableResultType = ResolvableType.forClassWithGenerics(Window.class, resultType);
|
||||
}
|
||||
|
||||
@@ -772,12 +835,12 @@ public abstract class QueryByExampleDataFetcher<T> {
|
||||
return ResolvableType.forClassWithGenerics(Iterable.class, this.scrollableResultType);
|
||||
}
|
||||
|
||||
@SuppressWarnings("OptionalGetWithoutIsPresent")
|
||||
@Override
|
||||
protected Iterable<R> getResult(FluentQuery.FetchableFluentQuery<R> queryToUse, DataFetchingEnvironment env) {
|
||||
ScrollSubrange range = RepositoryUtils.getScrollSubrange(env, this.cursorStrategy, this.defaultSubrange);
|
||||
int count = range.count().getAsInt();
|
||||
ScrollPosition position = range.position().get();
|
||||
ScrollSubrange range = RepositoryUtils.getScrollSubrange(env, this.cursorStrategy);
|
||||
int count = range.count().orElse(this.defaultCount);
|
||||
ScrollPosition position = (range.position().isPresent() ?
|
||||
range.position().get() : this.defaultPosition.apply(range.forward()));
|
||||
return queryToUse.limit(count).scroll(position);
|
||||
}
|
||||
|
||||
@@ -891,26 +954,30 @@ public abstract class QueryByExampleDataFetcher<T> {
|
||||
|
||||
private final CursorStrategy<ScrollPosition> cursorStrategy;
|
||||
|
||||
private final ScrollSubrange defaultSubrange;
|
||||
private final int defaultCount;
|
||||
|
||||
private final Function<Boolean, ScrollPosition> defaultPosition;
|
||||
|
||||
private final Sort sort;
|
||||
|
||||
ReactiveScrollableEntityFetcher(
|
||||
ReactiveQueryByExampleExecutor<T> executor, TypeInformation<T> domainType, Class<R> resultType,
|
||||
CursorStrategy<ScrollPosition> cursorStrategy, ScrollSubrange defaultSubrange, Sort sort) {
|
||||
CursorStrategy<ScrollPosition> cursorStrategy,
|
||||
int defaultCount,
|
||||
Function<Boolean, ScrollPosition> defaultPosition,
|
||||
Sort sort) {
|
||||
|
||||
super(domainType);
|
||||
|
||||
Assert.notNull(cursorStrategy, "CursorStrategy is required");
|
||||
Assert.notNull(defaultSubrange, "Default ScrollSubrange is required");
|
||||
Assert.isTrue(defaultSubrange.position().isPresent(), "Default ScrollPosition is required");
|
||||
Assert.isTrue(defaultSubrange.count().isPresent(), "Default scroll limit is required");
|
||||
Assert.notNull(defaultPosition, "'defaultPosition' is required");
|
||||
|
||||
this.executor = executor;
|
||||
this.resultType = resultType;
|
||||
this.scrollableResultType = ResolvableType.forClassWithGenerics(Iterable.class, resultType);
|
||||
this.cursorStrategy = cursorStrategy;
|
||||
this.defaultSubrange = defaultSubrange;
|
||||
this.defaultCount = defaultCount;
|
||||
this.defaultPosition = defaultPosition;
|
||||
this.sort = sort;
|
||||
}
|
||||
|
||||
@@ -920,7 +987,7 @@ public abstract class QueryByExampleDataFetcher<T> {
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings({"unchecked", "OptionalGetWithoutIsPresent"})
|
||||
@SuppressWarnings("unchecked")
|
||||
public Mono<Iterable<R>> get(DataFetchingEnvironment env) throws BindException {
|
||||
return this.executor.findBy(buildExample(env), query -> {
|
||||
FluentQuery.ReactiveFluentQuery<R> queryToUse = (FluentQuery.ReactiveFluentQuery<R>) query;
|
||||
@@ -936,9 +1003,10 @@ public abstract class QueryByExampleDataFetcher<T> {
|
||||
queryToUse = queryToUse.project(buildPropertyPaths(env.getSelectionSet(), this.resultType));
|
||||
}
|
||||
|
||||
ScrollSubrange range = RepositoryUtils.getScrollSubrange(env, this.cursorStrategy, this.defaultSubrange);
|
||||
int count = range.count().getAsInt();
|
||||
ScrollPosition position = range.position().get();
|
||||
ScrollSubrange range = RepositoryUtils.getScrollSubrange(env, this.cursorStrategy);
|
||||
int count = range.count().orElse(this.defaultCount);
|
||||
ScrollPosition position = (range.position().isPresent() ?
|
||||
range.position().get() : this.defaultPosition.apply(range.forward()));
|
||||
return queryToUse.limit(count).scroll(position).map(Function.identity());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.data.domain.KeysetScrollPosition;
|
||||
import org.springframework.data.domain.OffsetScrollPosition;
|
||||
import org.springframework.data.domain.ScrollPosition;
|
||||
import org.springframework.data.domain.Sort;
|
||||
@@ -412,7 +413,10 @@ public abstract class QuerydslDataFetcher<T> {
|
||||
private final CursorStrategy<ScrollPosition> cursorStrategy;
|
||||
|
||||
@Nullable
|
||||
private final ScrollSubrange defaultSubrange;
|
||||
private final Integer defaultScrollCount;
|
||||
|
||||
@Nullable
|
||||
private final Function<Boolean, ScrollPosition> defaultScrollPosition;
|
||||
|
||||
private final Sort sort;
|
||||
|
||||
@@ -421,18 +425,20 @@ public abstract class QuerydslDataFetcher<T> {
|
||||
@SuppressWarnings("unchecked")
|
||||
Builder(QuerydslPredicateExecutor<T> executor, Class<R> domainType) {
|
||||
this(executor, TypeInformation.of((Class<T>) domainType),
|
||||
domainType, null, null, Sort.unsorted(), NO_OP_BINDER_CUSTOMIZER);
|
||||
domainType, null, null, null, Sort.unsorted(), NO_OP_BINDER_CUSTOMIZER);
|
||||
}
|
||||
|
||||
Builder(QuerydslPredicateExecutor<T> executor, TypeInformation<T> domainType, Class<R> resultType,
|
||||
@Nullable CursorStrategy<ScrollPosition> cursorStrategy, @Nullable ScrollSubrange defaultSubrange,
|
||||
@Nullable CursorStrategy<ScrollPosition> cursorStrategy,
|
||||
@Nullable Integer defaultScrollCount, @Nullable Function<Boolean, ScrollPosition> defaultScrollPosition,
|
||||
Sort sort, QuerydslBinderCustomizer<? extends EntityPath<T>> customizer) {
|
||||
|
||||
this.executor = executor;
|
||||
this.domainType = domainType;
|
||||
this.resultType = resultType;
|
||||
this.cursorStrategy = cursorStrategy;
|
||||
this.defaultSubrange = defaultSubrange;
|
||||
this.defaultScrollCount = defaultScrollCount;
|
||||
this.defaultScrollPosition = defaultScrollPosition;
|
||||
this.sort = sort;
|
||||
this.customizer = customizer;
|
||||
}
|
||||
@@ -449,7 +455,8 @@ public abstract class QuerydslDataFetcher<T> {
|
||||
public <P> Builder<T, P> projectAs(Class<P> projectionType) {
|
||||
Assert.notNull(projectionType, "Projection type must not be null");
|
||||
return new Builder<>(this.executor, this.domainType, projectionType,
|
||||
this.cursorStrategy, this.defaultSubrange, this.sort, this.customizer);
|
||||
this.cursorStrategy, this.defaultScrollCount, this.defaultScrollPosition,
|
||||
this.sort, this.customizer);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -463,21 +470,43 @@ public abstract class QuerydslDataFetcher<T> {
|
||||
*/
|
||||
public Builder<T, R> cursorStrategy(@Nullable CursorStrategy<ScrollPosition> cursorStrategy) {
|
||||
return new Builder<>(this.executor, this.domainType, this.resultType,
|
||||
cursorStrategy, this.defaultSubrange, this.sort, this.customizer);
|
||||
cursorStrategy, this.defaultScrollCount, this.defaultScrollPosition,
|
||||
this.sort, this.customizer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure a default scroll count to use, and function to return a default
|
||||
* {@link ScrollPosition} for forward vs backward pagination.
|
||||
* <p>For offset scrolling, use {@link ScrollPosition#offset()} to scroll
|
||||
* from the beginning. Currently, it is not possible to go back from the end.
|
||||
* <p>For keyset scrolling, use {@link ScrollPosition#keyset()} to scroll
|
||||
* from the beginning, or {@link KeysetScrollPosition#reverse()} the same
|
||||
* to go back from the end.
|
||||
* <p>By default a count of 20 and {@link ScrollPosition#offset()} are used.
|
||||
* @since 1.2.5
|
||||
*/
|
||||
public Builder<T, R> defaultScrollSubrange(
|
||||
int defaultCount, Function<Boolean, ScrollPosition> defaultPosition) {
|
||||
|
||||
return new Builder<>(this.executor, this.domainType, this.resultType,
|
||||
this.cursorStrategy, defaultCount, defaultPosition, this.sort, this.customizer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure a {@link ScrollSubrange} to use when a paginated request does
|
||||
* not specify a cursor and/or a count of items.
|
||||
* <p>By default, this is {@link OffsetScrollPosition#offset()} with a
|
||||
* count of 20.
|
||||
* @return a new {@link Builder} instance with all previously configured
|
||||
* options and {@code Sort} applied
|
||||
* <p>By default, this is {@link OffsetScrollPosition#offset()} with a count of 20.
|
||||
* @return a new {@link Builder} instance
|
||||
* @since 1.2.0
|
||||
* @deprecated in favor of {@link #defaultScrollSubrange(int, Function)}
|
||||
*/
|
||||
@SuppressWarnings("OptionalGetWithoutIsPresent")
|
||||
@Deprecated(since = "1.2.5", forRemoval = true)
|
||||
public Builder<T, R> defaultScrollSubrange(@Nullable ScrollSubrange defaultSubrange) {
|
||||
return new Builder<>(this.executor, this.domainType, this.resultType,
|
||||
this.cursorStrategy, defaultSubrange, this.sort, this.customizer);
|
||||
return new Builder<>(this.executor, this.domainType, this.resultType, this.cursorStrategy,
|
||||
(defaultSubrange != null ? defaultSubrange.count().getAsInt() : null),
|
||||
(defaultSubrange != null ? forward -> defaultSubrange.position().get() : null),
|
||||
this.sort, this.customizer);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -489,7 +518,8 @@ public abstract class QuerydslDataFetcher<T> {
|
||||
public Builder<T, R> sortBy(Sort sort) {
|
||||
Assert.notNull(sort, "Sort must not be null");
|
||||
return new Builder<>(this.executor, this.domainType, this.resultType,
|
||||
this.cursorStrategy, this.defaultSubrange, sort, customizer);
|
||||
this.cursorStrategy, this.defaultScrollCount, this.defaultScrollPosition,
|
||||
sort, customizer);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -508,7 +538,8 @@ public abstract class QuerydslDataFetcher<T> {
|
||||
public Builder<T, R> customizer(QuerydslBinderCustomizer<? extends EntityPath<T>> customizer) {
|
||||
Assert.notNull(customizer, "QuerydslBinderCustomizer must not be null");
|
||||
return new Builder<>(this.executor, this.domainType, this.resultType,
|
||||
this.cursorStrategy, this.defaultSubrange, this.sort, customizer);
|
||||
this.cursorStrategy, this.defaultScrollCount, this.defaultScrollPosition,
|
||||
this.sort, customizer);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -536,7 +567,8 @@ public abstract class QuerydslDataFetcher<T> {
|
||||
return new ScrollableEntityFetcher<>(
|
||||
this.executor, this.domainType, this.resultType,
|
||||
(this.cursorStrategy != null ? this.cursorStrategy : RepositoryUtils.defaultCursorStrategy()),
|
||||
(this.defaultSubrange != null ? this.defaultSubrange : RepositoryUtils.defaultScrollSubrange()),
|
||||
(this.defaultScrollCount != null ? this.defaultScrollCount : RepositoryUtils.defaultScrollCount()),
|
||||
(this.defaultScrollPosition != null ? this.defaultScrollPosition : RepositoryUtils.defaultScrollPosition()),
|
||||
this.sort, this.customizer);
|
||||
}
|
||||
|
||||
@@ -583,7 +615,10 @@ public abstract class QuerydslDataFetcher<T> {
|
||||
private final CursorStrategy<ScrollPosition> cursorStrategy;
|
||||
|
||||
@Nullable
|
||||
private final ScrollSubrange defaultSubrange;
|
||||
private final Integer defaultScrollCount;
|
||||
|
||||
@Nullable
|
||||
private final Function<Boolean, ScrollPosition> defaultScrollPosition;
|
||||
|
||||
private final Sort sort;
|
||||
|
||||
@@ -592,19 +627,21 @@ public abstract class QuerydslDataFetcher<T> {
|
||||
@SuppressWarnings("unchecked")
|
||||
ReactiveBuilder(ReactiveQuerydslPredicateExecutor<T> executor, Class<R> domainType) {
|
||||
this(executor, TypeInformation.of((Class<T>) domainType),
|
||||
domainType, null, null, Sort.unsorted(), NO_OP_BINDER_CUSTOMIZER);
|
||||
domainType, null, null, null, Sort.unsorted(), NO_OP_BINDER_CUSTOMIZER);
|
||||
}
|
||||
|
||||
ReactiveBuilder(
|
||||
ReactiveQuerydslPredicateExecutor<T> executor, TypeInformation<T> domainType, Class<R> resultType,
|
||||
@Nullable CursorStrategy<ScrollPosition> cursorStrategy, @Nullable ScrollSubrange defaultSubrange,
|
||||
@Nullable CursorStrategy<ScrollPosition> cursorStrategy,
|
||||
@Nullable Integer defaultScrollCount, @Nullable Function<Boolean, ScrollPosition> defaultScrollPosition,
|
||||
Sort sort, QuerydslBinderCustomizer<? extends EntityPath<T>> customizer) {
|
||||
|
||||
this.executor = executor;
|
||||
this.domainType = domainType;
|
||||
this.resultType = resultType;
|
||||
this.cursorStrategy = cursorStrategy;
|
||||
this.defaultSubrange = defaultSubrange;
|
||||
this.defaultScrollCount = defaultScrollCount;
|
||||
this.defaultScrollPosition = defaultScrollPosition;
|
||||
this.sort = sort;
|
||||
this.customizer = customizer;
|
||||
}
|
||||
@@ -621,7 +658,8 @@ public abstract class QuerydslDataFetcher<T> {
|
||||
public <P> ReactiveBuilder<T, P> projectAs(Class<P> projectionType) {
|
||||
Assert.notNull(projectionType, "Projection type must not be null");
|
||||
return new ReactiveBuilder<>(this.executor, this.domainType, projectionType,
|
||||
this.cursorStrategy, this.defaultSubrange, this.sort, this.customizer);
|
||||
this.cursorStrategy, this.defaultScrollCount, this.defaultScrollPosition,
|
||||
this.sort, this.customizer);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -635,21 +673,44 @@ public abstract class QuerydslDataFetcher<T> {
|
||||
*/
|
||||
public ReactiveBuilder<T, R> cursorStrategy(@Nullable CursorStrategy<ScrollPosition> cursorStrategy) {
|
||||
return new ReactiveBuilder<>(this.executor, this.domainType, this.resultType,
|
||||
cursorStrategy, this.defaultSubrange, this.sort, this.customizer);
|
||||
cursorStrategy, this.defaultScrollCount, this.defaultScrollPosition,
|
||||
this.sort, this.customizer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure a default scroll count to use, and function to return a default
|
||||
* {@link ScrollPosition} for forward vs backward pagination.
|
||||
* <p>For offset scrolling, use {@link ScrollPosition#offset()} to scroll
|
||||
* from the beginning. Currently, it is not possible to go back from the end.
|
||||
* <p>For keyset scrolling, use {@link ScrollPosition#keyset()} to scroll
|
||||
* from the beginning, or {@link KeysetScrollPosition#reverse()} the same
|
||||
* to go back from the end.
|
||||
* <p>By default a count of 20 and {@link ScrollPosition#offset()} are used.
|
||||
* @since 1.2.5
|
||||
*/
|
||||
public ReactiveBuilder<T, R> defaultScrollSubrange(
|
||||
int defaultCount, Function<Boolean, ScrollPosition> defaultPosition) {
|
||||
|
||||
return new ReactiveBuilder<>(this.executor, this.domainType, this.resultType,
|
||||
this.cursorStrategy, defaultCount, defaultPosition, this.sort, this.customizer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure a {@link ScrollSubrange} to use when a paginated request does
|
||||
* not specify a cursor and/or a count of items.
|
||||
* <p>By default, this is {@link OffsetScrollPosition#offset()} with a
|
||||
* count of 20.
|
||||
* @return a new {@link Builder} instance with all previously configured
|
||||
* options and {@code Sort} applied
|
||||
* <p>By default, this is {@link OffsetScrollPosition#offset()} with a count of 20.
|
||||
* @return a new {@link Builder} instance
|
||||
* @since 1.2.0
|
||||
* @deprecated in favor of {@link #defaultScrollSubrange(int, Function)}
|
||||
*/
|
||||
@SuppressWarnings("OptionalGetWithoutIsPresent")
|
||||
@Deprecated(since = "1.2.5", forRemoval = true)
|
||||
public ReactiveBuilder<T, R> defaultScrollSubrange(@Nullable ScrollSubrange defaultSubrange) {
|
||||
return new ReactiveBuilder<>(this.executor, this.domainType, this.resultType,
|
||||
this.cursorStrategy, defaultSubrange, this.sort, this.customizer);
|
||||
this.cursorStrategy,
|
||||
(defaultSubrange != null ? defaultSubrange.count().getAsInt() : null),
|
||||
(defaultSubrange != null ? forward -> defaultSubrange.position().get() : null),
|
||||
this.sort, this.customizer);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -661,7 +722,8 @@ public abstract class QuerydslDataFetcher<T> {
|
||||
public ReactiveBuilder<T, R> sortBy(Sort sort) {
|
||||
Assert.notNull(sort, "Sort must not be null");
|
||||
return new ReactiveBuilder<>(this.executor, this.domainType, this.resultType,
|
||||
this.cursorStrategy, this.defaultSubrange, sort, this.customizer);
|
||||
this.cursorStrategy, this.defaultScrollCount, this.defaultScrollPosition,
|
||||
sort, this.customizer);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -680,7 +742,8 @@ public abstract class QuerydslDataFetcher<T> {
|
||||
public ReactiveBuilder<T, R> customizer(QuerydslBinderCustomizer<? extends EntityPath<T>> customizer) {
|
||||
Assert.notNull(customizer, "QuerydslBinderCustomizer must not be null");
|
||||
return new ReactiveBuilder<>(this.executor, this.domainType, this.resultType,
|
||||
this.cursorStrategy, this.defaultSubrange, this.sort, customizer);
|
||||
this.cursorStrategy, this.defaultScrollCount, this.defaultScrollPosition,
|
||||
this.sort, customizer);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -708,7 +771,8 @@ public abstract class QuerydslDataFetcher<T> {
|
||||
return new ReactiveScrollableEntityFetcher<>(
|
||||
this.executor, this.domainType, this.resultType,
|
||||
(this.cursorStrategy != null ? this.cursorStrategy : RepositoryUtils.defaultCursorStrategy()),
|
||||
(this.defaultSubrange != null ? this.defaultSubrange : RepositoryUtils.defaultScrollSubrange()),
|
||||
(this.defaultScrollCount != null ? this.defaultScrollCount : RepositoryUtils.defaultScrollCount()),
|
||||
(this.defaultScrollPosition != null ? this.defaultScrollPosition : RepositoryUtils.defaultScrollPosition()),
|
||||
this.sort, this.customizer);
|
||||
}
|
||||
|
||||
@@ -843,33 +907,35 @@ public abstract class QuerydslDataFetcher<T> {
|
||||
|
||||
private final CursorStrategy<ScrollPosition> cursorStrategy;
|
||||
|
||||
private final ScrollSubrange defaultSubrange;
|
||||
private final int defaultCount;
|
||||
|
||||
private final Function<Boolean, ScrollPosition> defaultPosition;
|
||||
|
||||
ScrollableEntityFetcher(QuerydslPredicateExecutor<T> executor,
|
||||
TypeInformation<T> domainType,
|
||||
Class<R> resultType,
|
||||
CursorStrategy<ScrollPosition> cursorStrategy,
|
||||
ScrollSubrange defaultSubrange,
|
||||
int defaultCount,
|
||||
Function<Boolean, ScrollPosition> defaultPosition,
|
||||
Sort sort,
|
||||
QuerydslBinderCustomizer<? extends EntityPath<T>> customizer) {
|
||||
|
||||
super(executor, domainType, resultType, sort, customizer);
|
||||
|
||||
Assert.notNull(cursorStrategy, "CursorStrategy is required");
|
||||
Assert.notNull(defaultSubrange, "Default ScrollSubrange is required");
|
||||
Assert.isTrue(defaultSubrange.position().isPresent(), "Default ScrollPosition is required");
|
||||
Assert.isTrue(defaultSubrange.count().isPresent(), "Default scroll limit is required");
|
||||
Assert.notNull(defaultPosition, "'defaultPosition' is required");
|
||||
|
||||
this.cursorStrategy = cursorStrategy;
|
||||
this.defaultSubrange = defaultSubrange;
|
||||
this.defaultCount = defaultCount;
|
||||
this.defaultPosition = defaultPosition;
|
||||
}
|
||||
|
||||
@SuppressWarnings("OptionalGetWithoutIsPresent")
|
||||
@Override
|
||||
protected Iterable<R> getResult(FetchableFluentQuery<R> queryToUse, DataFetchingEnvironment env) {
|
||||
ScrollSubrange range = RepositoryUtils.getScrollSubrange(env, this.cursorStrategy, this.defaultSubrange);
|
||||
int count = range.count().getAsInt();
|
||||
ScrollPosition position = range.position().get();
|
||||
ScrollSubrange range = RepositoryUtils.getScrollSubrange(env, this.cursorStrategy);
|
||||
int count = range.count().orElse(this.defaultCount);
|
||||
ScrollPosition position = (range.position().isPresent() ?
|
||||
range.position().get() : this.defaultPosition.apply(range.forward()));
|
||||
return queryToUse.limit(count).scroll(position);
|
||||
}
|
||||
|
||||
@@ -987,7 +1053,9 @@ public abstract class QuerydslDataFetcher<T> {
|
||||
|
||||
private final CursorStrategy<ScrollPosition> cursorStrategy;
|
||||
|
||||
private final ScrollSubrange defaultSubrange;
|
||||
private final int defaultCount;
|
||||
|
||||
private final Function<Boolean, ScrollPosition> defaultPosition;
|
||||
|
||||
private final Sort sort;
|
||||
|
||||
@@ -995,22 +1063,23 @@ public abstract class QuerydslDataFetcher<T> {
|
||||
ReactiveScrollableEntityFetcher(ReactiveQuerydslPredicateExecutor<T> executor,
|
||||
TypeInformation<T> domainType,
|
||||
Class<R> resultType,
|
||||
CursorStrategy<ScrollPosition> cursorStrategy, ScrollSubrange defaultSubrange,
|
||||
CursorStrategy<ScrollPosition> cursorStrategy,
|
||||
int defaultCount,
|
||||
Function<Boolean, ScrollPosition> defaultPosition,
|
||||
Sort sort,
|
||||
QuerydslBinderCustomizer<? extends EntityPath<T>> customizer) {
|
||||
|
||||
super(domainType, (QuerydslBinderCustomizer) customizer);
|
||||
|
||||
Assert.notNull(cursorStrategy, "CursorStrategy is required");
|
||||
Assert.notNull(defaultSubrange, "Default ScrollSubrange is required");
|
||||
Assert.isTrue(defaultSubrange.position().isPresent(), "Default ScrollPosition is required");
|
||||
Assert.isTrue(defaultSubrange.count().isPresent(), "Default scroll limit is required");
|
||||
Assert.notNull(defaultPosition, "'defaultPosition' is required");
|
||||
|
||||
this.executor = executor;
|
||||
this.resultType = resultType;
|
||||
this.scrollableResultType = ResolvableType.forClassWithGenerics(Iterable.class, resultType);
|
||||
this.cursorStrategy = cursorStrategy;
|
||||
this.defaultSubrange = defaultSubrange;
|
||||
this.defaultCount = defaultCount;
|
||||
this.defaultPosition = defaultPosition;
|
||||
this.sort = sort;
|
||||
}
|
||||
|
||||
@@ -1020,7 +1089,7 @@ public abstract class QuerydslDataFetcher<T> {
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings({"unchecked", "OptionalGetWithoutIsPresent"})
|
||||
@SuppressWarnings("unchecked")
|
||||
public Mono<Iterable<R>> get(DataFetchingEnvironment env) {
|
||||
return this.executor.findBy(buildPredicate(env), query -> {
|
||||
FluentQuery.ReactiveFluentQuery<R> queryToUse = (FluentQuery.ReactiveFluentQuery<R>) query;
|
||||
@@ -1036,9 +1105,10 @@ public abstract class QuerydslDataFetcher<T> {
|
||||
queryToUse = queryToUse.project(buildPropertyPaths(env.getSelectionSet(), this.resultType));
|
||||
}
|
||||
|
||||
ScrollSubrange range = RepositoryUtils.getScrollSubrange(env, this.cursorStrategy, this.defaultSubrange);
|
||||
int count = range.count().getAsInt();
|
||||
ScrollPosition position = range.position().get();
|
||||
ScrollSubrange range = RepositoryUtils.getScrollSubrange(env, this.cursorStrategy);
|
||||
int count = range.count().orElse(this.defaultCount);
|
||||
ScrollPosition position = (range.position().isPresent() ?
|
||||
range.position().get() : this.defaultPosition.apply(range.forward()));
|
||||
return queryToUse.limit(count).scroll(position).map(Function.identity());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,6 +16,7 @@
|
||||
package org.springframework.graphql.data.query;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.function.Function;
|
||||
|
||||
import graphql.schema.DataFetchingEnvironment;
|
||||
|
||||
@@ -84,22 +85,25 @@ class RepositoryUtils {
|
||||
return CursorStrategy.withEncoder(new ScrollPositionCursorStrategy(), CursorEncoder.base64());
|
||||
}
|
||||
|
||||
public static int defaultScrollCount() {
|
||||
return 20;
|
||||
}
|
||||
|
||||
public static Function<Boolean, ScrollPosition> defaultScrollPosition() {
|
||||
return forward -> ScrollPosition.offset();
|
||||
}
|
||||
|
||||
public static ScrollSubrange defaultScrollSubrange() {
|
||||
return ScrollSubrange.create(ScrollPosition.offset(), 20, true);
|
||||
}
|
||||
|
||||
public static ScrollSubrange getScrollSubrange(
|
||||
DataFetchingEnvironment env, CursorStrategy<ScrollPosition> strategy,
|
||||
ScrollSubrange defaultSubrange) {
|
||||
DataFetchingEnvironment env, CursorStrategy<ScrollPosition> strategy) {
|
||||
|
||||
boolean forward = !env.getArguments().containsKey("last");
|
||||
|
||||
Integer count = env.getArgument(forward ? "first" : "last");
|
||||
count = (count != null ? count : defaultSubrange.count().getAsInt());
|
||||
|
||||
String cursor = env.getArgument(forward ? "after" : "before");
|
||||
ScrollPosition position = (cursor != null ? strategy.fromCursor(cursor) : defaultSubrange.position().get());
|
||||
|
||||
ScrollPosition position = (cursor != null ? strategy.fromCursor(cursor) : null);
|
||||
return ScrollSubrange.create(position, count, forward);
|
||||
}
|
||||
|
||||
|
||||
@@ -71,16 +71,20 @@ public final class ScrollSubrange extends Subrange<ScrollPosition> {
|
||||
|
||||
|
||||
/**
|
||||
* Create a {@link ScrollSubrange} instance.
|
||||
* @param position the position relative to which to scroll, or {@code null}
|
||||
* for scrolling from the beginning
|
||||
* @param count the number of elements requested
|
||||
* @param forward whether to return elements after (true) or before (false)
|
||||
* the element at the given position
|
||||
* @return the created subrange
|
||||
* Create a {@link ScrollSubrange} from the given inputs.
|
||||
* <p>Pagination with offset-based scrolling is always forward and inclusive
|
||||
* of the referenced item. Therefore, an {@link OffsetScrollPosition} is
|
||||
* adjusted as follows. For forward pagination, advanced by 1. For backward
|
||||
* pagination, advanced back by the count, and switched to forward.
|
||||
* @param position the reference position, or {@code null} if not specified
|
||||
* @param count how many to return, or {@code null} if not specified
|
||||
* @param forward whether scroll forward (true) or backward (false)
|
||||
* @return the created instance
|
||||
* @since 1.2.4
|
||||
*/
|
||||
public static ScrollSubrange create(@Nullable ScrollPosition position, @Nullable Integer count, boolean forward) {
|
||||
public static ScrollSubrange create(
|
||||
@Nullable ScrollPosition position, @Nullable Integer count, boolean forward) {
|
||||
|
||||
if (count != null && count < 0) {
|
||||
count = null;
|
||||
}
|
||||
@@ -98,16 +102,24 @@ public final class ScrollSubrange extends Subrange<ScrollPosition> {
|
||||
private static ScrollSubrange initFromOffsetPosition(
|
||||
OffsetScrollPosition position, @Nullable Integer count, boolean forward) {
|
||||
|
||||
if (!forward) {
|
||||
// Offset is inclusive, adapt to exclusive:
|
||||
// - for forward, add 1 to return items after position
|
||||
// - for backward, subtract count to get items before position
|
||||
|
||||
if (forward) {
|
||||
position = position.advanceBy(1);
|
||||
}
|
||||
else {
|
||||
int countOrZero = (count != null ? count : 0);
|
||||
if (countOrZero < position.getOffset()) {
|
||||
position = position.advanceBy(-countOrZero-1);
|
||||
if (position.getOffset() >= countOrZero) {
|
||||
position = position.advanceBy(-countOrZero);
|
||||
}
|
||||
else {
|
||||
count = (position.getOffset() > 0 ? (int) (position.getOffset() - 1) : 0);
|
||||
position = null;
|
||||
count = (int) position.getOffset();
|
||||
position = ScrollPosition.offset();
|
||||
}
|
||||
}
|
||||
|
||||
return new ScrollSubrange(position, count, true, null);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -49,7 +49,7 @@ public class SchemaMappingPaginationTests {
|
||||
@Test
|
||||
void forwardPagination() {
|
||||
|
||||
String document = BookSource.booksConnectionQuery("first:2, after:\"O_3\"");
|
||||
String document = BookSource.booksConnectionQuery("first:2, after:\"O_2\"");
|
||||
Mono<ExecutionGraphQlResponse> response = graphQlService().execute(document);
|
||||
|
||||
ResponseHelper.forResponse(response).assertData(
|
||||
|
||||
@@ -139,12 +139,12 @@ class QuerydslDataFetcherTests {
|
||||
ResponseHelper.forResponse(response).assertData(
|
||||
"{\"books\":{" +
|
||||
"\"edges\":[" +
|
||||
"{\"cursor\":\"O_4\",\"node\":{\"id\":\"42\",\"name\":\"Hitchhiker's Guide to the Galaxy\"}}," +
|
||||
"{\"cursor\":\"O_5\",\"node\":{\"id\":\"53\",\"name\":\"Breaking Bad\"}}" +
|
||||
"{\"cursor\":\"O_0\",\"node\":{\"id\":\"42\",\"name\":\"Hitchhiker's Guide to the Galaxy\"}}," +
|
||||
"{\"cursor\":\"O_1\",\"node\":{\"id\":\"53\",\"name\":\"Breaking Bad\"}}" +
|
||||
"]," +
|
||||
"\"pageInfo\":{" +
|
||||
"\"startCursor\":\"O_4\"," +
|
||||
"\"endCursor\":\"O_5\"," +
|
||||
"\"startCursor\":\"O_0\"," +
|
||||
"\"endCursor\":\"O_1\"," +
|
||||
"\"hasPreviousPage\":true," +
|
||||
"\"hasNextPage\":false" +
|
||||
"}}}"
|
||||
|
||||
@@ -37,8 +37,6 @@ public class RepositoryUtilsTests {
|
||||
|
||||
private final CursorStrategy<ScrollPosition> cursorStrategy = RepositoryUtils.defaultCursorStrategy();
|
||||
|
||||
private final ScrollSubrange defaultSubrange = ScrollSubrange.create(ScrollPosition.offset(50), 20, true);
|
||||
|
||||
|
||||
@Test
|
||||
void buildScrollSubrangeForward() {
|
||||
@@ -48,9 +46,9 @@ public class RepositoryUtilsTests {
|
||||
DataFetchingEnvironment env = environment(
|
||||
Map.of("first", count, "after", cursorStrategy.toCursor(offset)));
|
||||
|
||||
ScrollSubrange range = RepositoryUtils.getScrollSubrange(env, cursorStrategy, defaultSubrange);
|
||||
ScrollSubrange range = RepositoryUtils.getScrollSubrange(env, cursorStrategy);
|
||||
|
||||
assertThat(range.position().get()).isEqualTo(offset);
|
||||
assertThat(range.position().get()).isEqualTo(ScrollPosition.offset(11));
|
||||
assertThat(range.count().getAsInt()).isEqualTo(count);
|
||||
assertThat(range.forward()).isTrue();
|
||||
}
|
||||
@@ -63,42 +61,49 @@ public class RepositoryUtilsTests {
|
||||
DataFetchingEnvironment env = environment(
|
||||
Map.of("last", count, "before", cursorStrategy.toCursor(offset)));
|
||||
|
||||
ScrollSubrange range = RepositoryUtils.getScrollSubrange(env, cursorStrategy, defaultSubrange);
|
||||
ScrollSubrange range = RepositoryUtils.getScrollSubrange(env, cursorStrategy);
|
||||
|
||||
assertThat(range.position().get()).isEqualTo(offset.advanceBy(-count-1));
|
||||
assertThat(range.position().get()).isEqualTo(ScrollPosition.offset(5));
|
||||
assertThat(range.count().getAsInt()).isEqualTo(count);
|
||||
assertThat(range.forward()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void buildScrollSubrangeForwardWithDefaultScrollSubrange() {
|
||||
void noInput() {
|
||||
DataFetchingEnvironment env = environment(Collections.emptyMap());
|
||||
ScrollSubrange range = RepositoryUtils.getScrollSubrange(env, cursorStrategy, defaultSubrange);
|
||||
ScrollSubrange range = RepositoryUtils.getScrollSubrange(env, cursorStrategy);
|
||||
|
||||
assertThat(range.position().get()).isEqualTo(getDefaultPosition());
|
||||
assertThat(range.count().getAsInt()).isEqualTo(this.defaultSubrange.count().getAsInt());
|
||||
assertThat(range.position()).isNotPresent();
|
||||
assertThat(range.count()).isNotPresent();
|
||||
assertThat(range.forward()).isTrue();
|
||||
}
|
||||
|
||||
@Test // gh-900
|
||||
void buildScrollSubrangeBackwardFromDefaultPosition() {
|
||||
@Test
|
||||
void buildScrollSubrangeForwardWithoutPosition() {
|
||||
int count = 5;
|
||||
DataFetchingEnvironment env = environment(Map.of("last", count));
|
||||
ScrollSubrange range = RepositoryUtils.getScrollSubrange(env, cursorStrategy, defaultSubrange);
|
||||
DataFetchingEnvironment env = environment(Map.of("first", count));
|
||||
ScrollSubrange range = RepositoryUtils.getScrollSubrange(env, cursorStrategy);
|
||||
|
||||
assertThat(range.position().get()).isEqualTo(getDefaultPosition().advanceBy(-count-1));
|
||||
assertThat(range.position()).isNotPresent();
|
||||
assertThat(range.count().getAsInt()).isEqualTo(count);
|
||||
assertThat(range.forward()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void buildScrollSubrangeBackwardWithoutPosition() {
|
||||
int count = 5;
|
||||
DataFetchingEnvironment env = environment(Map.of("last", count));
|
||||
ScrollSubrange range = RepositoryUtils.getScrollSubrange(env, cursorStrategy);
|
||||
|
||||
assertThat(range.position()).isNotPresent();
|
||||
assertThat(range.count().getAsInt()).isEqualTo(count);
|
||||
assertThat(range.forward()).isFalse();
|
||||
}
|
||||
|
||||
private static DataFetchingEnvironment environment(Map<String, Object> arguments) {
|
||||
return DataFetchingEnvironmentImpl.newDataFetchingEnvironment()
|
||||
.arguments(arguments)
|
||||
.build();
|
||||
}
|
||||
|
||||
private OffsetScrollPosition getDefaultPosition() {
|
||||
return (OffsetScrollPosition) defaultSubrange.position().get();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -37,40 +37,53 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
public class ScrollSubrangeTests {
|
||||
|
||||
@Test
|
||||
void offset() {
|
||||
ScrollPosition position = ScrollPosition.offset(30);
|
||||
void offsetForward() {
|
||||
int count = 10;
|
||||
ScrollSubrange subrange = ScrollSubrange.create(ScrollPosition.offset(30), count, true);
|
||||
|
||||
ScrollSubrange subrange = ScrollSubrange.create(position, count, true);
|
||||
assertThat(((OffsetScrollPosition) subrange.position().get())).isEqualTo(position);
|
||||
assertThat(subrange.count().orElse(0)).isEqualTo(count);
|
||||
assertThat(subrange.forward()).isTrue();
|
||||
|
||||
subrange = ScrollSubrange.create(position, count, false);
|
||||
assertThat(((OffsetScrollPosition) subrange.position().get()).getOffset()).isEqualTo(19);
|
||||
assertThat(getOffset(subrange)).isEqualTo(31);
|
||||
assertThat(subrange.count().orElse(0)).isEqualTo(count);
|
||||
assertThat(subrange.forward()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void keyset() {
|
||||
void offsetBackward() {
|
||||
int count = 10;
|
||||
ScrollSubrange subrange = ScrollSubrange.create(ScrollPosition.offset(30), count, false);
|
||||
|
||||
assertThat(getOffset(subrange)).isEqualTo(20);
|
||||
assertThat(subrange.count().orElse(0)).isEqualTo(count);
|
||||
assertThat(subrange.forward()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void keysetForward() {
|
||||
Map<String, Object> keys = new LinkedHashMap<>();
|
||||
keys.put("firstName", "Joseph");
|
||||
keys.put("lastName", "Heller");
|
||||
keys.put("id", 103);
|
||||
|
||||
ScrollPosition position = ScrollPosition.forward(keys);
|
||||
int count = 10;
|
||||
ScrollSubrange subrange = ScrollSubrange.create(ScrollPosition.forward(keys), count, true);
|
||||
|
||||
ScrollSubrange subrange = ScrollSubrange.create(position, count, true);
|
||||
KeysetScrollPosition actualPosition = (KeysetScrollPosition) subrange.position().get();
|
||||
assertThat(actualPosition.getKeys()).isEqualTo(keys);
|
||||
assertThat(actualPosition.getDirection()).isEqualTo(Direction.FORWARD);
|
||||
assertThat(subrange.count().orElse(0)).isEqualTo(count);
|
||||
assertThat(subrange.forward()).isTrue();
|
||||
}
|
||||
|
||||
subrange = ScrollSubrange.create(position, count, false);
|
||||
actualPosition = (KeysetScrollPosition) subrange.position().get();
|
||||
@Test
|
||||
void keysetBackward() {
|
||||
Map<String, Object> keys = new LinkedHashMap<>();
|
||||
keys.put("firstName", "Joseph");
|
||||
keys.put("lastName", "Heller");
|
||||
keys.put("id", 103);
|
||||
|
||||
int count = 10;
|
||||
ScrollSubrange subrange = ScrollSubrange.create(ScrollPosition.forward(keys), count, false);
|
||||
|
||||
KeysetScrollPosition actualPosition = (KeysetScrollPosition) subrange.position().get();
|
||||
assertThat(actualPosition.getKeys()).isEqualTo(keys);
|
||||
assertThat(actualPosition.getDirection()).isEqualTo(Direction.BACKWARD);
|
||||
assertThat(subrange.count().orElse(0)).isEqualTo(count);
|
||||
@@ -87,33 +100,34 @@ public class ScrollSubrangeTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void offsetBackwardPaginationWithInsufficientCount() {
|
||||
ScrollPosition position = ScrollPosition.offset(5);
|
||||
ScrollSubrange subrange = ScrollSubrange.create(position, 10, false);
|
||||
void offsetBackwardWithInsufficientCount() {
|
||||
ScrollSubrange subrange = ScrollSubrange.create(ScrollPosition.offset(5), 10, false);
|
||||
|
||||
assertThat(subrange.position()).isNotPresent();
|
||||
assertThat(subrange.count().getAsInt()).isEqualTo(4);
|
||||
assertThat(getOffset(subrange)).isEqualTo(0);
|
||||
assertThat(subrange.count().getAsInt()).isEqualTo(5);
|
||||
assertThat(subrange.forward()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void offsetBackwardPaginationWithOffsetZero() {
|
||||
ScrollPosition position = ScrollPosition.offset(0);
|
||||
ScrollSubrange subrange = ScrollSubrange.create(position, 10, false);
|
||||
void offsetBackwardFromInitialOffset() {
|
||||
ScrollSubrange subrange = ScrollSubrange.create(ScrollPosition.offset(0), 10, false);
|
||||
|
||||
assertThat(subrange.position()).isNotPresent();
|
||||
assertThat(getOffset(subrange)).isEqualTo(0);
|
||||
assertThat(subrange.count().getAsInt()).isEqualTo(0);
|
||||
assertThat(subrange.forward()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void offsetBackwardPaginationWithNullCount() {
|
||||
ScrollPosition position = ScrollPosition.offset(30);
|
||||
ScrollSubrange subrange = ScrollSubrange.create(position, null, false);
|
||||
void offsetBackwardWithNullCount() {
|
||||
ScrollSubrange subrange = ScrollSubrange.create(ScrollPosition.offset(30), null, false);
|
||||
|
||||
assertThat(subrange.position()).hasValue(ScrollPosition.offset(29));
|
||||
assertThat(getOffset(subrange)).isEqualTo(30);
|
||||
assertThat(subrange.count()).isNotPresent();
|
||||
assertThat(subrange.forward()).isTrue();
|
||||
}
|
||||
|
||||
private static long getOffset(ScrollSubrange subrange) {
|
||||
return ((OffsetScrollPosition) subrange.position().get()).getOffset();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ class QueryByExampleDataFetcherJpaTests {
|
||||
|
||||
Mono<WebGraphQlResponse> response = graphQlSetup
|
||||
.toWebGraphQlHandler()
|
||||
.handleRequest(request(BookSource.booksConnectionQuery("first:2, after:\"O_3\"")));
|
||||
.handleRequest(request(BookSource.booksConnectionQuery("first:2, after:\"O_2\"")));
|
||||
|
||||
List<Map<String, Object>> edges = ResponseHelper.forResponse(response).toEntity("books.edges", List.class);
|
||||
assertThat(edges.size()).isEqualTo(2);
|
||||
|
||||
@@ -134,7 +134,7 @@ class QueryByExampleDataFetcherMongoDbTests {
|
||||
|
||||
Mono<WebGraphQlResponse> response = graphQlSetup
|
||||
.toWebGraphQlHandler()
|
||||
.handleRequest(request(BookSource.booksConnectionQuery("first:2, after:\"O_3\"")));
|
||||
.handleRequest(request(BookSource.booksConnectionQuery("first:2, after:\"O_2\"")));
|
||||
|
||||
List<Map<String, Object>> edges = ResponseHelper.forResponse(response).toEntity("books.edges", List.class);
|
||||
assertThat(edges.size()).isEqualTo(2);
|
||||
|
||||
@@ -159,7 +159,7 @@ class QueryByExampleDataFetcherReactiveMongoDbTests {
|
||||
|
||||
Mono<WebGraphQlResponse> response = graphQlSetup
|
||||
.toWebGraphQlHandler()
|
||||
.handleRequest(request(BookSource.booksConnectionQuery("first:2, after:\"O_3\"")));
|
||||
.handleRequest(request(BookSource.booksConnectionQuery("first:2, after:\"O_2\"")));
|
||||
|
||||
List<Map<String, Object>> edges = ResponseHelper.forResponse(response).toEntity("books.edges", List.class);
|
||||
assertThat(edges.size()).isEqualTo(2);
|
||||
|
||||
@@ -136,7 +136,7 @@ class QueryByExampleDataFetcherNeo4jTests {
|
||||
|
||||
Mono<WebGraphQlResponse> response = graphQlSetup
|
||||
.toWebGraphQlHandler()
|
||||
.handleRequest(request(BookSource.booksConnectionQuery("first:2, after:\"O_3\"")));
|
||||
.handleRequest(request(BookSource.booksConnectionQuery("first:2, after:\"O_2\"")));
|
||||
|
||||
List<Map<String, Object>> edges = ResponseHelper.forResponse(response).toEntity("books.edges", List.class);
|
||||
assertThat(edges.size()).isEqualTo(2);
|
||||
|
||||
@@ -172,7 +172,7 @@ class QueryByExampleDataFetcherReactiveNeo4jDbTests {
|
||||
|
||||
Mono<WebGraphQlResponse> response = graphQlSetup
|
||||
.toWebGraphQlHandler()
|
||||
.handleRequest(request(BookSource.booksConnectionQuery("first:2, after:\"O_3\"")));
|
||||
.handleRequest(request(BookSource.booksConnectionQuery("first:2, after:\"O_2\"")));
|
||||
|
||||
List<Map<String, Object>> edges = ResponseHelper.forResponse(response).toEntity("books.edges", List.class);
|
||||
assertThat(edges.size()).isEqualTo(2);
|
||||
|
||||
Reference in New Issue
Block a user