graphql.execution.batched.BatchedExecutionStrategy Maven / Gradle / Ivy
package graphql.execution.batched;
import graphql.Assert;
import graphql.ExecutionResult;
import graphql.ExecutionResultImpl;
import graphql.GraphQLException;
import graphql.PublicApi;
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.FieldCollectorParameters;
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.GraphQLNonNull;
import graphql.schema.GraphQLObjectType;
import graphql.schema.GraphQLOutputType;
import graphql.schema.GraphQLScalarType;
import graphql.schema.GraphQLType;
import graphql.schema.GraphQLUnionType;
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.concurrent.CompletionStage;
import java.util.function.BiFunction;
import static graphql.execution.FieldCollectorParameters.newParameters;
import static graphql.schema.DataFetchingEnvironmentBuilder.newDataFetchingEnvironment;
import static java.util.Collections.singletonList;
/**
* 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.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 nodes,
Iterator curFieldNames,
CompletableFuture overallResult) {
if (!curFieldNames.hasNext() && nodes.isEmpty()) {
overallResult.complete(new ExecutionResultImpl(root.getParentResults().get(0).toObject(), executionContext.getErrors()));
return;
}
if (!curFieldNames.hasNext()) {
curNode = nodes.poll();
curFieldNames = curNode.getFields().keySet().iterator();
}
String fieldName = curFieldNames.next();
ExecutionPath fieldPath = parameters.path().segment(fieldName);
List currentField = curNode.getFields().get(fieldName);
ExecutionStrategyParameters newParameters = parameters
.transform(builder -> builder.path(fieldPath).field(currentField));
ExecutionNode finalCurNode = curNode;
Iterator finalCurFieldNames = curFieldNames;
resolveField(executionContext, newParameters, fieldName, curNode).whenComplete((childNodes, exception) -> {
if (exception != null) {
overallResult.completeExceptionally(exception);
return;
}
nodes.addAll(childNodes);
executeImpl(executionContext, newParameters, root, finalCurNode, nodes, 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();
InstrumentationContext fieldCtx = instrumentation.beginField(
new InstrumentationFieldParameters(executionContext, fieldDef, fieldTypeInfo(parameters, fieldDef))
);
CompletableFuture> fetchedData = fetchData(executionContext, parameters, fieldName, node, fieldDef);
CompletableFuture> result = fetchedData.thenApply((fetchedValues) -> {
Map argumentValues = valuesResolver.getArgumentValues(
fieldDef.getArguments(), fields.get(0).getArguments(), executionContext.getVariables());
return completeValues(executionContext, parentType, fetchedValues, fieldName, fields, fieldDef.getType(), argumentValues);
});
result.whenComplete((nodes, throwable) -> fieldCtx.onEnd(null, throwable));
return result;
}
private List completeValues(ExecutionContext executionContext, GraphQLObjectType parentType,
List fetchedValues, String fieldName, List fields,
GraphQLOutputType fieldType, Map argumentValues) {
GraphQLType unwrappedFieldType = handleNonNullType(fieldType, fetchedValues, parentType, fields);
if (isPrimitive(unwrappedFieldType)) {
handlePrimitives(fetchedValues, fieldName, unwrappedFieldType);
return Collections.emptyList();
} else if (isObject(unwrappedFieldType)) {
return handleObject(executionContext, argumentValues, fetchedValues, fieldName, fields, unwrappedFieldType);
} else if (isList(unwrappedFieldType)) {
return handleList(executionContext, argumentValues, fetchedValues, fieldName, fields, parentType, (GraphQLList) unwrappedFieldType);
} else {
return Assert.assertShouldNeverHappen("can't handle type: " + unwrappedFieldType);
}
}
@SuppressWarnings("unchecked")
private List handleList(ExecutionContext executionContext, Map argumentValues,
List values, String fieldName, List fields,
GraphQLObjectType parentType, GraphQLList listType) {
List flattenedValues = new ArrayList<>();
for (FetchedValue value : values) {
MapOrList mapOrList = value.getParentResult();
if (value.getValue() == null) {
mapOrList.putOrAdd(fieldName, null);
continue;
}
MapOrList listResult = mapOrList.createAndPutList(fieldName);
for (Object rawValue : (List
© 2015 - 2025 Weber Informatics LLC | Privacy Policy