Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
38dfab1
Added @Override as part of errorprone code health check
bbakerman Jun 22, 2018
a90a885
Revert "Added @Override as part of errorprone code health check"
bbakerman Jun 22, 2018
2653ea0
Merge remote-tracking branch 'upstream/master'
bbakerman Jun 25, 2018
ead56c4
Merge remote-tracking branch 'upstream/master'
bbakerman Jun 28, 2018
c893cee
Merge remote-tracking branch 'upstream/master'
bbakerman Jul 27, 2018
654fd8f
Merge remote-tracking branch 'upstream/master'
bbakerman Aug 8, 2018
bb2b874
Merge remote-tracking branch 'upstream/master'
bbakerman Aug 16, 2018
d149b85
Merge remote-tracking branch 'upstream/master'
bbakerman Aug 28, 2018
e4f451c
Merge remote-tracking branch 'upstream/master'
bbakerman Aug 29, 2018
79a4df8
Merge remote-tracking branch 'upstream/master'
bbakerman Aug 30, 2018
b116476
Merge remote-tracking branch 'upstream/master'
bbakerman Aug 31, 2018
d652315
Merge remote-tracking branch 'upstream/master'
bbakerman Sep 8, 2018
9e59603
Merge remote-tracking branch 'upstream/master'
bbakerman Sep 9, 2018
0789d60
Making @defer return null as a place holder and also the path in the …
bbakerman Sep 12, 2018
fe3785f
Missing tests
bbakerman Sep 12, 2018
e080d61
Documentation updates on defer
bbakerman Sep 12, 2018
2d5aa2e
Merge remote-tracking branch 'upstream/master' into 1210-deferred-ali…
bbakerman Sep 12, 2018
54b32a7
Updated tests
bbakerman Sep 12, 2018
ac85a4b
Build thy self
bbakerman Sep 13, 2018
36af569
try to deploy this branch
andimarek Sep 13, 2018
5d381dd
try to deploy this branch
andimarek Sep 13, 2018
fdefa43
try to deploy this branch
andimarek Sep 13, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 18 additions & 9 deletions docs/defer.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ to the client. All three data elements are bound to the one query

A naive approach would be to make two queries to get the most important data first but there is now a better way.

There is ``experimental`` support for deferred execution in graphql-java.
There is support for deferred execution in graphql-java.

.. code-block:: graphql

Expand Down Expand Up @@ -63,15 +63,15 @@ the ``extensions`` map
// then initial results happen first, the deferred ones will begin AFTER these initial
// results have completed
//
sendResult(httpServletResponse, initialResult);
sendMultipartHttpResult(httpServletResponse, initialResult);

Map<Object, Object> extensions = initialResult.getExtensions();
Publisher<ExecutionResult> deferredResults = (Publisher<ExecutionResult>) extensions.get(GraphQL.DEFERRED_RESULTS);
Publisher<DeferredExecutionResult> deferredResults = (Publisher<DeferredExecutionResult>) extensions.get(GraphQL.DEFERRED_RESULTS);

//
// you subscribe to the deferred results like any other reactive stream
//
deferredResults.subscribe(new Subscriber<ExecutionResult>() {
deferredResults.subscribe(new Subscriber<DeferredExecutionResult>() {

Subscription subscription;

Expand All @@ -84,11 +84,11 @@ the ``extensions`` map
}

@Override
public void onNext(ExecutionResult executionResult) {
public void onNext(DeferredExecutionResult executionResult) {
//
// as each deferred result arrives, send it to where it needs to go
//
sendResult(httpServletResponse, executionResult);
sendMultipartHttpResult(httpServletResponse, executionResult);
subscription.request(10);
}

Expand All @@ -103,19 +103,28 @@ the ``extensions`` map
}
});

The above code subscribes to the deferred results and when each one arrives, sends it down to the client.
The above code subscribes to the deferred results and when each one arrives, sends it down to the client as a http multipart message.

You can see more details on reactive-streams code here http://www.reactive-streams.org/

``RxJava`` is a popular implementation of reactive-streams. Check out http://reactivex.io/intro.html to find out more
about creating Subscriptions.

graphql-java only produces a stream of deferred results. It does not concern itself with sending these over the network on things
like web sockets and so on. That is important but not a concern of the base graphql-java library. Its up to you
graphql-java only produces a stream of deferred results. It does not concern itself with sending these over the network using techniques
like http multipart, web sockets and so on. That is important but not a concern of the base graphql-java library. Its up to you
to use whatever network mechanism (websockets / long poll / ....) to get results back to you clients.

Also note that this capability is currently ``experimental`` and not defined by the official ``graphql`` specification. We reserve the
right to change it in a future release or if it enters the official specification. The graphql-java project
is keen to get feedback on this capability.

Where @defer is allowed
-----------------------

- It can be used on query operations only, not mutations or subscriptions
- It takes an optional ''if'' argument that can turn the defer behaviour on or off
- It is only allowed on nullable types
- Fragments are supported, however if one field at the same level has the @defer directive, then all fields at that level must have it



16 changes: 16 additions & 0 deletions src/main/java/graphql/DeferredExecutionResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package graphql;

import java.util.List;

/**
* Results that come back from @defer fields have an extra path property that tells you where
* that deferred result came in the original query
*/
@PublicApi
public interface DeferredExecutionResult extends ExecutionResult {

/**
* @return the execution path of this deferred result in the original query
*/
List<Object> getPath();
}
68 changes: 68 additions & 0 deletions src/main/java/graphql/DeferredExecutionResultImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package graphql;

import graphql.execution.ExecutionPath;

import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import static graphql.Assert.assertNotNull;

/**
* Results that come back from @defer fields have an extra path property that tells you where
* that deferred result came in the original query
*/
@PublicApi
public class DeferredExecutionResultImpl extends ExecutionResultImpl implements DeferredExecutionResult {

private final List<Object> path;

private DeferredExecutionResultImpl(List<Object> path, ExecutionResultImpl executionResult) {
super(executionResult);
this.path = assertNotNull(path);
}

/**
* @return the execution path of this deferred result in the original query
*/
public List<Object> getPath() {
return path;
}

@Override
public Map<String, Object> toSpecification() {
Map<String, Object> map = new LinkedHashMap<>(super.toSpecification());
map.put("path", path);
return map;
}

public static Builder newDeferredExecutionResult() {
return new Builder();
}

public static class Builder {
private List<Object> path = Collections.emptyList();
private ExecutionResultImpl.Builder builder = ExecutionResultImpl.newExecutionResult();

public Builder path(ExecutionPath path) {
this.path = assertNotNull(path).toList();
return this;
}

public Builder from(ExecutionResult executionResult) {
builder.from((ExecutionResultImpl) executionResult);
return this;
}

public Builder addErrors(List<GraphQLError> errors) {
builder.addErrors(errors);
return this;
}

public DeferredExecutionResult build() {
ExecutionResultImpl build = builder.build();
return new DeferredExecutionResultImpl(path, build);
}
}
}
6 changes: 6 additions & 0 deletions src/main/java/graphql/Directives.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ public class Directives {
public static final GraphQLDirective DeferDirective = GraphQLDirective.newDirective()
.name("defer")
.description("This directive allows results to be deferred during execution")
.argument(newArgument()
.name("if")
.type(GraphQLBoolean)
.description("Deferred behaviour is controlled by this argument")
.defaultValue(true)
)
.validLocations(FIELD)
.build();

Expand Down
4 changes: 4 additions & 0 deletions src/main/java/graphql/ExecutionResultImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ public ExecutionResultImpl(Object data, List<? extends GraphQLError> errors, Map
this(true, data, errors, extensions);
}

public ExecutionResultImpl(ExecutionResultImpl other) {
this(other.dataPresent, other.data, other.errors, other.extensions);
}

private ExecutionResultImpl(boolean dataPresent, Object data, List<? extends GraphQLError> errors, Map<Object, Object> extensions) {
this.dataPresent = dataPresent;
this.data = data;
Expand Down
13 changes: 8 additions & 5 deletions src/main/java/graphql/execution/AsyncExecutionStrategy.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,15 @@ public CompletableFuture<ExecutionResult> execute(ExecutionContext executionCont
ExecutionStrategyParameters newParameters = parameters
.transform(builder -> builder.field(currentField).path(fieldPath).parent(parameters));

resolvedFields.add(fieldName);
CompletableFuture<FieldValueInfo> future;

if (isDeferred(executionContext, newParameters, currentField)) {
executionStrategyCtx.onDeferredField(currentField);
continue;
future = resolveFieldWithInfoToNull(executionContext, newParameters);
} else {
future = resolveFieldWithInfo(executionContext, newParameters);
}
resolvedFields.add(fieldName);
CompletableFuture<FieldValueInfo> future = resolveFieldWithInfo(executionContext, newParameters);
futures.add(future);
}
CompletableFuture<ExecutionResult> overallResult = new CompletableFuture<>();
Expand Down Expand Up @@ -96,7 +99,7 @@ public CompletableFuture<ExecutionResult> execute(ExecutionContext executionCont

private boolean isDeferred(ExecutionContext executionContext, ExecutionStrategyParameters parameters, List<Field> currentField) {
DeferSupport deferSupport = executionContext.getDeferSupport();
if (deferSupport.checkForDeferDirective(currentField)) {
if (deferSupport.checkForDeferDirective(currentField, executionContext.getVariables())) {
DeferredErrorSupport errorSupport = new DeferredErrorSupport();

// with a deferred field we are really resetting where we execute from, that is from this current field onwards
Expand All @@ -112,7 +115,7 @@ private boolean isDeferred(ExecutionContext executionContext, ExecutionStrategyP
.currentListIndex(0)
);

DeferredCall call = new DeferredCall(deferredExecutionResult(executionContext, callParameters), errorSupport);
DeferredCall call = new DeferredCall(parameters.getPath(), deferredExecutionResult(executionContext, callParameters), errorSupport);
deferSupport.enqueue(call);
return true;
}
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/graphql/execution/Execution.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package graphql.execution;


import graphql.DeferredExecutionResult;
import graphql.ExecutionInput;
import graphql.ExecutionResult;
import graphql.ExecutionResultImpl;
Expand Down Expand Up @@ -184,7 +185,7 @@ private CompletableFuture<ExecutionResult> deferSupport(ExecutionContext executi
if (deferSupport.isDeferDetected()) {
// we start the rest of the query now to maximize throughput. We have the initial important results
// and now we can start the rest of the calls as early as possible (even before some one subscribes)
Publisher<ExecutionResult> publisher = deferSupport.startDeferredCalls();
Publisher<DeferredExecutionResult> publisher = deferSupport.startDeferredCalls();
return ExecutionResultImpl.newExecutionResult().from((ExecutionResultImpl) er)
.addExtension(GraphQL.DEFERRED_RESULTS, publisher)
.build();
Expand Down
8 changes: 7 additions & 1 deletion src/main/java/graphql/execution/ExecutionStrategy.java
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,12 @@ protected CompletableFuture<FieldValueInfo> resolveFieldWithInfo(ExecutionContex
return result;
}

protected CompletableFuture<FieldValueInfo> resolveFieldWithInfoToNull(ExecutionContext executionContext, ExecutionStrategyParameters parameters) {
FieldValueInfo fieldValueInfo = completeField(executionContext, parameters, null);
return CompletableFuture.completedFuture(fieldValueInfo);
}


/**
* Called to fetch a value for a field from the {@link DataFetcher} associated with the field
* {@link GraphQLFieldDefinition}.
Expand Down Expand Up @@ -881,7 +887,7 @@ protected ExecutionResult handleNonNullException(ExecutionContext executionConte
protected ExecutionTypeInfo fieldTypeInfo(ExecutionStrategyParameters parameters, GraphQLFieldDefinition fieldDefinition) {
GraphQLOutputType fieldType = fieldDefinition.getType();
Field field = null;
if (parameters.getField() != null && ! parameters.getField().isEmpty()) {
if (parameters.getField() != null && !parameters.getField().isEmpty()) {
field = parameters.getField().get(0);
}
return newTypeInfo()
Expand Down
19 changes: 13 additions & 6 deletions src/main/java/graphql/execution/defer/DeferSupport.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
package graphql.execution.defer;

import graphql.DeferredExecutionResult;
import graphql.Directives;
import graphql.ExecutionResult;
import graphql.Internal;
import graphql.execution.ValuesResolver;
import graphql.execution.reactive.SingleSubscriberPublisher;
import graphql.language.Directive;
import graphql.language.Field;
import org.reactivestreams.Publisher;

import java.util.Deque;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.atomic.AtomicBoolean;
Expand All @@ -22,12 +26,15 @@ public class DeferSupport {

private final AtomicBoolean deferDetected = new AtomicBoolean(false);
private final Deque<DeferredCall> deferredCalls = new ConcurrentLinkedDeque<>();
private final SingleSubscriberPublisher<ExecutionResult> publisher = new SingleSubscriberPublisher<>();
private final SingleSubscriberPublisher<DeferredExecutionResult> publisher = new SingleSubscriberPublisher<>();
private final ValuesResolver valuesResolver = new ValuesResolver();

public boolean checkForDeferDirective(List<Field> currentField) {
public boolean checkForDeferDirective(List<Field> currentField, Map<String, Object> variables) {
for (Field field : currentField) {
if (field.getDirective(Directives.DeferDirective.getName()) != null) {
return true;
Directive directive = field.getDirective(Directives.DeferDirective.getName());
if (directive != null) {
Map<String, Object> argumentValues = valuesResolver.getArgumentValues(Directives.DeferDirective.getArguments(), directive.getArguments(), variables);
return (Boolean) argumentValues.get("if");
}
}
return false;
Expand All @@ -40,7 +47,7 @@ private void drainDeferredCalls() {
return;
}
DeferredCall deferredCall = deferredCalls.pop();
CompletableFuture<ExecutionResult> future = deferredCall.invoke();
CompletableFuture<DeferredExecutionResult> future = deferredCall.invoke();
future.whenComplete((executionResult, exception) -> {
if (exception != null) {
publisher.offerError(exception);
Expand All @@ -65,7 +72,7 @@ public boolean isDeferDetected() {
*
* @return the publisher of deferred results
*/
public Publisher<ExecutionResult> startDeferredCalls() {
public Publisher<DeferredExecutionResult> startDeferredCalls() {
drainDeferredCalls();
return publisher;
}
Expand Down
27 changes: 14 additions & 13 deletions src/main/java/graphql/execution/defer/DeferredCall.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package graphql.execution.defer;

import graphql.DeferredExecutionResult;
import graphql.DeferredExecutionResultImpl;
import graphql.ExecutionResult;
import graphql.ExecutionResultImpl;
import graphql.GraphQLError;
import graphql.Internal;
import graphql.execution.ExecutionPath;

import java.util.List;
import java.util.concurrent.CompletableFuture;
Expand All @@ -15,28 +17,27 @@
*/
@Internal
public class DeferredCall {
private final ExecutionPath path;
private final Supplier<CompletableFuture<ExecutionResult>> call;
private final DeferredErrorSupport errorSupport;

public DeferredCall(Supplier<CompletableFuture<ExecutionResult>> call, DeferredErrorSupport deferredErrorSupport) {
public DeferredCall(ExecutionPath path, Supplier<CompletableFuture<ExecutionResult>> call, DeferredErrorSupport deferredErrorSupport) {
this.path = path;
this.call = call;
this.errorSupport = deferredErrorSupport;
}

CompletableFuture<ExecutionResult> invoke() {
CompletableFuture<DeferredExecutionResult> invoke() {
CompletableFuture<ExecutionResult> future = call.get();
return future.thenApply(this::addErrorsEncountered);
return future.thenApply(this::transformToDeferredResult);
}

private ExecutionResult addErrorsEncountered(ExecutionResult executionResult) {
private DeferredExecutionResult transformToDeferredResult(ExecutionResult executionResult) {
List<GraphQLError> errorsEncountered = errorSupport.getErrors();
if (errorsEncountered.isEmpty()) {
return executionResult;
}
ExecutionResultImpl sourceResult = (ExecutionResultImpl) executionResult;
ExecutionResultImpl.Builder builder = ExecutionResultImpl.newExecutionResult().from(sourceResult);
builder.addErrors(errorsEncountered);
return builder.build();
DeferredExecutionResultImpl.Builder builder = DeferredExecutionResultImpl.newDeferredExecutionResult().from(executionResult);
return builder
.addErrors(errorsEncountered)
.path(path)
.build();
}

}
4 changes: 4 additions & 0 deletions src/main/java/graphql/validation/AbstractRule.java
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ public ValidationContext getValidationContext() {
return validationContext;
}

public ValidationErrorCollector getValidationErrorCollector() {
return validationErrorCollector;
}

protected List<String> getQueryPath() {
return validationContext.getQueryPath();
}
Expand Down
6 changes: 4 additions & 2 deletions src/main/java/graphql/validation/ValidationErrorType.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ public enum ValidationErrorType {
InvalidFragmentType,
LoneAnonymousOperationViolation,
NonExecutableDefinition,
DuplicateOperationName

DuplicateOperationName,
DeferDirectiveOnNonNullField,
DeferDirectiveNotOnQueryOperation,
DeferredMustBeOnAllFields
}
Loading