Merge branch '1.2.x'

This commit is contained in:
rstoyanchev
2024-02-20 23:20:07 +00:00
13 changed files with 343 additions and 170 deletions

View File

@@ -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());
});
}

View File

@@ -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());
});
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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(

View File

@@ -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" +
"}}}"

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);