Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
91 changes: 51 additions & 40 deletions src/main/java/graphql/execution/ExecutionStrategy.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import graphql.schema.GraphQLScalarType;
import graphql.schema.GraphQLSchema;
import graphql.schema.GraphQLType;
import graphql.schema.LightDataFetcher;
import graphql.util.FpKit;
import graphql.util.LogKit;
import org.slf4j.Logger;
Expand Down Expand Up @@ -237,68 +238,59 @@ protected CompletableFuture<FetchedValue> fetchField(ExecutionContext executionC
MergedField field = parameters.getField();
GraphQLObjectType parentType = (GraphQLObjectType) parameters.getExecutionStepInfo().getUnwrappedNonNullType();
GraphQLFieldDefinition fieldDef = getFieldDef(executionContext.getGraphQLSchema(), parentType, field.getSingleField());

GraphQLCodeRegistry codeRegistry = executionContext.getGraphQLSchema().getCodeRegistry();
GraphQLOutputType fieldType = fieldDef.getType();

// if the DF (like PropertyDataFetcher) does not use the arguments of execution step info then dont build any
Supplier<ExecutionStepInfo> executionStepInfo = FpKit.intraThreadMemoize(
() -> createExecutionStepInfo(executionContext, parameters, fieldDef, parentType));
Supplier<Map<String, Object>> argumentValues = () -> executionStepInfo.get().getArguments();
// if the DF (like PropertyDataFetcher) does not use the arguments or execution step info then dont build any

Supplier<ExecutableNormalizedField> normalizedFieldSupplier = getNormalizedField(executionContext, parameters, executionStepInfo);
Supplier<DataFetchingEnvironment> dataFetchingEnvironment = FpKit.intraThreadMemoize(() -> {

// DataFetchingFieldSelectionSet and QueryDirectives is a supplier of sorts - eg a lazy pattern

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.

DFE creation is now one big supplier

DataFetchingFieldSelectionSet fieldCollector = DataFetchingFieldSelectionSetImpl.newCollector(executionContext.getGraphQLSchema(), fieldType, normalizedFieldSupplier);
QueryDirectives queryDirectives = new QueryDirectivesImpl(field,
executionContext.getGraphQLSchema(),
executionContext.getCoercedVariables().toMap(),
executionContext.getGraphQLContext(),
executionContext.getLocale());

@bbakerman bbakerman Sep 10, 2022

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.

These all move down into the supplier of the DataFetchingEnv - they are ONLY needed for it so UNTIL we ask for the DFE - none of this code is run

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.

Its a bit hard to see - open the fill view to see it

Supplier<ExecutionStepInfo> executionStepInfo = FpKit.intraThreadMemoize(
() -> createExecutionStepInfo(executionContext, parameters, fieldDef, parentType));

Supplier<Map<String, Object>> argumentValues = () -> executionStepInfo.get().getArguments();

Supplier<ExecutableNormalizedField> normalizedFieldSupplier = getNormalizedField(executionContext, parameters, executionStepInfo);

// DataFetchingFieldSelectionSet and QueryDirectives is a supplier of sorts - eg a lazy pattern
DataFetchingFieldSelectionSet fieldCollector = DataFetchingFieldSelectionSetImpl.newCollector(executionContext.getGraphQLSchema(), fieldDef.getType(), normalizedFieldSupplier);
QueryDirectives queryDirectives = new QueryDirectivesImpl(field,
executionContext.getGraphQLSchema(),
executionContext.getCoercedVariables().toMap(),
executionContext.getGraphQLContext(),
executionContext.getLocale());

DataFetchingEnvironment environment = newDataFetchingEnvironment(executionContext)
.source(parameters.getSource())
.localContext(parameters.getLocalContext())
.arguments(argumentValues)
.fieldDefinition(fieldDef)
.mergedField(parameters.getField())
.fieldType(fieldType)
.executionStepInfo(executionStepInfo)
.parentType(parentType)
.selectionSet(fieldCollector)
.queryDirectives(queryDirectives)
.build();

return newDataFetchingEnvironment(executionContext)
.source(parameters.getSource())
.localContext(parameters.getLocalContext())
.arguments(argumentValues)
.fieldDefinition(fieldDef)
.mergedField(parameters.getField())
.fieldType(fieldDef.getType())
.executionStepInfo(executionStepInfo)
.parentType(parentType)
.selectionSet(fieldCollector)
.queryDirectives(queryDirectives)
.build();
});
DataFetcher<?> dataFetcher = codeRegistry.getDataFetcher(parentType, fieldDef);

Instrumentation instrumentation = executionContext.getInstrumentation();

InstrumentationFieldFetchParameters instrumentationFieldFetchParams = new InstrumentationFieldFetchParameters(executionContext, environment, parameters, dataFetcher instanceof TrivialDataFetcher);
InstrumentationFieldFetchParameters instrumentationFieldFetchParams = new InstrumentationFieldFetchParameters(executionContext, dataFetchingEnvironment, parameters, dataFetcher instanceof TrivialDataFetcher);
InstrumentationContext<Object> fetchCtx = nonNullCtx(instrumentation.beginFieldFetch(instrumentationFieldFetchParams,
executionContext.getInstrumentationState())
);

CompletableFuture<Object> fetchedValue;
dataFetcher = instrumentation.instrumentDataFetcher(dataFetcher, instrumentationFieldFetchParams, executionContext.getInstrumentationState());
ExecutionId executionId = executionContext.getExecutionId();
try {
Object fetchedValueRaw = dataFetcher.get(environment);
fetchedValue = Async.toCompletableFuture(fetchedValueRaw);
} catch (Exception e) {
if (logNotSafe.isDebugEnabled()) {
logNotSafe.debug(String.format("'%s', field '%s' fetch threw exception", executionId, executionStepInfo.get().getPath()), e);
}
CompletableFuture<Object> fetchedValue = invokeDataFetcher(executionContext, parameters, fieldDef, dataFetchingEnvironment, dataFetcher);

fetchedValue = new CompletableFuture<>();
fetchedValue.completeExceptionally(e);
}
fetchCtx.onDispatched(fetchedValue);
return fetchedValue
.handle((result, exception) -> {
fetchCtx.onCompleted(result, exception);
if (exception != null) {
return handleFetchingException(executionContext, environment, exception);
return handleFetchingException(executionContext, dataFetchingEnvironment.get(), exception);

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.

we ONLY use the DFE here if we have an exception which is least trodden path

} else {
return CompletableFuture.completedFuture(result);
}
Expand All @@ -307,6 +299,25 @@ protected CompletableFuture<FetchedValue> fetchField(ExecutionContext executionC
.thenApply(result -> unboxPossibleDataFetcherResult(executionContext, parameters, result));
}

private CompletableFuture<Object> invokeDataFetcher(ExecutionContext executionContext, ExecutionStrategyParameters parameters, GraphQLFieldDefinition fieldDef, Supplier<DataFetchingEnvironment> dataFetchingEnvironment, DataFetcher<?> dataFetcher) {
CompletableFuture<Object> fetchedValue;
try {
Object fetchedValueRaw;
if (dataFetcher instanceof LightDataFetcher) {
fetchedValueRaw = ((LightDataFetcher<?>) dataFetcher).get(fieldDef, parameters.getSource(), dataFetchingEnvironment);
} else {
fetchedValueRaw = dataFetcher.get(dataFetchingEnvironment.get());
}
fetchedValue = Async.toCompletableFuture(fetchedValueRaw);
} catch (Exception e) {
if (logNotSafe.isDebugEnabled()) {
logNotSafe.debug(String.format("'%s', field '%s' fetch threw exception", executionContext.getExecutionId(), parameters.getPath()), e);
}
fetchedValue = Async.exceptionallyCompletedFuture(e);
}
return fetchedValue;
}

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.

moved the fetch call into its own method to reduce method complexity

protected Supplier<ExecutableNormalizedField> getNormalizedField(ExecutionContext executionContext, ExecutionStrategyParameters parameters, Supplier<ExecutionStepInfo> executionStepInfo) {
Supplier<ExecutableNormalizedOperation> normalizedQuery = executionContext.getNormalizedQueryTree();
return () -> normalizedQuery.get().getNormalizedField(parameters.getField(), executionStepInfo.get().getObjectType(), executionStepInfo.get().getPath());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,26 @@
import graphql.execution.instrumentation.InstrumentationState;
import graphql.schema.DataFetchingEnvironment;

import java.util.function.Supplier;

/**
* Parameters sent to {@link Instrumentation} methods
*/
@PublicApi
public class InstrumentationFieldFetchParameters extends InstrumentationFieldParameters {
private final DataFetchingEnvironment environment;
private final Supplier<DataFetchingEnvironment> environment;

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.

needs to be a supplier until they ask for it

private final ExecutionStrategyParameters executionStrategyParameters;
private final boolean trivialDataFetcher;

public InstrumentationFieldFetchParameters(ExecutionContext getExecutionContext, DataFetchingEnvironment environment, ExecutionStrategyParameters executionStrategyParameters, boolean trivialDataFetcher) {
super(getExecutionContext, environment::getExecutionStepInfo);
public InstrumentationFieldFetchParameters(ExecutionContext getExecutionContext, Supplier<DataFetchingEnvironment> environment, ExecutionStrategyParameters executionStrategyParameters, boolean trivialDataFetcher) {
super(getExecutionContext, () -> environment.get().getExecutionStepInfo());
this.environment = environment;
this.executionStrategyParameters = executionStrategyParameters;
this.trivialDataFetcher = trivialDataFetcher;
}

private InstrumentationFieldFetchParameters(ExecutionContext getExecutionContext, DataFetchingEnvironment environment, InstrumentationState instrumentationState, ExecutionStrategyParameters executionStrategyParameters, boolean trivialDataFetcher) {
super(getExecutionContext, environment::getExecutionStepInfo, instrumentationState);
private InstrumentationFieldFetchParameters(ExecutionContext getExecutionContext, Supplier<DataFetchingEnvironment> environment, InstrumentationState instrumentationState, ExecutionStrategyParameters executionStrategyParameters, boolean trivialDataFetcher) {
super(getExecutionContext, () -> environment.get().getExecutionStepInfo(), instrumentationState);
this.environment = environment;
this.executionStrategyParameters = executionStrategyParameters;
this.trivialDataFetcher = trivialDataFetcher;
Expand All @@ -45,13 +47,13 @@ private InstrumentationFieldFetchParameters(ExecutionContext getExecutionContext
@Override
public InstrumentationFieldFetchParameters withNewState(InstrumentationState instrumentationState) {
return new InstrumentationFieldFetchParameters(
this.getExecutionContext(), this.getEnvironment(),
this.getExecutionContext(), this.environment,
instrumentationState, executionStrategyParameters, trivialDataFetcher);
}


public DataFetchingEnvironment getEnvironment() {
return environment;
return environment.get();
}

public boolean isTrivialDataFetcher() {
Expand Down
36 changes: 36 additions & 0 deletions src/main/java/graphql/schema/LightDataFetcher.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package graphql.schema;

import graphql.TrivialDataFetcher;

import java.util.function.Supplier;

/**
* A {@link LightDataFetcher} is a specialised version of {@link DataFetcher} that is passed more lightweight arguments
* when it is asked to fetch values. The most common example of this is the {@link PropertyDataFetcher} which does not need
* all the {@link DataFetchingEnvironment} values to perform its duties.
*
* @param <T> for two
*/
public interface LightDataFetcher<T> extends TrivialDataFetcher<T> {

/**
* This is called to by the engine to get a value from the source object in a lightweight fashion. Only the field
* and source object are passed in a materialised way. The more heavy weight {@link DataFetchingEnvironment} is wrapped
* in a supplier that is only created on demand.
* <p>
* If you are a lightweight data fetcher (like {@link PropertyDataFetcher} is) then you can implement this method to have a more lightweight
* method invocation. However, if you need field arguments etc. during fetching (most custom fetchers will) then you should use implement
* {@link #get(DataFetchingEnvironment)}.
*
* @param fieldDefinition the graphql field definition
* @param sourceObject the source object to get a value from
* @param environmentSupplier a supplier of the {@link DataFetchingEnvironment} that creates it lazily
*
* @return a value of type T. May be wrapped in a {@link graphql.execution.DataFetcherResult}
*
* @throws Exception to relieve the implementations from having to wrap checked exceptions. Any exception thrown
* from a {@code DataFetcher} will eventually be handled by the registered {@link graphql.execution.DataFetcherExceptionHandler}
* and the related field will have a value of {@code null} in the result.
*/
T get(GraphQLFieldDefinition fieldDefinition, Object sourceObject, Supplier<DataFetchingEnvironment> environmentSupplier) throws Exception;
}

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.

Added a new interface to keep Andi happy and to better delineate what the data fetcher does. Is it light or heavy?

Its MUST be a DataFetcher however because the CodeRegistry requires a DataFetcher when registered

21 changes: 17 additions & 4 deletions src/main/java/graphql/schema/PropertyDataFetcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@

import graphql.Assert;
import graphql.PublicApi;
import graphql.TrivialDataFetcher;

import java.util.function.Function;
import java.util.function.Supplier;

/**
* This is the default data fetcher used in graphql-java, and it will examine
Expand All @@ -31,7 +31,7 @@
* @see graphql.schema.DataFetcher
*/
@PublicApi
public class PropertyDataFetcher<T> implements DataFetcher<T>, TrivialDataFetcher<T> {
public class PropertyDataFetcher<T> implements LightDataFetcher<T> {

private final String propertyName;
private final Function<Object, Object> function;
Expand Down Expand Up @@ -69,6 +69,7 @@ private <O> PropertyDataFetcher(Function<O, T> function) {
*
* @param propertyName the name of the property to retrieve
* @param <T> the type of result
*
* @return a new PropertyDataFetcher using the provided function as its source of values
*/
public static <T> PropertyDataFetcher<T> fetching(String propertyName) {
Expand All @@ -92,6 +93,7 @@ public static <T> PropertyDataFetcher<T> fetching(String propertyName) {
* @param function the function to use to obtain a value from the source object
* @param <O> the type of the source object
* @param <T> the type of result
*
* @return a new PropertyDataFetcher using the provided function as its source of values
*/
public static <T, O> PropertyDataFetcher<T> fetching(Function<O, T> function) {
Expand All @@ -105,10 +107,19 @@ public String getPropertyName() {
return propertyName;
}

@SuppressWarnings("unchecked")
@Override
public T get(GraphQLFieldDefinition fieldDefinition, Object source, Supplier<DataFetchingEnvironment> environmentSupplier) throws Exception {
return getImpl(source, fieldDefinition.getType(), environmentSupplier);
}

@Override
public T get(DataFetchingEnvironment environment) {
Object source = environment.getSource();
return getImpl(source, environment.getFieldType(), () -> environment);
}

@SuppressWarnings("unchecked")
private T getImpl(Object source, GraphQLOutputType fieldDefinition, Supplier<DataFetchingEnvironment> environmentSupplier) {
if (source == null) {
return null;
}
Expand All @@ -117,7 +128,7 @@ public T get(DataFetchingEnvironment environment) {
return (T) function.apply(source);
}

return (T) PropertyDataFetcherHelper.getPropertyValue(propertyName, source, environment.getFieldType(), environment);
return (T) PropertyDataFetcherHelper.getPropertyValue(propertyName, source, fieldDefinition, environmentSupplier);
}

/**
Expand All @@ -138,6 +149,7 @@ public static void clearReflectionCache() {
* values. By default it PropertyDataFetcher WILL use setAccessible.
*
* @param flag whether to use setAccessible
*
* @return the previous value of the flag
*/
public static boolean setUseSetAccessible(boolean flag) {
Expand All @@ -148,6 +160,7 @@ public static boolean setUseSetAccessible(boolean flag) {
* This can be used to control whether PropertyDataFetcher will cache negative lookups for a property for performance reasons. By default it PropertyDataFetcher WILL cache misses.
*
* @param flag whether to cache misses
*
* @return the previous value of the flag
*/
public static boolean setUseNegativeCache(boolean flag) {
Expand Down
8 changes: 5 additions & 3 deletions src/main/java/graphql/schema/PropertyDataFetcherHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import graphql.Internal;
import graphql.VisibleForTesting;

import java.util.function.Supplier;

/**
* This class is the guts of a property data fetcher and also used in AST code to turn
* in memory java objects into AST elements
Expand All @@ -13,11 +15,11 @@ public class PropertyDataFetcherHelper {
private static final PropertyFetchingImpl impl = new PropertyFetchingImpl(DataFetchingEnvironment.class);

public static Object getPropertyValue(String propertyName, Object object, GraphQLType graphQLType) {
return impl.getPropertyValue(propertyName, object, graphQLType, null);
return impl.getPropertyValue(propertyName, object, graphQLType, false, () -> null);
}

public static Object getPropertyValue(String propertyName, Object object, GraphQLType graphQLType, DataFetchingEnvironment environment) {
return impl.getPropertyValue(propertyName, object, graphQLType, environment);
public static Object getPropertyValue(String propertyName, Object object, GraphQLType graphQLType, Supplier<DataFetchingEnvironment> environment) {
return impl.getPropertyValue(propertyName, object, graphQLType, true, environment::get);
}

public static void clearReflectionCache() {
Expand Down
Loading