Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 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
eef571c
experimental change in offer behaviour
andimarek Sep 16, 2018
efbcf37
run also testng tests
andimarek Sep 17, 2018
1958b21
Merge master onto this branch plus fix ups
bbakerman Mar 15, 2019
ad5f6b1
Fix ups after merge
bbakerman Mar 15, 2019
648b896
Fixed test since @defer is now opt in
bbakerman Mar 15, 2019
64cfd3f
moar test fix ups
bbakerman Mar 15, 2019
f3c8bb5
PR fixups
bbakerman Mar 15, 2019
75fb184
Add multi paet http @defer support into local example
bbakerman Mar 15, 2019
cbcee57
why do we hae testng - reactive
bbakerman Mar 15, 2019
a9a9839
if argument should be non null
bbakerman Mar 15, 2019
0c1083d
travis build back to master
bbakerman Mar 15, 2019
6553be8
Merge remote-tracking branch 'upstream/master' into 1210-deferred-ali…
bbakerman Mar 19, 2019
2364635
reverted the non null ness
bbakerman Mar 19, 2019
e1169db
Merge remote-tracking branch 'upstream/master' into 1210-deferred-ali…
bbakerman Mar 20, 2019
cff93c3
Made the if argument non nullable
bbakerman Mar 20, 2019
b2193af
Merged upstream master
bbakerman May 14, 2019
7e27cae
ending \n
bbakerman May 14, 2019
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
7 changes: 7 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,18 @@ dependencies {
testCompile 'org.reactivestreams:reactive-streams-tck:' + reactiveStreamsVersion
testCompile "io.reactivex.rxjava2:rxjava:2.1.5"

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

testCompile 'org.openjdk.jmh:jmh-core:1.21'
testCompile 'org.openjdk.jmh:jmh-generator-annprocess:1.21'
}


task testng(type: Test) {
useTestNG()
}
check.dependsOn testng

compileJava.source file("build/generated-src"), sourceSets.main.java

generateGrammarSource {
Expand Down
7 changes: 7 additions & 0 deletions src/main/java/graphql/Assert.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ public static <T> T assertNotNull(T object, String format, Object... args) {
throw new AssertException(format(format, args));
}

public static <T> T assertNotNullWithNPE(T object, String format, Object... args) {
if (object != null) {
return object;
}
throw new NullPointerException(format(format, args));
}

public static <T> T assertNotNull(T object) {
if (object != null) {
return object;
Expand Down
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 @@ -42,6 +42,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(nonNull(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 @@ -64,12 +64,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 @@ -98,7 +101,7 @@ public CompletableFuture<ExecutionResult> execute(ExecutionContext executionCont

private boolean isDeferred(ExecutionContext executionContext, ExecutionStrategyParameters parameters, MergedField 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 @@ -117,7 +120,7 @@ private boolean isDeferred(ExecutionContext executionContext, ExecutionStrategyP
}
);

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 @@ -186,7 +187,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(er)
.addExtension(GraphQL.DEFERRED_RESULTS, publisher)
.build();
Expand Down
12 changes: 10 additions & 2 deletions src/main/java/graphql/execution/ExecutionStrategy.java
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,13 @@ protected CompletableFuture<FieldValueInfo> resolveFieldWithInfo(ExecutionContex
return result;
}

protected CompletableFuture<FieldValueInfo> resolveFieldWithInfoToNull(ExecutionContext executionContext, ExecutionStrategyParameters parameters) {
FetchedValue fetchedValue = FetchedValue.newFetchedValue().build();
FieldValueInfo fieldValueInfo = completeField(executionContext, parameters, fetchedValue);
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 @@ -781,15 +788,16 @@ protected ExecutionStepInfo createExecutionStepInfo(ExecutionContext executionCo
GraphQLFieldDefinition fieldDefinition,
GraphQLObjectType fieldContainer) {
GraphQLOutputType fieldType = fieldDefinition.getType();
List<Argument> fieldArgs = parameters.getField().getArguments();
MergedField field = parameters.getField();
List<Argument> fieldArgs = field.getArguments();
GraphQLCodeRegistry codeRegistry = executionContext.getGraphQLSchema().getCodeRegistry();
Map<String, Object> argumentValues = valuesResolver.getArgumentValues(codeRegistry, fieldDefinition.getArguments(), fieldArgs, executionContext.getVariables());

return newExecutionStepInfo()
.type(fieldType)
.fieldDefinition(fieldDefinition)
.fieldContainer(fieldContainer)
.field(parameters.getField())
.field(field)
.path(parameters.getPath())
.parentInfo(parameters.getExecutionStepInfo())
.arguments(argumentValues)
Expand Down
22 changes: 16 additions & 6 deletions src/main/java/graphql/execution/defer/DeferSupport.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
package graphql.execution.defer;

import graphql.DeferredExecutionResult;
import graphql.Directives;
import graphql.ExecutionResult;
import graphql.Internal;
import graphql.execution.MergedField;
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;

import static graphql.Directives.*;

/**
* This provides support for @defer directives on fields that mean that results will be sent AFTER
* the main result is sent via a Publisher stream.
Expand All @@ -22,12 +29,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(MergedField currentField) {
public boolean checkForDeferDirective(MergedField currentField, Map<String,Object> variables) {
for (Field field : currentField.getFields()) {
if (field.getDirective(Directives.DeferDirective.getName()) != null) {
return true;
Directive directive = field.getDirective(DeferDirective.getName());
if (directive != null) {
Map<String, Object> argumentValues = valuesResolver.getArgumentValues(DeferDirective.getArguments(), directive.getArguments(), variables);
return (Boolean) argumentValues.get("if");

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this be null? => unboxing will cause NPE

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no - its default is true

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

now made it non null as an arg declaration

}
}
return false;
Expand All @@ -40,7 +50,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 +75,7 @@ public boolean isDeferDetected() {
*
* @return the publisher of deferred results
*/
public Publisher<ExecutionResult> startDeferredCalls() {
public Publisher<DeferredExecutionResult> startDeferredCalls() {
drainDeferredCalls();
return publisher;
}
Expand Down
26 changes: 14 additions & 12 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,27 +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.Builder builder = ExecutionResultImpl.newExecutionResult().from(executionResult);
builder.addErrors(errorsEncountered);
return builder.build();
DeferredExecutionResultImpl.Builder builder = DeferredExecutionResultImpl.newDeferredExecutionResult().from(executionResult);
return builder
.addErrors(errorsEncountered)
.path(path)
.build();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import java.util.concurrent.ConcurrentLinkedDeque;

import static graphql.Assert.assertNotNull;
import static graphql.Assert.assertNotNullWithNPE;

/**
* A Publisher of things that are buffered and handles a single subscriber at a time.
Expand Down Expand Up @@ -59,7 +60,10 @@ public SingleSubscriberPublisher(OnSubscriptionCallback subscriptionCallback) {
* @param data the data to offer
*/
public void offer(T data) {
mutex.execute(() -> dataQ.offer(data));
mutex.execute(() -> {
dataQ.offer(data);
maybeReadInMutex();
});
}

/**
Expand Down Expand Up @@ -98,7 +102,7 @@ private void handleOnComplete() {

@Override
public void subscribe(Subscriber<? super T> subscriber) {
assertNotNull(subscriber, "Subscriber passed to subscribe must not be null");
assertNotNullWithNPE(subscriber, "Subscriber passed to subscribe must not be null");
mutex.execute(() -> {
if (this.subscriber == null) {
this.subscriber = subscriber;
Expand Down
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
5 changes: 4 additions & 1 deletion src/main/java/graphql/validation/ValidationErrorType.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,8 @@ public enum ValidationErrorType {
LoneAnonymousOperationViolation,
NonExecutableDefinition,
DuplicateOperationName,
DuplicateDirectiveName
DuplicateDirectiveName,
DeferDirectiveOnNonNullField,
DeferDirectiveNotOnQueryOperation,
DeferMustBeOnAllFields
}
Loading