Skip to content

Commit 0fec4be

Browse files
andimarekbbakerman
authored andcommitted
1210 deferred aligned with apollo (#1221)
* Added @OverRide as part of errorprone code health check * Revert "Added @OverRide as part of errorprone code health check" This reverts commit 38dfab1 * Making @defer return null as a place holder and also the path in the results * Missing tests * Documentation updates on defer * Updated tests * Build thy self * try to deploy this branch * try to deploy this branch * try to deploy this branch * experimental change in offer behaviour * run also testng tests fix SubscriberPublisher test: should throw NPE * Fix ups after merge * Fixed test since @defer is now opt in * moar test fix ups * PR fixups * Add multi paet http @defer support into local example * why do we hae testng - reactive * if argument should be non null * travis build back to master * reverted the non null ness * Made the if argument non nullable * ending \n
1 parent 6020f87 commit 0fec4be

38 files changed

+1082
-113
lines changed

build.gradle

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,18 @@ dependencies {
7171
testCompile 'org.reactivestreams:reactive-streams-tck:' + reactiveStreamsVersion
7272
testCompile "io.reactivex.rxjava2:rxjava:2.1.5"
7373

74+
testCompile 'org.testng:testng:6.1.1' // use for reactive streams test inheritance
7475

7576
testCompile 'org.openjdk.jmh:jmh-core:1.21'
7677
testCompile 'org.openjdk.jmh:jmh-generator-annprocess:1.21'
7778
}
7879

80+
81+
task testng(type: Test) {
82+
useTestNG()
83+
}
84+
check.dependsOn testng
85+
7986
compileJava.source file("build/generated-src"), sourceSets.main.java
8087

8188
generateGrammarSource {

src/main/java/graphql/Assert.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@ public static <T> T assertNotNull(T object, String format, Object... args) {
1515
throw new AssertException(format(format, args));
1616
}
1717

18+
public static <T> T assertNotNullWithNPE(T object, String format, Object... args) {
19+
if (object != null) {
20+
return object;
21+
}
22+
throw new NullPointerException(format(format, args));
23+
}
24+
1825
public static <T> T assertNotNull(T object) {
1926
if (object != null) {
2027
return object;
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package graphql;
2+
3+
import java.util.List;
4+
5+
/**
6+
* Results that come back from @defer fields have an extra path property that tells you where
7+
* that deferred result came in the original query
8+
*/
9+
@PublicApi
10+
public interface DeferredExecutionResult extends ExecutionResult {
11+
12+
/**
13+
* @return the execution path of this deferred result in the original query
14+
*/
15+
List<Object> getPath();
16+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package graphql;
2+
3+
import graphql.execution.ExecutionPath;
4+
5+
import java.util.Collections;
6+
import java.util.LinkedHashMap;
7+
import java.util.List;
8+
import java.util.Map;
9+
10+
import static graphql.Assert.assertNotNull;
11+
12+
/**
13+
* Results that come back from @defer fields have an extra path property that tells you where
14+
* that deferred result came in the original query
15+
*/
16+
@PublicApi
17+
public class DeferredExecutionResultImpl extends ExecutionResultImpl implements DeferredExecutionResult {
18+
19+
private final List<Object> path;
20+
21+
private DeferredExecutionResultImpl(List<Object> path, ExecutionResultImpl executionResult) {
22+
super(executionResult);
23+
this.path = assertNotNull(path);
24+
}
25+
26+
/**
27+
* @return the execution path of this deferred result in the original query
28+
*/
29+
public List<Object> getPath() {
30+
return path;
31+
}
32+
33+
@Override
34+
public Map<String, Object> toSpecification() {
35+
Map<String, Object> map = new LinkedHashMap<>(super.toSpecification());
36+
map.put("path", path);
37+
return map;
38+
}
39+
40+
public static Builder newDeferredExecutionResult() {
41+
return new Builder();
42+
}
43+
44+
public static class Builder {
45+
private List<Object> path = Collections.emptyList();
46+
private ExecutionResultImpl.Builder builder = ExecutionResultImpl.newExecutionResult();
47+
48+
public Builder path(ExecutionPath path) {
49+
this.path = assertNotNull(path).toList();
50+
return this;
51+
}
52+
53+
public Builder from(ExecutionResult executionResult) {
54+
builder.from((ExecutionResultImpl) executionResult);
55+
return this;
56+
}
57+
58+
public Builder addErrors(List<GraphQLError> errors) {
59+
builder.addErrors(errors);
60+
return this;
61+
}
62+
63+
public DeferredExecutionResult build() {
64+
ExecutionResultImpl build = builder.build();
65+
return new DeferredExecutionResultImpl(path, build);
66+
}
67+
}
68+
}

src/main/java/graphql/Directives.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@ public class Directives {
4242
public static final GraphQLDirective DeferDirective = GraphQLDirective.newDirective()
4343
.name("defer")
4444
.description("This directive allows results to be deferred during execution")
45+
.argument(newArgument()
46+
.name("if")
47+
.type(nonNull(GraphQLBoolean))
48+
.description("Deferred behaviour is controlled by this argument")
49+
.defaultValue(true)
50+
)
4551
.validLocations(FIELD)
4652
.build();
4753

src/main/java/graphql/ExecutionResultImpl.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ public ExecutionResultImpl(Object data, List<? extends GraphQLError> errors, Map
3434
this(true, data, errors, extensions);
3535
}
3636

37+
public ExecutionResultImpl(ExecutionResultImpl other) {
38+
this(other.dataPresent, other.data, other.errors, other.extensions);
39+
}
40+
3741
private ExecutionResultImpl(boolean dataPresent, Object data, List<? extends GraphQLError> errors, Map<Object, Object> extensions) {
3842
this.dataPresent = dataPresent;
3943
this.data = data;

src/main/java/graphql/execution/AsyncExecutionStrategy.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,15 @@ public CompletableFuture<ExecutionResult> execute(ExecutionContext executionCont
6464
ExecutionStrategyParameters newParameters = parameters
6565
.transform(builder -> builder.field(currentField).path(fieldPath).parent(parameters));
6666

67+
resolvedFields.add(fieldName);
68+
CompletableFuture<FieldValueInfo> future;
69+
6770
if (isDeferred(executionContext, newParameters, currentField)) {
6871
executionStrategyCtx.onDeferredField(currentField);
69-
continue;
72+
future = resolveFieldWithInfoToNull(executionContext, newParameters);
73+
} else {
74+
future = resolveFieldWithInfo(executionContext, newParameters);
7075
}
71-
resolvedFields.add(fieldName);
72-
CompletableFuture<FieldValueInfo> future = resolveFieldWithInfo(executionContext, newParameters);
7376
futures.add(future);
7477
}
7578
CompletableFuture<ExecutionResult> overallResult = new CompletableFuture<>();
@@ -98,7 +101,7 @@ public CompletableFuture<ExecutionResult> execute(ExecutionContext executionCont
98101

99102
private boolean isDeferred(ExecutionContext executionContext, ExecutionStrategyParameters parameters, MergedField currentField) {
100103
DeferSupport deferSupport = executionContext.getDeferSupport();
101-
if (deferSupport.checkForDeferDirective(currentField)) {
104+
if (deferSupport.checkForDeferDirective(currentField, executionContext.getVariables())) {
102105
DeferredErrorSupport errorSupport = new DeferredErrorSupport();
103106

104107
// with a deferred field we are really resetting where we execute from, that is from this current field onwards
@@ -117,7 +120,7 @@ private boolean isDeferred(ExecutionContext executionContext, ExecutionStrategyP
117120
}
118121
);
119122

120-
DeferredCall call = new DeferredCall(deferredExecutionResult(executionContext, callParameters), errorSupport);
123+
DeferredCall call = new DeferredCall(parameters.getPath(), deferredExecutionResult(executionContext, callParameters), errorSupport);
121124
deferSupport.enqueue(call);
122125
return true;
123126
}

src/main/java/graphql/execution/Execution.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package graphql.execution;
22

33

4+
import graphql.DeferredExecutionResult;
45
import graphql.ExecutionInput;
56
import graphql.ExecutionResult;
67
import graphql.ExecutionResultImpl;
@@ -186,7 +187,7 @@ private CompletableFuture<ExecutionResult> deferSupport(ExecutionContext executi
186187
if (deferSupport.isDeferDetected()) {
187188
// we start the rest of the query now to maximize throughput. We have the initial important results
188189
// and now we can start the rest of the calls as early as possible (even before some one subscribes)
189-
Publisher<ExecutionResult> publisher = deferSupport.startDeferredCalls();
190+
Publisher<DeferredExecutionResult> publisher = deferSupport.startDeferredCalls();
190191
return ExecutionResultImpl.newExecutionResult().from(er)
191192
.addExtension(GraphQL.DEFERRED_RESULTS, publisher)
192193
.build();

src/main/java/graphql/execution/ExecutionStrategy.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,13 @@ protected CompletableFuture<FieldValueInfo> resolveFieldWithInfo(ExecutionContex
205205
return result;
206206
}
207207

208+
protected CompletableFuture<FieldValueInfo> resolveFieldWithInfoToNull(ExecutionContext executionContext, ExecutionStrategyParameters parameters) {
209+
FetchedValue fetchedValue = FetchedValue.newFetchedValue().build();
210+
FieldValueInfo fieldValueInfo = completeField(executionContext, parameters, fetchedValue);
211+
return CompletableFuture.completedFuture(fieldValueInfo);
212+
}
213+
214+
208215
/**
209216
* Called to fetch a value for a field from the {@link DataFetcher} associated with the field
210217
* {@link GraphQLFieldDefinition}.
@@ -782,15 +789,16 @@ protected ExecutionStepInfo createExecutionStepInfo(ExecutionContext executionCo
782789
GraphQLFieldDefinition fieldDefinition,
783790
GraphQLObjectType fieldContainer) {
784791
GraphQLOutputType fieldType = fieldDefinition.getType();
785-
List<Argument> fieldArgs = parameters.getField().getArguments();
792+
MergedField field = parameters.getField();
793+
List<Argument> fieldArgs = field.getArguments();
786794
GraphQLCodeRegistry codeRegistry = executionContext.getGraphQLSchema().getCodeRegistry();
787795
Map<String, Object> argumentValues = valuesResolver.getArgumentValues(codeRegistry, fieldDefinition.getArguments(), fieldArgs, executionContext.getVariables());
788796

789797
return newExecutionStepInfo()
790798
.type(fieldType)
791799
.fieldDefinition(fieldDefinition)
792800
.fieldContainer(fieldContainer)
793-
.field(parameters.getField())
801+
.field(field)
794802
.path(parameters.getPath())
795803
.parentInfo(parameters.getExecutionStepInfo())
796804
.arguments(argumentValues)

src/main/java/graphql/execution/defer/DeferSupport.java

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,25 @@
11
package graphql.execution.defer;
22

3+
import graphql.DeferredExecutionResult;
34
import graphql.Directives;
45
import graphql.ExecutionResult;
56
import graphql.Internal;
67
import graphql.execution.MergedField;
8+
import graphql.execution.ValuesResolver;
79
import graphql.execution.reactive.SingleSubscriberPublisher;
10+
import graphql.language.Directive;
811
import graphql.language.Field;
912
import org.reactivestreams.Publisher;
1013

1114
import java.util.Deque;
15+
import java.util.List;
16+
import java.util.Map;
1217
import java.util.concurrent.CompletableFuture;
1318
import java.util.concurrent.ConcurrentLinkedDeque;
1419
import java.util.concurrent.atomic.AtomicBoolean;
1520

21+
import static graphql.Directives.*;
22+
1623
/**
1724
* This provides support for @defer directives on fields that mean that results will be sent AFTER
1825
* the main result is sent via a Publisher stream.
@@ -22,12 +29,15 @@ public class DeferSupport {
2229

2330
private final AtomicBoolean deferDetected = new AtomicBoolean(false);
2431
private final Deque<DeferredCall> deferredCalls = new ConcurrentLinkedDeque<>();
25-
private final SingleSubscriberPublisher<ExecutionResult> publisher = new SingleSubscriberPublisher<>();
32+
private final SingleSubscriberPublisher<DeferredExecutionResult> publisher = new SingleSubscriberPublisher<>();
33+
private final ValuesResolver valuesResolver = new ValuesResolver();
2634

27-
public boolean checkForDeferDirective(MergedField currentField) {
35+
public boolean checkForDeferDirective(MergedField currentField, Map<String,Object> variables) {
2836
for (Field field : currentField.getFields()) {
29-
if (field.getDirective(Directives.DeferDirective.getName()) != null) {
30-
return true;
37+
Directive directive = field.getDirective(DeferDirective.getName());
38+
if (directive != null) {
39+
Map<String, Object> argumentValues = valuesResolver.getArgumentValues(DeferDirective.getArguments(), directive.getArguments(), variables);
40+
return (Boolean) argumentValues.get("if");
3141
}
3242
}
3343
return false;
@@ -40,7 +50,7 @@ private void drainDeferredCalls() {
4050
return;
4151
}
4252
DeferredCall deferredCall = deferredCalls.pop();
43-
CompletableFuture<ExecutionResult> future = deferredCall.invoke();
53+
CompletableFuture<DeferredExecutionResult> future = deferredCall.invoke();
4454
future.whenComplete((executionResult, exception) -> {
4555
if (exception != null) {
4656
publisher.offerError(exception);
@@ -65,7 +75,7 @@ public boolean isDeferDetected() {
6575
*
6676
* @return the publisher of deferred results
6777
*/
68-
public Publisher<ExecutionResult> startDeferredCalls() {
78+
public Publisher<DeferredExecutionResult> startDeferredCalls() {
6979
drainDeferredCalls();
7080
return publisher;
7181
}

0 commit comments

Comments
 (0)