Skip to content

Commit a0d7cc1

Browse files
committed
Expose CursorsStrategy and Subrange as builder options
This makes it possible to customize these settings individually, optionally, and via QuerydslBuilderCustomizer. See gh-597
1 parent 1f0c523 commit a0d7cc1

File tree

6 files changed

+236
-73
lines changed

6 files changed

+236
-73
lines changed

spring-graphql-docs/src/docs/asciidoc/index.adoc

+2-8
Original file line numberDiff line numberDiff line change
@@ -863,11 +863,8 @@ Then use it to create a `DataFetcher`:
863863
QuerydslDataFetcher.builder(repository).many();
864864
865865
// For paginated queries
866-
CursorStrategy<ScrollPosition> cursorStrategy = ... ;
867-
ScrollSubrange defaultSubrange = ... ;
868-
869866
DataFetcher<Iterable<Account>> dataFetcher =
870-
QuerydslDataFetcher.builder(repository).scrollable(cursorStrategy, defaultSubrange);
867+
QuerydslDataFetcher.builder(repository).scrollable();
871868
----
872869

873870
You can now register the above `DataFetcher` through a
@@ -1066,11 +1063,8 @@ Use `QueryByExampleDataFetcher` to turn the repository into a `DataFetcher`:
10661063
QueryByExampleDataFetcher.builder(repository).many();
10671064
10681065
// For paginated queries
1069-
CursorStrategy<ScrollPosition> cursorStrategy = ... ;
1070-
ScrollSubrange defaultSubrange = ... ;
1071-
10721066
DataFetcher<Iterable<Account>> dataFetcher =
1073-
QueryByExampleDataFetcher.builder(repository).scrollable(cursorStrategy, defaultSubrange);
1067+
QueryByExampleDataFetcher.builder(repository).scrollable();
10741068
----
10751069

10761070
You can now register the above `DataFetcher` through a

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

+111-21
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import org.springframework.data.util.TypeInformation;
4646
import org.springframework.graphql.data.GraphQlArgumentBinder;
4747
import org.springframework.graphql.data.GraphQlRepository;
48+
import org.springframework.graphql.data.pagination.CursorEncoder;
4849
import org.springframework.graphql.data.pagination.CursorStrategy;
4950
import org.springframework.graphql.data.query.AutoRegistrationRuntimeWiringConfigurer.DataFetcherFactory;
5051
import org.springframework.graphql.execution.RuntimeWiringConfigurer;
@@ -165,6 +166,7 @@ protected Collection<String> buildPropertyPaths(DataFetchingFieldSelectionSet se
165166
}
166167

167168
protected ScrollSubrange buildScrollSubrange(DataFetchingEnvironment environment) {
169+
Assert.state(this.cursorStrategy != null, "Expected CursorStrategy");
168170
return RepositoryUtils.buildScrollSubrange(environment, this.cursorStrategy);
169171
}
170172

@@ -234,7 +236,10 @@ public static RuntimeWiringConfigurer autoRegistrationConfigurer(
234236
for (QueryByExampleExecutor<?> executor : executors) {
235237
String typeName = RepositoryUtils.getGraphQlTypeName(executor);
236238
if (typeName != null) {
237-
Builder<?, ?> builder = customize(executor, builder(executor));
239+
Builder<?, ?> builder = customize(executor, builder(executor)
240+
.cursorStrategy(cursorStrategy)
241+
.defaultScrollSubrange(defaultScrollSubrange));
242+
238243
factories.put(typeName, new DataFetcherFactory() {
239244
@Override
240245
public DataFetcher<?> single() {
@@ -248,7 +253,7 @@ public DataFetcher<?> many() {
248253

249254
@Override
250255
public DataFetcher<?> scrollable() {
251-
return builder.scrollable(cursorStrategy, defaultScrollSubrange);
256+
return builder.scrollable();
252257
}
253258
});
254259
}
@@ -257,7 +262,10 @@ public DataFetcher<?> scrollable() {
257262
for (ReactiveQueryByExampleExecutor<?> executor : reactiveExecutors) {
258263
String typeName = RepositoryUtils.getGraphQlTypeName(executor);
259264
if (typeName != null) {
260-
ReactiveBuilder<?, ?> builder = customize(executor, builder(executor));
265+
ReactiveBuilder<?, ?> builder = customize(executor, builder(executor)
266+
.cursorStrategy(cursorStrategy)
267+
.defaultScrollSubrange(defaultScrollSubrange));
268+
261269
factories.put(typeName, new DataFetcherFactory() {
262270
@Override
263271
public DataFetcher<?> single() {
@@ -271,7 +279,7 @@ public DataFetcher<?> many() {
271279

272280
@Override
273281
public DataFetcher<?> scrollable() {
274-
return builder.scrollable(cursorStrategy, defaultScrollSubrange);
282+
return builder.scrollable();
275283
}
276284
});
277285
}
@@ -356,17 +364,28 @@ public static class Builder<T, R> {
356364

357365
private final Class<R> resultType;
358366

367+
@Nullable
368+
private final CursorStrategy<ScrollPosition> cursorStrategy;
369+
370+
@Nullable
371+
private final ScrollSubrange defaultSubrange;
372+
359373
private final Sort sort;
360374

361375
@SuppressWarnings("unchecked")
362376
Builder(QueryByExampleExecutor<T> executor, Class<R> domainType) {
363-
this(executor, TypeInformation.of((Class<T>) domainType), domainType, Sort.unsorted());
377+
this(executor, TypeInformation.of((Class<T>) domainType), domainType, null, null, Sort.unsorted());
364378
}
365379

366-
Builder(QueryByExampleExecutor<T> executor, TypeInformation<T> domainType, Class<R> resultType, Sort sort) {
380+
Builder(QueryByExampleExecutor<T> executor, TypeInformation<T> domainType, Class<R> resultType,
381+
@Nullable CursorStrategy<ScrollPosition> cursorStrategy, @Nullable ScrollSubrange defaultSubrange,
382+
Sort sort) {
383+
367384
this.executor = executor;
368385
this.domainType = domainType;
369386
this.resultType = resultType;
387+
this.cursorStrategy = cursorStrategy;
388+
this.defaultSubrange = defaultSubrange;
370389
this.sort = sort;
371390
}
372391

@@ -381,7 +400,36 @@ public static class Builder<T, R> {
381400
*/
382401
public <P> Builder<T, P> projectAs(Class<P> projectionType) {
383402
Assert.notNull(projectionType, "Projection type must not be null");
384-
return new Builder<>(this.executor, this.domainType, projectionType, this.sort);
403+
return new Builder<>(this.executor, this.domainType,
404+
projectionType, this.cursorStrategy, this.defaultSubrange, this.sort);
405+
}
406+
407+
/**
408+
* Configure strategy for decoding a cursor from a paginated request.
409+
* <p>By default, this is {@link ScrollPositionCursorStrategy} with
410+
* {@link CursorEncoder#base64()} encoding.
411+
* @param cursorStrategy the strategy to use
412+
* @return a new {@link Builder} instance with all previously configured
413+
* options and {@code Sort} applied
414+
* @since 1.2
415+
*/
416+
public Builder<T, R> cursorStrategy(@Nullable CursorStrategy<ScrollPosition> cursorStrategy) {
417+
return new Builder<>(this.executor, this.domainType,
418+
this.resultType, cursorStrategy, this.defaultSubrange, this.sort);
419+
}
420+
421+
/**
422+
* Configure a {@link ScrollSubrange} to use when a paginated request does
423+
* not specify a cursor and/or a count of items.
424+
* <p>By default, this is {@link OffsetScrollPosition#initial()} with a
425+
* count of 20.
426+
* @return a new {@link Builder} instance with all previously configured
427+
* options and {@code Sort} applied
428+
* @since 1.2
429+
*/
430+
public Builder<T, R> defaultScrollSubrange(@Nullable ScrollSubrange defaultSubrange) {
431+
return new Builder<>(this.executor, this.domainType,
432+
this.resultType, this.cursorStrategy, defaultSubrange, this.sort);
385433
}
386434

387435
/**
@@ -392,7 +440,8 @@ public <P> Builder<T, P> projectAs(Class<P> projectionType) {
392440
*/
393441
public Builder<T, R> sortBy(Sort sort) {
394442
Assert.notNull(sort, "Sort must not be null");
395-
return new Builder<>(this.executor, this.domainType, this.resultType, sort);
443+
return new Builder<>(this.executor, this.domainType,
444+
this.resultType, this.cursorStrategy, this.defaultSubrange, sort);
396445
}
397446

398447
/**
@@ -414,11 +463,12 @@ public DataFetcher<Iterable<R>> many() {
414463
* {@link org.springframework.data.domain.Window}.
415464
* @since 1.2
416465
*/
417-
public DataFetcher<Iterable<R>> scrollable(
418-
CursorStrategy<ScrollPosition> cursorStrategy, ScrollSubrange defaultScrollSubrange) {
419-
466+
public DataFetcher<Iterable<R>> scrollable() {
420467
return new ScrollableEntityFetcher<>(
421-
this.executor, this.domainType, this.resultType, cursorStrategy, defaultScrollSubrange, this.sort);
468+
this.executor, this.domainType, this.resultType,
469+
(this.cursorStrategy != null ? this.cursorStrategy : RepositoryUtils.defaultCursorStrategy()),
470+
(this.defaultSubrange != null ? this.defaultSubrange : RepositoryUtils.defaultScrollSubrange()),
471+
this.sort);
422472
}
423473

424474
}
@@ -460,20 +510,29 @@ public static class ReactiveBuilder<T, R> {
460510

461511
private final Class<R> resultType;
462512

513+
@Nullable
514+
private final CursorStrategy<ScrollPosition> cursorStrategy;
515+
516+
@Nullable
517+
private final ScrollSubrange defaultSubrange;
518+
463519
private final Sort sort;
464520

465521
@SuppressWarnings("unchecked")
466522
ReactiveBuilder(ReactiveQueryByExampleExecutor<T> executor, Class<R> domainType) {
467-
this(executor, TypeInformation.of((Class<T>) domainType), domainType, Sort.unsorted());
523+
this(executor, TypeInformation.of((Class<T>) domainType), domainType, null, null, Sort.unsorted());
468524
}
469525

470526
ReactiveBuilder(
471-
ReactiveQueryByExampleExecutor<T> executor, TypeInformation<T> domainType,
472-
Class<R> resultType, Sort sort) {
527+
ReactiveQueryByExampleExecutor<T> executor, TypeInformation<T> domainType, Class<R> resultType,
528+
@Nullable CursorStrategy<ScrollPosition> cursorStrategy, @Nullable ScrollSubrange defaultSubrange,
529+
Sort sort) {
473530

474531
this.executor = executor;
475532
this.domainType = domainType;
476533
this.resultType = resultType;
534+
this.cursorStrategy = cursorStrategy;
535+
this.defaultSubrange = defaultSubrange;
477536
this.sort = sort;
478537
}
479538

@@ -488,7 +547,36 @@ public static class ReactiveBuilder<T, R> {
488547
*/
489548
public <P> ReactiveBuilder<T, P> projectAs(Class<P> projectionType) {
490549
Assert.notNull(projectionType, "Projection type must not be null");
491-
return new ReactiveBuilder<>(this.executor, this.domainType, projectionType, this.sort);
550+
return new ReactiveBuilder<>(this.executor, this.domainType,
551+
projectionType, this.cursorStrategy, this.defaultSubrange, this.sort);
552+
}
553+
554+
/**
555+
* Configure strategy for decoding a cursor from a paginated request.
556+
* <p>By default, this is {@link ScrollPositionCursorStrategy} with
557+
* {@link CursorEncoder#base64()} encoding.
558+
* @param cursorStrategy the strategy to use
559+
* @return a new {@link Builder} instance with all previously configured
560+
* options and {@code Sort} applied
561+
* @since 1.2
562+
*/
563+
public ReactiveBuilder<T, R> cursorStrategy(@Nullable CursorStrategy<ScrollPosition> cursorStrategy) {
564+
return new ReactiveBuilder<>(this.executor, this.domainType,
565+
this.resultType, cursorStrategy, this.defaultSubrange, this.sort);
566+
}
567+
568+
/**
569+
* Configure a {@link ScrollSubrange} to use when a paginated request does
570+
* not specify a cursor and/or a count of items.
571+
* <p>By default, this is {@link OffsetScrollPosition#initial()} with a
572+
* count of 20.
573+
* @return a new {@link Builder} instance with all previously configured
574+
* options and {@code Sort} applied
575+
* @since 1.2
576+
*/
577+
public ReactiveBuilder<T, R> defaultScrollSubrange(@Nullable ScrollSubrange defaultSubrange) {
578+
return new ReactiveBuilder<>(this.executor, this.domainType,
579+
this.resultType, this.cursorStrategy, defaultSubrange, this.sort);
492580
}
493581

494582
/**
@@ -499,7 +587,8 @@ public <P> ReactiveBuilder<T, P> projectAs(Class<P> projectionType) {
499587
*/
500588
public ReactiveBuilder<T, R> sortBy(Sort sort) {
501589
Assert.notNull(sort, "Sort must not be null");
502-
return new ReactiveBuilder<>(this.executor, this.domainType, this.resultType, sort);
590+
return new ReactiveBuilder<>(this.executor, this.domainType,
591+
this.resultType, this.cursorStrategy, this.defaultSubrange, sort);
503592
}
504593

505594
/**
@@ -521,11 +610,12 @@ public DataFetcher<Flux<R>> many() {
521610
* {@link org.springframework.data.domain.Window}.
522611
* @since 1.2
523612
*/
524-
public DataFetcher<Mono<Iterable<R>>> scrollable(
525-
CursorStrategy<ScrollPosition> cursorStrategy, ScrollSubrange defaultScrollSubrange) {
526-
613+
public DataFetcher<Mono<Iterable<R>>> scrollable() {
527614
return new ReactiveScrollableEntityFetcher<>(
528-
this.executor, this.domainType, this.resultType, cursorStrategy, defaultScrollSubrange, this.sort);
615+
this.executor, this.domainType, this.resultType,
616+
(this.cursorStrategy != null ? this.cursorStrategy : RepositoryUtils.defaultCursorStrategy()),
617+
(this.defaultSubrange != null ? this.defaultSubrange : RepositoryUtils.defaultScrollSubrange()),
618+
this.sort);
529619
}
530620

531621
}

0 commit comments

Comments
 (0)