Skip to content

Commit

Permalink
Merge pull request #3395 from felipe-gdr/defer-support-in-enf
Browse files Browse the repository at this point in the history
Defer support on ENFs
  • Loading branch information
andimarek committed Jan 10, 2024
2 parents f6fe760 + b22fe7a commit 5a0fb3d
Show file tree
Hide file tree
Showing 14 changed files with 2,499 additions and 174 deletions.
31 changes: 30 additions & 1 deletion src/main/java/graphql/Directives.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package graphql;


import graphql.language.BooleanValue;
import graphql.language.Description;
import graphql.language.DirectiveDefinition;
import graphql.language.StringValue;
Expand Down Expand Up @@ -33,14 +34,14 @@ public class Directives {
private static final String SPECIFIED_BY = "specifiedBy";
private static final String DEPRECATED = "deprecated";
private static final String ONE_OF = "oneOf";
private static final String DEFER = "defer";

public static final String NO_LONGER_SUPPORTED = "No longer supported";
public static final DirectiveDefinition DEPRECATED_DIRECTIVE_DEFINITION;
public static final DirectiveDefinition SPECIFIED_BY_DIRECTIVE_DEFINITION;
@ExperimentalApi
public static final DirectiveDefinition ONE_OF_DIRECTIVE_DEFINITION;


static {
DEPRECATED_DIRECTIVE_DEFINITION = DirectiveDefinition.newDirectiveDefinition()
.name(DEPRECATED)
Expand Down Expand Up @@ -77,6 +78,34 @@ public class Directives {
.build();
}

/**
* The @defer directive can be used to defer sending data for a fragment until later in the query.
* This is an opt-in directive that is not available unless it is explicitly put into the schema.
* <p>
* This implementation is based on the state of <a href="https://github.com/graphql/graphql-spec/pull/742">Defer/Stream PR</a>
* More specifically at the state of this
* <a href="https://github.com/graphql/graphql-spec/commit/c630301560d9819d33255d3ba00f548e8abbcdc6">commit</a>
* <p>
* The execution behaviour should match what we get from running Apollo Server 4.9.5 with graphql-js v17.0.0-alpha.2
*/
@ExperimentalApi
public static final GraphQLDirective DeferDirective = GraphQLDirective.newDirective()
.name(DEFER)
.description("This directive allows results to be deferred during execution")
.validLocations(FRAGMENT_SPREAD, INLINE_FRAGMENT)
.argument(newArgument()
.name("if")
.type(nonNull(GraphQLBoolean))
.description("Deferred behaviour is controlled by this argument")
.defaultValueLiteral(BooleanValue.newBooleanValue(true).build())
)
.argument(newArgument()
.name("label")
.type(GraphQLString)
.description("A unique label that represents the fragment being deferred")
)
.build();

public static final GraphQLDirective IncludeDirective = GraphQLDirective.newDirective()
.name("include")
.description("Directs the executor to include this field or fragment only when the `if` argument is true")
Expand Down
6 changes: 3 additions & 3 deletions src/main/java/graphql/ExperimentalApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@

/**
* This represents code that the graphql-java project considers experimental API and while our intention is that it will
* progress to be {@link PublicApi}, its existence, signature of behavior may change between releases.
*
* In general unnecessary changes will be avoided but you should not depend on experimental classes being stable
* progress to be {@link PublicApi}, its existence, signature or behavior may change between releases.
* <p>
* In general unnecessary changes will be avoided, but you should not depend on experimental classes being stable.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {CONSTRUCTOR, METHOD, TYPE, FIELD})
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/graphql/execution/FieldCollector.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

/**
* A field collector can iterate over field selection sets and build out the sub fields that have been selected,
* expanding named and inline fragments as it goes.s
* expanding named and inline fragments as it goes.
*/
@Internal
public class FieldCollector {
Expand Down
18 changes: 15 additions & 3 deletions src/main/java/graphql/normalized/ENFMerger.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,12 @@
@Internal
public class ENFMerger {

public static void merge(ExecutableNormalizedField parent, List<ExecutableNormalizedField> childrenWithSameResultKey, GraphQLSchema schema) {
public static void merge(
ExecutableNormalizedField parent,
List<ExecutableNormalizedField> childrenWithSameResultKey,
GraphQLSchema schema,
boolean deferSupport
) {
// they have all the same result key
// we can only merge the fields if they have the same field name + arguments + all children are the same
List<Set<ExecutableNormalizedField>> possibleGroupsToMerge = new ArrayList<>();
Expand All @@ -28,7 +33,7 @@ public static void merge(ExecutableNormalizedField parent, List<ExecutableNormal
overPossibleGroups:
for (Set<ExecutableNormalizedField> group : possibleGroupsToMerge) {
for (ExecutableNormalizedField fieldInGroup : group) {
if(field.getFieldName().equals(Introspection.TypeNameMetaFieldDef.getName())) {
if (field.getFieldName().equals(Introspection.TypeNameMetaFieldDef.getName())) {
addToGroup = true;
group.add(field);
continue overPossibleGroups;
Expand Down Expand Up @@ -63,8 +68,15 @@ && isFieldInSharedInterface(field, fieldInGroup, schema)
// patching the first one to contain more objects, remove all others
Iterator<ExecutableNormalizedField> iterator = groupOfFields.iterator();
ExecutableNormalizedField first = iterator.next();

while (iterator.hasNext()) {
parent.getChildren().remove(iterator.next());
ExecutableNormalizedField next = iterator.next();
parent.getChildren().remove(next);

if (deferSupport) {
// Move defer executions from removed field into the merged field's entry
first.addDeferExecutions(next.getDeferExecutions());
}
}
first.setObjectTypeNames(mergedObjects);
}
Expand Down
34 changes: 33 additions & 1 deletion src/main/java/graphql/normalized/ExecutableNormalizedField.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import graphql.Assert;
import graphql.ExperimentalApi;
import graphql.Internal;
import graphql.Mutable;
import graphql.PublicApi;
import graphql.collect.ImmutableKit;
import graphql.introspection.Introspection;
import graphql.language.Argument;
import graphql.normalized.incremental.DeferExecution;
import graphql.schema.GraphQLFieldDefinition;
import graphql.schema.GraphQLInterfaceType;
import graphql.schema.GraphQLNamedOutputType;
Expand Down Expand Up @@ -63,6 +65,8 @@ public class ExecutableNormalizedField {
private final String fieldName;
private final int level;

// Mutable List on purpose: it is modified after creation
private final LinkedHashSet<DeferExecution> deferExecutions;

private ExecutableNormalizedField(Builder builder) {
this.alias = builder.alias;
Expand All @@ -74,6 +78,7 @@ private ExecutableNormalizedField(Builder builder) {
this.children = builder.children;
this.level = builder.level;
this.parent = builder.parent;
this.deferExecutions = builder.deferExecutions;
}

/**
Expand Down Expand Up @@ -129,6 +134,7 @@ private ExecutableNormalizedField(Builder builder) {
* NOT {@code Cat} or {@code Dog} as their respective implementations would say.
*
* @param schema - the graphql schema in play
*
* @return true if the field is conditional
*/
public boolean isConditional(@NotNull GraphQLSchema schema) {
Expand Down Expand Up @@ -255,6 +261,16 @@ public void clearChildren() {
this.children.clear();
}

@Internal
public void setDeferExecutions(Collection<DeferExecution> deferExecutions) {
this.deferExecutions.clear();
this.deferExecutions.addAll(deferExecutions);
}

public void addDeferExecutions(Collection<DeferExecution> deferExecutions) {
this.deferExecutions.addAll(deferExecutions);
}

/**
* All merged fields have the same name so this is the name of the {@link ExecutableNormalizedField}.
* <p>
Expand Down Expand Up @@ -364,7 +380,6 @@ public String getSingleObjectTypeName() {
return objectTypeNames.iterator().next();
}


/**
* @return a helper method show field details
*/
Expand Down Expand Up @@ -461,6 +476,15 @@ public ExecutableNormalizedField getParent() {
return parent;
}

/**
* @return the {@link DeferExecution}s associated with this {@link ExecutableNormalizedField}.
* @see DeferExecution
*/
@ExperimentalApi
public LinkedHashSet<DeferExecution> getDeferExecutions() {
return deferExecutions;
}

@Internal
public void replaceParent(ExecutableNormalizedField newParent) {
this.parent = newParent;
Expand Down Expand Up @@ -588,6 +612,8 @@ public static class Builder {
private LinkedHashMap<String, Object> resolvedArguments = new LinkedHashMap<>();
private ImmutableList<Argument> astArguments = ImmutableKit.emptyList();

private LinkedHashSet<DeferExecution> deferExecutions = new LinkedHashSet<>();

private Builder() {
}

Expand All @@ -601,6 +627,7 @@ private Builder(ExecutableNormalizedField existing) {
this.children = new ArrayList<>(existing.children);
this.level = existing.getLevel();
this.parent = existing.getParent();
this.deferExecutions = existing.getDeferExecutions();
}

public Builder clearObjectTypesNames() {
Expand Down Expand Up @@ -656,6 +683,11 @@ public Builder parent(ExecutableNormalizedField parent) {
return this;
}

public Builder deferExecutions(LinkedHashSet<DeferExecution> deferExecutions) {
this.deferExecutions = deferExecutions;
return this;
}

public ExecutableNormalizedField build() {
return new ExecutableNormalizedField(this);
}
Expand Down

0 comments on commit 5a0fb3d

Please sign in to comment.