graphql.execution.batched.BatchedExecutionStrategy Maven / Gradle / Ivy
package graphql.execution.batched;
import graphql.Assert;
import graphql.ExecutionResult;
import graphql.ExecutionResultImpl;
import graphql.PublicApi;
import graphql.execution.Async;
import graphql.execution.DataFetcherExceptionHandler;
import graphql.execution.DataFetcherExceptionHandlerParameters;
import graphql.execution.ExecutionContext;
import graphql.execution.ExecutionPath;
import graphql.execution.ExecutionStrategy;
import graphql.execution.ExecutionStrategyParameters;
import graphql.execution.ExecutionTypeInfo;
import graphql.execution.FieldCollectorParameters;
import graphql.execution.NonNullableFieldValidator;
import graphql.execution.SimpleDataFetcherExceptionHandler;
import graphql.execution.TypeResolutionParameters;
import graphql.execution.instrumentation.Instrumentation;
import graphql.execution.instrumentation.InstrumentationContext;
import graphql.execution.instrumentation.parameters.InstrumentationExecutionStrategyParameters;
import graphql.execution.instrumentation.parameters.InstrumentationFieldFetchParameters;
import graphql.execution.instrumentation.parameters.InstrumentationFieldParameters;
import graphql.language.Field;
import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;
import graphql.schema.DataFetchingFieldSelectionSet;
import graphql.schema.DataFetchingFieldSelectionSetImpl;
import graphql.schema.GraphQLEnumType;
import graphql.schema.GraphQLFieldDefinition;
import graphql.schema.GraphQLInterfaceType;
import graphql.schema.GraphQLList;
import graphql.schema.GraphQLObjectType;
import graphql.schema.GraphQLOutputType;
import graphql.schema.GraphQLScalarType;
import graphql.schema.GraphQLType;
import graphql.schema.GraphQLUnionType;
import graphql.schema.visibility.GraphqlFieldVisibility;
import java.lang.reflect.Array;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.function.BiFunction;
import java.util.stream.IntStream;
import static graphql.execution.ExecutionTypeInfo.newTypeInfo;
import static graphql.execution.FieldCollectorParameters.newParameters;
import static graphql.schema.DataFetchingEnvironmentBuilder.newDataFetchingEnvironment;
import static java.util.Collections.singletonList;
import static java.util.stream.Collectors.toList;
/**
* Execution Strategy that minimizes calls to the data fetcher when used in conjunction with {@link DataFetcher}s that have
* {@link DataFetcher#get(DataFetchingEnvironment)} methods annotated with {@link Batched}. See the javadoc comment on
* {@link Batched} for a more detailed description of batched data fetchers.
*
* The strategy runs a BFS over terms of the query and passes a list of all the relevant sources to the batched data fetcher.
*
* Normal DataFetchers can be used, however they will not see benefits of batching as they expect a single source object
* at a time.
*/
@PublicApi
public class BatchedExecutionStrategy extends ExecutionStrategy {
private final BatchedDataFetcherFactory batchingFactory = new BatchedDataFetcherFactory();
public BatchedExecutionStrategy() {
this(new SimpleDataFetcherExceptionHandler());
}
public BatchedExecutionStrategy(DataFetcherExceptionHandler dataFetcherExceptionHandler) {
super(dataFetcherExceptionHandler);
}
@Override
public CompletableFuture execute(ExecutionContext executionContext, ExecutionStrategyParameters parameters) {
InstrumentationContext> executionStrategyCtx = executionContext.getInstrumentation().beginExecutionStrategy(new InstrumentationExecutionStrategyParameters(executionContext));
GraphQLObjectType type = parameters.typeInfo().castType(GraphQLObjectType.class);
ExecutionNode root = new ExecutionNode(type,
parameters.typeInfo(),
parameters.fields(),
singletonList(MapOrList.createMap(new LinkedHashMap<>())),
Collections.singletonList(parameters.source())
);
Queue nodes = new ArrayDeque<>();
CompletableFuture result = new CompletableFuture<>();
executeImpl(executionContext,
parameters,
root,
root,
nodes,
root.getFields().keySet().iterator(),
result);
executionStrategyCtx.onEnd(result, null);
return result;
}
private void executeImpl(ExecutionContext executionContext,
ExecutionStrategyParameters parameters,
ExecutionNode root,
ExecutionNode curNode,
Queue queueOfNodes,
Iterator curFieldNames,
CompletableFuture overallResult) {
if (!curFieldNames.hasNext() && queueOfNodes.isEmpty()) {
overallResult.complete(new ExecutionResultImpl(root.getParentResults().get(0).toObject(), executionContext.getErrors()));
return;
}
if (!curFieldNames.hasNext()) {
curNode = queueOfNodes.poll();
curFieldNames = curNode.getFields().keySet().iterator();
}
String fieldName = curFieldNames.next();
List currentField = curNode.getFields().get(fieldName);
//
// once an object is resolved from a interface / union to a node with an object type, the
// parent type info has effectively changed (it has got more specific), even though the path etc...
// has not changed
ExecutionTypeInfo currentParentTypeInfo = parameters.typeInfo();
ExecutionTypeInfo newParentTypeInfo = newTypeInfo()
.type(curNode.getType())
.fieldDefinition(currentParentTypeInfo.getFieldDefinition())
.path(currentParentTypeInfo.getPath())
.parentInfo(currentParentTypeInfo.getParentTypeInfo())
.build();
ExecutionPath fieldPath = curNode.getTypeInfo().getPath().segment(fieldName);
GraphQLFieldDefinition fieldDefinition = getFieldDef(executionContext.getGraphQLSchema(), curNode.getType(), currentField.get(0));
ExecutionTypeInfo typeInfo = newTypeInfo()
.type(fieldDefinition.getType())
.fieldDefinition(fieldDefinition)
.path(fieldPath)
.parentInfo(newParentTypeInfo)
.build();
ExecutionStrategyParameters newParameters = parameters
.transform(builder -> builder
.path(fieldPath)
.field(currentField)
.typeInfo(typeInfo)
);
ExecutionNode finalCurNode = curNode;
Iterator finalCurFieldNames = curFieldNames;
resolveField(executionContext, newParameters, fieldName, curNode)
.whenComplete((childNodes, exception) -> {
if (exception != null) {
handleNonNullException(executionContext, overallResult, exception);
return;
}
queueOfNodes.addAll(childNodes);
executeImpl(executionContext, newParameters, root, finalCurNode, queueOfNodes, finalCurFieldNames, overallResult);
});
}
private CompletableFuture> resolveField(ExecutionContext executionContext,
ExecutionStrategyParameters parameters,
String fieldName,
ExecutionNode node) {
GraphQLObjectType parentType = node.getType();
List fields = node.getFields().get(fieldName);
GraphQLFieldDefinition fieldDef = getFieldDef(executionContext.getGraphQLSchema(), parentType, fields.get(0));
Instrumentation instrumentation = executionContext.getInstrumentation();
ExecutionTypeInfo typeInfo = parameters.typeInfo();
InstrumentationContext fieldCtx = instrumentation.beginField(
new InstrumentationFieldParameters(executionContext, fieldDef, typeInfo)
);
CompletableFuture fetchedData = fetchData(executionContext, parameters, fieldName, node, fieldDef);
CompletableFuture> result = fetchedData.thenApply((fetchedValues) -> {
GraphqlFieldVisibility fieldVisibility = executionContext.getGraphQLSchema().getFieldVisibility();
Map argumentValues = valuesResolver.getArgumentValues(
fieldVisibility,
fieldDef.getArguments(), fields.get(0).getArguments(), executionContext.getVariables());
return completeValues(executionContext, fetchedValues, typeInfo, fieldName, fields, argumentValues);
});
result.whenComplete((nodes, throwable) -> fieldCtx.onEnd(null, throwable));
return result;
}
private CompletableFuture fetchData(ExecutionContext executionContext,
ExecutionStrategyParameters parameters,
String fieldName,
ExecutionNode node,
GraphQLFieldDefinition fieldDef) {
GraphQLObjectType parentType = node.getType();
List fields = node.getFields().get(fieldName);
List parentResults = node.getParentResults();
GraphqlFieldVisibility fieldVisibility = executionContext.getGraphQLSchema().getFieldVisibility();
Map argumentValues = valuesResolver.getArgumentValues(
fieldVisibility,
fieldDef.getArguments(), fields.get(0).getArguments(), executionContext.getVariables());
GraphQLOutputType fieldType = fieldDef.getType();
DataFetchingFieldSelectionSet fieldCollector = DataFetchingFieldSelectionSetImpl.newCollector(executionContext, fieldType, fields);
DataFetchingEnvironment environment = newDataFetchingEnvironment(executionContext)
.source(node.getSources())
.arguments(argumentValues)
.fieldDefinition(fieldDef)
.fields(fields)
.fieldType(fieldDef.getType())
.fieldTypeInfo(parameters.typeInfo())
.parentType(parentType)
.selectionSet(fieldCollector)
.build();
Instrumentation instrumentation = executionContext.getInstrumentation();
InstrumentationFieldFetchParameters instrumentationFieldFetchParameters =
new InstrumentationFieldFetchParameters(executionContext, fieldDef, environment);
InstrumentationContext
© 2015 - 2025 Weber Informatics LLC | Privacy Policy