Skip to content
123 changes: 123 additions & 0 deletions src/main/java/graphql/execution/nextgen/depgraph/DependencyGraph.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package graphql.execution.nextgen.depgraph;

import graphql.language.OperationDefinition;
import graphql.schema.GraphQLSchema;
import graphql.util.TraversalControl;
import graphql.util.Traverser;
import graphql.util.TraverserContext;
import graphql.util.TraverserVisitorStub;

import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Function;

public class DependencyGraph {


public DependencyGraph createDependencyGraph(GraphQLSchema graphQLSchema, OperationDefinition operationDefinition) {

FieldCollectorWTC fieldCollector = new FieldCollectorWTC();
FieldCollectorParameters parameters = FieldCollectorParameters
.newParameters()
.schema(graphQLSchema)
.build();

Function<MergedFieldWithType, List<MergedFieldWithType>> getChildren = mergedFieldWithType -> {
List<MergedFieldWithType> childs = fieldCollector.collectFields(parameters, mergedFieldWithType);
return childs;
};


Set<FieldVertex> allVertices = new LinkedHashSet<>();

Traverser<MergedFieldWithType> traverser = Traverser.depthFirst(getChildren);
FieldVertex rootVertex = new FieldVertex(null, null, null, null, null);
traverser.rootVar(FieldVertex.class, rootVertex);
allVertices.add(rootVertex);
List<MergedFieldWithType> roots = fieldCollector.collectFromOperation(parameters, operationDefinition, graphQLSchema.getQueryType());
traverser.traverse(roots, new TraverserVisitorStub<MergedFieldWithType>() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think recursive collecting fields (graph traversal!) and then traversing collected fields again to build a graph is quite redundant. I would suggest to build the graph in 1 traverse cycle, incrementally with field collection.

@Override
public TraversalControl enter(TraverserContext<MergedFieldWithType> context) {
MergedFieldWithType mergedFieldWithType = context.thisNode();
System.out.println(mergedFieldWithType.getName() + "" + mergedFieldWithType.getObjectType().getName());

FieldVertex fieldVertex = createFieldVertex(mergedFieldWithType, graphQLSchema);
FieldVertex parentVertex = context.getVarFromParents(FieldVertex.class);
fieldVertex.addDependency(parentVertex);
parentVertex.addDependsOnMe(fieldVertex);
allVertices.add(fieldVertex);
context.setVar(FieldVertex.class, fieldVertex);

return TraversalControl.CONTINUE;
}

});

List<Set<FieldVertex>> listOfClosures = new ArrayList<>();
Set<FieldVertex> allClosed = new LinkedHashSet<>();
allClosed.add(rootVertex);
Set<FieldVertex> curSourceSet = new LinkedHashSet<>();
curSourceSet.add(rootVertex);

while (!curSourceSet.isEmpty()) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@andimarek
This is a topological sorting algorithm.
Currently it is used to perform static analysis only, but it actually needs to be used to do the field resolution.
The main difference between these 2 cases is that during query execution the actual runtime data should be used to evaluate vertex predicate and if that fails, that vertex and all its dependents should be excluded from the topological sequence so they won't make their entries in the resulting object.

Set<FieldVertex> nextClosure = new LinkedHashSet<>();
allClosed.addAll(curSourceSet);
for (FieldVertex source : curSourceSet) {
for (FieldVertex dependsOnSource : source.getDependsOnMe()) {
if (allClosed.containsAll(dependsOnSource.getDependencies())) {
nextClosure.add(dependsOnSource);
}
}
}
listOfClosures.add(nextClosure);
curSourceSet = nextClosure;
}

System.out.println(listOfClosures);

StringBuilder dotFile = new StringBuilder();
dotFile.append("digraph G{\n");
Traverser<FieldVertex> traverserVertex = Traverser.depthFirst(FieldVertex::getDependsOnMe);

traverserVertex.traverse(rootVertex, new TraverserVisitorStub<FieldVertex>() {
@Override
public TraversalControl enter(TraverserContext<FieldVertex> context) {
FieldVertex fieldVertex = context.thisNode();
FieldVertex parentVertex = context.getParentNode();
if (parentVertex != null) {
String vertexId = fieldVertex.getClass().getSimpleName() + Integer.toHexString(fieldVertex.hashCode());
String parentVertexId = parentVertex.getClass().getSimpleName() + Integer.toHexString(parentVertex.hashCode());
dotFile.append(vertexId).append(" -> ").append(parentVertexId).append(";\n");
}
allVertices.add(context.thisNode());
return TraversalControl.CONTINUE;
}
});

for (FieldVertex fieldVertex : allVertices) {
dotFile.append(fieldVertex.getClass().getSimpleName()).append(Integer.toHexString(fieldVertex.hashCode())).append("[label=\"")
.append(fieldVertex.toString()).append("\"];\n");
}

dotFile.append("}");
System.out.println(dotFile);
return null;
}

private FieldVertex createFieldVertex(MergedFieldWithType mergedFieldWithType, GraphQLSchema graphQLSchemas) {
return new FieldVertex(mergedFieldWithType.getFields(),
mergedFieldWithType.getFieldDefinition(),
mergedFieldWithType.getFieldsContainer(),
mergedFieldWithType.getParentType(),
mergedFieldWithType.getObjectType()
);

}


public static void main(String[] args) {

}
}
5 changes: 5 additions & 0 deletions src/main/java/graphql/execution/nextgen/depgraph/Edge.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package graphql.execution.nextgen.depgraph;

public class Edge {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package graphql.execution.nextgen.depgraph;

import graphql.Assert;
import graphql.Internal;
import graphql.language.FragmentDefinition;
import graphql.schema.GraphQLSchema;

import java.util.LinkedHashMap;
import java.util.Map;

/**
* Internal because FieldCollectorWTC is internal.
*/
@Internal
public class FieldCollectorParameters {
private final GraphQLSchema graphQLSchema;
private final Map<String, FragmentDefinition> fragmentsByName;
private final Map<String, Object> variables;

public GraphQLSchema getGraphQLSchema() {
return graphQLSchema;
}

public Map<String, FragmentDefinition> getFragmentsByName() {
return fragmentsByName;
}

public Map<String, Object> getVariables() {
return variables;
}


private FieldCollectorParameters(GraphQLSchema graphQLSchema,
Map<String, Object> variables,
Map<String, FragmentDefinition> fragmentsByName) {
this.fragmentsByName = fragmentsByName;
this.graphQLSchema = graphQLSchema;
this.variables = variables;
}

public static Builder newParameters() {
return new Builder();
}

public static class Builder {
private GraphQLSchema graphQLSchema;
private final Map<String, FragmentDefinition> fragmentsByName = new LinkedHashMap<>();
private final Map<String, Object> variables = new LinkedHashMap<>();

/**
* @see FieldCollectorParameters#newParameters()
*/
private Builder() {

}

public Builder schema(GraphQLSchema graphQLSchema) {
this.graphQLSchema = graphQLSchema;
return this;
}

public Builder fragments(Map<String, FragmentDefinition> fragmentsByName) {
this.fragmentsByName.putAll(fragmentsByName);
return this;
}

public Builder variables(Map<String, Object> variables) {
this.variables.putAll(variables);
return this;
}

public FieldCollectorParameters build() {
Assert.assertNotNull(graphQLSchema, "You must provide a schema");
return new FieldCollectorParameters(graphQLSchema, variables, fragmentsByName);
}

}
}
Loading