Skip to content

Commit 248cfe2

Browse files
committed
Revise default ScrollSubrange for QueryDsl and QBE
The QueryDsl and QBE data fetcher builders now accept a default count and a function for a default position with awareness of the scroll direction. Closes gh-917
1 parent 9817230 commit 248cfe2

File tree

4 files changed

+264
-117
lines changed

4 files changed

+264
-117
lines changed

spring-graphql/src/main/java/org/springframework/graphql/data/query/QueryByExampleDataFetcher.java

Lines changed: 111 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535

3636
import org.springframework.core.ResolvableType;
3737
import org.springframework.data.domain.Example;
38+
import org.springframework.data.domain.KeysetScrollPosition;
3839
import org.springframework.data.domain.OffsetScrollPosition;
3940
import org.springframework.data.domain.ScrollPosition;
4041
import org.springframework.data.domain.Sort;
@@ -370,24 +371,29 @@ public static class Builder<T, R> {
370371
private final CursorStrategy<ScrollPosition> cursorStrategy;
371372

372373
@Nullable
373-
private final ScrollSubrange defaultSubrange;
374+
private final Integer defaultScrollCount;
375+
376+
@Nullable
377+
private final Function<Boolean, ScrollPosition> defaultScrollPosition;
374378

375379
private final Sort sort;
376380

377381
@SuppressWarnings("unchecked")
378382
Builder(QueryByExampleExecutor<T> executor, Class<R> domainType) {
379-
this(executor, TypeInformation.of((Class<T>) domainType), domainType, null, null, Sort.unsorted());
383+
this(executor, TypeInformation.of((Class<T>) domainType), domainType, null, null, null, Sort.unsorted());
380384
}
381385

382386
Builder(QueryByExampleExecutor<T> executor, TypeInformation<T> domainType, Class<R> resultType,
383-
@Nullable CursorStrategy<ScrollPosition> cursorStrategy, @Nullable ScrollSubrange defaultSubrange,
387+
@Nullable CursorStrategy<ScrollPosition> cursorStrategy,
388+
@Nullable Integer defaultScrollCount, @Nullable Function<Boolean, ScrollPosition> defaultScrollPosition,
384389
Sort sort) {
385390

386391
this.executor = executor;
387392
this.domainType = domainType;
388393
this.resultType = resultType;
389394
this.cursorStrategy = cursorStrategy;
390-
this.defaultSubrange = defaultSubrange;
395+
this.defaultScrollCount = defaultScrollCount;
396+
this.defaultScrollPosition = defaultScrollPosition;
391397
this.sort = sort;
392398
}
393399

@@ -402,8 +408,8 @@ public static class Builder<T, R> {
402408
*/
403409
public <P> Builder<T, P> projectAs(Class<P> projectionType) {
404410
Assert.notNull(projectionType, "Projection type must not be null");
405-
return new Builder<>(this.executor, this.domainType,
406-
projectionType, this.cursorStrategy, this.defaultSubrange, this.sort);
411+
return new Builder<>(this.executor, this.domainType, projectionType,
412+
this.cursorStrategy, this.defaultScrollCount, this.defaultScrollPosition, this.sort);
407413
}
408414

409415
/**
@@ -416,8 +422,26 @@ public <P> Builder<T, P> projectAs(Class<P> projectionType) {
416422
* @since 1.2.0
417423
*/
418424
public Builder<T, R> cursorStrategy(@Nullable CursorStrategy<ScrollPosition> cursorStrategy) {
425+
return new Builder<>(this.executor, this.domainType, this.resultType,
426+
cursorStrategy, this.defaultScrollCount, this.defaultScrollPosition, this.sort);
427+
}
428+
429+
/**
430+
* Configure a default scroll count to use, and function to return a default
431+
* {@link ScrollPosition} for forward vs backward pagination.
432+
* <p>For offset scrolling, use {@link ScrollPosition#offset()} to scroll
433+
* from the beginning. Currently, it is not possible to go back from the end.
434+
* <p>For keyset scrolling, use {@link ScrollPosition#keyset()} to scroll
435+
* from the beginning, or {@link KeysetScrollPosition#reverse()} the same
436+
* to go back from the end.
437+
* <p>By default a count of 20 and {@link ScrollPosition#offset()} are used.
438+
* @since 1.2.5
439+
*/
440+
public Builder<T, R> defaultScrollSubrange(
441+
int defaultCount, Function<Boolean, ScrollPosition> defaultPosition) {
442+
419443
return new Builder<>(this.executor, this.domainType,
420-
this.resultType, cursorStrategy, this.defaultSubrange, this.sort);
444+
this.resultType, this.cursorStrategy, defaultCount, defaultPosition, this.sort);
421445
}
422446

423447
/**
@@ -427,11 +451,16 @@ public Builder<T, R> cursorStrategy(@Nullable CursorStrategy<ScrollPosition> cur
427451
* count of 20.
428452
* @return a new {@link Builder} instance with all previously configured
429453
* options and {@code Sort} applied
430-
* @since 1.2.0
454+
* @deprecated in favor of {@link #defaultScrollSubrange(int, Function)}
431455
*/
456+
@SuppressWarnings("OptionalGetWithoutIsPresent")
457+
@Deprecated(since = "1.2.5", forRemoval = true)
432458
public Builder<T, R> defaultScrollSubrange(@Nullable ScrollSubrange defaultSubrange) {
433459
return new Builder<>(this.executor, this.domainType,
434-
this.resultType, this.cursorStrategy, defaultSubrange, this.sort);
460+
this.resultType, this.cursorStrategy,
461+
(defaultSubrange != null ? defaultSubrange.count().getAsInt() : null),
462+
(defaultSubrange != null ? forward -> defaultSubrange.position().get() : null),
463+
this.sort);
435464
}
436465

437466
/**
@@ -442,8 +471,8 @@ public Builder<T, R> defaultScrollSubrange(@Nullable ScrollSubrange defaultSubra
442471
*/
443472
public Builder<T, R> sortBy(Sort sort) {
444473
Assert.notNull(sort, "Sort must not be null");
445-
return new Builder<>(this.executor, this.domainType,
446-
this.resultType, this.cursorStrategy, this.defaultSubrange, sort);
474+
return new Builder<>(this.executor, this.domainType, this.resultType,
475+
this.cursorStrategy, this.defaultScrollCount, this.defaultScrollPosition, sort);
447476
}
448477

449478
/**
@@ -469,7 +498,8 @@ public DataFetcher<Iterable<R>> scrollable() {
469498
return new ScrollableEntityFetcher<>(
470499
this.executor, this.domainType, this.resultType,
471500
(this.cursorStrategy != null ? this.cursorStrategy : RepositoryUtils.defaultCursorStrategy()),
472-
(this.defaultSubrange != null ? this.defaultSubrange : RepositoryUtils.defaultScrollSubrange()),
501+
(this.defaultScrollCount != null ? this.defaultScrollCount : RepositoryUtils.defaultScrollCount()),
502+
(this.defaultScrollPosition != null ? this.defaultScrollPosition : RepositoryUtils.defaultScrollPosition()),
473503
this.sort);
474504
}
475505

@@ -516,25 +546,30 @@ public static class ReactiveBuilder<T, R> {
516546
private final CursorStrategy<ScrollPosition> cursorStrategy;
517547

518548
@Nullable
519-
private final ScrollSubrange defaultSubrange;
549+
private final Integer defaultScrollCount;
550+
551+
@Nullable
552+
private final Function<Boolean, ScrollPosition> defaultScrollPosition;
520553

521554
private final Sort sort;
522555

523556
@SuppressWarnings("unchecked")
524557
ReactiveBuilder(ReactiveQueryByExampleExecutor<T> executor, Class<R> domainType) {
525-
this(executor, TypeInformation.of((Class<T>) domainType), domainType, null, null, Sort.unsorted());
558+
this(executor, TypeInformation.of((Class<T>) domainType), domainType, null, null, null, Sort.unsorted());
526559
}
527560

528561
ReactiveBuilder(
529562
ReactiveQueryByExampleExecutor<T> executor, TypeInformation<T> domainType, Class<R> resultType,
530-
@Nullable CursorStrategy<ScrollPosition> cursorStrategy, @Nullable ScrollSubrange defaultSubrange,
563+
@Nullable CursorStrategy<ScrollPosition> cursorStrategy,
564+
@Nullable Integer defaultScrollCount, @Nullable Function<Boolean, ScrollPosition> defaultScrollPosition,
531565
Sort sort) {
532566

533567
this.executor = executor;
534568
this.domainType = domainType;
535569
this.resultType = resultType;
536570
this.cursorStrategy = cursorStrategy;
537-
this.defaultSubrange = defaultSubrange;
571+
this.defaultScrollCount = defaultScrollCount;
572+
this.defaultScrollPosition = defaultScrollPosition;
538573
this.sort = sort;
539574
}
540575

@@ -550,7 +585,7 @@ public static class ReactiveBuilder<T, R> {
550585
public <P> ReactiveBuilder<T, P> projectAs(Class<P> projectionType) {
551586
Assert.notNull(projectionType, "Projection type must not be null");
552587
return new ReactiveBuilder<>(this.executor, this.domainType,
553-
projectionType, this.cursorStrategy, this.defaultSubrange, this.sort);
588+
projectionType, this.cursorStrategy, this.defaultScrollCount, this.defaultScrollPosition, this.sort);
554589
}
555590

556591
/**
@@ -563,8 +598,26 @@ public <P> ReactiveBuilder<T, P> projectAs(Class<P> projectionType) {
563598
* @since 1.2.0
564599
*/
565600
public ReactiveBuilder<T, R> cursorStrategy(@Nullable CursorStrategy<ScrollPosition> cursorStrategy) {
601+
return new ReactiveBuilder<>(this.executor, this.domainType, this.resultType,
602+
cursorStrategy, this.defaultScrollCount, this.defaultScrollPosition, this.sort);
603+
}
604+
605+
/**
606+
* Configure a default scroll count to use, and function to return a default
607+
* {@link ScrollPosition} for forward vs backward pagination.
608+
* <p>For offset scrolling, use {@link ScrollPosition#offset()} to scroll
609+
* from the beginning. Currently, it is not possible to go back from the end.
610+
* <p>For keyset scrolling, use {@link ScrollPosition#keyset()} to scroll
611+
* from the beginning, or {@link KeysetScrollPosition#reverse()} the same
612+
* to go back from the end.
613+
* <p>By default a count of 20 and {@link ScrollPosition#offset()} are used.
614+
* @since 1.2.5
615+
*/
616+
public ReactiveBuilder<T, R> defaultScrollSubrange(
617+
int defaultCount, Function<Boolean, ScrollPosition> defaultPosition) {
618+
566619
return new ReactiveBuilder<>(this.executor, this.domainType,
567-
this.resultType, cursorStrategy, this.defaultSubrange, this.sort);
620+
this.resultType, this.cursorStrategy, defaultCount, defaultPosition, this.sort);
568621
}
569622

570623
/**
@@ -574,11 +627,16 @@ public ReactiveBuilder<T, R> cursorStrategy(@Nullable CursorStrategy<ScrollPosit
574627
* count of 20.
575628
* @return a new {@link Builder} instance with all previously configured
576629
* options and {@code Sort} applied
577-
* @since 1.2.0
630+
* @deprecated in favor of {@link #defaultScrollSubrange(int, Function)}
578631
*/
632+
@SuppressWarnings("OptionalGetWithoutIsPresent")
633+
@Deprecated(since = "1.2.5", forRemoval = true)
579634
public ReactiveBuilder<T, R> defaultScrollSubrange(@Nullable ScrollSubrange defaultSubrange) {
580635
return new ReactiveBuilder<>(this.executor, this.domainType,
581-
this.resultType, this.cursorStrategy, defaultSubrange, this.sort);
636+
this.resultType, this.cursorStrategy,
637+
(defaultSubrange != null ? defaultSubrange.count().getAsInt() : null),
638+
(defaultSubrange != null ? forward -> defaultSubrange.position().get() : null),
639+
this.sort);
582640
}
583641

584642
/**
@@ -589,8 +647,8 @@ public ReactiveBuilder<T, R> defaultScrollSubrange(@Nullable ScrollSubrange defa
589647
*/
590648
public ReactiveBuilder<T, R> sortBy(Sort sort) {
591649
Assert.notNull(sort, "Sort must not be null");
592-
return new ReactiveBuilder<>(this.executor, this.domainType,
593-
this.resultType, this.cursorStrategy, this.defaultSubrange, sort);
650+
return new ReactiveBuilder<>(this.executor, this.domainType, this.resultType,
651+
this.cursorStrategy, this.defaultScrollCount, this.defaultScrollPosition, sort);
594652
}
595653

596654
/**
@@ -616,7 +674,8 @@ public DataFetcher<Mono<Iterable<R>>> scrollable() {
616674
return new ReactiveScrollableEntityFetcher<>(
617675
this.executor, this.domainType, this.resultType,
618676
(this.cursorStrategy != null ? this.cursorStrategy : RepositoryUtils.defaultCursorStrategy()),
619-
(this.defaultSubrange != null ? this.defaultSubrange : RepositoryUtils.defaultScrollSubrange()),
677+
(this.defaultScrollCount != null ? this.defaultScrollCount : RepositoryUtils.defaultScrollCount()),
678+
(this.defaultScrollPosition != null ? this.defaultScrollPosition : RepositoryUtils.defaultScrollPosition()),
620679
this.sort);
621680
}
622681

@@ -747,23 +806,27 @@ private static class ScrollableEntityFetcher<T, R> extends ManyEntityFetcher<T,
747806

748807
private final CursorStrategy<ScrollPosition> cursorStrategy;
749808

750-
private final ScrollSubrange defaultSubrange;
809+
private final int defaultCount;
810+
811+
private final Function<Boolean, ScrollPosition> defaultPosition;
751812

752813
private final ResolvableType scrollableResultType;
753814

754815
ScrollableEntityFetcher(
755816
QueryByExampleExecutor<T> executor, TypeInformation<T> domainType, Class<R> resultType,
756-
CursorStrategy<ScrollPosition> cursorStrategy, ScrollSubrange defaultSubrange, Sort sort) {
817+
CursorStrategy<ScrollPosition> cursorStrategy,
818+
int defaultCount,
819+
Function<Boolean, ScrollPosition> defaultPosition,
820+
Sort sort) {
757821

758822
super(executor, domainType, resultType, sort);
759823

760824
Assert.notNull(cursorStrategy, "CursorStrategy is required");
761-
Assert.notNull(defaultSubrange, "Default ScrollSubrange is required");
762-
Assert.isTrue(defaultSubrange.position().isPresent(), "Default ScrollPosition is required");
763-
Assert.isTrue(defaultSubrange.count().isPresent(), "Default scroll limit is required");
825+
Assert.notNull(defaultPosition, "'defaultPosition' is required");
764826

765827
this.cursorStrategy = cursorStrategy;
766-
this.defaultSubrange = defaultSubrange;
828+
this.defaultCount = defaultCount;
829+
this.defaultPosition = defaultPosition;
767830
this.scrollableResultType = ResolvableType.forClassWithGenerics(Window.class, resultType);
768831
}
769832

@@ -772,12 +835,12 @@ public ResolvableType getReturnType() {
772835
return ResolvableType.forClassWithGenerics(Iterable.class, this.scrollableResultType);
773836
}
774837

775-
@SuppressWarnings("OptionalGetWithoutIsPresent")
776838
@Override
777839
protected Iterable<R> getResult(FluentQuery.FetchableFluentQuery<R> queryToUse, DataFetchingEnvironment env) {
778-
ScrollSubrange range = RepositoryUtils.getScrollSubrange(env, this.cursorStrategy, this.defaultSubrange);
779-
int count = range.count().getAsInt();
780-
ScrollPosition position = range.position().get();
840+
ScrollSubrange range = RepositoryUtils.getScrollSubrange(env, this.cursorStrategy);
841+
int count = range.count().orElse(this.defaultCount);
842+
ScrollPosition position = (range.position().isPresent() ?
843+
range.position().get() : this.defaultPosition.apply(range.forward()));
781844
return queryToUse.limit(count).scroll(position);
782845
}
783846

@@ -891,26 +954,30 @@ private static class ReactiveScrollableEntityFetcher<T, R>
891954

892955
private final CursorStrategy<ScrollPosition> cursorStrategy;
893956

894-
private final ScrollSubrange defaultSubrange;
957+
private final int defaultCount;
958+
959+
private final Function<Boolean, ScrollPosition> defaultPosition;
895960

896961
private final Sort sort;
897962

898963
ReactiveScrollableEntityFetcher(
899964
ReactiveQueryByExampleExecutor<T> executor, TypeInformation<T> domainType, Class<R> resultType,
900-
CursorStrategy<ScrollPosition> cursorStrategy, ScrollSubrange defaultSubrange, Sort sort) {
965+
CursorStrategy<ScrollPosition> cursorStrategy,
966+
int defaultCount,
967+
Function<Boolean, ScrollPosition> defaultPosition,
968+
Sort sort) {
901969

902970
super(domainType);
903971

904972
Assert.notNull(cursorStrategy, "CursorStrategy is required");
905-
Assert.notNull(defaultSubrange, "Default ScrollSubrange is required");
906-
Assert.isTrue(defaultSubrange.position().isPresent(), "Default ScrollPosition is required");
907-
Assert.isTrue(defaultSubrange.count().isPresent(), "Default scroll limit is required");
973+
Assert.notNull(defaultPosition, "'defaultPosition' is required");
908974

909975
this.executor = executor;
910976
this.resultType = resultType;
911977
this.scrollableResultType = ResolvableType.forClassWithGenerics(Iterable.class, resultType);
912978
this.cursorStrategy = cursorStrategy;
913-
this.defaultSubrange = defaultSubrange;
979+
this.defaultCount = defaultCount;
980+
this.defaultPosition = defaultPosition;
914981
this.sort = sort;
915982
}
916983

@@ -920,7 +987,7 @@ public ResolvableType getReturnType() {
920987
}
921988

922989
@Override
923-
@SuppressWarnings({"unchecked", "OptionalGetWithoutIsPresent"})
990+
@SuppressWarnings("unchecked")
924991
public Mono<Iterable<R>> get(DataFetchingEnvironment env) throws BindException {
925992
return this.executor.findBy(buildExample(env), query -> {
926993
FluentQuery.ReactiveFluentQuery<R> queryToUse = (FluentQuery.ReactiveFluentQuery<R>) query;
@@ -936,9 +1003,10 @@ public Mono<Iterable<R>> get(DataFetchingEnvironment env) throws BindException {
9361003
queryToUse = queryToUse.project(buildPropertyPaths(env.getSelectionSet(), this.resultType));
9371004
}
9381005

939-
ScrollSubrange range = RepositoryUtils.getScrollSubrange(env, this.cursorStrategy, this.defaultSubrange);
940-
int count = range.count().getAsInt();
941-
ScrollPosition position = range.position().get();
1006+
ScrollSubrange range = RepositoryUtils.getScrollSubrange(env, this.cursorStrategy);
1007+
int count = range.count().orElse(this.defaultCount);
1008+
ScrollPosition position = (range.position().isPresent() ?
1009+
range.position().get() : this.defaultPosition.apply(range.forward()));
9421010
return queryToUse.limit(count).scroll(position).map(Function.identity());
9431011
});
9441012
}

0 commit comments

Comments
 (0)