Skip to content

Commit e86286e

Browse files
WIP: hacky defer is working, some new tests for basic stuff, but a lot still missing
1 parent c7088b1 commit e86286e

27 files changed

+1238
-199
lines changed

src/main/java/graphql/ExecutionInput.java

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public class ExecutionInput {
2929
private final DataLoaderRegistry dataLoaderRegistry;
3030
private final ExecutionId executionId;
3131
private final Locale locale;
32+
private final boolean incrementalSupport;
3233

3334

3435
@Internal
@@ -44,6 +45,7 @@ private ExecutionInput(Builder builder) {
4445
this.locale = builder.locale != null ? builder.locale : Locale.getDefault(); // always have a locale in place
4546
this.localContext = builder.localContext;
4647
this.extensions = builder.extensions;
48+
this.incrementalSupport = builder.incrementalSupport;
4749
}
4850

4951
/**
@@ -139,6 +141,16 @@ public Map<String, Object> getExtensions() {
139141
return extensions;
140142
}
141143

144+
/**
145+
* TODO: Javadoc
146+
* @return
147+
*/
148+
@ExperimentalApi
149+
public boolean isIncrementalSupport() {
150+
return incrementalSupport;
151+
}
152+
153+
142154
/**
143155
* This helps you transform the current ExecutionInput object into another one by starting a builder with all
144156
* the current values and allows you to transform it how you want.
@@ -159,7 +171,8 @@ public ExecutionInput transform(Consumer<Builder> builderConsumer) {
159171
.variables(this.rawVariables.toMap())
160172
.extensions(this.extensions)
161173
.executionId(this.executionId)
162-
.locale(this.locale);
174+
.locale(this.locale)
175+
.incrementalSupport(this.incrementalSupport);
163176

164177
builderConsumer.accept(builder);
165178

@@ -216,6 +229,7 @@ public static class Builder {
216229
private DataLoaderRegistry dataLoaderRegistry = DataLoaderDispatcherInstrumentationState.EMPTY_DATALOADER_REGISTRY;
217230
private Locale locale = Locale.getDefault();
218231
private ExecutionId executionId;
232+
public boolean incrementalSupport = false;
219233

220234
public Builder query(String query) {
221235
this.query = assertNotNull(query, () -> "query can't be null");
@@ -379,8 +393,19 @@ public Builder dataLoaderRegistry(DataLoaderRegistry dataLoaderRegistry) {
379393
return this;
380394
}
381395

396+
/**
397+
* TODO: Javadoc
398+
* @param incrementalSupport
399+
* @return
400+
*/
401+
@ExperimentalApi
402+
public Builder incrementalSupport(boolean incrementalSupport) {
403+
this.incrementalSupport = incrementalSupport;
404+
return this;
405+
}
406+
382407
public ExecutionInput build() {
383408
return new ExecutionInput(this);
384409
}
385410
}
386-
}
411+
}

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

Lines changed: 139 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,31 @@
11
package graphql.execution;
22

3+
import com.google.common.collect.ImmutableListMultimap;
4+
import com.google.common.collect.ImmutableSet;
35
import graphql.ExecutionResult;
46
import graphql.PublicApi;
5-
import graphql.execution.defer.DeferSupport;
7+
import graphql.execution.defer.DeferExecutionSupport;
68
import graphql.execution.defer.DeferredCall;
79
import graphql.execution.defer.DeferredErrorSupport;
8-
import graphql.execution.instrumentation.DeferredFieldInstrumentationContext;
10+
import graphql.execution.incremental.DeferExecution;
911
import graphql.execution.instrumentation.ExecutionStrategyInstrumentationContext;
1012
import graphql.execution.instrumentation.Instrumentation;
1113
import graphql.execution.instrumentation.InstrumentationContext;
1214
import graphql.execution.instrumentation.parameters.InstrumentationDeferredFieldParameters;
1315
import graphql.execution.instrumentation.parameters.InstrumentationExecutionStrategyParameters;
1416
import graphql.schema.GraphQLFieldDefinition;
1517
import graphql.util.FpKit;
18+
import graphql.util.Pair;
1619

1720
import java.util.LinkedHashMap;
1821
import java.util.List;
1922
import java.util.Map;
23+
import java.util.Set;
2024
import java.util.concurrent.CompletableFuture;
2125
import java.util.function.BiConsumer;
26+
import java.util.function.BiFunction;
2227
import java.util.function.Supplier;
28+
import java.util.stream.Collectors;
2329

2430
import static graphql.execution.MergedSelectionSet.newMergedSelectionSet;
2531

@@ -56,7 +62,20 @@ public CompletableFuture<ExecutionResult> execute(ExecutionContext executionCont
5662

5763
MergedSelectionSet fields = parameters.getFields();
5864
List<String> fieldNames = fields.getKeys();
59-
Async.CombinedBuilder<FieldValueInfo> futures = Async.ofExpectedSize(fields.size());
65+
66+
// if(true /* check if incremental support is enabled*/) {
67+
SomethingDefer somethingDefer = new SomethingDefer(
68+
fields,
69+
parameters,
70+
executionContext,
71+
this::resolveFieldWithInfo
72+
);
73+
74+
executionContext.getDeferSupport().enqueue(somethingDefer.createCalls());
75+
// }
76+
77+
Async.CombinedBuilder<FieldValueInfo> futures = Async.ofExpectedSize(fields.size() - somethingDefer.deferredFields.size());
78+
6079
for (String fieldName : fieldNames) {
6180
MergedField currentField = fields.getSubField(fieldName);
6281

@@ -66,13 +85,14 @@ public CompletableFuture<ExecutionResult> execute(ExecutionContext executionCont
6685

6786
CompletableFuture<FieldValueInfo> future;
6887

69-
if (isDeferred(executionContext, newParameters, currentField)) {
88+
if (somethingDefer.isDeferredField(currentField)) {
7089
executionStrategyCtx.onDeferredField(currentField);
71-
future = resolveFieldWithInfoToNull(executionContext, newParameters);
90+
// future = resolveFieldWithInfoToNull(executionContext, newParameters);
7291
} else {
7392
future = resolveFieldWithInfo(executionContext, newParameters);
93+
futures.add(future);
7494
}
75-
futures.add(future);
95+
7696
}
7797
CompletableFuture<ExecutionResult> overallResult = new CompletableFuture<>();
7898
executionStrategyCtx.onDispatched(overallResult);
@@ -104,8 +124,9 @@ public CompletableFuture<ExecutionResult> execute(ExecutionContext executionCont
104124
}
105125

106126
private boolean isDeferred(ExecutionContext executionContext, ExecutionStrategyParameters parameters, MergedField currentField) {
107-
DeferSupport deferSupport = executionContext.getDeferSupport();
108-
if (deferSupport.checkForDeferDirective(currentField, executionContext)) {
127+
DeferExecutionSupport deferSupport = executionContext.getDeferSupport();
128+
129+
if (currentField.getDeferExecutions() != null && !currentField.getDeferExecutions().isEmpty()) {
109130
DeferredErrorSupport errorSupport = new DeferredErrorSupport();
110131

111132
// with a deferred field we are really resetting where we execute from, that is from this current field onwards
@@ -122,8 +143,8 @@ private boolean isDeferred(ExecutionContext executionContext, ExecutionStrategyP
122143
}
123144
);
124145

125-
DeferredCall call = new DeferredCall(parameters.getPath(), deferredExecutionResult(executionContext, callParameters), errorSupport);
126-
deferSupport.enqueue(call);
146+
// DeferredCall call = new DeferredCall(null /* TODO extract label somehow*/, parameters.getPath(), deferredExecutionResult(executionContext, callParameters), errorSupport);
147+
// deferSupport.enqueue(call);
127148
return true;
128149
}
129150
return false;
@@ -160,4 +181,112 @@ private Supplier<CompletableFuture<ExecutionResult>> deferredExecutionResult(Exe
160181
return result;
161182
};
162183
}
184+
185+
186+
private static class SomethingDefer {
187+
private final ImmutableListMultimap<DeferExecution, MergedField> deferExecutionToFields;
188+
private final ImmutableSet<MergedField> deferredFields;
189+
private final ExecutionStrategyParameters parameters;
190+
private final ExecutionContext executionContext;
191+
private final BiFunction<ExecutionContext, ExecutionStrategyParameters, CompletableFuture<FieldValueInfo>> resolveFieldWithInfoFn;
192+
193+
private SomethingDefer(
194+
MergedSelectionSet mergedSelectionSet,
195+
ExecutionStrategyParameters parameters,
196+
ExecutionContext executionContext, BiFunction<ExecutionContext, ExecutionStrategyParameters, CompletableFuture<FieldValueInfo>> resolveFieldWithInfoFn
197+
) {
198+
this.executionContext = executionContext;
199+
this.resolveFieldWithInfoFn = resolveFieldWithInfoFn;
200+
ImmutableListMultimap.Builder<DeferExecution, MergedField> deferExecutionToFieldsBuilder = ImmutableListMultimap.builder();
201+
ImmutableSet.Builder<MergedField> deferredFieldsBuilder = ImmutableSet.builder();
202+
203+
mergedSelectionSet.getSubFields().values().forEach(mergedField -> {
204+
mergedField.getDeferExecutions().forEach(de -> {
205+
deferExecutionToFieldsBuilder.put(de, mergedField);
206+
deferredFieldsBuilder.add(mergedField);
207+
});
208+
});
209+
210+
this.deferExecutionToFields = deferExecutionToFieldsBuilder.build();
211+
this.deferredFields = deferredFieldsBuilder.build();
212+
this.parameters = parameters;
213+
}
214+
215+
private boolean isDeferredField(MergedField mergedField) {
216+
return deferredFields.contains(mergedField);
217+
}
218+
219+
private Set<DeferredCall> createCalls() {
220+
return deferExecutionToFields.keySet().stream().map(deferExecution -> {
221+
DeferredErrorSupport errorSupport = new DeferredErrorSupport();
222+
223+
List<MergedField> mergedFields = deferExecutionToFields.get(deferExecution);
224+
225+
List<Supplier<CompletableFuture<DeferredCall.FieldWithExecutionResult>>> collect = mergedFields.stream()
226+
.map(currentField -> {
227+
Map<String, MergedField> fields = new LinkedHashMap<>();
228+
fields.put(currentField.getName(), currentField);
229+
230+
ExecutionStrategyParameters callParameters = parameters.transform(builder ->
231+
{
232+
MergedSelectionSet mergedSelectionSet = newMergedSelectionSet().subFields(fields).build();
233+
builder.deferredErrorSupport(errorSupport)
234+
.field(currentField)
235+
.fields(mergedSelectionSet)
236+
.parent(null); // this is a break in the parent -> child chain - its a new start effectively
237+
}
238+
);
239+
240+
241+
return (Supplier<CompletableFuture<DeferredCall.FieldWithExecutionResult>>) () -> resolveFieldWithInfoFn
242+
.apply(executionContext, callParameters)
243+
.thenCompose(FieldValueInfo::getFieldValue)
244+
.thenApply(executionResult -> new DeferredCall.FieldWithExecutionResult(currentField.getName(), executionResult));
245+
246+
})
247+
.collect(Collectors.toList());
248+
249+
// with a deferred field we are really resetting where we execute from, that is from this current field onwards
250+
return new DeferredCall(
251+
deferExecution.getLabel(),
252+
this.parameters.getPath(),
253+
collect,
254+
errorSupport
255+
);
256+
})
257+
.collect(Collectors.toSet());
258+
}
259+
260+
@SuppressWarnings("FutureReturnValueIgnored")
261+
private Supplier<CompletableFuture<ExecutionResult>> deferredExecutionResult(ExecutionContext executionContext, ExecutionStrategyParameters parameters) {
262+
return () -> {
263+
// GraphQLFieldDefinition fieldDef = getFieldDef(executionContext, parameters, parameters.getField().getSingleField());
264+
// TODO: freis: This is suddenly not needed anymore
265+
// GraphQLObjectType fieldContainer = (GraphQLObjectType) parameters.getExecutionStepInfo().getUnwrappedNonNullType();
266+
267+
// Instrumentation instrumentation = executionContext.getInstrumentation();
268+
269+
// Supplier<ExecutionStepInfo> executionStepInfo = FpKit.intraThreadMemoize(() -> createExecutionStepInfo(executionContext, parameters, fieldDef, null));
270+
271+
// InstrumentationContext<ExecutionResult> fieldCtx = instrumentation.beginDeferredField(
272+
// new InstrumentationDeferredFieldParameters(executionContext, executionStepInfo, parameters),
273+
// executionContext.getInstrumentationState()
274+
// );
275+
276+
CompletableFuture<ExecutionResult> result = new CompletableFuture<>();
277+
// fieldCtx.onDispatched(result);
278+
CompletableFuture<FieldValueInfo> fieldValueInfoFuture = resolveFieldWithInfoFn.apply(executionContext, parameters);
279+
280+
fieldValueInfoFuture.whenComplete((fieldValueInfo, throwable) -> {
281+
// TODO:
282+
// fieldCtx.onFieldValueInfo(fieldValueInfo);
283+
284+
CompletableFuture<ExecutionResult> execResultFuture = fieldValueInfo.getFieldValue();
285+
// execResultFuture = execResultFuture.whenComplete(fieldCtx::onCompleted);
286+
Async.copyResults(execResultFuture, result);
287+
});
288+
return result;
289+
};
290+
}
291+
}
163292
}

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

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

33

4-
import graphql.DeferredExecutionResult;
54
import graphql.ExecutionInput;
65
import graphql.ExecutionResult;
76
import graphql.ExecutionResultImpl;
8-
import graphql.GraphQL;
97
import graphql.GraphQLContext;
108
import graphql.GraphQLError;
119
import graphql.Internal;
12-
import graphql.execution.defer.DeferSupport;
10+
import graphql.execution.defer.DeferExecutionSupport;
1311
import graphql.execution.instrumentation.Instrumentation;
1412
import graphql.execution.instrumentation.InstrumentationContext;
1513
import graphql.execution.instrumentation.InstrumentationState;
1614
import graphql.execution.instrumentation.parameters.InstrumentationExecuteOperationParameters;
1715
import graphql.execution.instrumentation.parameters.InstrumentationExecutionParameters;
1816
import graphql.extensions.ExtensionsBuilder;
17+
import graphql.incremental.DelayedIncrementalExecutionResult;
18+
import graphql.incremental.IncrementalExecutionResultImpl;
1919
import graphql.language.Document;
2020
import graphql.language.FragmentDefinition;
2121
import graphql.language.NodeUtil;
@@ -186,18 +186,26 @@ private CompletableFuture<ExecutionResult> executeOperation(ExecutionContext exe
186186
}
187187

188188
/*
189-
* Adds the deferred publisher if its needed at the end of the query. This is also a good time for the deferred code to start running
189+
* Adds the deferred publisher if it's needed at the end of the query. This is also a good time for the deferred code to start running
190190
*/
191191
private CompletableFuture<ExecutionResult> deferSupport(ExecutionContext executionContext, CompletableFuture<ExecutionResult> result) {
192192
return result.thenApply(er -> {
193-
DeferSupport deferSupport = executionContext.getDeferSupport();
193+
DeferExecutionSupport deferSupport = executionContext.getDeferSupport();
194194
if (deferSupport.isDeferDetected()) {
195195
// we start the rest of the query now to maximize throughput. We have the initial important results
196196
// and now we can start the rest of the calls as early as possible (even before some one subscribes)
197-
Publisher<DeferredExecutionResult> publisher = deferSupport.startDeferredCalls();
198-
return ExecutionResultImpl.newExecutionResult().from(er)
199-
.addExtension(GraphQL.DEFERRED_RESULTS, publisher)
197+
Publisher<DelayedIncrementalExecutionResult> publisher = deferSupport.startDeferredCalls();
198+
199+
return IncrementalExecutionResultImpl.fromExecutionResult(er)
200+
// TODO: would `hasNext` ever be false?
201+
.hasNext(true)
202+
.incrementalItemPublisher(publisher)
200203
.build();
204+
//
205+
//
206+
// return ExecutionResultImpl.newExecutionResult().from(er)
207+
// .addExtension(GraphQL.DEFERRED_RESULTS, publisher)
208+
// .build();
201209
}
202210
return er;
203211
});

src/main/java/graphql/execution/ExecutionContext.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import graphql.GraphQLError;
99
import graphql.PublicApi;
1010
import graphql.collect.ImmutableKit;
11-
import graphql.execution.defer.DeferSupport;
11+
import graphql.execution.defer.DeferExecutionSupport;
1212
import graphql.execution.instrumentation.Instrumentation;
1313
import graphql.execution.instrumentation.InstrumentationState;
1414
import graphql.language.Document;
@@ -54,7 +54,7 @@ public class ExecutionContext {
5454
private final Set<ResultPath> errorPaths = new HashSet<>();
5555
private final DataLoaderRegistry dataLoaderRegistry;
5656
private final Locale locale;
57-
private final DeferSupport deferSupport = new DeferSupport();
57+
private final DeferExecutionSupport deferSupport = new DeferExecutionSupport();
5858
private final ValueUnboxer valueUnboxer;
5959
private final ExecutionInput executionInput;
6060
private final Supplier<ExecutableNormalizedOperation> queryTree;
@@ -257,7 +257,7 @@ public ExecutionStrategy getSubscriptionStrategy() {
257257
return subscriptionStrategy;
258258
}
259259

260-
public DeferSupport getDeferSupport() {
260+
public DeferExecutionSupport getDeferSupport() {
261261
return deferSupport;
262262
}
263263

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -696,7 +696,11 @@ protected CompletableFuture<ExecutionResult> completeValueForObject(ExecutionCon
696696
.variables(executionContext.getCoercedVariables().toMap())
697697
.build();
698698

699-
MergedSelectionSet subFields = fieldCollector.collectFields(collectorParameters, parameters.getField());
699+
MergedSelectionSet subFields = fieldCollector.collectFields(
700+
collectorParameters,
701+
parameters.getField(),
702+
executionContext.getExecutionInput().isIncrementalSupport()
703+
);
700704

701705
ExecutionStepInfo newExecutionStepInfo = executionStepInfo.changeTypeWithPreservedNonNull(resolvedObjectType);
702706
NonNullableFieldValidator nonNullableFieldValidator = new NonNullableFieldValidator(executionContext, newExecutionStepInfo);

0 commit comments

Comments
 (0)