Skip to content

Commit 21ad7d7

Browse files
committed
Add configuration to allow usage of normalized documents
1 parent e9baf2f commit 21ad7d7

16 files changed

+403
-142
lines changed

src/main/java/graphql/ExperimentalApi.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,9 @@
2424
* The key that should be associated with a boolean value which indicates whether @defer and @stream behaviour is enabled for this execution.
2525
*/
2626
String ENABLE_INCREMENTAL_SUPPORT = "ENABLE_INCREMENTAL_SUPPORT";
27+
28+
/**
29+
* The key that should be associated with a boolean value which indicates whether normalized document behaviour is enabled for this execution.
30+
*/
31+
String ENABLE_NORMALIZED_DOCUMENT_SUPPORT = "ENABLE_NORMALIZED_DOCUMENT_SUPPORT";
2732
}

src/main/java/graphql/GraphQLUnusualConfiguration.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,13 @@ public IncrementalSupportConfig incrementalSupport() {
265265
return new IncrementalSupportConfig(this);
266266
}
267267

268+
/**
269+
* @return an element that allows you to control normalized document behavior
270+
*/
271+
public NormalizedDocumentSupportConfig normalizedDocumentSupport() {
272+
return new NormalizedDocumentSupportConfig(this);
273+
}
274+
268275
/**
269276
* @return an element that allows you to precisely control {@link org.dataloader.DataLoader} behavior
270277
* in graphql-java.
@@ -321,6 +328,28 @@ public GraphQLContextConfiguration then() {
321328
}
322329
}
323330

331+
public static class NormalizedDocumentSupportConfig extends BaseContextConfig {
332+
private NormalizedDocumentSupportConfig(GraphQLContextConfiguration contextConfig) {
333+
super(contextConfig);
334+
}
335+
336+
/**
337+
* @return true if normalized document behaviour is enabled for this execution.
338+
*/
339+
public boolean isNormalizedDocumentSupportEnabled() {
340+
return contextConfig.getBoolean(ExperimentalApi.ENABLE_NORMALIZED_DOCUMENT_SUPPORT);
341+
}
342+
343+
/**
344+
* This controls whether normalized document behaviour is enabled for this execution.
345+
*/
346+
@ExperimentalApi
347+
public NormalizedDocumentSupportConfig enableNormalizedDocumentSupport(boolean enable) {
348+
contextConfig.put(ExperimentalApi.ENABLE_NORMALIZED_DOCUMENT_SUPPORT, enable);
349+
return this;
350+
}
351+
}
352+
324353
public static class IncrementalSupportConfig extends BaseContextConfig {
325354
private IncrementalSupportConfig(GraphQLContextConfiguration contextConfig) {
326355
super(contextConfig);

src/main/java/graphql/execution/ExecutionContext.java

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,17 @@
1515
import graphql.execution.instrumentation.Instrumentation;
1616
import graphql.execution.instrumentation.InstrumentationState;
1717
import graphql.execution.instrumentation.parameters.InstrumentationCreateExecutableNormalizedOperationParameters;
18+
import graphql.execution.instrumentation.parameters.InstrumentationCreateNormalizedDocumentParameters;
19+
import graphql.execution.instrumentation.parameters.InstrumentationExecutionParameters;
1820
import graphql.language.Document;
1921
import graphql.language.FragmentDefinition;
2022
import graphql.language.OperationDefinition;
2123
import graphql.normalized.ExecutableNormalizedOperation;
2224
import graphql.normalized.ExecutableNormalizedOperationFactory;
25+
import graphql.normalized.GraphQlNormalizedOperation;
26+
import graphql.normalized.nf.NormalizedDocument;
27+
import graphql.normalized.nf.NormalizedDocumentFactory;
28+
import graphql.normalized.nf.NormalizedOperation;
2329
import graphql.schema.GraphQLSchema;
2430
import graphql.util.FpKit;
2531
import graphql.util.LockKit;
@@ -66,7 +72,7 @@ public class ExecutionContext {
6672
private final ResponseMapFactory responseMapFactory;
6773

6874
private final ExecutionInput executionInput;
69-
private final Supplier<ExecutableNormalizedOperation> queryTree;
75+
private final Supplier<GraphQlNormalizedOperation> queryTree;
7076
private final boolean propagateErrorsOnNonNullContractFailure;
7177

7278
private final AtomicInteger isRunning = new AtomicInteger(0);
@@ -101,7 +107,7 @@ public class ExecutionContext {
101107
this.localContext = builder.localContext;
102108
this.executionInput = builder.executionInput;
103109
this.dataLoaderDispatcherStrategy = builder.dataLoaderDispatcherStrategy;
104-
this.queryTree = FpKit.interThreadMemoize(this::createExecutableNormalizedOperation);
110+
this.queryTree = FpKit.interThreadMemoize(this::createGraphQLNormalizedOperation);
105111
this.propagateErrorsOnNonNullContractFailure = builder.propagateErrorsOnNonNullContractFailure;
106112
this.engineRunningState = builder.engineRunningState;
107113
}
@@ -337,7 +343,7 @@ public ExecutionStrategy getStrategy(OperationDefinition.Operation operation) {
337343
}
338344
}
339345

340-
public Supplier<ExecutableNormalizedOperation> getNormalizedQueryTree() {
346+
public Supplier<GraphQlNormalizedOperation> getNormalizedQueryTree() {
341347
return queryTree;
342348
}
343349

@@ -369,14 +375,57 @@ public ResultNodesInfo getResultNodesInfo() {
369375
return resultNodesInfo;
370376
}
371377

378+
private GraphQlNormalizedOperation createGraphQLNormalizedOperation() {
379+
// Check for experimental support for normalized documents
380+
if (hasNormalizedDocumentSupport()) {
381+
return createNormalizedOperation();
382+
}
383+
384+
return createExecutableNormalizedOperation();
385+
}
386+
387+
@ExperimentalApi
388+
private NormalizedOperation createNormalizedOperation() {
389+
var parameters = new InstrumentationCreateNormalizedDocumentParameters(executionInput, graphQLSchema);
390+
var instrument = instrumentation.beginCreateNormalizedDocument(parameters, instrumentationState);
391+
var normalizedDocument = NormalizedDocumentFactory.createNormalizedDocument(graphQLSchema, document);
392+
393+
// Search the document for the operation that matches the operationDefinition name,
394+
// if no match then it could be anonymous query, then fallback to the first operation.
395+
var normalizedOperations = normalizedDocument.getNormalizedOperations();
396+
var normalizedOperation = normalizedOperations.stream()
397+
.filter(this::isExecutingOperation)
398+
.findAny()
399+
.map(NormalizedDocument.NormalizedOperationWithAssumedSkipIncludeVariables::getNormalizedOperation)
400+
.orElseGet(normalizedDocument::getSingleNormalizedOperation);
401+
402+
if (instrument != null) {
403+
instrument.onCompleted(normalizedDocument, null);
404+
}
405+
406+
return normalizedOperation;
407+
}
408+
372409
private ExecutableNormalizedOperation createExecutableNormalizedOperation() {
373410
var parameters = new InstrumentationCreateExecutableNormalizedOperationParameters(executionInput, graphQLSchema);
374411
var instrument = instrumentation.beginCreateExecutableNormalizedOperation(parameters, instrumentationState);
375-
var result = ExecutableNormalizedOperationFactory.createExecutableNormalizedOperation(graphQLSchema, operationDefinition, fragmentsByName, coercedVariables);
412+
var executableNormalizedOperation = ExecutableNormalizedOperationFactory.createExecutableNormalizedOperation(graphQLSchema, operationDefinition, fragmentsByName, coercedVariables);
376413
if (instrument != null) {
377-
instrument.onCompleted(result, null);
414+
instrument.onCompleted(executableNormalizedOperation, null);
415+
}
416+
417+
return executableNormalizedOperation;
418+
}
419+
420+
private boolean isExecutingOperation(NormalizedDocument.NormalizedOperationWithAssumedSkipIncludeVariables op) {
421+
var operation = op.getNormalizedOperation();
422+
var operationName = operation.getOperationName();
423+
var operationDefinitionName = operationDefinition.getName();
424+
if (operationName == null || operationDefinitionName == null) {
425+
return false;
378426
}
379-
return result;
427+
428+
return operationName.equals(operationDefinitionName);
380429
}
381430

382431
@Internal
@@ -385,6 +434,12 @@ public boolean hasIncrementalSupport() {
385434
return graphqlContext != null && graphqlContext.getBoolean(ExperimentalApi.ENABLE_INCREMENTAL_SUPPORT);
386435
}
387436

437+
@Internal
438+
private boolean hasNormalizedDocumentSupport() {
439+
GraphQLContext graphqlContext = getGraphQLContext();
440+
return graphqlContext != null && graphqlContext.getBoolean(ExperimentalApi.ENABLE_NORMALIZED_DOCUMENT_SUPPORT);
441+
}
442+
388443
@Internal
389444
public EngineRunningState getEngineRunningState() {
390445
return engineRunningState;

src/main/java/graphql/execution/ExecutionStrategy.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@
2727
import graphql.extensions.ExtensionsBuilder;
2828
import graphql.introspection.Introspection;
2929
import graphql.language.Field;
30-
import graphql.normalized.ExecutableNormalizedField;
31-
import graphql.normalized.ExecutableNormalizedOperation;
30+
import graphql.normalized.GraphQlNormalizedField;
31+
import graphql.normalized.GraphQlNormalizedOperation;
3232
import graphql.schema.CoercingSerializeException;
3333
import graphql.schema.DataFetcher;
3434
import graphql.schema.DataFetchingEnvironment;
@@ -420,7 +420,7 @@ private Object fetchField(GraphQLFieldDefinition fieldDef, ExecutionContext exec
420420

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

423-
Supplier<ExecutableNormalizedField> normalizedFieldSupplier = getNormalizedField(executionContext, parameters, executionStepInfo);
423+
Supplier<GraphQlNormalizedField> normalizedFieldSupplier = getNormalizedField(executionContext, parameters, executionStepInfo);
424424

425425
// DataFetchingFieldSelectionSet and QueryDirectives is a supplier of sorts - eg a lazy pattern
426426
DataFetchingFieldSelectionSet fieldCollector = DataFetchingFieldSelectionSetImpl.newCollector(executionContext.getGraphQLSchema(), fieldDef.getType(), normalizedFieldSupplier);
@@ -515,9 +515,9 @@ private Object invokeDataFetcher(ExecutionContext executionContext, ExecutionStr
515515
return fetchedValue;
516516
}
517517

518-
protected Supplier<ExecutableNormalizedField> getNormalizedField(ExecutionContext executionContext, ExecutionStrategyParameters parameters, Supplier<ExecutionStepInfo> executionStepInfo) {
519-
Supplier<ExecutableNormalizedOperation> normalizedQuery = executionContext.getNormalizedQueryTree();
520-
return () -> normalizedQuery.get().getNormalizedField(parameters.getField(), executionStepInfo.get().getObjectType(), executionStepInfo.get().getPath());
518+
protected Supplier<GraphQlNormalizedField> getNormalizedField(ExecutionContext executionContext, ExecutionStrategyParameters parameters, Supplier<ExecutionStepInfo> executionStepInfo) {
519+
Supplier<GraphQlNormalizedOperation> normalizedQuery = executionContext.getNormalizedQueryTree();
520+
return () -> normalizedQuery.get().getGraphQlNormalizedField(parameters.getField(), executionStepInfo.get().getObjectType(), executionStepInfo.get().getPath());
521521
}
522522

523523
protected FetchedValue unboxPossibleDataFetcherResult(ExecutionContext executionContext,

src/main/java/graphql/execution/ResolveType.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
import graphql.Assert;
44
import graphql.Internal;
55
import graphql.TypeResolutionEnvironment;
6-
import graphql.normalized.ExecutableNormalizedField;
7-
import graphql.normalized.ExecutableNormalizedOperation;
6+
import graphql.normalized.GraphQlNormalizedField;
7+
import graphql.normalized.GraphQlNormalizedOperation;
88
import graphql.schema.DataFetchingFieldSelectionSet;
99
import graphql.schema.DataFetchingFieldSelectionSetImpl;
1010
import graphql.schema.GraphQLInterfaceType;
@@ -43,8 +43,8 @@ public GraphQLObjectType resolveType(ExecutionContext executionContext, MergedFi
4343
}
4444

4545
private DataFetchingFieldSelectionSet buildSelectionSet(ExecutionContext executionContext, MergedField field, GraphQLOutputType fieldType, ExecutionStepInfo executionStepInfo) {
46-
Supplier<ExecutableNormalizedOperation> normalizedQuery = executionContext.getNormalizedQueryTree();
47-
Supplier<ExecutableNormalizedField> normalizedFieldSupplier = () -> normalizedQuery.get().getNormalizedField(field, executionStepInfo.getObjectType(), executionStepInfo.getPath());
46+
Supplier<GraphQlNormalizedOperation> normalizedQuery = executionContext.getNormalizedQueryTree();
47+
Supplier<GraphQlNormalizedField> normalizedFieldSupplier = () -> normalizedQuery.get().getGraphQlNormalizedField(field, executionStepInfo.getObjectType(), executionStepInfo.getPath());
4848
return DataFetchingFieldSelectionSetImpl.newCollector(executionContext.getGraphQLSchema(), fieldType, normalizedFieldSupplier);
4949
}
5050

src/main/java/graphql/execution/instrumentation/ChainedInstrumentation.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import graphql.execution.ExecutionContext;
1010
import graphql.execution.FieldValueInfo;
1111
import graphql.execution.instrumentation.parameters.InstrumentationCreateExecutableNormalizedOperationParameters;
12+
import graphql.execution.instrumentation.parameters.InstrumentationCreateNormalizedDocumentParameters;
1213
import graphql.execution.instrumentation.parameters.InstrumentationCreateStateParameters;
1314
import graphql.execution.instrumentation.parameters.InstrumentationExecuteOperationParameters;
1415
import graphql.execution.instrumentation.parameters.InstrumentationExecutionParameters;
@@ -19,6 +20,7 @@
1920
import graphql.execution.instrumentation.parameters.InstrumentationValidationParameters;
2021
import graphql.language.Document;
2122
import graphql.normalized.ExecutableNormalizedOperation;
23+
import graphql.normalized.nf.NormalizedDocument;
2224
import graphql.schema.DataFetcher;
2325
import graphql.schema.GraphQLSchema;
2426
import graphql.validation.ValidationError;
@@ -134,6 +136,11 @@ public InstrumentationContext<ExecutableNormalizedOperation> beginCreateExecutab
134136
instrumentation.beginCreateExecutableNormalizedOperation(parameters, specificState));
135137
}
136138

139+
@Override
140+
public InstrumentationContext<NormalizedDocument> beginCreateNormalizedDocument(InstrumentationCreateNormalizedDocumentParameters parameters, InstrumentationState state) {
141+
return chainedCtx(state, (instrumentation, specificState) ->
142+
instrumentation.beginCreateNormalizedDocument(parameters, specificState));
143+
}
137144

138145
@Override
139146
public InstrumentationContext<List<ValidationError>> beginValidation(InstrumentationValidationParameters parameters, InstrumentationState state) {

src/main/java/graphql/execution/instrumentation/Instrumentation.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import graphql.ExperimentalApi;
66
import graphql.PublicSpi;
77
import graphql.execution.ExecutionContext;
8+
import graphql.execution.instrumentation.parameters.InstrumentationCreateNormalizedDocumentParameters;
89
import graphql.execution.instrumentation.parameters.InstrumentationCreateStateParameters;
910
import graphql.execution.instrumentation.parameters.InstrumentationExecuteOperationParameters;
1011
import graphql.execution.instrumentation.parameters.InstrumentationExecutionParameters;
@@ -16,6 +17,7 @@
1617
import graphql.execution.instrumentation.parameters.InstrumentationValidationParameters;
1718
import graphql.language.Document;
1819
import graphql.normalized.ExecutableNormalizedOperation;
20+
import graphql.normalized.nf.NormalizedDocument;
1921
import graphql.schema.DataFetcher;
2022
import graphql.schema.GraphQLSchema;
2123
import graphql.validation.ValidationError;
@@ -135,6 +137,19 @@ default InstrumentationContext<ExecutableNormalizedOperation> beginCreateExecuta
135137
return noOp();
136138
}
137139

140+
/**
141+
* This is called just before the creation of the normalized document is started.
142+
*
143+
* @param parameters the parameters to this step
144+
* @param state the state created during the call to {@link #createState(InstrumentationCreateStateParameters)}
145+
* @return a nullable {@link InstrumentationContext} object that will be called back when the step ends (assuming it's not null)
146+
*/
147+
@ExperimentalApi
148+
@Nullable
149+
default InstrumentationContext<NormalizedDocument> beginCreateNormalizedDocument(InstrumentationCreateNormalizedDocumentParameters parameters, InstrumentationState state) {
150+
return noOp();
151+
}
152+
138153
/**
139154
* This is called each time an {@link graphql.execution.ExecutionStrategy} is invoked, which may be multiple times
140155
* per query as the engine recursively descends over the query.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package graphql.execution.instrumentation.parameters;
2+
3+
import graphql.ExecutionInput;
4+
import graphql.ExperimentalApi;
5+
import graphql.execution.instrumentation.Instrumentation;
6+
import graphql.schema.GraphQLSchema;
7+
8+
/**
9+
* Parameters sent to {@link Instrumentation} methods
10+
*/
11+
@SuppressWarnings("TypeParameterUnusedInFormals")
12+
@ExperimentalApi
13+
public class InstrumentationCreateNormalizedDocumentParameters extends InstrumentationExecutionParameters {
14+
public InstrumentationCreateNormalizedDocumentParameters(ExecutionInput executionInput, GraphQLSchema schema) {
15+
super(executionInput, schema);
16+
}
17+
}

src/main/java/graphql/language/OperationDefinition.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import graphql.language.NodeUtil.DirectivesHolder;
99
import graphql.util.TraversalControl;
1010
import graphql.util.TraverserContext;
11+
import org.jspecify.annotations.Nullable;
1112

1213
import java.util.ArrayList;
1314
import java.util.LinkedHashMap;
@@ -93,7 +94,7 @@ public OperationDefinition withNewChildren(NodeChildrenContainer newChildren) {
9394
);
9495
}
9596

96-
public String getName() {
97+
public @Nullable String getName() {
9798
return name;
9899
}
99100

0 commit comments

Comments
 (0)