diff --git a/spring-graphql/src/main/java/org/springframework/graphql/data/query/QueryByExampleDataFetcher.java b/spring-graphql/src/main/java/org/springframework/graphql/data/query/QueryByExampleDataFetcher.java index f9d90569..352a8a99 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/data/query/QueryByExampleDataFetcher.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/data/query/QueryByExampleDataFetcher.java @@ -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 { private final CursorStrategy cursorStrategy; @Nullable - private final ScrollSubrange defaultSubrange; + private final Integer defaultScrollCount; + + @Nullable + private final Function defaultScrollPosition; private final Sort sort; @SuppressWarnings("unchecked") Builder(QueryByExampleExecutor executor, Class domainType) { - this(executor, TypeInformation.of((Class) domainType), domainType, null, null, Sort.unsorted()); + this(executor, TypeInformation.of((Class) domainType), domainType, null, null, null, Sort.unsorted()); } Builder(QueryByExampleExecutor executor, TypeInformation domainType, Class resultType, - @Nullable CursorStrategy cursorStrategy, @Nullable ScrollSubrange defaultSubrange, + @Nullable CursorStrategy cursorStrategy, + @Nullable Integer defaultScrollCount, @Nullable Function 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 { */ public

Builder projectAs(Class

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 { * @since 1.2.0 */ public Builder cursorStrategy(@Nullable CursorStrategy 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. + *

For offset scrolling, use {@link ScrollPosition#offset()} to scroll + * from the beginning. Currently, it is not possible to go back from the end. + *

For keyset scrolling, use {@link ScrollPosition#keyset()} to scroll + * from the beginning, or {@link KeysetScrollPosition#reverse()} the same + * to go back from the end. + *

By default a count of 20 and {@link ScrollPosition#offset()} are used. + * @since 1.2.5 + */ + public Builder defaultScrollSubrange( + int defaultCount, Function 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 { * 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 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 { */ public Builder 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 { 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 { private final CursorStrategy cursorStrategy; @Nullable - private final ScrollSubrange defaultSubrange; + private final Integer defaultScrollCount; + + @Nullable + private final Function defaultScrollPosition; private final Sort sort; @SuppressWarnings("unchecked") ReactiveBuilder(ReactiveQueryByExampleExecutor executor, Class domainType) { - this(executor, TypeInformation.of((Class) domainType), domainType, null, null, Sort.unsorted()); + this(executor, TypeInformation.of((Class) domainType), domainType, null, null, null, Sort.unsorted()); } ReactiveBuilder( ReactiveQueryByExampleExecutor executor, TypeInformation domainType, Class resultType, - @Nullable CursorStrategy cursorStrategy, @Nullable ScrollSubrange defaultSubrange, + @Nullable CursorStrategy cursorStrategy, + @Nullable Integer defaultScrollCount, @Nullable Function 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 { public

ReactiveBuilder projectAs(Class

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 { * @since 1.2.0 */ public ReactiveBuilder cursorStrategy(@Nullable CursorStrategy 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. + *

For offset scrolling, use {@link ScrollPosition#offset()} to scroll + * from the beginning. Currently, it is not possible to go back from the end. + *

For keyset scrolling, use {@link ScrollPosition#keyset()} to scroll + * from the beginning, or {@link KeysetScrollPosition#reverse()} the same + * to go back from the end. + *

By default a count of 20 and {@link ScrollPosition#offset()} are used. + * @since 1.2.5 + */ + public ReactiveBuilder defaultScrollSubrange( + int defaultCount, Function 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 { * 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 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 { */ public ReactiveBuilder 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 { 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 { private final CursorStrategy cursorStrategy; - private final ScrollSubrange defaultSubrange; + private final int defaultCount; + + private final Function defaultPosition; private final ResolvableType scrollableResultType; ScrollableEntityFetcher( QueryByExampleExecutor executor, TypeInformation domainType, Class resultType, - CursorStrategy cursorStrategy, ScrollSubrange defaultSubrange, Sort sort) { + CursorStrategy cursorStrategy, + int defaultCount, + Function 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 { return ResolvableType.forClassWithGenerics(Iterable.class, this.scrollableResultType); } - @SuppressWarnings("OptionalGetWithoutIsPresent") @Override protected Iterable getResult(FluentQuery.FetchableFluentQuery 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 { private final CursorStrategy cursorStrategy; - private final ScrollSubrange defaultSubrange; + private final int defaultCount; + + private final Function defaultPosition; private final Sort sort; ReactiveScrollableEntityFetcher( ReactiveQueryByExampleExecutor executor, TypeInformation domainType, Class resultType, - CursorStrategy cursorStrategy, ScrollSubrange defaultSubrange, Sort sort) { + CursorStrategy cursorStrategy, + int defaultCount, + Function 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 { } @Override - @SuppressWarnings({"unchecked", "OptionalGetWithoutIsPresent"}) + @SuppressWarnings("unchecked") public Mono> get(DataFetchingEnvironment env) throws BindException { return this.executor.findBy(buildExample(env), query -> { FluentQuery.ReactiveFluentQuery queryToUse = (FluentQuery.ReactiveFluentQuery) query; @@ -936,9 +1003,10 @@ public abstract class QueryByExampleDataFetcher { 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()); }); } diff --git a/spring-graphql/src/main/java/org/springframework/graphql/data/query/QuerydslDataFetcher.java b/spring-graphql/src/main/java/org/springframework/graphql/data/query/QuerydslDataFetcher.java index 93813f77..532aacae 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/data/query/QuerydslDataFetcher.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/data/query/QuerydslDataFetcher.java @@ -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 { private final CursorStrategy cursorStrategy; @Nullable - private final ScrollSubrange defaultSubrange; + private final Integer defaultScrollCount; + + @Nullable + private final Function defaultScrollPosition; private final Sort sort; @@ -421,18 +425,20 @@ public abstract class QuerydslDataFetcher { @SuppressWarnings("unchecked") Builder(QuerydslPredicateExecutor executor, Class domainType) { this(executor, TypeInformation.of((Class) domainType), - domainType, null, null, Sort.unsorted(), NO_OP_BINDER_CUSTOMIZER); + domainType, null, null, null, Sort.unsorted(), NO_OP_BINDER_CUSTOMIZER); } Builder(QuerydslPredicateExecutor executor, TypeInformation domainType, Class resultType, - @Nullable CursorStrategy cursorStrategy, @Nullable ScrollSubrange defaultSubrange, + @Nullable CursorStrategy cursorStrategy, + @Nullable Integer defaultScrollCount, @Nullable Function defaultScrollPosition, Sort sort, QuerydslBinderCustomizer> 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 { public

Builder projectAs(Class

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 { */ public Builder cursorStrategy(@Nullable CursorStrategy 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. + *

For offset scrolling, use {@link ScrollPosition#offset()} to scroll + * from the beginning. Currently, it is not possible to go back from the end. + *

For keyset scrolling, use {@link ScrollPosition#keyset()} to scroll + * from the beginning, or {@link KeysetScrollPosition#reverse()} the same + * to go back from the end. + *

By default a count of 20 and {@link ScrollPosition#offset()} are used. + * @since 1.2.5 + */ + public Builder defaultScrollSubrange( + int defaultCount, Function 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. - *

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 + *

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 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 { public Builder 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 { public Builder customizer(QuerydslBinderCustomizer> 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 { 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 { private final CursorStrategy cursorStrategy; @Nullable - private final ScrollSubrange defaultSubrange; + private final Integer defaultScrollCount; + + @Nullable + private final Function defaultScrollPosition; private final Sort sort; @@ -592,19 +627,21 @@ public abstract class QuerydslDataFetcher { @SuppressWarnings("unchecked") ReactiveBuilder(ReactiveQuerydslPredicateExecutor executor, Class domainType) { this(executor, TypeInformation.of((Class) domainType), - domainType, null, null, Sort.unsorted(), NO_OP_BINDER_CUSTOMIZER); + domainType, null, null, null, Sort.unsorted(), NO_OP_BINDER_CUSTOMIZER); } ReactiveBuilder( ReactiveQuerydslPredicateExecutor executor, TypeInformation domainType, Class resultType, - @Nullable CursorStrategy cursorStrategy, @Nullable ScrollSubrange defaultSubrange, + @Nullable CursorStrategy cursorStrategy, + @Nullable Integer defaultScrollCount, @Nullable Function defaultScrollPosition, Sort sort, QuerydslBinderCustomizer> 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 { public

ReactiveBuilder projectAs(Class

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 { */ public ReactiveBuilder cursorStrategy(@Nullable CursorStrategy 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. + *

For offset scrolling, use {@link ScrollPosition#offset()} to scroll + * from the beginning. Currently, it is not possible to go back from the end. + *

For keyset scrolling, use {@link ScrollPosition#keyset()} to scroll + * from the beginning, or {@link KeysetScrollPosition#reverse()} the same + * to go back from the end. + *

By default a count of 20 and {@link ScrollPosition#offset()} are used. + * @since 1.2.5 + */ + public ReactiveBuilder defaultScrollSubrange( + int defaultCount, Function 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. - *

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 + *

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 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 { public ReactiveBuilder 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 { public ReactiveBuilder customizer(QuerydslBinderCustomizer> 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 { 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 { private final CursorStrategy cursorStrategy; - private final ScrollSubrange defaultSubrange; + private final int defaultCount; + + private final Function defaultPosition; ScrollableEntityFetcher(QuerydslPredicateExecutor executor, TypeInformation domainType, Class resultType, CursorStrategy cursorStrategy, - ScrollSubrange defaultSubrange, + int defaultCount, + Function defaultPosition, Sort sort, QuerydslBinderCustomizer> 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 getResult(FetchableFluentQuery 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 { private final CursorStrategy cursorStrategy; - private final ScrollSubrange defaultSubrange; + private final int defaultCount; + + private final Function defaultPosition; private final Sort sort; @@ -995,22 +1063,23 @@ public abstract class QuerydslDataFetcher { ReactiveScrollableEntityFetcher(ReactiveQuerydslPredicateExecutor executor, TypeInformation domainType, Class resultType, - CursorStrategy cursorStrategy, ScrollSubrange defaultSubrange, + CursorStrategy cursorStrategy, + int defaultCount, + Function defaultPosition, Sort sort, QuerydslBinderCustomizer> 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 { } @Override - @SuppressWarnings({"unchecked", "OptionalGetWithoutIsPresent"}) + @SuppressWarnings("unchecked") public Mono> get(DataFetchingEnvironment env) { return this.executor.findBy(buildPredicate(env), query -> { FluentQuery.ReactiveFluentQuery queryToUse = (FluentQuery.ReactiveFluentQuery) query; @@ -1036,9 +1105,10 @@ public abstract class QuerydslDataFetcher { 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()); }); } diff --git a/spring-graphql/src/main/java/org/springframework/graphql/data/query/RepositoryUtils.java b/spring-graphql/src/main/java/org/springframework/graphql/data/query/RepositoryUtils.java index 7b0a1875..3734f1c1 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/data/query/RepositoryUtils.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/data/query/RepositoryUtils.java @@ -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 defaultScrollPosition() { + return forward -> ScrollPosition.offset(); + } + public static ScrollSubrange defaultScrollSubrange() { return ScrollSubrange.create(ScrollPosition.offset(), 20, true); } public static ScrollSubrange getScrollSubrange( - DataFetchingEnvironment env, CursorStrategy strategy, - ScrollSubrange defaultSubrange) { + DataFetchingEnvironment env, CursorStrategy 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); } diff --git a/spring-graphql/src/main/java/org/springframework/graphql/data/query/ScrollSubrange.java b/spring-graphql/src/main/java/org/springframework/graphql/data/query/ScrollSubrange.java index 3cd347d3..4aefdcf6 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/data/query/ScrollSubrange.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/data/query/ScrollSubrange.java @@ -71,16 +71,20 @@ public final class ScrollSubrange extends Subrange { /** - * 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. + *

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 { 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); } diff --git a/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/SchemaMappingPaginationTests.java b/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/SchemaMappingPaginationTests.java index 8008091a..04e2f097 100644 --- a/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/SchemaMappingPaginationTests.java +++ b/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/SchemaMappingPaginationTests.java @@ -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 response = graphQlService().execute(document); ResponseHelper.forResponse(response).assertData( diff --git a/spring-graphql/src/test/java/org/springframework/graphql/data/query/QuerydslDataFetcherTests.java b/spring-graphql/src/test/java/org/springframework/graphql/data/query/QuerydslDataFetcherTests.java index b8aa82af..0eb54c67 100644 --- a/spring-graphql/src/test/java/org/springframework/graphql/data/query/QuerydslDataFetcherTests.java +++ b/spring-graphql/src/test/java/org/springframework/graphql/data/query/QuerydslDataFetcherTests.java @@ -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" + "}}}" diff --git a/spring-graphql/src/test/java/org/springframework/graphql/data/query/RepositoryUtilsTests.java b/spring-graphql/src/test/java/org/springframework/graphql/data/query/RepositoryUtilsTests.java index 0a523e3b..239fab79 100644 --- a/spring-graphql/src/test/java/org/springframework/graphql/data/query/RepositoryUtilsTests.java +++ b/spring-graphql/src/test/java/org/springframework/graphql/data/query/RepositoryUtilsTests.java @@ -37,8 +37,6 @@ public class RepositoryUtilsTests { private final CursorStrategy 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 arguments) { return DataFetchingEnvironmentImpl.newDataFetchingEnvironment() .arguments(arguments) .build(); } - private OffsetScrollPosition getDefaultPosition() { - return (OffsetScrollPosition) defaultSubrange.position().get(); - } - } diff --git a/spring-graphql/src/test/java/org/springframework/graphql/data/query/ScrollSubrangeTests.java b/spring-graphql/src/test/java/org/springframework/graphql/data/query/ScrollSubrangeTests.java index 0776e8ac..c8babeea 100644 --- a/spring-graphql/src/test/java/org/springframework/graphql/data/query/ScrollSubrangeTests.java +++ b/spring-graphql/src/test/java/org/springframework/graphql/data/query/ScrollSubrangeTests.java @@ -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 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 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(); + } + } diff --git a/spring-graphql/src/test/java/org/springframework/graphql/data/query/jpa/QueryByExampleDataFetcherJpaTests.java b/spring-graphql/src/test/java/org/springframework/graphql/data/query/jpa/QueryByExampleDataFetcherJpaTests.java index 0a41f632..18d30715 100644 --- a/spring-graphql/src/test/java/org/springframework/graphql/data/query/jpa/QueryByExampleDataFetcherJpaTests.java +++ b/spring-graphql/src/test/java/org/springframework/graphql/data/query/jpa/QueryByExampleDataFetcherJpaTests.java @@ -137,7 +137,7 @@ class QueryByExampleDataFetcherJpaTests { Mono response = graphQlSetup .toWebGraphQlHandler() - .handleRequest(request(BookSource.booksConnectionQuery("first:2, after:\"O_3\""))); + .handleRequest(request(BookSource.booksConnectionQuery("first:2, after:\"O_2\""))); List> edges = ResponseHelper.forResponse(response).toEntity("books.edges", List.class); assertThat(edges.size()).isEqualTo(2); diff --git a/spring-graphql/src/test/java/org/springframework/graphql/data/query/mongo/QueryByExampleDataFetcherMongoDbTests.java b/spring-graphql/src/test/java/org/springframework/graphql/data/query/mongo/QueryByExampleDataFetcherMongoDbTests.java index 58dbf134..9cd3d855 100644 --- a/spring-graphql/src/test/java/org/springframework/graphql/data/query/mongo/QueryByExampleDataFetcherMongoDbTests.java +++ b/spring-graphql/src/test/java/org/springframework/graphql/data/query/mongo/QueryByExampleDataFetcherMongoDbTests.java @@ -134,7 +134,7 @@ class QueryByExampleDataFetcherMongoDbTests { Mono response = graphQlSetup .toWebGraphQlHandler() - .handleRequest(request(BookSource.booksConnectionQuery("first:2, after:\"O_3\""))); + .handleRequest(request(BookSource.booksConnectionQuery("first:2, after:\"O_2\""))); List> edges = ResponseHelper.forResponse(response).toEntity("books.edges", List.class); assertThat(edges.size()).isEqualTo(2); diff --git a/spring-graphql/src/test/java/org/springframework/graphql/data/query/mongo/QueryByExampleDataFetcherReactiveMongoDbTests.java b/spring-graphql/src/test/java/org/springframework/graphql/data/query/mongo/QueryByExampleDataFetcherReactiveMongoDbTests.java index 3019acd6..a5e32c0b 100644 --- a/spring-graphql/src/test/java/org/springframework/graphql/data/query/mongo/QueryByExampleDataFetcherReactiveMongoDbTests.java +++ b/spring-graphql/src/test/java/org/springframework/graphql/data/query/mongo/QueryByExampleDataFetcherReactiveMongoDbTests.java @@ -159,7 +159,7 @@ class QueryByExampleDataFetcherReactiveMongoDbTests { Mono response = graphQlSetup .toWebGraphQlHandler() - .handleRequest(request(BookSource.booksConnectionQuery("first:2, after:\"O_3\""))); + .handleRequest(request(BookSource.booksConnectionQuery("first:2, after:\"O_2\""))); List> edges = ResponseHelper.forResponse(response).toEntity("books.edges", List.class); assertThat(edges.size()).isEqualTo(2); diff --git a/spring-graphql/src/test/java/org/springframework/graphql/data/query/neo4j/QueryByExampleDataFetcherNeo4jTests.java b/spring-graphql/src/test/java/org/springframework/graphql/data/query/neo4j/QueryByExampleDataFetcherNeo4jTests.java index 9943dd6d..406f096a 100644 --- a/spring-graphql/src/test/java/org/springframework/graphql/data/query/neo4j/QueryByExampleDataFetcherNeo4jTests.java +++ b/spring-graphql/src/test/java/org/springframework/graphql/data/query/neo4j/QueryByExampleDataFetcherNeo4jTests.java @@ -136,7 +136,7 @@ class QueryByExampleDataFetcherNeo4jTests { Mono response = graphQlSetup .toWebGraphQlHandler() - .handleRequest(request(BookSource.booksConnectionQuery("first:2, after:\"O_3\""))); + .handleRequest(request(BookSource.booksConnectionQuery("first:2, after:\"O_2\""))); List> edges = ResponseHelper.forResponse(response).toEntity("books.edges", List.class); assertThat(edges.size()).isEqualTo(2); diff --git a/spring-graphql/src/test/java/org/springframework/graphql/data/query/neo4j/QueryByExampleDataFetcherReactiveNeo4jDbTests.java b/spring-graphql/src/test/java/org/springframework/graphql/data/query/neo4j/QueryByExampleDataFetcherReactiveNeo4jDbTests.java index 994162d2..0361f5d1 100644 --- a/spring-graphql/src/test/java/org/springframework/graphql/data/query/neo4j/QueryByExampleDataFetcherReactiveNeo4jDbTests.java +++ b/spring-graphql/src/test/java/org/springframework/graphql/data/query/neo4j/QueryByExampleDataFetcherReactiveNeo4jDbTests.java @@ -172,7 +172,7 @@ class QueryByExampleDataFetcherReactiveNeo4jDbTests { Mono response = graphQlSetup .toWebGraphQlHandler() - .handleRequest(request(BookSource.booksConnectionQuery("first:2, after:\"O_3\""))); + .handleRequest(request(BookSource.booksConnectionQuery("first:2, after:\"O_2\""))); List> edges = ResponseHelper.forResponse(response).toEntity("books.edges", List.class); assertThat(edges.size()).isEqualTo(2);